/** * Base class for all aes objects * $Id$ * $HeadURL$ * $Author$ */ #pragma once #include #include #include #include "aes_enums.hpp" #include "main_handler.h" #include "special_area_provider.h" #include "logger.h" #include #include #include 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 void dbSegment2DdsSegment(const T &dbSegments, std::vector &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 form() const = 0; virtual AesType aesType() const = 0; virtual bool isCentral() const = 0; template static inline void ddsVecFromPoly(const T &in, std::vector &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 dds::core::optional toOptional(T inVal) { return inVal != T() ? dds::core::optional(inVal) : dds::core::optional(); } dds::core::optional toOptional(const QDateTime &dt) { qint64 t = (dt.isValid() && !dt.isNull()) ? dt.toMSecsSinceEpoch() : 0; return toOptional(t); } template static inline void ddsVecFromPoly(const T &in, const U &heights, std::vector &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 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 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 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 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 &_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 &form, const std::vector &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 &form, const std::vector &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(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(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 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