/* Copyright (C) 2015 * swift project Community / Contributors * * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, * or distributed except according to the terms contained in the LICENSE file. */ #include "blackcore/airspaceanalyzer.h" #include "airspacemonitor.h" #include "blackmisc/aviation/aircraftsituation.h" #include "blackmisc/aviation/callsign.h" #include "blackmisc/aviation/transponder.h" #include "blackmisc/logmessage.h" #include "blackmisc/simulation/simulatedaircraftlist.h" #include "blackmisc/statusmessage.h" #include "blackmisc/threadutils.h" #include #include #include #include #include #include using namespace BlackMisc; using namespace BlackMisc::Aviation; using namespace BlackMisc::Geo; using namespace BlackMisc::Network; using namespace BlackMisc::PhysicalQuantities; using namespace BlackMisc::Simulation; using namespace BlackCore::Fsd; namespace BlackCore { CAirspaceAnalyzer::CAirspaceAnalyzer(IOwnAircraftProvider *ownAircraftProvider, CFSDClient *fsdClient, CAirspaceMonitor *airspaceMonitorParent) : CContinuousWorker(airspaceMonitorParent, "CAirspaceAnalyzer"), COwnAircraftAware(ownAircraftProvider), CRemoteAircraftAware(airspaceMonitorParent) { Q_ASSERT_X(fsdClient, Q_FUNC_INFO, "Network object required to connect"); // all in new thread from here on this->setObjectName(this->getName()); m_updateTimer.start(7500); m_lastWatchdogCallMsSinceEpoch = QDateTime::currentMSecsSinceEpoch(); bool c = connect(&m_updateTimer, &QTimer::timeout, this, &CAirspaceAnalyzer::onTimeout); Q_ASSERT(c); // network connected // those are CID and not callsign related // c = connect(fsdClient, &CFSDClient::deletePilotReceived, this, &CAirspaceAnalyzer::watchdogRemoveAircraftCallsign, Qt::QueuedConnection); // Q_ASSERT(c); // c = connect(fsdClient, &CFSDClient::deleteAtcReceived, this, &CAirspaceAnalyzer::watchdogRemoveAtcCallsign, Qt::QueuedConnection); // Q_ASSERT(c); c = connect(fsdClient, &CFSDClient::connectionStatusChanged, this, &CAirspaceAnalyzer::onConnectionStatusChanged, Qt::QueuedConnection); Q_ASSERT(c); // network situations c = connect(fsdClient, &CFSDClient::pilotDataUpdateReceived, this, &CAirspaceAnalyzer::onNetworkPositionUpdate, Qt::QueuedConnection); Q_ASSERT(c); c = connect(fsdClient, &CFSDClient::atcDataUpdateReceived, this, &CAirspaceAnalyzer::watchdogTouchAtcCallsign, Qt::QueuedConnection); Q_ASSERT(c); // Monitor c = connect(airspaceMonitorParent, &CAirspaceMonitor::addedAircraftSituation, this, &CAirspaceAnalyzer::watchdogTouchAircraftCallsign); Q_ASSERT(c); c = connect(airspaceMonitorParent, &CAirspaceMonitor::removedAircraft, this, &CAirspaceAnalyzer::watchdogRemoveAircraftCallsign); Q_ASSERT(c); c = connect(airspaceMonitorParent, &CAirspaceMonitor::changedAtcStationOnlineConnectionStatus, this, &CAirspaceAnalyzer::onChangedAtcStationOnlineConnectionStatus); Q_ASSERT(c); // -------------------- Q_UNUSED(c) // start in own thread this->start(QThread::LowestPriority); } CAirspaceAircraftSnapshot CAirspaceAnalyzer::getLatestAirspaceAircraftSnapshot() const { QReadLocker l(&m_lockSnapshot); return m_latestAircraftSnapshot; } void CAirspaceAnalyzer::setSimulatorRenderRestrictionsChanged(bool restricted, bool enabled, int maxAircraft, const CLength &maxRenderedDistance) { QWriteLocker l(&m_lockRestrictions); m_simulatorRenderedAircraftRestricted = restricted; m_simulatorRenderingEnabled = enabled; m_simulatorMaxRenderedAircraft = maxAircraft; m_simulatorMaxRenderedDistance = maxRenderedDistance; } CAirspaceAnalyzer::~CAirspaceAnalyzer() { } void CAirspaceAnalyzer::onNetworkPositionUpdate(const CAircraftSituation &situation, const CTransponder &transponder) { Q_UNUSED(transponder) this->watchdogTouchAircraftCallsign(situation); } void CAirspaceAnalyzer::onChangedAtcStationOnlineConnectionStatus(const CAtcStation &station, bool isConnected) { const CCallsign cs = station.getCallsign(); if (isConnected) { m_atcCallsignTimestamps[cs] = QDateTime::currentMSecsSinceEpoch(); } else { this->watchdogRemoveAtcCallsign(cs); } } void CAirspaceAnalyzer::watchdogTouchAircraftCallsign(const CAircraftSituation &situation) { const CCallsign cs = situation.getCallsign(); Q_ASSERT_X(!cs.isEmpty(), Q_FUNC_INFO, "No callsign in situaton"); m_aircraftCallsignTimestamps[cs] = QDateTime::currentMSecsSinceEpoch(); } void CAirspaceAnalyzer::watchdogTouchAtcCallsign(const CCallsign &callsign, const CFrequency &frequency, const CCoordinateGeodetic &position, const CLength &range) { Q_UNUSED(frequency) Q_UNUSED(position) Q_UNUSED(range) m_atcCallsignTimestamps[callsign] = QDateTime::currentMSecsSinceEpoch(); } void CAirspaceAnalyzer::onConnectionStatusChanged(CConnectionStatus oldStatus, CConnectionStatus newStatus) { Q_UNUSED(oldStatus) if (newStatus.isDisconnected()) { this->clear(); m_updateTimer.stop(); } else if (newStatus.isConnected()) { m_updateTimer.start(); } } void CAirspaceAnalyzer::onTimeout() { if (!this->isEnabled()) { return; } this->analyzeAirspace(); this->watchdogCheckTimeouts(); } void CAirspaceAnalyzer::clear() { m_aircraftCallsignTimestamps.clear(); m_atcCallsignTimestamps.clear(); QWriteLocker l(&m_lockSnapshot); m_latestAircraftSnapshot = CAirspaceAircraftSnapshot(); } void CAirspaceAnalyzer::watchdogRemoveAircraftCallsign(const CCallsign &callsign) { m_aircraftCallsignTimestamps.remove(callsign); } void CAirspaceAnalyzer::watchdogRemoveAtcCallsign(const CCallsign &callsign) { m_atcCallsignTimestamps.remove(callsign); } void CAirspaceAnalyzer::watchdogCheckTimeouts() { // this is a trick to not remove everything while debugging const qint64 currentTimeMsEpoch = QDateTime::currentMSecsSinceEpoch(); const qint64 callDiffMs = currentTimeMsEpoch - m_lastWatchdogCallMsSinceEpoch; const qint64 callThresholdMs = static_cast(m_updateTimer.interval() * 1.5); m_lastWatchdogCallMsSinceEpoch = currentTimeMsEpoch; if (callDiffMs > callThresholdMs) { // allow some time to normalize before checking again m_doNotRunAgainBefore = currentTimeMsEpoch + 2 * callThresholdMs; return; } if (m_doNotRunAgainBefore > currentTimeMsEpoch) { return; } m_doNotRunAgainBefore = -1; // checks const qint64 aircraftTimeoutMs = m_timeoutAircraft.valueInteger(CTimeUnit::ms()); const qint64 atcTimeoutMs = m_timeoutAtc.valueInteger(CTimeUnit::ms()); const qint64 timeoutAircraftEpochMs = currentTimeMsEpoch - aircraftTimeoutMs; const qint64 timeoutAtcEpochMs = currentTimeMsEpoch - atcTimeoutMs; const bool enabled = m_enabledWatchdog; const QList callsignsAircraft = m_aircraftCallsignTimestamps.keys(); for (const CCallsign &callsign : callsignsAircraft) // clazy:exclude=container-anti-pattern,range-loop { if (!enabled) { m_aircraftCallsignTimestamps[callsign] = timeoutAircraftEpochMs + 1000; } // fake value so it can be re-enabled const qint64 tsv = m_aircraftCallsignTimestamps.value(callsign); if (tsv > timeoutAircraftEpochMs) { continue; } CLogMessage(this).debug() << QStringLiteral("Aircraft '%1' timed out after %2ms").arg(callsign.toQString()).arg(currentTimeMsEpoch - tsv); m_aircraftCallsignTimestamps.remove(callsign); emit this->timeoutAircraft(callsign); } const QList callsignsAtc = m_atcCallsignTimestamps.keys(); for (const CCallsign &callsign : callsignsAtc) // clazy:exclude=container-anti-pattern,range-loop { if (!enabled) { m_aircraftCallsignTimestamps[callsign] = timeoutAtcEpochMs + 1000; } // fake value so it can be re-enabled const qint64 tsv = m_atcCallsignTimestamps.value(callsign); if (tsv > timeoutAtcEpochMs) { continue; } CLogMessage(this).debug() << QStringLiteral("ATC '%1' timed out after %2ms").arg(callsign.toQString()).arg(currentTimeMsEpoch - tsv); m_atcCallsignTimestamps.remove(callsign); emit this->timeoutAtc(callsign); } } void CAirspaceAnalyzer::analyzeAirspace() { Q_ASSERT_X(!CThreadUtils::thisIsMainThread(), Q_FUNC_INFO, "Expect to run in background thread"); Q_ASSERT_X(thread() != qApp->thread(), Q_FUNC_INFO, "Expect to run in background thread affinity"); bool restricted, enabled; int maxAircraft; CLength maxRenderedDistance; { QReadLocker l(&m_lockRestrictions); restricted = m_simulatorRenderedAircraftRestricted; enabled = m_simulatorRenderingEnabled; maxAircraft = m_simulatorMaxRenderedAircraft; maxRenderedDistance = m_simulatorMaxRenderedDistance; } // remark for simulation snapshot is used when there are restrictions // nevertheless we calculate all the time as the snapshot could be used in other scenarios CSimulatedAircraftList aircraftInRange(this->getAircraftInRange()); // thread safe copy from provider CAirspaceAircraftSnapshot snapshot( aircraftInRange, restricted, enabled, maxAircraft, maxRenderedDistance ); // lock block { QWriteLocker l(&m_lockSnapshot); bool wasValid = m_latestAircraftSnapshot.isValidSnapshot(); if (wasValid) { snapshot.setRestrictionChanged(m_latestAircraftSnapshot); } m_latestAircraftSnapshot = snapshot; if (!wasValid) { return; } // ignore the 1st snapshot } emit this->airspaceAircraftSnapshot(snapshot); } } // ns