/** * @RDE_HEADER@ * Firing Timer, a helperclass for ExplosionCluster * $Id$ * $HeadURL$ * $Author$ * Created by Norbert Thoden, ESVG, 2024-09-03 */ #include "firing_timer.h" #include "../logger.h" #include #include #include #include #include #include #include #include namespace AreaEffectSimulation { // To be able to aesily differentiate between different instances: #define MY_LOGFUNCTION "debugInfo='" << d->m_debugInfo << "' " << Q_FUNC_INFO static long s_counter{0}; //------------------------------------------------------------------------------ // class //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ // class FiringTimer::Private //------------------------------------------------------------------------------ class FiringTimer::Private { public: Q_DISABLE_COPY(Private); Private() { m_etActive = new QElapsedTimer(); m_etPaused = new QElapsedTimer(); m_firingTimer = new QTimer(); reset(); } ~Private() { delete m_etActive; delete m_etPaused; } QString m_debugInfo; double m_effectDurationMsec{-1}; int m_ammoCount; long m_intervallMsec{-1}; long m_ammoFlightDurationMsec{-1}; FiringMode m_currentFiringMode {FiringMode::FM_Undefined}; QElapsedTimer *m_etActive{nullptr}; qint64 m_activeMsec{0}; QElapsedTimer *m_etPaused{nullptr}; qint64 m_pausedMsec{0}; QTimer *m_firingTimer{nullptr}; int m_firedAmmoCount{0}; FiringTimer::CMD m_lastDelayedStateCmd {CMD::CMD_UNDEFINED}; QStringList m_logHistorie; void reset() { m_effectDurationMsec = -1; m_ammoCount = -1; m_intervallMsec = -1; m_ammoFlightDurationMsec = -1; m_currentFiringMode = FiringMode::FM_Undefined; m_etActive->invalidate(); m_activeMsec = 0; m_etPaused->invalidate(); m_pausedMsec = 0; m_firingTimer->stop(); m_firedAmmoCount = 0; m_lastDelayedStateCmd = CMD::CMD_UNDEFINED; m_logHistorie.clear(); } void dump() { static LOGGER_T logger = getLogger("Utils.FiringTimer"); INFO_LOG(logger) << " DUMP m_effectDurationMsec=" << m_effectDurationMsec << ", m_ammoCount=" << m_ammoCount << "-> m_intervallMsec=" << m_intervallMsec << ", m_ammoFlightDurationMsec=" << m_ammoFlightDurationMsec << ", m_currentFiringMode=" << (int)m_currentFiringMode << ", m_lastDelayedStateCmd=" << m_lastDelayedStateCmd << ", m_etActive=" << m_etActive << ", m_activeMsec=" << m_activeMsec << ", m_etPaused=" << m_etPaused << ", m_pausedMsec=" << m_pausedMsec << ", m_firingTimer=" << m_firingTimer << ", m_firedAmmoCount=" << m_firedAmmoCount; } }; //------------------------------------------------------------------------------ // class FiringTimer //------------------------------------------------------------------------------ FiringTimer::FiringTimer(const QString &debugInfo) : BaseClass(nullptr) , d(new Private) , m_logger(getLogger("Utils.FiringTimer")) { ++s_counter; d->m_debugInfo = debugInfo; DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " START, s_counter=" << s_counter << " this=" << this; DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " END, s_counter=" << s_counter << " this=" << this; } FiringTimer::~FiringTimer() { --s_counter; DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " START, s_counter=" << s_counter << " this=" << this; QString debugInfoSave = d->m_debugInfo; delete d; //DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " END, s_counter=" << s_counter << " this=" << this; DEBUG_LOG(m_logger) << "debugInfo='" << debugInfoSave << "'" << LOGFUNCTION << " END, s_counter=" << s_counter << " this=" << this; } void FiringTimer::setDebugInfo(const QString &debugInfo) { d->m_debugInfo = debugInfo; } void FiringTimer::reset() { TRACE_LOG(m_logger) << MY_LOGFUNCTION << " START"; d->reset(); TRACE_LOG(m_logger) << MY_LOGFUNCTION << " END"; } bool FiringTimer::init(long effectDurationMsec, int ammoCount, long ammoFlightDurationMsec) { TRACE_LOG(m_logger) << MY_LOGFUNCTION << " START," << ", effectDurationMsec=" << effectDurationMsec << ", ammoCount=" << ammoCount << ", ammoFlightDurationMsec=" << ammoFlightDurationMsec; reset(); d->m_effectDurationMsec = effectDurationMsec; d->m_ammoCount = ammoCount; d->m_ammoFlightDurationMsec = ammoFlightDurationMsec; d->m_intervallMsec = static_cast(d->m_effectDurationMsec) / d->m_ammoCount; // ensure float calculation DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " m_intervallMsec " << d->m_intervallMsec; d->m_firingTimer->stop(); d->m_firingTimer->setSingleShot(false); d->m_firingTimer->setInterval(d->m_intervallMsec); connect(d->m_firingTimer, &QTimer::timeout, this, &FiringTimer::onFiringTimout, Qt::UniqueConnection); TRACE_LOG(m_logger) << MY_LOGFUNCTION << " END"; return true; } bool FiringTimer::startFiring() { TRACE_LOG(m_logger) << MY_LOGFUNCTION << " START"; if ((d->m_currentFiringMode != FiringMode::FM_Undefined) && (d->m_currentFiringMode != FiringMode::FM_Paused)) { ERROR_LOG(m_logger) << MY_LOGFUNCTION << " wrong firingMode, cur = " << (int)d->m_currentFiringMode; return false; } if (remainingAmmoCount() <= 0) { ERROR_LOG(m_logger) << MY_LOGFUNCTION << " no ammo left" << " (total ammoCount was: " << d->m_ammoCount << " cur firingMode = " << (int)d->m_currentFiringMode; return false; } #ifdef SUPPORT_STATE_CHANGE_DELAYED if (d->m_currentFiringMode == FiringMode::FM_Paused) { int firedAmmoCountCopy = d->m_firedAmmoCount; QTimer::singleShot(d->m_ammoFlightDurationMsec, this, [this, firedAmmoCountCopy](){ emitStateChangeDelayed(CMD::CMD_RESUME, firedAmmoCountCopy); }); } #endif // SUPPORT_STATE_CHANGE_DELAYED DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " start firing with m_firedAmmoCount = " << d->m_firedAmmoCount; d->m_firingTimer->start(); d->m_currentFiringMode = FiringMode::FM_Active; d->m_etActive->start(); // maybe m_etPaused is active, maybe not: qint64 pausedElapsedMsec = 0; if (d->m_etPaused->isValid()) { pausedElapsedMsec = d->m_etPaused->elapsed(); d->m_pausedMsec += pausedElapsedMsec; d->m_etPaused->invalidate(); } DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " start firing after [ms]: " << pausedElapsedMsec << ", m_activeMsec in total = " << d->m_activeMsec << ", m_pausedMsec in total = " << d->m_pausedMsec; QString cmd = QString(QStringLiteral("StartFiring_at_%1")).arg(QDateTime::currentMSecsSinceEpoch()); d->m_logHistorie.push_back(cmd); // ammoCount : 2 ammo // effect Duration : 10000 msec // => interval : 5000 msec // _____________ // | | | // _____________ // ^ ^ // | |_ fire 2 // |_ fire 1 // // ^ ^ // |_ signal firing(1, 2) // |_ signal firing(2,2) // // ^ ^ // |_ signal firingFinishedPlus // |_ signal firingFinished // to fire at the beginning of the interval: onFiringTimout(); TRACE_LOG(m_logger) << MY_LOGFUNCTION << " END"; return true; } bool FiringTimer::startFiringDelay(qint64 delayMsec) { if (delayMsec <= 0) return false; QTimer::singleShot(delayMsec, this, &FiringTimer::startFiring); return true; } bool FiringTimer::pauseFiring() { TRACE_LOG(m_logger) << MY_LOGFUNCTION << " START"; if (d->m_currentFiringMode != FiringMode::FM_Active) { ERROR_LOG(m_logger) << MY_LOGFUNCTION << " wrong firingMode"; return false; } if (remainingAmmoCount() <= 0) { ERROR_LOG(m_logger) << MY_LOGFUNCTION << " no ammo left" << " (total ammoCount was: " << d->m_ammoCount; return false; } #ifdef SUPPORT_STATE_CHANGE_DELAYED int curAmmoCountCopy = d->m_firedAmmoCount; QTimer::singleShot(d->m_ammoFlightDurationMsec, this, [this, curAmmoCountCopy](){ emitStateChangeDelayed(CMD::CMD_PAUSE, curAmmoCountCopy); }); #endif // SUPPORT_STATE_CHANGE_DELAYED DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " stop firing at m_firedAmmoCount = " << d->m_firedAmmoCount; d->m_firingTimer->stop(); d->m_currentFiringMode = FiringMode::FM_Paused; d->m_etPaused->start(); Q_ASSERT_X(d->m_etActive->isValid(), "FiringTimer", "d->m_etActive not valid"); const qint64 activeElapsedMsec = d->m_etActive->elapsed(); d->m_activeMsec += activeElapsedMsec; d->m_etActive->invalidate(); DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " stop firing after [ms]: " << activeElapsedMsec << ", m_activeMsec in total = " << d->m_activeMsec << ", m_pausedMsec in total = " << d->m_pausedMsec; QString log = QString(QStringLiteral("pauseFiring_at_%1")).arg(QDateTime::currentMSecsSinceEpoch()) ; d->m_logHistorie.append(log); TRACE_LOG(m_logger) << MY_LOGFUNCTION << " END"; return true; } long FiringTimer::ammoFlightDurationMsec() const { return d->m_ammoFlightDurationMsec; } int FiringTimer::firedAmmoCount() const { return d->m_firedAmmoCount; } int FiringTimer::nextAmmoToFire() const { if (remainingAmmoCount() <= 0) return -1; return firedAmmoCount() + 1; } int FiringTimer::remainingAmmoCount() const { return d->m_ammoCount - d->m_firedAmmoCount; } long FiringTimer::intervallMsec() const { return d->m_intervallMsec; } #ifdef SUPPORT_STATE_CHANGE_DELAYED void FiringTimer::emitStateChangeDelayed(AreaEffectSimulation::FiringTimer::CMD cmd, int firedAmmoCount) { DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " SUPPORT_STATE_CHANGE_DELAYED: " << cmd << ", firedAmmoCount = " << firedAmmoCount; emit stateChangeDelayedAsInt(static_cast(cmd), firedAmmoCount); } #endif // SUPPORT_STATE_CHANGE_DELAYED QStringList FiringTimer::getLogHistorie() const { return d->m_logHistorie; } QString FiringTimer::toString(const CMD cmd) { switch(cmd) { case CMD::CMD_UNDEFINED: return QStringLiteral("CMD_UNDEFINED"); case CMD::CMD_START: return QStringLiteral("CMD_START"); case CMD::CMD_RESUME: return QStringLiteral("CMD_RESUME"); case CMD::CMD_PAUSE: return QStringLiteral("CMD_PAUSE"); case CMD::CMD_EXPIRED: return QStringLiteral("CMD_EXPIRED"); case CMD::CMD_EXPIRED_PLUS: return QStringLiteral("CMD_EXPIRED_PLUS"); default: return QStringLiteral("CMD_Error"); } } void FiringTimer::onFiringTimout() { TRACE_LOG(m_logger) << MY_LOGFUNCTION << " START"; ++d->m_firedAmmoCount; INFO_LOG(m_logger) << MY_LOGFUNCTION << " emit firing with: " << d->m_firedAmmoCount << " of total ammoCount = " << d->m_ammoCount; emit firing(d->m_firedAmmoCount, d->m_ammoCount); // prepared signal for impact, not interruptable if (d->m_ammoFlightDurationMsec >= 0) { // copy values: // i am not sure, if '=' would work here, so copy to local vars: int currentFiringNumber = d->m_firedAmmoCount; int totalFiringNumber = d->m_ammoCount; DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " XXXXXXXXX schedule 'emit impact' " << currentFiringNumber; QTimer::singleShot(d->m_ammoFlightDurationMsec, this, [this, currentFiringNumber, totalFiringNumber]() { #ifdef SUPPORT_STATE_CHANGE_DELAYED if (currentFiringNumber == 1) // Number -> start counting at '1' { emitStateChangeDelayed(CMD::CMD_START, currentFiringNumber); } #endif // SUPPORT_STATE_CHANGE_DELAYED INFO_LOG(m_logger) << MY_LOGFUNCTION << " emit impact " << currentFiringNumber << " of total = " << totalFiringNumber; emit impact(currentFiringNumber, totalFiringNumber); #ifdef SUPPORT_STATE_CHANGE_DELAYED if (currentFiringNumber >= totalFiringNumber) { emitStateChangeDelayed(CMD::CMD_EXPIRED, currentFiringNumber); // for ..PLUS we need a delay: QTimer::singleShot(d->m_intervallMsec, this, [this, currentFiringNumber]() { emitStateChangeDelayed(CMD::CMD_EXPIRED_PLUS, currentFiringNumber); }); } #endif // SUPPORT_STATE_CHANGE_DELAYED }); } if (d->m_firedAmmoCount >= d->m_ammoCount) { INFO_LOG(m_logger) << MY_LOGFUNCTION << " no ammo left, finishing firing" << ", " << d->m_firedAmmoCount << " of total ammoCount = " << d->m_ammoCount << " are fired"; d->m_firingTimer->stop(); d->m_currentFiringMode = FiringMode::FM_Expired; const qint64 activeElapsedMsec = d->m_etActive->elapsed(); // !! Attention: do pauseFiring() WITHIN the intervall will reduce amount of time the firing is active !! DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " stop firing after [ms]: " << activeElapsedMsec << ", m_activeMsec in total = " << d->m_activeMsec << ", m_pausedMsec in total = " << d->m_pausedMsec << " now FiringTimoutExpiredPlus ..."; // !! Attention: do pauseFiring() WITHIN the intervall will reduce amount of time the firing is active !! emit firingFinished(); d->dump(); // FM_ExpiredPlus // maybe use disconnect via QMetaObject : delete d->m_firingTimer; d->m_firingTimer = new QTimer(); d->m_firingTimer->setInterval(d->m_intervallMsec); // : maybe use disconnect via QMetaObject connect(d->m_firingTimer, &QTimer::timeout, this, &FiringTimer::onFiringTimoutExpiredPlus, Qt::UniqueConnection); d->m_firingTimer->setSingleShot(true); d->m_firingTimer->start(); } TRACE_LOG(m_logger) << MY_LOGFUNCTION << " END" << ", m_firedAmmoCount " << d->m_firedAmmoCount; } void FiringTimer::onFiringTimoutExpiredPlus() { TRACE_LOG(m_logger) << MY_LOGFUNCTION << " START"; d->m_currentFiringMode = FiringMode::FM_ExpiredPlus; const qint64 activeElapsedMsec = d->m_etActive->elapsed(); d->m_activeMsec += activeElapsedMsec; d->m_etActive->invalidate(); // !! Attention: do pauseFiring() WITHIN the intervall will reduce amount of time the firing is active !! DEBUG_LOG(m_logger) << MY_LOGFUNCTION << " stop firing2 after [ms]: " << activeElapsedMsec << ", m_activeMsec in total = " << d->m_activeMsec << ", m_pausedMsec in total = " << d->m_pausedMsec; // !! Attention: do pauseFiring() WITHIN the intervall will reduce amount of time the firing is active !! emit firingFinishedPlus(); TRACE_LOG(m_logger) << MY_LOGFUNCTION << " END"; } AES_COMMON_EXPORT std::ostream &operator<< (std::ostream &stream, const FiringTimer::CMD value) { stream << FiringTimer::toString(value); return stream; } } // namespace AreaEffectSimulation