Files
qt5-legatus-legacy/Lts/Services/AreaEffectSimulation/object_base.h
T

396 lines
14 KiB
C++
Executable File

/**
* Base class for all aes objects
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <QObject>
#include <QPolygonF>
#include <QTimer>
#include "aes_enums.hpp"
#include "main_handler.h"
#include "special_area_provider.h"
#include "logger.h"
#include <Core/function_reply_object.h>
#include <ProjectDataManagement/pdm_dtc.h>
#include <Trajectory/trajectory_generator.h>
namespace AreaEffectSimulation
{
// Keep it here as we need the main handler
inline TrajectoryGenerator *getTrajectoryInstance(MainHandler *mainHandler)
{
int exerciseId = mainHandler->dds()->exerciseId();
return TrajectoryGenerator::getInstance(exerciseId, mainHandler->dds()->exmDtc(), mainHandler->dds()->asmDtc());
}
template <typename T> void dbSegment2DdsSegment(const T &dbSegments, std::vector<Core::GeoPosition2D> &ddsSegments)
{
ddsSegments.clear();
ddsSegments.reserve(dbSegments.size());
for (const auto &segment : dbSegments)
ddsSegments.emplace_back(segment.position.x(), segment.position.y());
}
class ObjectBase : public QObject
{
Q_OBJECT
public:
ObjectBase(MainHandler *mainHandler, const QString &typeName, QObject *parent);
~ObjectBase();
virtual AesEffectState effectState() const = 0;
virtual QDateTime activationTime() const = 0;
virtual QDateTime endTime() const = 0;
virtual int id() const = 0;
virtual const QString &objectName() const = 0;
virtual void setUseEffectStateReceived(bool useReceived) = 0;
virtual qint32 flightDurationMsec() const { return 0; }
// this function tries to advance to the next state if possible.
// the two special cases (user input) 'Delete' and 'Pause' must
// be handled on its own (by calling trySwitchToPause/Delete() directly)
AesEffectState checkEnterNewState();
// Possible transitions
// Unknown -> Received
bool trySwitchToReceived();
// Prepared, Received, Unknown -> Created
bool trySwitchToCreated();
// Created, Received -> Prepared
bool trySwitchToPrepared();
// Prepared -> Fired (only Artillery)
bool trySwitchToFired();
// Prepared, Fired, Paused -> Activated
bool trySwitchToActivated();
// Activated -> Paused
bool trySwitchToPaused();
// Activated -> Done
bool trySwitchToExpired();
// -> Deleted
bool trySwitchToRemoved();
// state machine conditions
bool isActivationTimeSet() const { return activationTime().isValid(); }
virtual bool isActivationTimeReached() const { return isActivationTimeSet() && partitionTime() >= activationTime(); }
virtual bool isFiringTimeReached() const { return false; }
bool isPausingSupported() const { return m_bSupportsPausing; }
bool isEndTimeSet() const { return endTime().isValid(); }
bool isEndTimeReached() const { return isEndTimeSet() && partitionTime() >= endTime(); }
virtual bool isUsingEffectStateReceived() const = 0;
// other conditions
virtual bool isObjectModifiable() const; // is at least one parameter in the object modifyable
virtual bool isOnlyObjectNameModifiable() const; // is only the name modifyable (e.g. in Active State)
bool isObjectInTrainingArea() const; // is available in TA (important due to limited count per object type)
virtual void setEndTimeToNow() = 0; // adjust the duration so that the end time is reached now
virtual QPolygonF generateForm() = 0;
virtual std::vector<Core::GeoPosition> form() const = 0;
virtual AesType aesType() const = 0;
virtual bool isCentral() const = 0;
template <typename T> static inline void ddsVecFromPoly(const T &in, std::vector<Core::GeoPosition> &out)
{
out.clear();
out.reserve(in.size());
for (const QPointF &pt : in)
out.emplace_back(Core::GeoPosition(pt.x(), pt.y(), 0.0));
}
protected Q_SLOTS:
virtual void onPublishData();
Q_SIGNALS:
void effectStateChanged();
void newAiv(const AreaEffectSimulation::AesItemView &item);
void removeAiv(int32_t aesItemId);
void changedToCentral();
protected:
virtual void setEffectState(AesEffectState newState);
virtual qint64 partitionTimeMSec() const; // only virtual for statemachine test
virtual QDateTime partitionTime() const; // only virtual for statemachine test
static qint64 toMSec(const QDateTime &dt);
template <typename T> dds::core::optional<T> toOptional(T inVal) { return inVal != T() ? dds::core::optional<T>(inVal) : dds::core::optional<T>(); }
dds::core::optional<qint64> toOptional(const QDateTime &dt)
{
qint64 t = (dt.isValid() && !dt.isNull()) ? dt.toMSecsSinceEpoch() : 0;
return toOptional(t);
}
template <typename T, typename U> static inline void ddsVecFromPoly(const T &in, const U &heights, std::vector<Core::GeoPosition> &out)
{
Q_ASSERT(in.size() == heights.size());
out.clear();
out.reserve(in.size());
for (int i = 0; i < in.size(); ++i)
out.emplace_back(Core::GeoPosition(in[i].x(), in[i].y(), heights[i]));
}
template <typename T> static inline QPolygonF polyFromDdsVec(const T &in)
{
QPolygonF out;
out.reserve(in.size());
for (const auto &pos : qAsConst(in))
out.push_back(QPointF(pos.lon(), pos.lat()));
return out;
}
template <typename T, typename U> static inline bool intersects(const T &vec, const U &specialArea)
{
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
return polyFromDdsVec(vec).intersected(polyFromDdsVec(specialArea)).size() > 0;
#else
return polyFromDdsVec(vec).intersects(polyFromDdsVec(specialArea));
#endif
}
protected:
QTimer *m_aesTriggerTimer; // for state changes (activation time, end time)
QTimer *m_publishDataTimer; // for publishing data when data changed
MainHandler *m_mainHandler;
bool m_bSupportsPausing; // can this entity be paused (e.g. artillery)
QString m_typeName;
LOGGER_T m_logger;
};
template <typename T> class ObjectBaseTmpl : public ObjectBase
{
public:
ObjectBaseTmpl(MainHandler *mainHandler, const QString &typeName, QObject *parent)
: ObjectBase(mainHandler, typeName, parent)
{
}
~ObjectBaseTmpl() = default;
AesEffectState effectState() const override { return m_data.effectStateId; }
QDateTime activationTime() const override { return m_data.effectStart; }
QDateTime endTime() const override { return m_data.effectDuration <= 0 ? QDateTime() : m_data.effectStart.addMSecs(m_data.effectDuration); }
int id() const override { return m_data.id; }
qint64 externalId() const { return m_data.externalId; }
void setUseEffectStateReceived(bool useReceived) override { m_data.isUsingEffectStateReceived = useReceived; }
bool isUsingEffectStateReceived() const override { return m_data.isUsingEffectStateReceived; }
const QString &objectName() const override { return m_data.name; }
virtual Core::FunctionReplyObject createObject(const T &data, bool ignoreActivationTimeCheck = false, bool setIsCentral = false)
{
bool originOc = false;
m_isAlwaysCentral = setIsCentral;
m_data = data;
// recalc effect state except it's already expired, removed, paused or received.
if (data.effectStateId != AesEffectState::EsRemoved && data.effectStateId != AesEffectState::EsExpired && data.effectStateId != AesEffectState::EsPaused)
m_data.effectStateId = AesEffectState::EsUnknown;
if (m_data.id <= 0)
{
// check effect start for new elements if not OC
if ((0 == m_data.id) && (isActivationTimeSet() && activationTime() < QDateTime::currentDateTimeUtc()) && !ignoreActivationTimeCheck)
{
DEBUG_LOG(m_logger) << "m_data.id : " << m_data.id << ", isActivationTimeSet :" << isActivationTimeSet()
<< ", activationTime :" << activationTime().toMSecsSinceEpoch() << ", ignoreActivationTimeCheck :" << ignoreActivationTimeCheck;
// << ", QDateTime::currentDateTimeUtc() :" << QDateTime::currentDateTimeUtc().msecsTo();
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Effect start is in the past."));
}
#ifndef AES_STATEMACHINE_TEST
std::vector<Core::GeoPosition> form;
ddsVecFromPoly(generateForm(), form);
if ((aesType() == AesType::AtBoobyTrap) || (aesType() == AesType::AtOffRouteMine) || setIsCentral)
m_data.isCentral = true;
else
m_data.isCentral = checkIsCentral(form, {}, false);
if (m_data.id < 0) // is the id given as negative? make positive again -> used by OC-Laptop
{
m_data.id *= -1;
m_data.priv_entityId = m_data.id; // OC is using id from Tetra and these are unique
originOc = true;
}
if (!m_mainHandler->db()->insert(m_data))
return Core::FunctionReplyObject(m_data.id * -1, tr_lts("dds", "Error inserting effect simulation into database."));
#endif
}
// always publish on creation
m_publishDataTimer->start();
// start state machine
checkEnterNewState();
return Core::FunctionReplyObject((originOc) ? (m_data.id * -1) : (m_data.id));
}
virtual Core::FunctionReplyObject updateObject(const T &data)
{
if (m_data.id != std::abs(data.id))
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Internal error - id does not match. Is: %1, should be: %2."))
.arg(std::abs(data.id))
.arg(m_data.id); // data not m_data for the possible negative id in the oc-laptop
if (data.id > 0) // do not check if OC-Laptop
{
if (!isObjectModifiable())
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Failed to update effect simulation with id %1 as it is unmodifyable."))
.arg(data.id); // data not m_data for the possible negative id in the oc-laptop
if (isOnlyObjectNameModifiable())
{
// only rename allowed
m_data.name = data.name;
checkEnterNewState();
return Core::FunctionReplyObject(m_data.id);
}
if (isActivationTimeSet() && activationTime() < QDateTime::currentDateTimeUtc())
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "New effect start for effect simulation is in the past."));
}
return Core::FunctionReplyObject(data.id); // data not m_data for the possible negative id in the oc-laptop
}
virtual Core::FunctionReplyObject updateName(const QString &name)
{
m_data.name = name;
checkEnterNewState();
return Core::FunctionReplyObject(m_data.id);
}
virtual Core::FunctionReplyObject removeObject()
{
if (!trySwitchToRemoved())
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Can not remove effect simulation - wrong state (active or already expired)."));
return Core::FunctionReplyObject(m_data.id);
}
virtual Core::FunctionReplyObject setActivationOffset(qint64 msecs)
{
Core::FunctionReplyObject ret;
T data = m_data;
data.effectStart = msecs >= 0 ? QDateTime::currentDateTimeUtc().addMSecs(msecs) : QDateTime();
ret = updateObject(data);
if (ret.error() || isOnlyObjectNameModifiable())
return ret;
return Core::FunctionReplyObject(m_data.id);
}
virtual Core::FunctionReplyObject activateObjectNow()
{
const QDateTime &tTempStart = m_data.effectStart;
m_data.effectStart = QDateTime::currentDateTimeUtc();
if (!trySwitchToActivated())
{
m_data.effectStart = tTempStart;
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Can not activate effect simulation with id %1 which is in the effect state with id %2."))
.arg(m_data.id)
.arg(m_data.effectStateId.underlying());
}
return Core::FunctionReplyObject();
}
const T &data() const { return m_data; }
void setEndTimeToNow() override
{
if (!m_data.effectStart.isValid())
m_data.effectStart = partitionTime().addMSecs(-100);
m_data.effectDuration = m_data.effectStart.msecsTo(partitionTime());
if (m_data.effectDuration < 0)
m_data.effectDuration = 1;
}
bool checkIsCentral(const std::vector<Core::GeoPosition> &_form) { return checkIsCentral(_form, form(), m_data.isCentral); }
protected:
// will be called from trySwitchFoo()
void setEffectState(AesEffectState newState) override
{
if (newState == m_data.effectStateId && newState != AesEffectState::EsUnknown)
return;
m_data.effectStateId = newState;
ObjectBase::setEffectState(newState);
}
void onPublishData() override
{
DEBUG_LOG(m_logger) << id() << " Publishing data for " << m_typeName << " with name '" << m_data.name << "'"
<< " isCentral=" << m_data.isCentral;
ObjectBase::onPublishData();
}
void checkIsCentralAndPublish(const std::vector<Core::GeoPosition> &form, const std::vector<Core::GeoPosition> &oldForm, bool wasCentral)
{
if (m_isAlwaysCentral)
return;
bool isCentral = checkIsCentral(form, oldForm, wasCentral);
if (isCentral != m_data.isCentral)
{
m_data.isCentral = isCentral;
onPublishData();
if (isOnlyObjectNameModifiable() && isCentral)
emit changedToCentral();
}
}
bool checkIsCentral(const std::vector<Core::GeoPosition> &form, const std::vector<Core::GeoPosition> &oldForm, bool wasCentral)
{
// Previously central areas stay central when state is at least activated:
if (wasCentral && (effectState() == AesEffectState::EsExpired || effectState() == AesEffectState::EsActivated || effectState() == AesEffectState::EsPaused))
return true;
if (static_cast<MineClass::type>(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId)) == MineClass::VirtualSingle)
return true;
if (m_mainHandler->dds()->pdmDtc()->gearClassFromChargeTypeId(m_data.chargeTypeId) == ProjectDataManagement::GearClass::AirSupport)
return true;
// Laser-Guidable Weapons must always be calculated by CES
if (m_mainHandler->dds()->pdmDtc()->isLaserGuidable(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId))
return true;
// PzAbwVMi B always true
if (static_cast<MineClass::type>(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId)) == MineClass::Concrete)
return true;
// If form didn't change, isCentral cannot change either:
if (form == oldForm)
return wasCentral;
// Now let's check wether it actually is in a CES area.
const QMap<int, InfrastructureManagement::SpecialArea> spAMap = m_mainHandler->specialAreaProvider()->getSpecialAreas();
for (const InfrastructureManagement::SpecialArea &sa : spAMap)
{
if (sa.specialType() != InfrastructureManagement::SpecialAreaType::SatCesArea)
continue;
if (intersects(form, sa.polygon()))
return true;
}
return false;
}
protected:
bool m_isAlwaysCentral = false;
T m_data;
};
} // namespace AreaEffectSimulation