diff --git a/src/blackcore/airspace_analyzer.cpp b/src/blackcore/airspace_analyzer.cpp new file mode 100644 index 000000000..3ea1a904d --- /dev/null +++ b/src/blackcore/airspace_analyzer.cpp @@ -0,0 +1,106 @@ +/* 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 and at http://www.swift-project.org/license.html. 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 "airspace_analyzer.h" +#include "blackmisc/logmessage.h" + +using namespace BlackMisc; +using namespace BlackMisc::Aviation; +using namespace BlackMisc::PhysicalQuantities; + +namespace BlackCore +{ + + CAirspaceAnalyzer::CAirspaceAnalyzer(INetwork *network, QObject *parent) : CContinuousWorker(parent, "CAirspaceAnalyzer") + { + Q_ASSERT_X(network, Q_FUNC_INFO, "Network object required to connect"); + + // disconnect + bool c = connect(network, &INetwork::pilotDisconnected, this, &CAirspaceAnalyzer::ps_watchdogRemoveAircraftCallsign); + Q_ASSERT(c); + c = connect(network, &INetwork::atcDisconnected, this, &CAirspaceAnalyzer::ps_watchdogRemoveAtcCallsign); + Q_ASSERT(c); + + // update + c = connect(network, &INetwork::aircraftPositionUpdate, this, &CAirspaceAnalyzer::ps_watchdogTouchAircraftCallsign); + Q_ASSERT(c); + c = connect(network, &INetwork::atcPositionUpdate, this, &CAirspaceAnalyzer::ps_watchdogTouchAtcCallsign); + Q_ASSERT(c); + + // network + c = connect(network, &INetwork::connectionStatusChanged, this, &CAirspaceAnalyzer::ps_onConnectionStatusChanged); + Q_ASSERT(c); + Q_UNUSED(c); + } + + void CAirspaceAnalyzer::ps_watchdogTouchAircraftCallsign(const CAircraftSituation &situation, const CTransponder &transponder) + { + Q_ASSERT_X(!situation.getCallsign().isEmpty(), Q_FUNC_INFO, "No callsign in situaton"); + Q_UNUSED(transponder); + m_aircraftCallsignTimestamps[situation.getCallsign()] = QDateTime::currentMSecsSinceEpoch(); + } + + void CAirspaceAnalyzer::ps_watchdogTouchAtcCallsign(const CCallsign &callsign, const CFrequency &freq, const Geo::CCoordinateGeodetic &pos, const CLength &range) + { + Q_UNUSED(freq); + Q_UNUSED(pos); + Q_UNUSED(range); + m_atcCallsignTimestamps[callsign] = QDateTime::currentMSecsSinceEpoch(); + } + + void CAirspaceAnalyzer::ps_onConnectionStatusChanged(INetwork::ConnectionStatus oldStatus, INetwork::ConnectionStatus newStatus) + { + Q_UNUSED(oldStatus); + if (newStatus == INetwork::Disconnected) + { + this->clear(); + } + } + + void CAirspaceAnalyzer::clear() + { + m_aircraftCallsignTimestamps.clear(); + m_atcCallsignTimestamps.clear(); + } + + void CAirspaceAnalyzer::ps_watchdogRemoveAircraftCallsign(const CCallsign &callsign) + { + m_aircraftCallsignTimestamps.remove(callsign); + } + + void CAirspaceAnalyzer::ps_watchdogRemoveAtcCallsign(const CCallsign &callsign) + { + m_atcCallsignTimestamps.remove(callsign); + } + + void CAirspaceAnalyzer::watchdogCheckTimeouts() + { + qint64 aircraftTimeoutMs = m_timeoutAircraft.valueInteger(CTimeUnit::ms()); + qint64 atcTimeoutMs = m_timeoutAtc.valueInteger(CTimeUnit::ms()); + qint64 currentTimeMsEpoch = QDateTime::currentMSecsSinceEpoch(); + qint64 timeoutAircraftEpochMs = currentTimeMsEpoch - aircraftTimeoutMs; + qint64 timeoutAtcEpochMs = currentTimeMsEpoch - atcTimeoutMs; + + for (const CCallsign &callsign : m_aircraftCallsignTimestamps.keys()) + { + if (m_aircraftCallsignTimestamps.value(callsign) > timeoutAircraftEpochMs) { continue; } + CLogMessage(this).debug() << "Aircraft " << callsign.toQString() << "timed out!"; + m_aircraftCallsignTimestamps.remove(callsign); + emit timeoutAircraft(callsign); + } + + for (const CCallsign &callsign : m_atcCallsignTimestamps.keys()) + { + if (m_atcCallsignTimestamps.value(callsign) > timeoutAtcEpochMs) { continue; } + CLogMessage(this).debug() << "ATC " << callsign.toQString() << "timed out!"; + m_atcCallsignTimestamps.remove(callsign); + emit timeoutAtc(callsign); + } + } +} // ns diff --git a/src/blackcore/airspace_analyzer.h b/src/blackcore/airspace_analyzer.h new file mode 100644 index 000000000..481dd76b2 --- /dev/null +++ b/src/blackcore/airspace_analyzer.h @@ -0,0 +1,83 @@ +/* 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 and at http://www.swift-project.org/license.html. 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. + */ + +//! \file + +#ifndef BLACKCORE_AIRSPACE_ANALYZER_H +#define BLACKCORE_AIRSPACE_ANALYZER_H + +#include "blackcore/network.h" +#include "blackmisc/worker.h" +#include "blackmisc/pq/time.h" +#include "blackmisc/aviation/callsign.h" +#include +#include + +namespace BlackCore +{ + //! Class monitoring and analyzing (closests aircraft, outdated aircraft / watchdog) airspace + //! in background. + //! + //! \details Watchdog functionality: This class was introduced due to a flaw in the VATSIM server implementation: Every client needs to send an add/delete packet on its own to inform other + //! clients nearby. The server does not take care of that. When a client crashes, no delete packet is ever sent. This class therefore monitors callsigns and emits a timeout signal if it + //! wasn't resetted during the specified timeout value. + //! + class CAirspaceAnalyzer : public BlackMisc::CContinuousWorker + { + Q_OBJECT + + public: + //! List of callsigns and their last activity + typedef QHash CCallsignTimestampSet; + + //! Constructor + CAirspaceAnalyzer(INetwork *network, QObject *parent); + + public slots: + //! Clear + void clear(); + + signals: + //! Callsign has timed out + void timeoutAircraft(const BlackMisc::Aviation::CCallsign &callsign); + + //! Callsign has timed out + void timeoutAtc(const BlackMisc::Aviation::CCallsign &callsign); + + private slots: + //! Remove callsign from watch list + void ps_watchdogRemoveAircraftCallsign(const BlackMisc::Aviation::CCallsign &callsign); + + //! Remove callsign from watch list + void ps_watchdogRemoveAtcCallsign(const BlackMisc::Aviation::CCallsign &callsign); + + //! Reset timestamp for callsign + void ps_watchdogTouchAircraftCallsign(const BlackMisc::Aviation::CAircraftSituation &situation, const BlackMisc::Aviation::CTransponder &transponder); + + //! Reset timestamp for callsign + void ps_watchdogTouchAtcCallsign(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::PhysicalQuantities::CFrequency &freq, + const BlackMisc::Geo::CCoordinateGeodetic &pos, const BlackMisc::PhysicalQuantities::CLength &range); + + //! Connection status of network changed + void ps_onConnectionStatusChanged(INetwork::ConnectionStatus oldStatus, INetwork::ConnectionStatus newStatus); + + private: + //! Check for time outs + void watchdogCheckTimeouts(); + + CCallsignTimestampSet m_aircraftCallsignTimestamps; + CCallsignTimestampSet m_atcCallsignTimestamps; + BlackMisc::PhysicalQuantities::CTime m_timeoutAircraft = {15, BlackMisc::PhysicalQuantities::CTimeUnit::s() }; //!< Timeout value for watchdog functionality + BlackMisc::PhysicalQuantities::CTime m_timeoutAtc = {50, BlackMisc::PhysicalQuantities::CTimeUnit::s() }; //!< Timeout value for watchdog functionality + + }; + +} // namespace + +#endif diff --git a/src/blackcore/airspace_monitor.cpp b/src/blackcore/airspace_monitor.cpp index d6bd923a4..5ee9544a6 100644 --- a/src/blackcore/airspace_monitor.cpp +++ b/src/blackcore/airspace_monitor.cpp @@ -30,7 +30,7 @@ namespace BlackCore : QObject(parent), COwnAircraftAwareReadOnly(ownAircraftProvider), m_network(network), m_vatsimBookingReader(bookings), m_vatsimDataFileReader(dataFile), - m_atcWatchdog(this), m_aircraftWatchdog(this) + m_analyzer(new CAirspaceAnalyzer(network, this)) { this->connect(this->m_network, &INetwork::atcPositionUpdate, this, &CAirspaceMonitor::ps_atcPositionUpdate); this->connect(this->m_network, &INetwork::atisReplyReceived, this, &CAirspaceMonitor::ps_atisReceived); @@ -49,18 +49,15 @@ namespace BlackCore this->connect(this->m_network, &INetwork::customFSinnPacketReceived, this, &CAirspaceMonitor::ps_customFSinnPacketReceived); this->connect(this->m_network, &INetwork::serverReplyReceived, this, &CAirspaceMonitor::ps_serverReplyReceived); this->connect(this->m_network, &INetwork::aircraftConfigPacketReceived, this, &CAirspaceMonitor::ps_aircraftConfigReceived); - + this->connect(&m_interimPositionUpdateTimer, &QTimer::timeout, this, &CAirspaceMonitor::ps_sendInterimPosition); + // AutoConnection: this should also avoid race conditions by updating the bookings this->connect(this->m_vatsimBookingReader, &CVatsimBookingReader::dataRead, this, &CAirspaceMonitor::ps_receivedBookings); this->connect(this->m_vatsimDataFileReader, &CVatsimDataFileReader::dataRead, this, &CAirspaceMonitor::ps_receivedDataFile); - // Watchdog - // ATC stations send updates every 25s. Configure timeout after 50s. - this->m_atcWatchdog.setTimeout(CTime(50, CTimeUnit::s())); - this->connect(&this->m_aircraftWatchdog, &CAirspaceWatchdog::timeout, this, &CAirspaceMonitor::ps_pilotDisconnected); - this->connect(&this->m_atcWatchdog, &CAirspaceWatchdog::timeout, this, &CAirspaceMonitor::ps_atcControllerDisconnected); - - this->connect(&m_interimPositionUpdateTimer, &QTimer::timeout, this, &CAirspaceMonitor::ps_sendInterimPosition); + // Analyzer + this->connect(this->m_analyzer, &CAirspaceAnalyzer::timeoutAircraft, this, &CAirspaceMonitor::ps_pilotDisconnected); + this->connect(this->m_analyzer, &CAirspaceAnalyzer::timeoutAtc, this, &CAirspaceMonitor::ps_atcControllerDisconnected); } const CSimulatedAircraftList &CAirspaceMonitor::remoteAircraft() const @@ -504,13 +501,11 @@ namespace BlackCore void CAirspaceMonitor::removeAllOnlineAtcStations() { - m_atcWatchdog.removeAll(); m_atcStationsOnline.clear(); } void CAirspaceMonitor::removeAllAircraft() { - m_aircraftWatchdog.removeAll(); // upfront for (CAircraft aircraft : m_aircraftInRange) { const CCallsign cs(aircraft.getCallsign()); @@ -637,7 +632,6 @@ namespace BlackCore emit this->m_network->sendServerQuery(callsign); } - this->m_atcWatchdog.addCallsign(callsign); emit this->changedAtcStationsOnline(); // Remark: this->changedAtcStationOnlineConnectionStatus(station, true); // will be sent in psFsdAtisVoiceRoomReceived @@ -651,7 +645,6 @@ namespace BlackCore vm.addValue(CAtcStation::IndexRange, range); int changed = this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsign, vm, true); if (changed > 0) { emit this->changedAtcStationsOnline(); } - this->m_atcWatchdog.resetCallsign(callsign); } } @@ -659,9 +652,7 @@ namespace BlackCore { Q_ASSERT(BlackCore::isCurrentThreadCreatingThread(this)); - this->m_atcWatchdog.removeCallsign(callsign); this->m_otherClients.removeByCallsign(callsign); - if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsign)) { CAtcStation removedStation = this->m_atcStationsOnline.findFirstByCallsign(callsign); @@ -837,8 +828,6 @@ namespace BlackCore { this->m_network->sendIcaoCodesQuery(callsign); } - - this->m_aircraftWatchdog.addCallsign(callsign); emit this->addedAircraft(aircraft); } // connected } @@ -854,7 +843,6 @@ namespace BlackCore // here I expect always a changed value this->m_aircraftInRange.applyIfCallsign(callsign, vm); - this->m_aircraftWatchdog.resetCallsign(callsign); } emit this->changedAircraftInRange(); @@ -901,7 +889,6 @@ namespace BlackCore Q_ASSERT(BlackCore::isCurrentThreadCreatingThread(this)); // in case of inconsistencies I always remove here - this->m_aircraftWatchdog.removeCallsign(callsign); this->m_otherClients.removeByCallsign(callsign); this->removeFromAircraftCaches(callsign); @@ -967,7 +954,6 @@ namespace BlackCore // here I expect always a changed value this->m_aircraftInRange.setAircraftParts(callsign, parts); - this->m_aircraftWatchdog.resetCallsign(callsign); } void CAirspaceMonitor::ps_sendInterimPosition() diff --git a/src/blackcore/airspace_monitor.h b/src/blackcore/airspace_monitor.h index ddd86a7f9..097db6a43 100644 --- a/src/blackcore/airspace_monitor.h +++ b/src/blackcore/airspace_monitor.h @@ -25,7 +25,7 @@ #include "network.h" #include "vatsimbookingreader.h" #include "vatsimdatafilereader.h" -#include "airspace_watchdog.h" +#include "airspace_analyzer.h" namespace BlackCore { @@ -187,8 +187,7 @@ namespace BlackCore INetwork *m_network = nullptr; CVatsimBookingReader *m_vatsimBookingReader = nullptr; CVatsimDataFileReader *m_vatsimDataFileReader = nullptr; - CAirspaceWatchdog m_atcWatchdog; - CAirspaceWatchdog m_aircraftWatchdog; + CAirspaceAnalyzer *m_analyzer = nullptr; bool m_serverSupportsNameQuery = false; //!< not all servers support name query bool m_connected = false; //!< retrieve data bool m_sendInterimPositions = false; diff --git a/src/blackcore/airspace_watchdog.cpp b/src/blackcore/airspace_watchdog.cpp deleted file mode 100644 index 2901fb992..000000000 --- a/src/blackcore/airspace_watchdog.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* Copyright (C) 2014 - * 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 and at http://www.swift-project.org/license.html. 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 "airspace_watchdog.h" -#include "blackmisc/logmessage.h" - -using namespace BlackMisc; -using namespace BlackMisc::Aviation; -using namespace BlackMisc::PhysicalQuantities; - -namespace BlackCore -{ - - CAirspaceWatchdog::CAirspaceWatchdog(QObject *parent) - : QObject(parent) - { - startTimer(5000); - } - - CAirspaceWatchdog::CAirspaceWatchdog(const CTime &initialTimeOut, QObject *parent) - : QObject(parent), m_timeout(initialTimeOut) - { - } - - void CAirspaceWatchdog::addCallsign(const CCallsign &callsign) - { - if (m_callsignTimestamps.contains(callsign)) return; - m_callsignTimestamps.insert(callsign, QDateTime::currentDateTimeUtc()); - } - - void CAirspaceWatchdog::resetCallsign(const CCallsign &callsign) - { - if (m_callsignTimestamps.contains(callsign)) - { - m_callsignTimestamps[callsign] = QDateTime::currentDateTimeUtc(); - } - else - { - // that should rarely happen - CLogMessage(this).warning("Watchdog reset for non-existing callsign: %1") << callsign; - this->addCallsign(callsign); - } - } - - void CAirspaceWatchdog::removeCallsign(const CCallsign &callsign) - { - m_callsignTimestamps.remove(callsign); - } - - void CAirspaceWatchdog::removeAll() - { - m_callsignTimestamps.clear(); - } - - void CAirspaceWatchdog::timerEvent(QTimerEvent *event) - { - Q_UNUSED(event) - checkTimeouts(); - } - - void CAirspaceWatchdog::checkTimeouts() - { - for (CCallsign callsign : m_callsignTimestamps.keys()) - { - QDateTime timestamp = m_callsignTimestamps.value(callsign); - if (timestamp.secsTo(QDateTime::currentDateTimeUtc()) > m_timeout.value(CTimeUnit::s())) - { - CLogMessage(this).debug() << "Aircraft " << callsign.toQString() << "timed out!"; - m_callsignTimestamps.remove(callsign); - emit timeout(callsign); - } - } - } -} // ns