swift
airspaceanalyzer.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QDateTime>
7 #include <QMetaObject>
8 #include <QReadLocker>
9 #include <QString>
10 #include <QThread>
11 #include <QWriteLocker>
12 
13 #include "core/airspacemonitor.h"
15 #include "misc/aviation/callsign.h"
17 #include "misc/logmessage.h"
19 #include "misc/statusmessage.h"
20 #include "misc/threadutils.h"
21 
22 using namespace swift::misc;
23 using namespace swift::misc::aviation;
24 using namespace swift::misc::geo;
25 using namespace swift::misc::network;
26 using namespace swift::misc::physical_quantities;
27 using namespace swift::misc::simulation;
28 using namespace swift::core::fsd;
29 
30 namespace swift::core
31 {
32  CAirspaceAnalyzer::CAirspaceAnalyzer(IOwnAircraftProvider *ownAircraftProvider, CFSDClient *fsdClient,
33  CAirspaceMonitor *airspaceMonitorParent)
34  : CContinuousWorker(airspaceMonitorParent, "CAirspaceAnalyzer"), COwnAircraftAware(ownAircraftProvider),
35  CRemoteAircraftAware(airspaceMonitorParent)
36  {
37  Q_ASSERT_X(fsdClient, Q_FUNC_INFO, "Network object required to connect");
38 
39  // all in new thread from here on
40  this->setObjectName(this->getName());
41  m_updateTimer.start(7500);
42  m_lastWatchdogCallMsSinceEpoch = QDateTime::currentMSecsSinceEpoch();
43  bool c = connect(&m_updateTimer, &QTimer::timeout, this, &CAirspaceAnalyzer::onTimeout);
44  Q_ASSERT(c);
45 
46  // network connected
47 
48  // those are CID and not callsign related
49  // c = connect(fsdClient, &CFSDClient::deletePilotReceived, this,
50  // &CAirspaceAnalyzer::watchdogRemoveAircraftCallsign, Qt::QueuedConnection); Q_ASSERT(c); c =
51  // connect(fsdClient, &CFSDClient::deleteAtcReceived, this, &CAirspaceAnalyzer::watchdogRemoveAtcCallsign,
52  // Qt::QueuedConnection); Q_ASSERT(c);
53 
54  c = connect(fsdClient, &CFSDClient::connectionStatusChanged, this,
55  &CAirspaceAnalyzer::onConnectionStatusChanged, Qt::QueuedConnection);
56  Q_ASSERT(c);
57 
58  // network situations
59  c = connect(fsdClient, &CFSDClient::pilotDataUpdateReceived, this, &CAirspaceAnalyzer::onNetworkPositionUpdate,
60  Qt::QueuedConnection);
61  Q_ASSERT(c);
62  c = connect(fsdClient, &CFSDClient::atcDataUpdateReceived, this, &CAirspaceAnalyzer::watchdogTouchAtcCallsign,
63  Qt::QueuedConnection);
64  Q_ASSERT(c);
65 
66  // Monitor
67  c = connect(airspaceMonitorParent, &CAirspaceMonitor::addedAircraftSituation, this,
68  &CAirspaceAnalyzer::watchdogTouchAircraftCallsign);
69  Q_ASSERT(c);
70  c = connect(airspaceMonitorParent, &CAirspaceMonitor::removedAircraft, this,
71  &CAirspaceAnalyzer::watchdogRemoveAircraftCallsign);
72  Q_ASSERT(c);
73  c = connect(airspaceMonitorParent, &CAirspaceMonitor::atcStationDisconnected, this,
74  &CAirspaceAnalyzer::onAtcStationDisconnected);
75  Q_ASSERT(c);
76 
77  // --------------------
78  Q_UNUSED(c)
79 
80  // start in own thread
81  this->start(QThread::LowestPriority);
82  }
83 
85  {
86  QReadLocker l(&m_lockSnapshot);
87  return m_latestAircraftSnapshot;
88  }
89 
90  void CAirspaceAnalyzer::setSimulatorRenderRestrictionsChanged(bool restricted, bool enabled, int maxAircraft,
91  const CLength &maxRenderedDistance)
92  {
93  QWriteLocker l(&m_lockRestrictions);
94  m_simulatorRenderedAircraftRestricted = restricted;
95  m_simulatorRenderingEnabled = enabled;
96  m_simulatorMaxRenderedAircraft = maxAircraft;
97  m_simulatorMaxRenderedDistance = maxRenderedDistance;
98  }
99 
101 
102  void CAirspaceAnalyzer::onNetworkPositionUpdate(const CAircraftSituation &situation,
103  const CTransponder &transponder)
104  {
105  Q_UNUSED(transponder)
106  this->watchdogTouchAircraftCallsign(situation);
107  }
108 
109  void CAirspaceAnalyzer::onAtcStationDisconnected(const CAtcStation &station)
110  {
111  const CCallsign cs = station.getCallsign();
112  this->watchdogRemoveAtcCallsign(cs);
113  }
114 
115  void CAirspaceAnalyzer::watchdogTouchAircraftCallsign(const CAircraftSituation &situation)
116  {
117  const CCallsign cs = situation.getCallsign();
118  Q_ASSERT_X(!cs.isEmpty(), Q_FUNC_INFO, "No callsign in situaton");
119  m_aircraftCallsignTimestamps[cs] = QDateTime::currentMSecsSinceEpoch();
120  }
121 
122  void CAirspaceAnalyzer::watchdogTouchAtcCallsign(const CCallsign &callsign, const CFrequency &frequency,
123  const CCoordinateGeodetic &position, const CLength &range)
124  {
125  Q_UNUSED(frequency)
126  Q_UNUSED(position)
127  Q_UNUSED(range)
128  m_atcCallsignTimestamps[callsign] = QDateTime::currentMSecsSinceEpoch();
129  }
130 
131  void CAirspaceAnalyzer::onConnectionStatusChanged(CConnectionStatus oldStatus, CConnectionStatus newStatus)
132  {
133  Q_UNUSED(oldStatus)
134  if (newStatus.isDisconnected())
135  {
136  this->clear();
137  m_updateTimer.stop();
138  }
139  else if (newStatus.isConnected()) { m_updateTimer.start(); }
140  }
141 
142  void CAirspaceAnalyzer::onTimeout()
143  {
144  if (!this->isEnabled()) { return; }
145  this->analyzeAirspace();
146  this->watchdogCheckTimeouts();
147  }
148 
150  {
151  m_aircraftCallsignTimestamps.clear();
152  m_atcCallsignTimestamps.clear();
153 
154  QWriteLocker l(&m_lockSnapshot);
155  m_latestAircraftSnapshot = CAirspaceAircraftSnapshot();
156  }
157 
158  void CAirspaceAnalyzer::watchdogRemoveAircraftCallsign(const CCallsign &callsign)
159  {
160  m_aircraftCallsignTimestamps.remove(callsign);
161  }
162 
163  void CAirspaceAnalyzer::watchdogRemoveAtcCallsign(const CCallsign &callsign)
164  {
165  m_atcCallsignTimestamps.remove(callsign);
166  }
167 
168  void CAirspaceAnalyzer::watchdogCheckTimeouts()
169  {
170  // this is a trick to not remove everything while debugging
171  const qint64 currentTimeMsEpoch = QDateTime::currentMSecsSinceEpoch();
172  const qint64 callDiffMs = currentTimeMsEpoch - m_lastWatchdogCallMsSinceEpoch;
173  const qint64 callThresholdMs = static_cast<qint64>(m_updateTimer.interval() * 1.5);
174  m_lastWatchdogCallMsSinceEpoch = currentTimeMsEpoch;
175  if (callDiffMs > callThresholdMs)
176  {
177  // allow some time to normalize before checking again
178  m_doNotRunAgainBefore = currentTimeMsEpoch + 2 * callThresholdMs;
179  return;
180  }
181  if (m_doNotRunAgainBefore > currentTimeMsEpoch) { return; }
182  m_doNotRunAgainBefore = -1;
183 
184  // checks
185  const std::chrono::milliseconds aircraftTimeoutMs = m_timeoutAircraft;
186  const std::chrono::milliseconds atcTimeoutMs = m_timeoutAtc;
187  const qint64 timeoutAircraftEpochMs = currentTimeMsEpoch - aircraftTimeoutMs.count();
188  const qint64 timeoutAtcEpochMs = currentTimeMsEpoch - atcTimeoutMs.count();
189  const bool enabled = m_enabledWatchdog;
190 
191  const QList<CCallsign> callsignsAircraft = m_aircraftCallsignTimestamps.keys();
192  for (const CCallsign &callsign : callsignsAircraft) // clazy:exclude=container-anti-pattern,range-loop
193  {
194  if (!enabled)
195  {
196  m_aircraftCallsignTimestamps[callsign] = timeoutAircraftEpochMs + 1000;
197  } // fake value so it can be re-enabled
198  const qint64 tsv = m_aircraftCallsignTimestamps.value(callsign);
199  if (tsv > timeoutAircraftEpochMs) { continue; }
200  CLogMessage(this).debug() << QStringLiteral("Aircraft '%1' timed out after %2ms")
201  .arg(callsign.toQString())
202  .arg(currentTimeMsEpoch - tsv);
203  m_aircraftCallsignTimestamps.remove(callsign);
204  emit this->timeoutAircraft(callsign);
205  }
206 
207  const QList<CCallsign> callsignsAtc = m_atcCallsignTimestamps.keys();
208  for (const CCallsign &callsign : callsignsAtc) // clazy:exclude=container-anti-pattern,range-loop
209  {
210  if (!enabled)
211  {
212  m_aircraftCallsignTimestamps[callsign] = timeoutAtcEpochMs + 1000;
213  } // fake value so it can be re-enabled
214  const qint64 tsv = m_atcCallsignTimestamps.value(callsign);
215  if (tsv > timeoutAtcEpochMs) { continue; }
216  CLogMessage(this).debug() << QStringLiteral("ATC '%1' timed out after %2ms")
217  .arg(callsign.toQString())
218  .arg(currentTimeMsEpoch - tsv);
219  m_atcCallsignTimestamps.remove(callsign);
220  emit this->timeoutAtc(callsign);
221  }
222  }
223 
224  void CAirspaceAnalyzer::analyzeAirspace()
225  {
226  Q_ASSERT_X(!CThreadUtils::thisIsMainThread(), Q_FUNC_INFO, "Expect to run in background thread");
227  Q_ASSERT_X(thread() != qApp->thread(), Q_FUNC_INFO, "Expect to run in background thread affinity");
228 
229  bool restricted, enabled;
230  int maxAircraft;
231  CLength maxRenderedDistance;
232  {
233  QReadLocker l(&m_lockRestrictions);
234  restricted = m_simulatorRenderedAircraftRestricted;
235  enabled = m_simulatorRenderingEnabled;
236  maxAircraft = m_simulatorMaxRenderedAircraft;
237  maxRenderedDistance = m_simulatorMaxRenderedDistance;
238  }
239 
240  // remark for simulation snapshot is used when there are restrictions
241  // nevertheless we calculate all the time as the snapshot could be used in other scenarios
242 
243  CSimulatedAircraftList aircraftInRange(this->getAircraftInRange()); // thread safe copy from provider
244  CAirspaceAircraftSnapshot snapshot(aircraftInRange, restricted, enabled, maxAircraft, maxRenderedDistance);
245 
246  // lock block
247  {
248  QWriteLocker l(&m_lockSnapshot);
249  bool wasValid = m_latestAircraftSnapshot.isValidSnapshot();
250  if (wasValid) { snapshot.setRestrictionChanged(m_latestAircraftSnapshot); }
251  m_latestAircraftSnapshot = snapshot;
252  if (!wasValid) { return; } // ignore the 1st snapshot
253  }
254 
255  emit this->airspaceAircraftSnapshot(snapshot);
256  }
257 } // namespace swift::core
void timeoutAircraft(const swift::misc::aviation::CCallsign &callsign)
Callsign has timed out.
virtual ~CAirspaceAnalyzer()
Destructor.
void setSimulatorRenderRestrictionsChanged(bool restricted, bool enabled, int maxAircraft, const swift::misc::physical_quantities::CLength &maxRenderedDistance)
Render restrictions in simulator.
void timeoutAtc(const swift::misc::aviation::CCallsign &callsign)
Callsign has timed out.
void airspaceAircraftSnapshot(const swift::misc::simulation::CAirspaceAircraftSnapshot &snapshot)
New aircraft snapshot.
swift::misc::simulation::CAirspaceAircraftSnapshot getLatestAirspaceAircraftSnapshot() const
Get the latest snapshot.
Keeps track of other entities in the airspace: aircraft, ATC stations, etc. Central instance of data ...
void atcStationDisconnected(const swift::misc::aviation::CAtcStation &station)
ATC station disconnected.
FSD client Todo: Send (interim) data updates automatically Todo Check ':' in FSD messages....
Definition: fsdclient.h:86
Base class for a long-lived worker object which lives in its own thread.
Definition: worker.h:275
bool isEnabled() const
Enabled (running)?
Definition: worker.h:300
const QString & getName()
Name of the worker.
Definition: worker.h:311
QTimer m_updateTimer
timer which can be used by implementing classes
Definition: worker.h:333
void start(QThread::Priority priority=QThread::InheritPriority)
Starts a thread and moves the worker into it.
Definition: worker.cpp:166
Class for emitting a log message.
Definition: logmessage.h:27
Derived & debug()
Set the severity to debug.
static bool thisIsMainThread()
Is the current thread the application thread?
Definition: threadutils.cpp:21
Value object encapsulating information of an aircraft's situation.
const CCallsign & getCallsign() const
Corresponding callsign.
Value object encapsulating information about an ATC station.
Definition: atcstation.h:38
const CCallsign & getCallsign() const
Get callsign.
Definition: atcstation.h:84
Value object encapsulating information of a callsign.
Definition: callsign.h:30
bool isEmpty() const
Is empty?
Definition: callsign.h:63
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
Value object encapsulating information about a connection status.
bool isConnected() const
Query status.
bool isDisconnected() const
Query status.
Physical unit length (length)
Definition: length.h:18
Delegating class which can be directly used to access an.
Class which can be directly used to access an.
CSimulatedAircraftList getAircraftInRange() const
All remote aircraft.
void addedAircraftSituation(const swift::misc::aviation::CAircraftSituation &situation)
Situation added.
void removedAircraft(const swift::misc::aviation::CCallsign &callsign)
An aircraft disappeared.
Value object encapsulating a list of aircraft.
Direct threadsafe in memory access to own aircraft.
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
Free functions in swift::misc.