swift
simulator.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "core/simulator.h"
5 
6 #include <functional>
7 
8 #include <QDateTime>
9 #include <QDesktopServices>
10 #include <QDir>
11 #include <QFlag>
12 #include <QPointer>
13 #include <QString>
14 #include <QStringBuilder>
15 #include <QThread>
16 #include <QUrl>
17 #include <Qt>
18 #include <QtGlobal>
19 
20 #include "core/application.h"
21 #include "core/db/databaseutils.h"
22 #include "core/webdataservices.h"
23 #include "misc/crashhandler.h"
24 #include "misc/directoryutils.h"
25 #include "misc/logmessage.h"
26 #include "misc/math/mathutils.h"
28 #include "misc/threadutils.h"
29 #include "misc/verify.h"
30 
31 using namespace swift::config;
32 using namespace swift::misc;
33 using namespace swift::misc::aviation;
34 using namespace swift::misc::geo;
35 using namespace swift::misc::math;
36 using namespace swift::misc::simulation;
37 using namespace swift::misc::simulation::data;
38 using namespace swift::misc::simulation::settings;
39 using namespace swift::misc::physical_quantities;
40 using namespace swift::misc::network;
41 using namespace swift::misc::weather;
42 using namespace swift::core::db;
43 
44 namespace swift::core
45 {
46  const QStringList &ISimulator::getLogCategories()
47  {
48  static const QStringList cats({ CLogCategories::driver(), CLogCategories::plugin() });
49  return cats;
50  }
51 
52  ISimulator::~ISimulator() { this->safeKillTimer(); }
53 
54  ISimulator::SimulatorStatus ISimulator::getSimulatorStatus() const
55  {
56  if (!this->isConnected()) { return ISimulator::Disconnected; }
57  const SimulatorStatus status =
58  Connected |
59  (this->isSimulating() ? ISimulator::Simulating : static_cast<ISimulator::SimulatorStatusFlag>(0)) |
60  (this->isPaused() ? ISimulator::Paused : static_cast<ISimulator::SimulatorStatusFlag>(0));
61  return status;
62  }
63 
64  bool ISimulator::logicallyRemoveRemoteAircraft(const CCallsign &callsign)
65  {
66  // if not restricted, directly change
67  if (!this->getInterpolationSetupGlobal().isRenderingRestricted())
68  {
69  m_statsPhysicallyAddedAircraft++;
70  this->callPhysicallyRemoveRemoteAircraft(callsign);
71  return true;
72  }
73 
74  // will be removed with next snapshot onRecalculatedRenderedAircraft
75  return false;
76  }
77 
78  bool ISimulator::logicallyAddRemoteAircraft(const CSimulatedAircraft &remoteAircraft)
79  {
80  if (!this->validateModelOfAircraft(remoteAircraft))
81  {
82  const CCallsign cs = remoteAircraft.getCallsign();
83  CLogMessage(this).warning(u"Invalid aircraft detected, which will be disabled: '%1' '%2'")
84  << cs << remoteAircraft.getModelString();
85  this->updateAircraftEnabled(cs, false);
86  this->updateAircraftRendered(cs, false);
87  return false;
88  }
89 
90  // no invalid model should ever reach this place here
91  const bool renderingRestricted = this->getInterpolationSetupGlobal().isRenderingRestricted();
92  if (this->showDebugLogMessage())
93  {
94  this->debugLogMessage(Q_FUNC_INFO,
95  QStringLiteral("Restricted: %1 cs: '%2' enabled: %3")
96  .arg(boolToYesNo(renderingRestricted), remoteAircraft.getCallsignAsString(),
97  boolToYesNo(remoteAircraft.isEnabled())));
98  }
99  if (!remoteAircraft.isEnabled()) { return false; }
100 
101  // if not restricted, directly change
102  if (!renderingRestricted)
103  {
104  this->callPhysicallyAddRemoteAircraft(remoteAircraft);
105  return true;
106  }
107 
108  // restricted -> will be added with next snapshot onRecalculatedRenderedAircraft
109  return false;
110  }
111 
112  bool ISimulator::followAircraft(const CCallsign &callsign)
113  {
114  Q_UNUSED(callsign)
115  return false;
116  }
117 
118  void ISimulator::recalculateAllAircraft() { this->setUpdateAllRemoteAircraft(); }
119 
120  void ISimulator::setFlightNetworkConnected(bool connected) { m_networkConnected = connected; }
121 
122  void ISimulator::clearAllRemoteAircraftData()
123  {
124  // rendering related stuff
125  m_addAgainAircraftWhenRemoved.clear();
126  m_callsignsToBeRendered.clear();
127  this->resetLastSentValues(); // clear all last sent values
128  m_updateRemoteAircraftInProgress = false;
129 
130  this->clearInterpolationSetupsPerCallsign();
131  this->resetAircraftStatistics();
132  }
133 
134  void ISimulator::debugLogMessage(const QString &msg)
135  {
136  if (!this->showDebugLogMessage()) { return; }
137  if (msg.isEmpty()) { return; }
138  const CStatusMessage m = CStatusMessage(this).info(u"%1") << msg;
139  emit this->driverMessages(m);
140  }
141 
142  void ISimulator::debugLogMessage(const QString &funcInfo, const QString &msg)
143  {
144  if (!this->showDebugLogMessage()) { return; }
145  if (msg.isEmpty()) { return; }
146  const CStatusMessage m = CStatusMessage(this).info(u"%1 %2") << msg << funcInfo;
147  emit this->driverMessages(m);
148  }
149 
150  bool ISimulator::showDebugLogMessage() const
151  {
152  const bool show = this->getInterpolationSetupGlobal().showSimulatorDebugMessages();
153  return show;
154  }
155 
156  void ISimulator::resetAircraftFromProvider(const CCallsign &callsign)
157  {
158  const CSimulatedAircraft aircraft(this->getAircraftInRangeForCallsign(callsign));
159  const bool enabled = aircraft.isEnabled();
160  if (enabled)
161  {
162  // are we already visible?
163  if (!this->isPhysicallyRenderedAircraft(callsign))
164  {
165  this->callPhysicallyAddRemoteAircraft(aircraft); // enable/disable
166  }
167  }
168  else { this->callPhysicallyRemoveRemoteAircraft(callsign); }
169  }
170 
171  void ISimulator::clearData(const CCallsign &callsign)
172  {
173  m_statsPhysicallyRemovedAircraft++;
174  m_lastSentParts.remove(callsign);
175  m_lastSentSituations.remove(callsign);
176  m_loopbackSituations.clear();
177  this->removeInterpolationSetupPerCallsign(callsign);
178  }
179 
180  bool ISimulator::addLoopbackSituation(const CAircraftSituation &situation)
181  {
182  const CCallsign cs = situation.getCallsign();
183  if (!this->isLogCallsign(cs)) { return false; }
184  CAircraftSituationList &situations = m_loopbackSituations[cs];
185  situations.push_frontKeepLatestAdjustedFirst(situation, true, 10);
186  return true;
187  }
188 
189  bool ISimulator::addLoopbackSituation(const CCallsign &callsign, const CElevationPlane &elevationPlane,
190  const CLength &cg)
191  {
192  if (!this->isLogCallsign(callsign)) { return false; }
193  CAircraftSituation situation(callsign, elevationPlane);
194  situation.setGroundElevation(elevationPlane, CAircraftSituation::FromProvider);
195  situation.setCG(cg);
196  situation.setCurrentUtcTime();
197  situation.setTimeOffsetMs(0);
198  CAircraftSituationList &situations = m_loopbackSituations[callsign];
199  situations.push_frontKeepLatestAdjustedFirst(situation, true, 10);
200  return true;
201  }
202 
203  void ISimulator::reset()
204  {
205  this->clearAllRemoteAircraftData(); // reset
206  m_averageFps = -1.0;
207  m_simTimeRatio = 1.0;
208  m_trackMilesShort = 0.0;
209  m_minutesLate = 0.0;
210  }
211 
212  bool ISimulator::isUpdateAllRemoteAircraft(qint64 currentTimestamp) const
213  {
214  if (m_updateAllRemoteAircraftUntil < 1) { return false; }
215  if (currentTimestamp < 0) { currentTimestamp = QDateTime::currentMSecsSinceEpoch(); }
216  return (m_updateAllRemoteAircraftUntil > currentTimestamp);
217  }
218 
219  void ISimulator::setUpdateAllRemoteAircraft(qint64 currentTimestamp, qint64 forMs)
220  {
221  if (currentTimestamp < 0) { currentTimestamp = QDateTime::currentMSecsSinceEpoch(); }
222  if (forMs < 0) { forMs = 10 * 1000; }
223  m_updateAllRemoteAircraftUntil = currentTimestamp + forMs;
224  this->resetLastSentValues();
225  }
226 
227  void ISimulator::resetUpdateAllRemoteAircraft() { m_updateAllRemoteAircraftUntil = -1; }
228 
229  void ISimulator::safeKillTimer()
230  {
231  if (m_timerId < 0) { return; }
232  SWIFT_AUDIT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Try to kill timer from another thread");
233  this->killTimer(m_timerId);
234  m_timerId = -1;
235  }
236 
237  CInterpolationAndRenderingSetupPerCallsign ISimulator::getInterpolationSetupConsolidated(const CCallsign &callsign,
238  bool forceFullUpdate) const
239  {
240  CInterpolationAndRenderingSetupPerCallsign setup = this->getInterpolationSetupPerCallsignOrDefault(callsign);
241  const CClient client = this->getClientOrDefaultForCallsign(callsign);
242  setup.consolidateWithClient(client);
243  if (forceFullUpdate) { setup.setForceFullInterpolation(forceFullUpdate); }
244  return setup;
245  }
246 
247  bool ISimulator::requestElevation(const ICoordinateGeodetic &reference, const CCallsign &callsign)
248  {
249  Q_UNUSED(reference)
250  Q_UNUSED(callsign)
251  return false;
252  }
253 
254  void ISimulator::callbackReceivedRequestedElevation(const CElevationPlane &plane, const CCallsign &callsign,
255  bool isWater)
256  {
257  if (this->isShuttingDown()) { return; }
258  if (plane.isNull()) { return; } // this happens if requested for a coordinate where scenery is not available
259 
260  // Update in remote aircraft for given callsign
261  // this will trigger also a position update, new interpolant etc.
262  bool updatedForOnGroundPosition = false;
263  const int updated = CRemoteAircraftAware::updateAircraftGroundElevation(
264  callsign, plane, CAircraftSituation::FromProvider, &updatedForOnGroundPosition);
265 
266  // update in simulator and cache
267  const bool likelyOnGroundElevation = updated > 0 && updatedForOnGroundPosition;
268  ISimulationEnvironmentProvider::rememberGroundElevation(callsign, likelyOnGroundElevation,
269  plane); // in simulator
270 
271  // signal we have received the elevation
272  // used by log display
273  emit this->receivedRequestedElevation(plane, callsign);
274  Q_UNUSED(isWater)
275  }
276 
277  void ISimulator::resetAircraftStatistics()
278  {
279  m_statsUpdateAircraftRuns = 0;
280  m_statsUpdateAircraftTimeAvgMs = 0;
281  m_statsUpdateAircraftTimeTotalMs = 0;
282  m_statsMaxUpdateTimeMs = 0;
283  m_statsCurrentUpdateTimeMs = 0;
284  m_statsPhysicallyAddedAircraft = 0;
285  m_statsPhysicallyRemovedAircraft = 0;
286  m_statsUpdateAircraftLimited = 0;
287  m_statsLastUpdateAircraftRequestedMs = 0;
288  m_statsUpdateAircraftRequestedDeltaMs = 0;
289  ISimulationEnvironmentProvider::resetSimulationEnvironmentStatistics();
290  }
291 
292  bool ISimulator::isEmulatedDriver() const
293  {
294  const QString className = this->metaObject()->className();
295  return className.contains("emulated", Qt::CaseInsensitive);
296  }
297 
298  bool ISimulator::parseCommandLine(const QString &commandLine, const CIdentifier &originator)
299  {
300  if (this->isMyIdentifier(originator)) { return false; }
301  if (this->isShuttingDown()) { return false; }
302 
303  if (commandLine.isEmpty()) { return false; }
304  CSimpleCommandParser parser({ ".plugin", ".drv", ".driver" });
305  parser.parse(commandLine);
306  if (!parser.isKnownCommand()) { return false; }
307 
308  // .plugin unload
309  if (parser.matchesPart(1, "unload"))
310  {
311  this->unload();
312  return true;
313  }
314 
315  // .plugin log interpolator
316  const QString part1(parser.part(1).toLower().trimmed());
317  if (part1.startsWith("logint") && parser.hasPart(2))
318  {
319  const QString part2 = parser.part(2).toLower();
320  if (part2 == "off" || part2 == "false")
321  {
322  CLogMessage(this).info(u"Disabled interpolation logging");
323  this->clearInterpolationLogCallsigns();
324  return true;
325  }
326  if (part2 == "clear" || part2 == "clr")
327  {
328  m_interpolationLogger.clearLog();
329  CLogMessage(this).info(u"Cleared interpolation logging");
330  this->clearInterpolationLogCallsigns();
331  return true;
332  }
333  if (part2.startsWith("max"))
334  {
335  if (!parser.hasPart(3)) { return false; }
336  bool ok;
337  const int max = parser.part(3).toInt(&ok);
338  if (!ok) { return false; }
339  m_interpolationLogger.setMaxSituations(max);
340  CLogMessage(this).info(u"Max.situations logged: %1") << max;
341  return true;
342  }
343  if (part2 == "write" || part2 == "save")
344  {
345  // stop logging of other log
346  this->clearInterpolationLogCallsigns();
347 
348  // write
349  const bool clearLog = true;
350  m_interpolationLogger.writeLogInBackground(clearLog);
351  CLogMessage(this).info(u"Started writing interpolation log");
352  return true;
353  }
354  if (part2 == "show")
355  {
356  const QDir dir(CInterpolationLogger::getLogDirectory());
357  if (CDirectoryUtils::isDirExisting(dir))
358  {
359  const QUrl dirUrl = QUrl::fromLocalFile(dir.absolutePath());
360  QDesktopServices::openUrl(dirUrl); // show dir in browser
361  }
362  else { CLogMessage(this).warning(u"No interpolation log directory"); }
363  return true;
364  }
365 
366  const CCallsign cs(part2.toUpper());
367  if (!cs.isValid()) { return false; }
368  if (this->getAircraftInRangeCallsigns().contains(cs))
369  {
370  CLogMessage(this).info(u"Will log interpolation for '%1'") << cs.asString();
371  this->setLogCallsign(true, cs);
372  return true;
373  }
374  else
375  {
376  CLogMessage(this).warning(u"Cannot log interpolation for '%1', no aircraft in range") << cs.asString();
377  return false;
378  }
379  } // logint
380 
381  if (part1.startsWith("spline") || part1.startsWith("linear"))
382  {
383  if (parser.hasPart(2))
384  {
385  const CCallsign cs(parser.part(2));
386  const bool changed = this->setInterpolationMode(part1, cs);
387  CLogMessage(this).info(changed ? QStringLiteral("Changed interpolation mode for '%1'") :
388  QStringLiteral("Unchanged interpolation mode for '%1'"))
389  << cs.asString();
390  return true;
391  }
392  else
393  {
394  CInterpolationAndRenderingSetupGlobal setup = this->getInterpolationSetupGlobal();
395  const bool changed = setup.setInterpolatorMode(part1);
396  if (changed) { this->setInterpolationSetupGlobal(setup); }
397  CLogMessage(this).info(changed ? QStringLiteral("Changed interpolation mode globally") :
398  QStringLiteral("Unchanged interpolation mode"));
399  return true;
400  }
401  } // spline/linear
402 
403  if (part1.startsWith("pos"))
404  {
405  CCallsign cs(parser.part(2).toUpper());
406  if (!cs.isValid())
407  {
408  const CCallsignSet csSet = this->getLogCallsigns();
409  if (csSet.size() != 1) { return false; }
410 
411  // if there is just one we take that one
412  cs = *csSet.begin();
413  }
414 
415  this->setLogCallsign(true, cs);
416  CLogMessage(this).info(u"Display position for '%1'") << cs.asString();
417  this->displayLoggedSituationInSimulator(cs, true);
418  return true;
419  }
420 
421  if (parser.hasPart(2) && (part1.startsWith("aircraft") || part1.startsWith("ac")))
422  {
423  const QString part2 = parser.part(2).toLower();
424  if (parser.hasPart(3) && (part2.startsWith("readd") || part2.startsWith("re-add")))
425  {
426  const QString cs = parser.part(3).toUpper();
427  if (cs == "all")
428  {
429  this->physicallyRemoveAllRemoteAircraft();
430  const CStatusMessageList msgs = this->debugVerifyStateAfterAllAircraftRemoved();
431  this->clearAllRemoteAircraftData(); // "dot command"
432  if (!msgs.isEmpty()) { emit this->driverMessages(msgs); }
433  const CSimulatedAircraftList aircraft = this->getAircraftInRange();
434  for (const CSimulatedAircraft &a : aircraft)
435  {
436  if (a.isEnabled()) { this->logicallyAddRemoteAircraft(a); }
437  }
438  }
439  else if (CCallsign::isValidAircraftCallsign(cs))
440  {
441  this->logicallyReAddRemoteAircraft(cs);
442  return true;
443  }
444  return false;
445  }
446  if (parser.hasPart(3) && (part2.startsWith("rm") || part2.startsWith("remove")))
447  {
448  const QString cs = parser.part(3).toUpper();
449  if (CCallsign::isValidAircraftCallsign(cs)) { this->logicallyRemoveRemoteAircraft(cs); }
450  }
451 
452  return false;
453  }
454 
455  if (part1.startsWith("limit"))
456  {
457  const int perSecond = parser.toInt(2, -1);
458  this->limitToUpdatesPerSecond(perSecond);
459  CLogMessage(this).info(u"Remote aircraft updates limitations: %1") << this->updateAircraftLimitationInfo();
460  return true;
461  }
462 
463  // CG override
464  if (part1 == QStringView(u"cg"))
465  {
466  if (parser.part(2).startsWith("clear", Qt::CaseInsensitive))
467  {
468  CLogMessage(this).info(u"Clear all overridden CGs");
469  const CLengthPerCallsign cgsPerCallsign = this->clearCGOverrides();
470 
471  // restore all CGs
472  for (const CCallsign &cs : this->getAircraftInRangeCallsigns())
473  {
474  // reset CGs per callsign
475  const CLength cg = cgsPerCallsign.contains(cs) ? cgsPerCallsign[cs] : CLength::null();
476  this->updateCG(cs, cg);
477  }
478  return true;
479  }
480 
481  if (parser.hasPart(3))
482  {
483  // ms can be a string like "B773 B773_RR SDM"
484  const QString ms = parser.partAndRemainingStringAfter(3).toUpper();
485  CLength cg;
486  cg.parseFromString(parser.part(2), CPqString::SeparatorBestGuess);
487  if (!ms.isEmpty())
488  {
489  CLogMessage(this).info(u"Setting CG for '%1': %2") << ms << cg.valueRoundedWithUnit();
490  const bool set = this->insertCGForModelStringOverridden(cg, ms);
491  if (set)
492  {
493  const CCallsignSet callsigns = this->updateCGForModel(ms, cg);
494  if (!callsigns.isEmpty())
495  {
496  this->insertCGOverridden(cg, callsigns);
497  CLogMessage(this).info(u"Setting CG for '%1': %2")
498  << callsigns.getCallsignsAsString(true) << cg.valueRoundedWithUnit();
499  }
500  return true;
501 
502  } // set
503  } // model string
504 
505  } // 3 parts
506  }
507 
508  // driver specific cmd line arguments
509  return this->parseDetails(parser);
510  }
511 
512  void ISimulator::registerHelp()
513  {
514  if (CSimpleCommandParser::registered("swift::core::ISimulator")) { return; }
515  CSimpleCommandParser::registerCommand({ ".drv", "alias: .driver .plugin" });
516  CSimpleCommandParser::registerCommand({ ".drv unload", "unload driver" });
517  CSimpleCommandParser::registerCommand({ ".drv cg length clear|modelstr.", "override CG" });
518  CSimpleCommandParser::registerCommand(
519  { ".drv limit number/secs.", "limit updates to number per second (0..off)" });
520  CSimpleCommandParser::registerCommand({ ".drv logint callsign", "log interpolator for callsign" });
521  CSimpleCommandParser::registerCommand({ ".drv logint off", "no log information for interpolator" });
522  CSimpleCommandParser::registerCommand({ ".drv logint write", "write interpolator log to file" });
523  CSimpleCommandParser::registerCommand({ ".drv logint clear", "clear current log" });
524  CSimpleCommandParser::registerCommand({ ".drv logint max number", "max. number of entries logged" });
525  CSimpleCommandParser::registerCommand({ ".drv pos callsign", "show position for callsign" });
526  CSimpleCommandParser::registerCommand(
527  { ".drv spline|linear callsign", "set spline/linear interpolator for one/all callsign(s)" });
528  CSimpleCommandParser::registerCommand(
529  { ".drv aircraft readd callsign", "add again (re-add) a given callsign" });
530  CSimpleCommandParser::registerCommand({ ".drv aircraft readd all", "add again (re-add) all aircraft" });
531  CSimpleCommandParser::registerCommand(
532  { ".drv aircraft rm callsign", "remove a given callsign from simulator" });
533 
535  {
536  CSimpleCommandParser::registerCommand({ ".drv fsuipc on|off", "enable/disable FSUIPC (if applicable)" });
537  }
538  }
539 
540  QString ISimulator::statusToString(SimulatorStatus status)
541  {
542  QStringList s;
543  if (status.testFlag(Unspecified)) { s << QStringLiteral("Unspecified"); }
544  if (status.testFlag(Disconnected)) { s << QStringLiteral("Disconnected"); }
545  if (status.testFlag(Connected)) { s << QStringLiteral("Connected"); }
546  if (status.testFlag(Simulating)) { s << QStringLiteral("Simulating"); }
547  if (status.testFlag(Paused)) { s << QStringLiteral("Paused"); }
548  return s.join(", ");
549  }
550 
551  bool ISimulator::isEqualLastSent(const CAircraftSituation &compare) const
552  {
553  Q_ASSERT_X(compare.hasCallsign(), Q_FUNC_INFO, "Need callsign");
554  if (!m_lastSentSituations.contains(compare.getCallsign())) { return false; }
555  if (compare.isNull()) { return false; }
556  return compare.equalPbhVectorAltitudeElevation(m_lastSentSituations.value(compare.getCallsign()));
557  // return compare.equalPbhVectorAltitude(m_lastSentSituations.value(compare.getCallsign()));
558  }
559 
560  bool ISimulator::isEqualLastSent(const CAircraftParts &compare, const CCallsign &callsign) const
561  {
562  if (callsign.isEmpty()) { return false; }
563  if (!m_lastSentParts.contains(callsign)) { return false; }
564  return compare.equalValues(m_lastSentParts.value(callsign));
565  }
566 
567  void ISimulator::rememberLastSent(const CAircraftSituation &sent)
568  {
569  // normally we should never end up without callsign, but it has happened in real world scenarios
570  // https://discordapp.com/channels/539048679160676382/568904623151382546/575712119513677826
571  const bool hasCs = sent.hasCallsign();
572  SWIFT_VERIFY_X(hasCs, Q_FUNC_INFO, "Need callsign");
573  if (!hasCs) { return; }
574  m_lastSentSituations.insert(sent.getCallsign(), sent);
575  }
576 
577  void ISimulator::rememberLastSent(const CAircraftParts &sent, const CCallsign &callsign)
578  {
579  // normally we should never end up without callsign, but it has happened in real world scenarios
580  // https://discordapp.com/channels/539048679160676382/568904623151382546/575712119513677826
581  SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
582  if (callsign.isEmpty()) { return; }
583  m_lastSentParts.insert(callsign, sent);
584  }
585 
586  CAircraftSituationList ISimulator::getLastSentCanLikelySkipNearGroundInterpolation() const
587  {
588  const QList<CAircraftSituation> situations = m_lastSentSituations.values();
589  CAircraftSituationList skipped;
590  for (const CAircraftSituation &s : situations)
591  {
592  if (s.canLikelySkipNearGroundInterpolation()) { skipped.push_back(s); }
593  }
594  return skipped;
595  }
596 
597  bool ISimulator::isAnyConnectedStatus(SimulatorStatus status)
598  {
599  return (status.testFlag(Connected) || status.testFlag(Simulating) || status.testFlag(Paused));
600  }
601 
602  const CCallsign &ISimulator::getTestCallsign()
603  {
604  static const CCallsign cs("SWIFT");
605  return cs;
606  }
607 
608  ISimulator::ISimulator(const CSimulatorPluginInfo &pluginInfo, IOwnAircraftProvider *ownAircraftProvider,
609  IRemoteAircraftProvider *remoteAircraftProvider, IClientProvider *clientProvider,
610  QObject *parent)
611  : QObject(parent), COwnAircraftAware(ownAircraftProvider), CRemoteAircraftAware(remoteAircraftProvider),
613  CIdentifiable(this)
614  {
615  this->setObjectName("Simulator: " + pluginInfo.getIdentifier());
616  m_interpolationLogger.setObjectName("Logger: " + pluginInfo.getIdentifier());
617 
619 
620  // provider signals, hook up with remote aircraft provider
621  m_remoteAircraftProviderConnections.append(
622  CRemoteAircraftAware::provider()->connectRemoteAircraftProviderSignals(
623  this, // receiver must match object in bind
624  nullptr, nullptr,
625  [](const aviation::CCallsign &) {
626  /* currently not used, the calls are handled by context call logicallyRemoveRemoteAircraft*/
627  },
628  [this](const CAirspaceAircraftSnapshot &snapshot) {
629  this->rapOnRecalculatedRenderedAircraft(snapshot);
630  }));
631 
632  // swift data
633  if (sApp && sApp->hasWebDataServices())
634  {
636  &ISimulator::onSwiftDbAllDataRead, Qt::QueuedConnection);
638  &ISimulator::onSwiftDbModelMatchingEntitiesRead, Qt::QueuedConnection);
639  }
640  connect(sApp, &CApplication::aboutToShutdown, this, &ISimulator::unload, Qt::QueuedConnection);
641 
642  // provider
643  if (pluginInfo.isEmulatedPlugin() && !pluginInfo.getSimulatorInfo().isSingleSimulator())
644  {
645  // emulated driver with NO info yet
646  CLogMessage(this).info(u"Plugin '%1' with no simulator info yet, hope it will be set later")
647  << pluginInfo.getIdentifier();
648  }
649  else
650  {
651  // NORMAL CASE or plugin with info already set
652  this->setNewPluginInfo(pluginInfo, m_multiSettings.getSettings(pluginInfo.getSimulatorInfo()));
653  }
654 
655  // info data
658 
659  // model changed
660  connect(this, &ISimulator::ownAircraftModelChanged, this, &ISimulator::onOwnModelChanged, Qt::QueuedConnection);
661 
662  // info
663  CLogMessage(this).info(u"Initialized simulator driver: '%1'")
665  this->getSimulatorInfo().toQString());
666  }
667 
669  {
670  if (!snapshot.isValidSnapshot()) { return; }
671 
672  // for unrestricted values all add/remove actions are directly linked
673  // when changing back from restricted->unrestricted an one time update is required
674  if (!snapshot.isRestricted() && !snapshot.isRestrictionChanged()) { return; }
675 
676  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Needs to run in object thread");
677  Q_ASSERT_X(snapshot.generatingThreadName() != QThread::currentThread()->objectName(), Q_FUNC_INFO,
678  "Expect snapshot from background thread");
679 
680  // restricted snapshot values?
681  bool changed = false;
682  if (snapshot.isRenderingEnabled())
683  {
684  // make sure not to add aircraft again which are no longer in range
685  const CCallsignSet callsignsInRange = this->getAircraftInRangeCallsigns();
686  const CCallsignSet callsignsEnabledAndStillInRange =
687  snapshot.getEnabledAircraftCallsignsByDistance().intersection(callsignsInRange);
688  const CCallsignSet callsignsInSimulator(this->physicallyRenderedAircraft()); // state in simulator
689  const CCallsignSet callsignsToBeRemoved(callsignsInSimulator.difference(callsignsEnabledAndStillInRange));
690  const CCallsignSet callsignsToBeAdded(callsignsEnabledAndStillInRange.difference(callsignsInSimulator));
691  if (!callsignsToBeRemoved.isEmpty())
692  {
693  const int r = this->physicallyRemoveMultipleRemoteAircraft(callsignsToBeRemoved);
694  changed = r > 0;
695  }
696 
697  if (!callsignsToBeAdded.isEmpty())
698  {
699  CSimulatedAircraftList aircraftToBeAdded(
700  this->getAircraftInRange().findByCallsigns(callsignsToBeAdded)); // thread safe copy
701  for (const CSimulatedAircraft &aircraft : aircraftToBeAdded)
702  {
703  Q_ASSERT_X(aircraft.isEnabled(), Q_FUNC_INFO, "Disabled aircraft detected as to be added");
704  Q_ASSERT_X(aircraft.hasModelString(), Q_FUNC_INFO, "Missing model string");
705  this->callPhysicallyAddRemoteAircraft(aircraft); // recalculate snapshot
706  changed = true;
707  }
708  }
709  }
710  else
711  {
712  // no rendering at all, we remove everything
713  const int r = this->physicallyRemoveAllRemoteAircraft();
714  changed = r > 0;
715  }
716 
717  // we have handled snapshot
718  if (changed) { emit this->airspaceSnapshotHandled(); }
719  }
720 
722  {
723  this->resetLastSentValues(callsign);
724  return true;
725  }
726 
728  {
729  if (callsigns.isEmpty()) { return 0; }
730  int removed = 0;
731  for (const CCallsign &callsign : callsigns)
732  {
733  this->callPhysicallyRemoveRemoteAircraft(callsign);
734  removed++;
735  }
736  return removed;
737  }
738 
740  {
741  // a default implementation, but normally overridden by the sims
742  const CCallsignSet callsigns = this->getAircraftInRangeCallsigns();
743 
744  // normally that would be already done in the specialized implementation
745  const int r = this->physicallyRemoveMultipleRemoteAircraft(callsigns);
746 
747  // leave no trash
748  this->clearAllRemoteAircraftData(); // remove all aircraft
749  return r;
750  }
751 
753  {
754  // void, can be overridden in specialized drivers
755  }
756 
758  {
759  // void, can be overridden in specialized drivers
760  }
761 
763  {
768  }
769 
771  bool likelyOnGroundElevation, const CElevationPlane &elevation,
772  const CLength &simulatorCG)
773  {
774  if (callsign.isEmpty()) { return; }
775  if (elevation.hasMSLGeodeticHeight())
776  {
777  const int aircraftCount = this->getAircraftInRangeCount();
779  aircraftCount *
780  3); // at least 3 elevations per aircraft, even better as not all are requesting elevations
781  this->rememberGroundElevation(callsign, likelyOnGroundElevation, elevation);
782  }
783 
784  const QString modelString = model.getModelString();
785  if (modelString.isEmpty()) { return; }
786 
787  // this value now is the simulator or overridden value
788  const CLength cgOvr = this->overriddenCGorDefault(simulatorCG, modelString);
789  if (!cgOvr.isNull() && !this->hasSameSimulatorCG(cgOvr, callsign))
790  {
791  // the value did change
792  const CSimulatorSettings::CGSource source =
794  if (source != CSimulatorSettings::CGFromDBOnly)
795  {
796  this->insertCG(cgOvr, modelString, callsign); // per model string and CG
797  }
798 
799  // here we know we have a valid model and CG did change
800  const CSimulatorInfo sim = this->getSimulatorInfo();
801  m_autoPublishing.insert(modelString,
802  simulatorCG); // still using simulator CG here, not the overridden value
803 
804  // if simulator did change, add as well
805  if (!model.getSimulator().matchesAll(sim))
806  {
807  m_autoPublishing.insert(modelString, this->getSimulatorInfo());
808  }
809  }
810  }
811 
812  void ISimulator::emitSimulatorCombinedStatus(SimulatorStatus oldStatus)
813  {
814  const SimulatorStatus newStatus = this->getSimulatorStatus();
815  if (oldStatus != newStatus)
816  {
817  // decouple, follow up of signal can include unloading
818  // simulator so this should happen strictly asynchronously (which is like forcing Qt::QueuedConnection)
819  QPointer<ISimulator> myself(this);
820  QTimer::singleShot(0, this, [=] {
821  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
822 
823  // now simulating
824  if (newStatus.testFlag(Simulating))
825  {
826  this->setUpdateAllRemoteAircraft(); // force an update of every remote aircraft
827  }
828 
829  emit this->simulatorStatusChanged(
830  newStatus); // only place where we should emit the signal, use emitSimulatorCombinedStatus to emit
831  });
832  }
833  }
834 
836  {
837  QPointer<ISimulator> myself(this);
838  QTimer::singleShot(5, this, [=] {
839  if (!myself) { return; }
841  });
842  }
843 
845  {
846  if (!IInterpolationSetupProvider::setInterpolationSetupGlobal(setup)) { return false; }
847  const bool r = setup.isRenderingRestricted();
848  const bool e = setup.isRenderingEnabled();
849 
850  if (!this->isShuttingDown())
851  {
852  CCrashHandler::instance()->crashAndLogAppendInfo(u"Rendering setup: " % setup.toQString(true));
853  }
855  return true;
856  }
857 
859  {
860  return m_loopbackSituations.value(callsign);
861  }
862 
864  {
865  bool modified = false;
866  const CAircraftModel reverseModel =
867  CDatabaseUtils::consolidateOwnAircraftModelWithDbData(model, false, &modified);
868  return reverseModel;
869  }
870 
871  bool ISimulator::isUpdateAircraftLimited(qint64 timestamp)
872  {
873  if (!m_limitUpdateAircraft) { return false; }
874  const bool hasToken = m_limitUpdateAircraftBucket.tryConsume(1, timestamp);
875  return !hasToken;
876  }
877 
879  {
880  const bool limited = this->isUpdateAircraftLimited(startTime);
881  return limited;
882  }
883 
884  bool ISimulator::limitToUpdatesPerSecond(int numberPerSecond)
885  {
886  if (numberPerSecond < 1)
887  {
888  m_limitUpdateAircraft = false;
889  return false;
890  }
891 
892  int tokens = qRound(0.1 * numberPerSecond); // 100ms
893  do {
894  if (tokens >= 3)
895  {
897  break;
898  }
899  tokens = qRound(0.25 * numberPerSecond); // 250ms
900  if (tokens >= 3)
901  {
903  break;
904  }
905  tokens = qRound(0.5 * numberPerSecond); // 500ms
906  if (tokens >= 3)
907  {
909  break;
910  }
911  tokens = numberPerSecond;
913  }
914  while (false);
915 
917  m_limitUpdateAircraft = true;
918  return true;
919  }
920 
922  {
923  if (!m_limitUpdateAircraft) { return QStringLiteral("not limited"); }
924  static const QString limInfo("Limited %1 time(s) with %2/secs.");
926  }
927 
929  {
930  m_lastSentParts.clear();
931  m_lastSentSituations.clear();
932  }
933 
935  {
936  m_lastSentParts.remove(callsign);
937  m_lastSentSituations.remove(callsign);
938  }
939 
941  {
942  this->disconnectFrom(); // disconnect from simulator
943  const bool saved = m_autoPublishing.writeJsonToFile(); // empty data are ignored
944  if (saved) { emit this->autoPublishDataWritten(this->getSimulatorInfo()); }
946  m_remoteAircraftProviderConnections.disconnectAll(); // disconnect signals from provider
947  }
948 
950  {
951  return this->isTestMode() || this->isAircraftInRange(callsign);
952  }
953 
955  {
956  m_averageFps = -1.0;
957  m_simTimeRatio = 1.0;
958  m_trackMilesShort = 0.0;
959  m_minutesLate = 0.0;
960  return true;
961  }
962 
964  {
965  if (this->isShuttingDown()) { return false; }
966  if (callsign.isEmpty()) { return false; }
967 
968  this->logicallyRemoveRemoteAircraft(callsign);
969  if (!this->isAircraftInRange(callsign)) { return false; }
970  const QPointer<ISimulator> myself(this);
971  QTimer::singleShot(2500, this, [=] {
972  if (myself.isNull()) { return; }
973  if (this->isShuttingDown()) { return; }
974  if (!this->isAircraftInRange(callsign)) { return; }
975  const CSimulatedAircraft aircraft = this->getAircraftInRangeForCallsign(callsign);
976  if (aircraft.isEnabled() && aircraft.hasModelString()) { this->logicallyAddRemoteAircraft(aircraft); }
977  });
978  return true;
979  }
980 
982  {
983  if (this->isShuttingDown()) { return false; }
984  return aircraft.isEnabled() ? this->physicallyAddRemoteAircraft(aircraft) :
986  }
987 
989  {
990  // we expect the new model "in aircraft"
991  // remove upfront, and then enable / disable again
992  if (this->isShuttingDown()) { return false; }
993  const CCallsign callsign = aircraft.getCallsign();
994  if (!this->isPhysicallyRenderedAircraft(callsign)) { return false; }
995  this->physicallyRemoveRemoteAircraft(callsign);
996  // return this->changeRemoteAircraftEnabled(aircraft);
997 
998  const QPointer<ISimulator> myself(this);
999  QTimer::singleShot(1000, this, [=] {
1000  if (!myself) { return; }
1001  if (this->isAircraftInRange(callsign)) { this->changeRemoteAircraftEnabled(aircraft); }
1002  });
1003  return true;
1004  }
1005 
1007  {
1008  CStatusMessageList msgs;
1009  if (!CBuildConfig::isLocalDeveloperDebugBuild()) { return msgs; }
1011  {
1012  msgs.push_back(CStatusMessage(this).error(u"m_addAgainAircraftWhenRemoved not empty: '%1'")
1014  }
1015  return msgs;
1016  }
1017 
1019  const QString &details) const
1020  {
1021  static const QString msg("Interpolation ('%1'): '%2'");
1022  const QString m = msg.arg(callsign.asString(), status.toQString());
1023  if (details.isEmpty()) { return m; }
1024 
1025  static const QString addDetails(" details: '%1'");
1026  return m % addDetails.arg(details);
1027  }
1028 
1029  void ISimulator::finishUpdateRemoteAircraftAndSetStatistics(qint64 startTime, bool limited)
1030  {
1031  const qint64 now = QDateTime::currentMSecsSinceEpoch();
1032  const qint64 dt = now - startTime;
1037  static_cast<double>(m_statsUpdateAircraftTimeTotalMs) / static_cast<double>(m_statsUpdateAircraftRuns);
1040 
1041  if (!this->isUpdateAllRemoteAircraft(startTime)) { this->resetUpdateAllRemoteAircraft(); }
1042 
1045  {
1047  }
1048  if (limited) { m_statsUpdateAircraftLimited++; }
1049  }
1050 
1052  {
1053  Q_UNUSED(newModel)
1054  // can be overridden
1055  }
1056 
1058  {
1059  const bool updated = this->updateOwnSituation(situation);
1060 
1061  // do not use every situation, but every deltaMs and only on ground
1062  constexpr qint64 deltaMs = 5000;
1063  if (situation.isOnGround() && situation.getTimeDifferenceAbsMs(m_lastRecordedGndElevationMs) > deltaMs)
1064  {
1067  if (settings.isRecordOwnAircraftGnd())
1068  {
1069  const CSimulatedAircraft ownAircraft = this->getOwnAircraft();
1070  CAltitude elevation = situation.getGroundElevation();
1071  if (elevation.isNull())
1072  {
1073  // calculate elevation
1074  const CLength cg = ownAircraft.getModel().getCG();
1075  elevation = (cg.isNull() || situation.getAltitude().isNull()) ?
1076  CAltitude::null() :
1077  (situation.getAltitude().withOffset(cg * -1.0));
1078  }
1079 
1080  // own ground elevations
1081  if (elevation.hasMeanSeaLevelValue())
1082  {
1083  const CCallsign cs = situation.hasCallsign() ? situation.getCallsign() : ownAircraft.getCallsign();
1084  const CLength radius = settings.getRecordedGndRadius().isNull() ?
1085  CElevationPlane::singlePointRadius() :
1086  settings.getRecordedGndRadius();
1087  const CElevationPlane ep(situation, radius);
1088  const bool remembered = this->rememberGroundElevation(cs, situation.isOnGround(), ep, radius);
1089 
1090  if (CBuildConfig::isLocalDeveloperDebugBuild())
1091  {
1092  const bool invalid = situation.isOnGround() && elevation.isZeroEpsilonConsidered();
1093  SWIFT_AUDIT_X(!invalid, Q_FUNC_INFO, "On ground in water");
1094  }
1095  Q_UNUSED(remembered) // false means it was already in that cache, or something else is wrong
1096  }
1097  }
1098  }
1099  return updated;
1100  }
1101 
1103  {
1104  const CSimulatorInfo simulator = this->getSimulatorInfo();
1105  if (!simulator.isSingleSimulator()) { return {}; }
1106 
1107  CCentralMultiSimulatorModelSetCachesProvider::modelCachesInstance().synchronizeCache(simulator);
1108  return CCentralMultiSimulatorModelSetCachesProvider::modelCachesInstance().getCachedModels(simulator);
1109  }
1110 
1112  {
1113  const CAircraftModel model = aircraft.getModel();
1114  if (!aircraft.hasCallsign())
1115  {
1116  CLogMessage(this).warning(u"Missing callsign for '%1'") << aircraft.getModelString();
1117  return false;
1118  }
1119  if (!model.hasModelString())
1120  {
1121  CLogMessage(this).warning(u"No model string for callsign '%1'") << aircraft.getCallsign();
1122  return false;
1123  }
1124  if (model.isCallsignEmpty())
1125  {
1126  CLogMessage(this).warning(u"No callsign for model of aircraft '%1'") << aircraft.getCallsign();
1127  return false;
1128  }
1129  return true;
1130  }
1131 
1133  {
1134  CLogMessage(this).info(u"Adding '%1' '%2' to '%3'")
1135  << aircraft.getCallsign() << aircraft.getModel().getModelStringAndDbKey()
1136  << this->getSimulatorInfo().toQString(true);
1137  }
1138 
1140  {
1143 
1144  static const QString sep("\n------\n");
1145  QString dm;
1146  if (s.tsCurrent > 0)
1147  {
1148  dm = u"Setup: " % s.usedSetup.toQString(true) % u"\n\n" % u"Situation: " %
1149  s.toQString(false, true, true, true, true, sep);
1150  }
1151  if (p.tsCurrent > 0) { dm += (dm.isEmpty() ? u"Parts: " : u"\n\nParts: ") % p.toQString(sep); }
1152  return dm;
1153  }
1154 
1155  void ISimulator::rapOnRecalculatedRenderedAircraft(const CAirspaceAircraftSnapshot &snapshot)
1156  {
1157  if (!this->isConnected()) { return; }
1158  if (this->isShuttingDown()) { return; }
1159  this->onRecalculatedRenderedAircraft(snapshot);
1160  }
1161 
1162  void ISimulator::callPhysicallyAddRemoteAircraft(const CSimulatedAircraft &remoteAircraft)
1163  {
1164  m_statsPhysicallyAddedAircraft++;
1165  this->physicallyAddRemoteAircraft(remoteAircraft);
1166  }
1167 
1168  void ISimulator::callPhysicallyRemoveRemoteAircraft(const CCallsign &remoteCallsign)
1169  {
1170  this->clearData(remoteCallsign);
1171  this->physicallyRemoveRemoteAircraft(remoteCallsign);
1172  }
1173 
1174  void ISimulator::displayLoggedSituationInSimulator(const CCallsign &cs, bool stopLogging, int times)
1175  {
1176  if (cs.isEmpty()) { return; }
1177  if (this->isShuttingDown()) { return; }
1179  const bool logsCs = setup.logInterpolation();
1180  if (!logsCs) { return; }
1181 
1182  stopLogging = stopLogging || !this->isSimulating(); // stop when sim was stopped
1183  stopLogging = stopLogging && logsCs;
1184  if (!stopLogging && times < 1) { return; }
1185 
1186  const bool inRange = this->getAircraftInRangeCallsigns().contains(cs);
1187  if (!stopLogging && !inRange) { return; }
1188  if (stopLogging && (times < 1 || !inRange))
1189  {
1190  this->setLogCallsign(false, cs);
1191  return;
1192  }
1193 
1194  const QString dm = this->latestLoggedDataFormatted(cs);
1195  if (!dm.isEmpty()) { this->displayStatusMessage(CStatusMessage(this).info(dm)); }
1196 
1197  const int t = CMathUtils::randomInteger(4500, 5500); // makes sure not always using the same time difference
1198  const QPointer<ISimulator> myself(this);
1199  QTimer::singleShot(t, this, [=] {
1200  if (!myself || myself->isShuttingDown()) { return; }
1201  this->displayLoggedSituationInSimulator(cs, stopLogging, times - 1);
1202  });
1203  }
1204 
1206  {
1207  CAircraftModel model = this->getOwnAircraftModel();
1208  model.setModelString(modelString);
1210  }
1211 
1213  {
1214  if (!model.hasModelString()) { return; }
1215  if (this->isShuttingDown()) { return; }
1216  Q_ASSERT_X(sApp->hasWebDataServices(), Q_FUNC_INFO, "Missing web services");
1217 
1218  if (this->getOwnAircraftModel() != model)
1219  {
1220  if (CDatabaseUtils::hasDbAircraftData())
1221  {
1223  const bool updated = this->updateOwnModel(newModel); // update in provider (normally the context)
1224  if (updated) { emit this->ownAircraftModelChanged(this->getOwnAircraftModel()); }
1225  }
1226  else
1227  {
1228  // we wait for the data
1230  [=] { this->reverseLookupAndUpdateOwnAircraftModel(model); });
1231  }
1232  }
1233  }
1234 
1235  ISimulatorListener::ISimulatorListener(const CSimulatorPluginInfo &info) : QObject(), m_info(info)
1236  {
1237  this->setObjectName("ISimulatorListener:" + info.toQString());
1238 
1239  // stop listener after it reports simulator ready
1240  bool s =
1241  connect(this, &ISimulatorListener::simulatorStarted, this, &ISimulatorListener::stop, Qt::QueuedConnection);
1242  Q_ASSERT_X(s, Q_FUNC_INFO, "connect failed");
1243 
1244  if (sApp)
1245  {
1246  s = connect(sApp, &CApplication::aboutToShutdown, this, &ISimulatorListener::onAboutToShutdown,
1247  Qt::QueuedConnection);
1248  Q_ASSERT_X(s, Q_FUNC_INFO, "connect failed");
1249  }
1250 
1251  Q_UNUSED(s)
1252  }
1253 
1254  QString ISimulatorListener::backendInfo() const { return m_info.toQString(); }
1255 
1256  bool ISimulatorListener::isShuttingDown() const { return (!sApp || sApp->isShuttingDown() || m_aboutToShutdown); }
1257 
1258  void ISimulatorListener::onAboutToShutdown()
1259  {
1260  if (!m_aboutToShutdown) { return; }
1261  m_aboutToShutdown = true;
1262  this->stop();
1263  }
1264 
1266  {
1267  if (m_isRunning) { return; }
1268  if (!CThreadUtils::isInThisThread(this))
1269  {
1270  // call in correct thread
1271  QPointer<ISimulatorListener> myself(this);
1272  QTimer::singleShot(0, this, [=] {
1273  if (myself) { this->start(); }
1274  });
1275  return;
1276  }
1277 
1278  m_isRunning = true;
1279  this->startImpl();
1280  }
1281 
1283  {
1284  if (!m_isRunning) { return; }
1285  if (!CThreadUtils::isInThisThread(this))
1286  {
1287  // call in correct thread
1288  QPointer<ISimulatorListener> myself(this);
1289  QTimer::singleShot(0, this, [=] {
1290  if (myself) { this->stop(); }
1291  });
1292  return;
1293  }
1294 
1295  this->stopImpl();
1296  m_isRunning = false;
1297  }
1298 
1300  {
1301  if (!m_isRunning) { return; }
1302  if (!CThreadUtils::isInThisThread(this))
1303  {
1304  // call in correct thread
1305  QPointer<ISimulatorListener> myself(this);
1306  QTimer::singleShot(0, this, [=] {
1307  if (myself) { this->check(); }
1308  });
1309  return;
1310  }
1311 
1312  this->checkImpl();
1313  }
1314 } // namespace swift::core
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
static constexpr bool isCompiledWithFsuipcSupport()
with FSUIPC support?
void aboutToShutdown()
About to shutdown.
bool hasWebDataServices() const
Web data services available?
bool isShuttingDown() const
Is application shutting down?
CWebDataServices * getWebDataServices() const
Get the web data services.
void swiftDbModelMatchingEntitiesRead()
All entities needed for model matching.
void swiftDbAllDataRead()
All swift DB data have been read.
virtual bool isPhysicallyRenderedAircraft(const swift::misc::aviation::CCallsign &callsign) const =0
Is the aircraft rendered (displayed in simulator)? This shall only return true if the aircraft is rea...
virtual bool logicallyReAddRemoteAircraft(const swift::misc::aviation::CCallsign &callsign)
Removes and adds again the aircraft.
Definition: simulator.cpp:963
swift::misc::simulation::settings::CMultiSimulatorSettings m_multiSettings
simulator settings for all simulators
Definition: simulator.h:597
void resetLastSentValues()
Reset the last sent values.
Definition: simulator.cpp:928
double m_simTimeRatio
ratio of simulation time to real time, due to low FPS (X-Plane)
Definition: simulator.h:563
void resetUpdateAllRemoteAircraft()
Reset.
Definition: simulator.cpp:227
void renderRestrictionsChanged(bool restricted, bool enabled, int maxAircraft, const swift::misc::physical_quantities::CLength &maxRenderedDistance)
Render restrictions have been changed.
bool setInterpolationSetupGlobal(const swift::misc::simulation::CInterpolationAndRenderingSetupGlobal &setup)
Set the global setup.
Definition: simulator.cpp:844
bool updateOwnSituationAndGroundElevation(const swift::misc::aviation::CAircraftSituation &situation)
Update own aircraft position and if suitable use it to update ground elevation.
Definition: simulator.cpp:1057
virtual void onOwnModelChanged(const swift::misc::simulation::CAircraftModel &newModel)
Own model has been changed.
Definition: simulator.cpp:1051
bool isUpdateAllRemoteAircraft(qint64 currentTimestamp=-1) const
Do update all remote aircraft?
Definition: simulator.cpp:212
double m_averageFps
FPS.
Definition: simulator.h:562
virtual bool isShuttingDown() const
Is overall (swift) application shutting down.
Definition: simulator.h:211
QString updateAircraftLimitationInfo() const
Info about update aircraft limitations.
Definition: simulator.cpp:921
virtual bool logicallyRemoveRemoteAircraft(const swift::misc::aviation::CCallsign &callsign)
Logically remove remote aircraft from simulator. Depending on max. aircraft, enabled status etc....
Definition: simulator.cpp:64
bool limitToUpdatesPerSecond(int numberPerSecond)
Limit to updates per second.
Definition: simulator.cpp:884
virtual void onSwiftDbAllDataRead()
When swift DB data are read.
Definition: simulator.cpp:752
bool m_updateRemoteAircraftInProgress
currently updating remote aircraft
Definition: simulator.h:556
swift::misc::CTokenBucket m_limitUpdateAircraftBucket
means 50 per second
Definition: simulator.h:593
virtual swift::misc::aviation::CCallsignSet physicallyRenderedAircraft() const =0
Physically rendered (displayed in simulator) This shall only return aircraft handled in the simulator...
qint64 m_lastRecordedGndElevationMs
when gnd.elevation was last modified
Definition: simulator.h:571
virtual void clearAllRemoteAircraftData()
Clear all aircraft related data, but do not physically remove the aircraft.
Definition: simulator.cpp:122
void rememberElevationAndSimulatorCG(const swift::misc::aviation::CCallsign &callsign, const swift::misc::simulation::CAircraftModel &model, bool likelyOnGroundElevation, const swift::misc::geo::CElevationPlane &elevation, const swift::misc::physical_quantities::CLength &simulatorCG)
Set elevation and CG in the providers and for auto publishing.
Definition: simulator.cpp:770
double m_trackMilesShort
difference between real and reported groundspeed, multiplied by time
Definition: simulator.h:564
virtual bool physicallyRemoveRemoteAircraft(const swift::misc::aviation::CCallsign &callsign)=0
Remove remote aircraft from simulator.
Definition: simulator.cpp:721
void ownAircraftModelChanged(const swift::misc::simulation::CAircraftModel &model)
Emitted when own aircraft model has changed.
double m_minutesLate
difference between real and reported groundspeed, integrated over time
Definition: simulator.h:565
bool isAircraftInRangeOrTestMode(const swift::misc::aviation::CCallsign &callsign) const
Test version aware version of isAircraftInRange.
Definition: simulator.cpp:949
void finishUpdateRemoteAircraftAndSetStatistics(qint64 startTime, bool limited=false)
Update stats and flags.
Definition: simulator.cpp:1029
swift::misc::aviation::CAircraftSituationList getLoopbackSituations(const swift::misc::aviation::CCallsign &callsign) const
The traced loopback situations.
Definition: simulator.cpp:858
virtual bool physicallyAddRemoteAircraft(const swift::misc::simulation::CSimulatedAircraft &remoteAircraft)=0
Add new remote aircraft physically to the simulator.
void reverseLookupAndUpdateOwnAircraftModel(const swift::misc::simulation::CAircraftModel &model)
Set own model.
Definition: simulator.cpp:1212
int m_statsUpdateAircraftLimited
skipped because of max.update limitations
Definition: simulator.h:560
swift::misc::aviation::CAircraftPartsPerCallsign m_lastSentParts
last parts sent to simulator
Definition: simulator.h:583
virtual void clearData(const swift::misc::aviation::CCallsign &callsign)
Clear the related data as statistics etc.
Definition: simulator.cpp:171
void emitInterpolationSetupChanged()
Pseudo signal, override to emit signal.
Definition: simulator.cpp:835
swift::misc::simulation::CAutoPublishData m_autoPublishing
for the DB
Definition: simulator.h:580
virtual void displayStatusMessage(const swift::misc::CStatusMessage &message) const =0
Display a status message in the simulator.
qint64 m_statsUpdateAircraftRequestedDeltaMs
delta time between 2 aircraft updates
Definition: simulator.h:573
bool isTestMode() const
Test mode? (driver can skip code parts etc., driver dependent)
Definition: simulator.h:180
virtual void onRecalculatedRenderedAircraft(const swift::misc::simulation::CAirspaceAircraftSnapshot &snapshot)
Recalculate the rendered aircraft, this happens when restrictions are applied (max....
Definition: simulator.cpp:668
virtual void unload()
Driver will be unloaded.
Definition: simulator.cpp:940
swift::misc::simulation::CAircraftModelList getModelSet() const
Get the model set.
Definition: simulator.cpp:1102
bool isUpdateAircraftLimited(qint64 timestamp=-1)
Limit reached (max number of updates by token bucket if enabled)
Definition: simulator.cpp:871
virtual swift::misc::CStatusMessageList debugVerifyStateAfterAllAircraftRemoved() const
Debug function to check state after all aircraft have been removed.
Definition: simulator.cpp:1006
virtual bool disconnectFrom()
Disconnect from simulator.
Definition: simulator.cpp:954
bool validateModelOfAircraft(const swift::misc::simulation::CSimulatedAircraft &aircraft) const
Validate if model has callsign and such.
Definition: simulator.cpp:1111
void emitSimulatorCombinedStatus(SimulatorStatus oldStatus=Unspecified)
Emit the combined status.
Definition: simulator.cpp:812
int m_statsUpdateAircraftRuns
statistics update count
Definition: simulator.h:559
swift::misc::simulation::CSimulatedAircraftList m_addAgainAircraftWhenRemoved
add this model again when removed, normally used to change model
Definition: simulator.h:587
bool m_limitUpdateAircraft
limit the update frequency by using swift::misc::CTokenBucket
Definition: simulator.h:594
swift::misc::simulation::CSimulatorInternals m_simulatorInternals
setup read from the sim
Definition: simulator.h:578
virtual void onSwiftDbModelMatchingEntitiesRead()
When swift DB data are read.
Definition: simulator.cpp:757
void autoPublishDataWritten(const swift::misc::simulation::CSimulatorInfo &simulator)
Auto publish data written for simulator.
void airspaceSnapshotHandled()
An airspace snapshot was handled.
virtual int physicallyRemoveAllRemoteAircraft()
Remove all remote aircraft and their data via ISimulator::clearAllRemoteAircraftData.
Definition: simulator.cpp:739
void logAddingAircraftModel(const swift::misc::simulation::CSimulatedAircraft &aircraft) const
Unified qeeing aircraft message.
Definition: simulator.cpp:1132
SimulatorStatusFlag
ISimulator status.
Definition: simulator.h:67
@ Simulating
Is the simulator actually simulating?
Definition: simulator.h:71
virtual SimulatorStatus getSimulatorStatus() const
Combined status.
Definition: simulator.cpp:54
qint64 m_statsUpdateAircraftTimeTotalMs
statistics total update time
Definition: simulator.h:568
swift::misc::simulation::settings::CSpecializedSimulatorSettings getSimulatorSettings() const
Settings for current simulator.
Definition: simulator.h:159
qint64 m_statsLastUpdateAircraftRequestedMs
when was the last aircraft update requested
Definition: simulator.h:572
swift::misc::simulation::CInterpolationLogger m_interpolationLogger
log.interpolation
Definition: simulator.h:579
bool isUpdateAircraftLimitedWithStats(qint64 startTime=-1)
Limited as ISimulator::isUpdateAircraftLimited plus updating statistics.
Definition: simulator.cpp:878
double m_statsUpdateAircraftTimeAvgMs
statistics average update time
Definition: simulator.h:561
virtual int physicallyRemoveMultipleRemoteAircraft(const swift::misc::aviation::CCallsignSet &callsigns)
Remove remote aircraft from simulator.
Definition: simulator.cpp:727
swift::misc::aviation::CAircraftSituationListPerCallsign m_loopbackSituations
traced loopback situations
Definition: simulator.h:590
virtual void initSimulatorInternals()
Init the internals info from the simulator.
Definition: simulator.cpp:762
qint64 m_statsMaxUpdateTimeMs
statistics max.update time
Definition: simulator.h:570
virtual bool isSimulating() const
Simulator running?
Definition: simulator.h:174
virtual bool isConnected() const =0
Are we connected to the simulator?
static swift::misc::simulation::CAircraftModel reverseLookupModel(const swift::misc::simulation::CAircraftModel &model)
Lookup against DB data.
Definition: simulator.cpp:863
QString latestLoggedDataFormatted(const swift::misc::aviation::CCallsign &cs) const
The latest logged data formatted.
Definition: simulator.cpp:1139
swift::misc::aviation::CAircraftSituationPerCallsign m_lastSentSituations
last situations sent to simulator
Definition: simulator.h:582
virtual bool logicallyAddRemoteAircraft(const swift::misc::simulation::CSimulatedAircraft &remoteAircraft)
Logically add a new aircraft. Depending on max. aircraft, enabled status etc. it will physically adde...
Definition: simulator.cpp:78
virtual bool changeRemoteAircraftModel(const swift::misc::simulation::CSimulatedAircraft &aircraft)
Change remote aircraft per property.
Definition: simulator.cpp:988
static void registerHelp()
Register help.
Definition: simulator.cpp:512
qint64 m_statsCurrentUpdateTimeMs
statistics current update time
Definition: simulator.h:569
void interpolationAndRenderingSetupChanged()
Interpolation or rendering setup changed.
virtual bool changeRemoteAircraftEnabled(const swift::misc::simulation::CSimulatedAircraft &aircraft)
Aircraft got enabled / disabled.
Definition: simulator.cpp:981
QString getInvalidSituationLogMessage(const swift::misc::aviation::CCallsign &callsign, const swift::misc::simulation::CInterpolationStatus &status, const QString &details={}) const
Info about invalid situation.
Definition: simulator.cpp:1018
virtual void startImpl()=0
Plugin specific implementation to start listener.
void simulatorStarted(const swift::misc::simulation::CSimulatorPluginInfo &info)
Emitted when the listener discovers the simulator running.
ISimulatorListener(const swift::misc::simulation::CSimulatorPluginInfo &info)
Constructor.
Definition: simulator.cpp:1235
virtual bool isShuttingDown() const
Overall (swift) application shutting down.
Definition: simulator.cpp:1256
void start()
Start listening for the simulator to start.
Definition: simulator.cpp:1265
void stop()
Stops listening.
Definition: simulator.cpp:1282
void check()
Check simulator availability.
Definition: simulator.cpp:1299
virtual QString backendInfo() const
Info about the backend system (if available)
Definition: simulator.cpp:1254
virtual void checkImpl()=0
Plugin specific implementation to check.
virtual void stopImpl()=0
Plugin specific implementation to stop listener.
size_type size() const
Returns number of elements in the collection.
Definition: collection.h:185
CCollection difference(const C &other) const
Returns a collection which contains all the elements from this collection which are not in the other ...
Definition: collection.h:278
CCollection intersection(const C &other) const
Returns a collection which is the intersection of this collection and another.
Definition: collection.h:268
iterator begin()
Returns iterator at the beginning of the collection.
Definition: collection.h:164
bool isEmpty() const
Synonym for empty.
Definition: collection.h:191
bool append(const QMetaObject::Connection &connection)
Add connection.
int disconnectAll()
Disconnect all.
static CCrashHandler * instance()
Get singleton instance.
void crashAndLogAppendInfo(const QString &info)
Append crash info.
Base class with a member CIdentifier to be inherited by a class which has an identity in the environm...
Definition: identifiable.h:24
Value object encapsulating information identifying a component of a modular distributed swift process...
Definition: identifier.h:29
Class for emitting a log message.
Definition: logmessage.h:27
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
bool contains(const T &object) const
Return true if there is an element equal to given object. Uses the most efficient implementation avai...
Definition: range.h:109
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Utility methods for simple line parsing used with the command line.
void parse(const QString &commandLine)
Parse.
Streamable status message, e.g.
Status messages, e.g. from Core -> GUI.
static bool isInThisThread(const QObject *toBeTested)
Is the current thread the object's thread?
Definition: threadutils.cpp:16
void setCapacityAndTokensToRefill(int numTokens)
Tokens/capacity if both are same.
Definition: tokenbucket.cpp:45
void setInterval(qint64 intervalMs)
Set the interval.
Definition: tokenbucket.h:40
bool tryConsume(int numTokens=1, qint64 msSinceEpoch=-1)
Try to consume a number of tokens.
Definition: tokenbucket.cpp:14
int getTokensPerSecond() const
Tokens per second.
Definition: tokenbucket.cpp:51
IRemoteAircraftProvider * provider()
Provider.
Definition: provider.h:72
qint64 getMSecsSinceEpoch() const
Timestamp as ms value.
void setCurrentUtcTime()
Set the current time as timestamp.
qint64 getTimeDifferenceAbsMs(qint64 compareTime) const
Time difference in ms.
void setTimeOffsetMs(qint64 offset)
Milliseconds to add to timestamp for interpolation.
void push_frontKeepLatestAdjustedFirst(const OBJ &value, bool replaceSameTimestamp=true, int maxElements=-1)
Insert as first element by keeping maxElements and the latest first.
Value object encapsulating information of aircraft's parts.
Definition: aircraftparts.h:26
Value object encapsulating information of an aircraft's situation.
const CAltitude & getGroundElevation() const
Elevation of the ground directly beneath.
void setCG(const physical_quantities::CLength &cg)
Set CG.
bool setGroundElevation(const aviation::CAltitude &altitude, GndElevationInfo info, bool transferred=false)
Elevation of the ground directly beneath at the given situation.
bool hasCallsign() const
Has corresponding callsign.
const CCallsign & getCallsign() const
Corresponding callsign.
const CAltitude & getAltitude() const
Get altitude.
Altitude as used in aviation, can be AGL or MSL altitude.
Definition: altitude.h:52
bool hasMeanSeaLevelValue() const
Non-NULL MSL value?
Definition: altitude.h:154
CAltitude withOffset(const CLength &offset) const
Altitude with offset.
Definition: altitude.cpp:61
Value object encapsulating information of a callsign.
Definition: callsign.h:30
const QString & asString() const
Get callsign (normalized)
Definition: callsign.h:96
bool isEmpty() const
Is empty?
Definition: callsign.h:63
bool isValid() const
Valid callsign?
Definition: callsign.cpp:359
Value object for a set of callsigns.
Definition: callsignset.h:26
QString getCallsignsAsString(bool sorted=false, const QString &separator=", ") const
Callsigns as string.
Definition: callsignset.cpp:45
QStringList getCallsignStrings(bool sorted=false) const
Get callsign string list.
Plane of same elevation, can be a single point or larger area (e.g. airport)
virtual bool isNull() const
Existing value?
Geodetic coordinate, a position in 3D space relative to the reference geoid.
bool hasMSLGeodeticHeight() const
Geodetic height not null and aviation::CAltitude::MeanSeaLevel.
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
Class which can be directly used to access an.
Another client software.
Definition: client.h:27
Direct in memory access to client (network client) data.
Physical unit length (length)
Definition: length.h:18
void parseFromString(const QString &value)
Parse value from string.
QString valueRoundedWithUnit(const MU &unit, int digits=-1, bool withGroupSeparator=false, bool i18n=false) const
Value to QString with the given unit, e.g. "5.00m".
bool isZeroEpsilonConsidered() const
Quantity value <= epsilon.
Aircraft model (used by another pilot, my models on disk)
Definition: aircraftmodel.h:71
void setModelString(const QString &modelString)
Model string.
CSimulatorInfo getSimulator() const
Simulator info.
const QString & getModelString() const
Model key, either queried or loaded from simulator model.
bool isCallsignEmpty() const
Callsign empty.
const physical_quantities::CLength & getCG() const
Get center of gravity.
QString getModelStringAndDbKey() const
Model string and DB key (if available)
bool hasModelString() const
Non empty model string?
Value object encapsulating a list of aircraft models.
const QString & generatingThreadName() const
Generating thread name.
bool isRenderingEnabled() const
Rendering enabled or all aircraft disabled?
bool isRestrictionChanged() const
Did the restriction flag change?
const swift::misc::aviation::CCallsignSet & getEnabledAircraftCallsignsByDistance() const
Callsigns by distance, only enabled aircraft.
void insert(const QString &modelString, const physical_quantities::CLength &cg)
Insert values we might want to update in the DB.
bool writeJsonToFile() const
Write to file.
bool setInterpolatorMode(InterpolatorMode mode)
Set interpolator mode.
void consolidateWithClient(const network::CClient &client)
Consolidate with a network client.
physical_quantities::CLength getMaxRenderedDistance() const
Max.distance for rendering.
bool isRenderingRestricted() const
Rendering enabled, but restricted.
Value object for interpolator and rendering per callsign.
SituationLog getLastSituationLog() const
Get last log.
Delegating class which can be directly used to access an.
bool updateOwnModel(const swift::misc::simulation::CAircraftModel &model)
Update model.
bool updateOwnSituation(const aviation::CAircraftSituation &situation)
Update own situation.
swift::misc::simulation::CAircraftModel getOwnAircraftModel() const
Own aircraft model.
CSimulatedAircraft getOwnAircraft() const
Own aircraft.
Class which can be directly used to access an.
aviation::CCallsignSet getAircraftInRangeCallsigns() const
Unique callsigns for aircraft in range.
bool isAircraftInRange(const aviation::CCallsign &callsign) const
Is aircraft in range?
CSimulatedAircraftList getAircraftInRange() const
All remote aircraft.
int getAircraftInRangeCount() const
Count remote aircraft.
CSimulatedAircraft getAircraftInRangeForCallsign(const aviation::CCallsign &callsign) const
Aircraft for callsign.
Comprehensive information of an aircraft.
bool hasModelString() const
Has model string?
bool hasCallsign() const
Callsign not empty, no further checks.
const aviation::CCallsign & getCallsign() const
Get callsign.
const simulation::CAircraftModel & getModel() const
Get model (model used for mapping)
bool isEnabled() const
Enabled? Enable means it shall be displayed in the simulator.
QString getCallsignAsString() const
Get callsign.
const QString & getModelString() const
Get model string.
Value object encapsulating a list of aircraft.
Simple hardcoded info about the corresponding simulator.
Definition: simulatorinfo.h:41
bool matchesAll(const CSimulatorInfo &otherInfo) const
Matches all simulators.
bool isSingleSimulator() const
Single simulator selected.
bool isUnspecified() const
Unspecified simulator.
void setSimulatorName(const QString &name)
Set simulator name.
void setSimulatorInstallationDirectory(const QString &fullFilePath)
Path where simulator is installed.
void setSwiftPluginName(const QString &name)
Set plugin name.
bool isEmulatedPlugin() const
Is this the emulated driver?
const QString & getIdentifier() const
Identifier.
const CSimulatorInfo & getSimulatorInfo() const
Simulator info object.
Direct in memory access to interpolation setup, normally implemented by simulator.
void setLogCallsign(bool log, const aviation::CCallsign &callsign)
Log/un-log given callsign.
CInterpolationAndRenderingSetupPerCallsign getInterpolationSetupPerCallsignOrDefault(const aviation::CCallsign &callsign) const
Get the setup for callsign, if not existing the global setup.
virtual bool setInterpolationSetupGlobal(const CInterpolationAndRenderingSetupGlobal &setup)
Set the global setup.
Direct threadsafe in memory access to own aircraft.
Direct thread safe in memory access to remote aircraft.
void setNewPluginInfo(const CSimulatorPluginInfo &info, const settings::CSimulatorSettings &settings, const CAircraftModel &defaultModel)
New plugin info and default model.
CSimulatorPluginInfo getSimulatorPluginInfo() const
Get the represented plugin.
bool rememberGroundElevation(const aviation::CCallsign &requestedForCallsign, bool likelyOnGroundElevation, const geo::ICoordinateGeodetic &elevationCoordinate, const physical_quantities::CLength &epsilon=geo::CElevationPlane::singlePointRadius())
Remember a given elevation.
bool insertCG(const physical_quantities::CLength &cg, const aviation::CCallsign &cs)
Insert or replace a CG.
int setMaxElevationsRemembered(int max)
Set number of elevations kept.
CSimulatorInfo getSimulatorInfo() const
Get the represented simulator.
QString getSimulatorName() const
Simulator name as set from the running simulator.
physical_quantities::CLength overriddenCGorDefault(const physical_quantities::CLength &defaultCG, const QString &modelString) const
Return the overridden CG value or the given default CG value.
CSimulatorSettings getSettings(const CSimulatorInfo &simulator) const
Settings per simulator.
Settings for simulator Driver independent parts (such as directories), also used in model loaders.
swift::misc::physical_quantities::CLength getRecordedGndRadius() const
Record GND values with radius.
CGSource
Where we get the CG (aka vertical offset) from.
bool isRecordOwnAircraftGnd() const
Record GND values (of own aircraft)
Allows to have specific utility functions for each simulator.
const CSimulatorSettings & getSimulatorSettings() const
The generic settings.
const QString & getSimulatorDirectoryOrDefault() const
Simulator directory or default path.
Classes interacting with the swift database (aka "datastore").
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
Free functions in swift::misc.
QString className(const QObject *object)
Class name as from QMetaObject::className with namespace.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
Log entry for parts interpolation.
QString toQString(const QString &separator={ " " }) const
To string.
Log entry for situation interpolation.
QString toQString(bool withSetup, bool withCurrentSituation, bool withElevation, bool withOtherPositions, bool withDeltaTimes, const QString &separator={ " " }) const
To string.
CInterpolationAndRenderingSetupPerCallsign usedSetup
used setup
#define SWIFT_AUDIT_X(COND, WHERE, WHAT)
A weaker kind of verify.
Definition: verify.h:38
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26