Files
pilotclient/src/blackcore/simulator.cpp

1392 lines
55 KiB
C++

/* Copyright (C) 2013
* swift Project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated,
* or distributed except according to the terms contained in the LICENSE file.
*/
#include "blackcore/db/databaseutils.h"
#include "blackcore/simulator.h"
#include "blackcore/webdataservices.h"
#include "blackcore/application.h"
#include "blackmisc/simulation/data/modelcaches.h"
#include "blackmisc/math/mathutils.h"
#include "blackmisc/crashhandler.h"
#include "blackmisc/directoryutils.h"
#include "blackmisc/threadutils.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/verify.h"
#include <QFlag>
#include <Qt>
#include <QtGlobal>
#include <QPointer>
#include <QDateTime>
#include <QString>
#include <QStringBuilder>
#include <QThread>
#include <QDir>
#include <QUrl>
#include <QDesktopServices>
#include <functional>
using namespace BlackConfig;
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Geo;
using namespace BlackMisc::Math;
using namespace BlackMisc::Simulation;
using namespace BlackMisc::Simulation::Data;
using namespace BlackMisc::Simulation::Settings;
using namespace BlackMisc::PhysicalQuantities;
using namespace BlackMisc::Network;
using namespace BlackMisc::Weather;
using namespace BlackCore::Db;
namespace BlackCore
{
const CLogCategoryList &ISimulator::getLogCategories()
{
static const CLogCategoryList cats({ CLogCategory::driver(), CLogCategory::plugin() });
return cats;
}
ISimulator::~ISimulator()
{
this->safeKillTimer();
}
ISimulator::SimulatorStatus ISimulator::getSimulatorStatus() const
{
if (!this->isConnected()) { return ISimulator::Disconnected; }
const SimulatorStatus status =
Connected
| (this->isSimulating() ? ISimulator::Simulating : static_cast<ISimulator::SimulatorStatusFlag>(0))
| (this->isPaused() ? ISimulator::Paused : static_cast<ISimulator::SimulatorStatusFlag>(0));
return status;
}
bool ISimulator::logicallyRemoveRemoteAircraft(const CCallsign &callsign)
{
// if not restriced, directly change
if (!this->getInterpolationSetupGlobal().isRenderingRestricted())
{
m_statsPhysicallyAddedAircraft++;
this->callPhysicallyRemoveRemoteAircraft(callsign);
return true;
}
// will be removed with next snapshot onRecalculatedRenderedAircraft
return false;
}
bool ISimulator::logicallyAddRemoteAircraft(const CSimulatedAircraft &remoteAircraft)
{
if (!this->validateModelOfAircraft(remoteAircraft))
{
const CCallsign cs = remoteAircraft.getCallsign();
CLogMessage(this).warning(u"Invalid aircraft detected, which will be disabled: '%1' '%2'") << cs << remoteAircraft.getModelString();
this->updateAircraftEnabled(cs, false);
this->updateAircraftRendered(cs, false);
return false;
}
// no invalid model should ever reach this place here
const bool renderingRestricted = this->getInterpolationSetupGlobal().isRenderingRestricted();
if (this->showDebugLogMessage()) { this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("Restricted: %1 cs: '%2' enabled: %3").arg(boolToYesNo(renderingRestricted), remoteAircraft.getCallsignAsString(), boolToYesNo(remoteAircraft.isEnabled()))); }
if (!remoteAircraft.isEnabled()) { return false; }
// if not restriced, directly change
if (!renderingRestricted)
{
this->callPhysicallyAddRemoteAircraft(remoteAircraft);
return true;
}
// restricted -> will be added with next snapshot onRecalculatedRenderedAircraft
return false;
}
void ISimulator::highlightAircraft(const CSimulatedAircraft &aircraftToHighlight, bool enableHighlight, const CTime &displayTime)
{
const CCallsign cs(aircraftToHighlight.getCallsign());
m_highlightedAircraft.removeByCallsign(cs);
if (enableHighlight)
{
const qint64 deltaT = displayTime.valueInteger(CTimeUnit::ms());
m_highlightEndTimeMsEpoch = QDateTime::currentMSecsSinceEpoch() + deltaT;
m_highlightedAircraft.push_back(aircraftToHighlight);
}
}
bool ISimulator::followAircraft(const CCallsign &callsign)
{
Q_UNUSED(callsign);
return false;
}
void ISimulator::recalculateAllAircraft()
{
this->setUpdateAllRemoteAircraft();
}
bool ISimulator::isWeatherActivated() const
{
return m_isWeatherActivated;
}
void ISimulator::setWeatherActivated(bool activated)
{
m_isWeatherActivated = activated;
if (m_isWeatherActivated)
{
const auto selectedWeatherScenario = m_weatherScenarioSettings.get();
if (!CWeatherScenario::isRealWeatherScenario(selectedWeatherScenario))
{
m_lastWeatherPosition = {};
this->injectWeatherGrid(CWeatherGrid::getByScenario(selectedWeatherScenario));
}
}
}
void ISimulator::setFlightNetworkConnected(bool connected)
{
m_networkConnected = connected;
}
void ISimulator::reloadWeatherSettings()
{
// log crash info about weather
if (sApp && !sApp->isShuttingDown()) { CCrashHandler::instance()->crashAndLogAppendInfo(u"Simulator weather: " % boolToYesNo(m_isWeatherActivated)); }
if (!m_isWeatherActivated) { return; }
const CWeatherScenario selectedWeatherScenario = m_weatherScenarioSettings.get();
if (!CWeatherScenario::isRealWeatherScenario(selectedWeatherScenario))
{
m_lastWeatherPosition = {};
this->injectWeatherGrid(CWeatherGrid::getByScenario(selectedWeatherScenario));
}
// log crash info about weather
if (sApp && !sApp->isShuttingDown()) { CCrashHandler::instance()->crashAndLogAppendInfo(selectedWeatherScenario.toQString(true)); }
}
void ISimulator::clearAllRemoteAircraftData()
{
// rendering related stuff
m_addAgainAircraftWhenRemoved.clear();
m_callsignsToBeRendered.clear();
this->resetLastSentValues(); // clear all last sent values
m_updateRemoteAircraftInProgress = false;
this->clearInterpolationSetupsPerCallsign();
this->resetHighlighting();
this->resetAircraftStatistics();
}
void ISimulator::debugLogMessage(const QString &msg)
{
if (!this->showDebugLogMessage()) { return; }
if (msg.isEmpty()) { return; }
const CStatusMessage m = CStatusMessage(this).info(u"%1") << msg;
emit this->driverMessages(m);
}
void ISimulator::debugLogMessage(const QString &funcInfo, const QString &msg)
{
if (!this->showDebugLogMessage()) { return; }
if (msg.isEmpty()) { return; }
const CStatusMessage m = CStatusMessage(this).info(u"%1 %2") << msg << funcInfo;
emit this->driverMessages(m);
}
bool ISimulator::showDebugLogMessage() const
{
const bool show = this->getInterpolationSetupGlobal().showSimulatorDebugMessages();
return show;
}
void ISimulator::resetAircraftFromProvider(const CCallsign &callsign)
{
const CSimulatedAircraft aircraft(this->getAircraftInRangeForCallsign(callsign));
const bool enabled = aircraft.isEnabled();
if (enabled)
{
// are we already visible?
if (!this->isPhysicallyRenderedAircraft(callsign))
{
this->callPhysicallyAddRemoteAircraft(aircraft); // enable/disable
}
}
else
{
this->callPhysicallyRemoveRemoteAircraft(callsign);
}
}
void ISimulator::clearData(const CCallsign &callsign)
{
m_highlightedAircraft.removeByCallsign(callsign);
m_statsPhysicallyRemovedAircraft++;
m_lastSentParts.remove(callsign);
m_lastSentSituations.remove(callsign);
m_loopbackSituations.clear();
this->removeInterpolationSetupPerCallsign(callsign);
}
bool ISimulator::addLoopbackSituation(const CAircraftSituation &situation)
{
const CCallsign cs = situation.getCallsign();
if (!this->isLogCallsign(cs)) { return false; }
CAircraftSituationList &situations = m_loopbackSituations[cs];
situations.push_frontKeepLatestAdjustedFirst(situation, true, 10);
return true;
}
bool ISimulator::addLoopbackSituation(const CCallsign &callsign, const CElevationPlane &elevationPlane, const CLength &cg)
{
if (!this->isLogCallsign(callsign)) { return false; }
CAircraftSituation situation(callsign, elevationPlane);
situation.setGroundElevation(elevationPlane, CAircraftSituation::FromProvider);
situation.setCG(cg);
situation.setCurrentUtcTime();
situation.setTimeOffsetMs(0);
CAircraftSituationList &situations = m_loopbackSituations[callsign];
situations.push_frontKeepLatestAdjustedFirst(situation, true, 10);
return true;
}
void ISimulator::reset()
{
this->clearAllRemoteAircraftData(); // reset
}
bool ISimulator::isUpdateAllRemoteAircraft(qint64 currentTimestamp) const
{
if (m_updateAllRemoteAircraftUntil < 1) { return false; }
if (currentTimestamp < 0) { currentTimestamp = QDateTime::currentMSecsSinceEpoch(); }
return (m_updateAllRemoteAircraftUntil > currentTimestamp);
}
void ISimulator::setUpdateAllRemoteAircraft(qint64 currentTimestamp, qint64 forMs)
{
if (currentTimestamp < 0) { currentTimestamp = QDateTime::currentMSecsSinceEpoch(); }
if (forMs < 0) { forMs = 10 * 1000; }
m_updateAllRemoteAircraftUntil = currentTimestamp + forMs;
this->resetLastSentValues();
}
void ISimulator::resetUpdateAllRemoteAircraft()
{
m_updateAllRemoteAircraftUntil = -1;
}
void ISimulator::resetHighlighting()
{
m_highlightedAircraft.clear();
m_blinkCycle = false;
m_highlightEndTimeMsEpoch = false;
}
void ISimulator::stopHighlighting()
{
// restore
const CSimulatedAircraftList highlightedAircraft(m_highlightedAircraft);
for (const CSimulatedAircraft &aircraft : highlightedAircraft)
{
// get the current state for this aircraft
// it might has been removed in the meantime
const CCallsign cs(aircraft.getCallsign());
this->resetAircraftFromProvider(cs);
}
this->resetHighlighting();
}
void ISimulator::oneSecondTimerTimeout()
{
this->blinkHighlightedAircraft();
}
void ISimulator::safeKillTimer()
{
if (m_timerId < 0) { return; }
this->killTimer(m_timerId);
m_timerId = -1;
}
void ISimulator::injectWeatherGrid(const CWeatherGrid &weatherGrid)
{
Q_UNUSED(weatherGrid);
}
void ISimulator::blinkHighlightedAircraft()
{
if (m_highlightedAircraft.isEmpty() || m_highlightEndTimeMsEpoch < 1) { return; }
if (this->isShuttingDown()) { return; }
m_blinkCycle = !m_blinkCycle;
if (QDateTime::currentMSecsSinceEpoch() > m_highlightEndTimeMsEpoch)
{
this->stopHighlighting();
return;
}
// blink mode, toggle aircraft
for (const CSimulatedAircraft &aircraft : as_const(m_highlightedAircraft))
{
if (m_blinkCycle) { this->callPhysicallyRemoveRemoteAircraft(aircraft.getCallsign(), true); }
else { this->callPhysicallyAddRemoteAircraft(aircraft); }
}
}
CInterpolationAndRenderingSetupPerCallsign ISimulator::getInterpolationSetupConsolidated(const CCallsign &callsign, bool forceFullUpdate) const
{
CInterpolationAndRenderingSetupPerCallsign setup = this->getInterpolationSetupPerCallsignOrDefault(callsign);
const CClient client = this->getClientOrDefaultForCallsign(callsign);
setup.consolidateWithClient(client);
if (forceFullUpdate) { setup.setForceFullInterpolation(forceFullUpdate); }
return setup;
}
bool ISimulator::requestElevation(const ICoordinateGeodetic &reference, const CCallsign &callsign)
{
Q_UNUSED(reference);
Q_UNUSED(callsign);
return false;
}
void ISimulator::callbackReceivedRequestedElevation(const CElevationPlane &plane, const CCallsign &callsign)
{
if (this->isShuttingDown()) { return; }
ISimulationEnvironmentProvider::rememberGroundElevation(callsign, plane); // in simulator
const int updated = CRemoteAircraftAware::updateAircraftGroundElevation(callsign, plane, CAircraftSituation::FromProvider);
Q_UNUSED(updated);
emit this->receivedRequestedElevation(plane, callsign);
}
void ISimulator::resetAircraftStatistics()
{
m_statsUpdateAircraftRuns = 0;
m_statsUpdateAircraftTimeAvgMs = 0;
m_statsUpdateAircraftTimeTotalMs = 0;
m_statsMaxUpdateTimeMs = 0;
m_statsCurrentUpdateTimeMs = 0;
m_statsPhysicallyAddedAircraft = 0;
m_statsPhysicallyRemovedAircraft = 0;
m_statsLastUpdateAircraftRequestedMs = 0;
m_statsUpdateAircraftRequestedDeltaMs = 0;
m_statsUpdateAircraftLimited = 0;
ISimulationEnvironmentProvider::resetSimulationEnvironmentStatistics();
}
bool ISimulator::isEmulatedDriver() const
{
const QString className = this->metaObject()->className();
return className.contains("emulated", Qt::CaseInsensitive);
}
bool ISimulator::parseCommandLine(const QString &commandLine, const CIdentifier &originator)
{
if (this->isMyIdentifier(originator)) { return false; }
if (this->isShuttingDown()) { return false; }
if (commandLine.isEmpty()) { return false; }
CSimpleCommandParser parser({ ".plugin", ".drv", ".driver" });
parser.parse(commandLine);
if (!parser.isKnownCommand()) { return false; }
// .plugin unload
if (parser.matchesPart(1, "unload")) { this->unload(); return true; }
// .plugin log interpolator
const QString part1(parser.part(1).toLower().trimmed());
if (part1.startsWith("logint") && parser.hasPart(2))
{
const QString part2 = parser.part(2).toLower();
if (part2 == "off" || part2 == "false")
{
CStatusMessage(this).info(u"Disabled interpolation logging");
this->clearInterpolationLogCallsigns();
return true;
}
if (part2 == "clear" || part2 == "clr")
{
m_interpolationLogger.clearLog();
CStatusMessage(this).info(u"Cleared interpolation logging");
this->clearInterpolationLogCallsigns();
return true;
}
if (part2.startsWith("max"))
{
if (!parser.hasPart(3)) { return false; }
bool ok;
const int max = parser.part(3).toInt(&ok);
if (!ok) { return false; }
m_interpolationLogger.setMaxSituations(max);
CStatusMessage(this).info(u"Max.situations logged: %1") << max;
return true;
}
if (part2 == "write" || part2 == "save")
{
// stop logging of other log
this->clearInterpolationLogCallsigns();
// write
m_interpolationLogger.writeLogInBackground();
CLogMessage(this).info(u"Started writing interpolation log");
return true;
}
if (part2 == "show")
{
const QDir dir(CInterpolationLogger::getLogDirectory());
if (CDirectoryUtils::isDirExisting(dir))
{
const QUrl dirUrl = QUrl::fromLocalFile(dir.absolutePath());
QDesktopServices::openUrl(dirUrl); // show dir in browser
}
else
{
CLogMessage(this).warning(u"No interpolation log directory");
}
return true;
}
const CCallsign cs(part2.toUpper());
if (!cs.isValid()) { return false; }
if (this->getAircraftInRangeCallsigns().contains(cs))
{
CLogMessage(this).info(u"Will log interpolation for '%1'") << cs.asString();
this->setLogCallsign(true, cs);
return true;
}
else
{
CLogMessage(this).warning(u"Cannot log interpolation for '%1', no aircraft in range") << cs.asString();
return false;
}
} // logint
if (part1.startsWith("spline") || part1.startsWith("linear"))
{
if (parser.hasPart(2))
{
const CCallsign cs(parser.part(2));
const bool changed = this->setInterpolationMode(part1, cs);
CLogMessage(this).info(changed ? QStringLiteral("Changed interpolation mode for '%1'")
: QStringLiteral("Unchanged interpolation mode for '%1'")) << cs.asString();
return true;
}
else
{
CInterpolationAndRenderingSetupGlobal setup = this->getInterpolationSetupGlobal();
const bool changed = setup.setInterpolatorMode(part1);
if (changed) { this->setInterpolationSetupGlobal(setup); }
CLogMessage(this).info(changed ? QStringLiteral("Changed interpolation mode globally")
: QStringLiteral("Unchanged interpolation mode"));
return true;
}
} // spline/linear
if (part1.startsWith("pos"))
{
CCallsign cs(parser.part(2).toUpper());
if (!cs.isValid())
{
const CCallsignSet csSet = this->getLogCallsigns();
if (csSet.size() != 1) { return false; }
// if there is just one we take that one
cs = *csSet.begin();
}
this->setLogCallsign(true, cs);
CLogMessage(this).info(u"Display position for '%1'") << cs.asString();
this->displayLoggedSituationInSimulator(cs, true);
return true;
}
if (parser.hasPart(2) && (part1.startsWith("aircraft") || part1.startsWith("ac")))
{
const QString part2 = parser.part(2).toLower();
if (parser.hasPart(3) && (part2.startsWith("readd") || part2.startsWith("re-add")))
{
const QString cs = parser.part(3).toUpper();
if (cs == "all")
{
this->physicallyRemoveAllRemoteAircraft();
const CStatusMessageList msgs = this->debugVerifyStateAfterAllAircraftRemoved();
this->clearAllRemoteAircraftData(); // "dot command"
if (!msgs.isEmpty()) { emit this->driverMessages(msgs); }
const CSimulatedAircraftList aircraft = this->getAircraftInRange();
for (const CSimulatedAircraft &a : aircraft)
{
if (a.isEnabled()) { this->logicallyAddRemoteAircraft(a); }
}
}
else if (CCallsign::isValidAircraftCallsign(cs))
{
this->logicallyReAddRemoteAircraft(cs);
return true;
}
return false;
}
if (parser.hasPart(3) && (part2.startsWith("rm") || part2.startsWith("remove")))
{
const QString cs = parser.part(3).toUpper();
if (CCallsign::isValidAircraftCallsign(cs))
{
this->logicallyRemoveRemoteAircraft(cs);
}
}
return false;
}
if (part1.startsWith("limit"))
{
const int perSecond = parser.toInt(2, -1);
this->limitToUpdatesPerSecond(perSecond);
CLogMessage(this).info(u"Remote aircraft updates limitations: %1") << this->updateAircraftLimitationInfo();
return true;
}
// CG override
if (part1 == QStringView(u"cg") && parser.hasPart(3))
{
const QString ms = parser.part(3).toUpper();
CLength cg;
cg.parseFromString(parser.part(2), CPqString::SeparatorBestGuess);
if (!ms.isEmpty())
{
CLogMessage(this).info(u"Setting CG for '%1': %2") << ms << cg.valueRoundedWithUnit();
this->insertCGForModelStringOverridden(cg, ms);
}
}
// driver specific cmd line arguments
return this->parseDetails(parser);
}
void ISimulator::registerHelp()
{
if (CSimpleCommandParser::registered("BlackCore::ISimulator")) { return; }
CSimpleCommandParser::registerCommand({".drv", "alias: .driver .plugin"});
CSimpleCommandParser::registerCommand({".drv unload", "unload driver"});
CSimpleCommandParser::registerCommand({".drv cg length modelstring", "override CG"});
CSimpleCommandParser::registerCommand({".drv limit number/secs.", "limit updates to number per second (0..off)"});
CSimpleCommandParser::registerCommand({".drv logint callsign", "log interpolator for callsign"});
CSimpleCommandParser::registerCommand({".drv logint off", "no log information for interpolator"});
CSimpleCommandParser::registerCommand({".drv logint write", "write interpolator log to file"});
CSimpleCommandParser::registerCommand({".drv logint clear", "clear current log"});
CSimpleCommandParser::registerCommand({".drv logint max number", "max. number of entries logged"});
CSimpleCommandParser::registerCommand({".drv pos callsign", "show position for callsign"});
CSimpleCommandParser::registerCommand({".drv spline|linear callsign", "set spline/linear interpolator for one/all callsign(s)"});
CSimpleCommandParser::registerCommand({".drv aircraft readd callsign", "add again (re-add) a given callsign"});
CSimpleCommandParser::registerCommand({".drv aircraft readd all", "add again (re-add) all aircraft"});
CSimpleCommandParser::registerCommand({".drv aircraft rm callsign", "remove a given callsign from simulator"});
if (CBuildConfig::isCompiledWithFsuipcSupport())
{
CSimpleCommandParser::registerCommand({".drv fsuipc on|off", "enable/disable FSUIPC (if applicable)"});
}
}
QString ISimulator::statusToString(SimulatorStatus status)
{
QStringList s;
if (status.testFlag(Unspecified)) { s << QStringLiteral("Unspecified"); }
if (status.testFlag(Disconnected)) { s << QStringLiteral("Disconnected"); }
if (status.testFlag(Connected)) { s << QStringLiteral("Connected"); }
if (status.testFlag(Simulating)) { s << QStringLiteral("Simulating"); }
if (status.testFlag(Paused)) { s << QStringLiteral("Paused"); }
return s.join(", ");
}
bool ISimulator::isEqualLastSent(const CAircraftSituation &compare) const
{
Q_ASSERT_X(compare.hasCallsign(), Q_FUNC_INFO, "Need callsign");
if (!m_lastSentSituations.contains(compare.getCallsign())) { return false; }
if (compare.isNull()) { return false; }
return compare.equalPbhVectorAltitude(m_lastSentSituations.value(compare.getCallsign()));
}
bool ISimulator::isEqualLastSent(const CAircraftParts &compare, const CCallsign &callsign) const
{
if (callsign.isEmpty()) { return false; }
if (!m_lastSentParts.contains(callsign)) { return false; }
return compare.equalValues(m_lastSentParts.value(callsign));
}
void ISimulator::rememberLastSent(const CAircraftSituation &sent)
{
// normally we should never end up without callsign, but it has happened in real world scenarios
// https://discordapp.com/channels/539048679160676382/568904623151382546/575712119513677826
const bool hasCs = sent.hasCallsign();
BLACK_VERIFY_X(hasCs, Q_FUNC_INFO, "Need callsign");
if (!hasCs) { return; }
m_lastSentSituations.insert(sent.getCallsign(), sent);
}
void ISimulator::rememberLastSent(const CAircraftParts &sent, const CCallsign &callsign)
{
// normally we should never end up without callsign, but it has happened in real world scenarios
// https://discordapp.com/channels/539048679160676382/568904623151382546/575712119513677826
BLACK_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
if (callsign.isEmpty()) { return; }
m_lastSentParts.insert(callsign, sent);
}
CAircraftSituationList ISimulator::getLastSentCanLikelySkipNearGroundInterpolation() const
{
const QList<CAircraftSituation> situations = m_lastSentSituations.values();
CAircraftSituationList skipped;
for (const CAircraftSituation &s : situations)
{
if (s.canLikelySkipNearGroundInterpolation()) { skipped.push_back(s); }
}
return skipped;
}
bool ISimulator::isAnyConnectedStatus(SimulatorStatus status)
{
return (status.testFlag(Connected) || status.testFlag(Simulating) || status.testFlag(Paused));
}
ISimulator::ISimulator(const CSimulatorPluginInfo &pluginInfo,
IOwnAircraftProvider *ownAircraftProvider,
IRemoteAircraftProvider *remoteAircraftProvider,
IWeatherGridProvider *weatherGridProvider,
IClientProvider *clientProvider,
QObject *parent) :
QObject(parent),
COwnAircraftAware(ownAircraftProvider),
CRemoteAircraftAware(remoteAircraftProvider),
CWeatherGridAware(weatherGridProvider),
CClientAware(clientProvider),
ISimulationEnvironmentProvider(pluginInfo),
IInterpolationSetupProvider(),
CIdentifiable(this)
{
this->setObjectName("Simulator: " + pluginInfo.getIdentifier());
ISimulator::registerHelp();
// provider signals, hook up with remote aircraft provider
m_remoteAircraftProviderConnections.append(
CRemoteAircraftAware::provider()->connectRemoteAircraftProviderSignals(
this, // receiver must match object in bind
nullptr,
nullptr,
std::bind(&ISimulator::rapOnRemoteProviderRemovedAircraft, this, std::placeholders::_1),
std::bind(&ISimulator::rapOnRecalculatedRenderedAircraft, this, std::placeholders::_1))
);
// timer
connect(&m_oneSecondTimer, &QTimer::timeout, this, &ISimulator::oneSecondTimerTimeout);
m_oneSecondTimer.setObjectName(this->objectName().append(":m_oneSecondTimer"));
m_oneSecondTimer.start(1000);
// swift data
if (sApp && sApp->hasWebDataServices())
{
connect(sApp->getWebDataServices(), &CWebDataServices::swiftDbAllDataRead, this, &ISimulator::onSwiftDbAllDataRead, Qt::QueuedConnection);
connect(sApp->getWebDataServices(), &CWebDataServices::swiftDbAirportsRead, this, &ISimulator::onSwiftDbAirportsRead, Qt::QueuedConnection);
connect(sApp->getWebDataServices(), &CWebDataServices::swiftDbModelMatchingEntitiesRead, this, &ISimulator::onSwiftDbModelMatchingEntitiesRead, Qt::QueuedConnection);
}
connect(sApp, &CApplication::aboutToShutdown, this, &ISimulator::unload, Qt::QueuedConnection);
// provider
if (pluginInfo.isEmulatedPlugin() && !pluginInfo.getSimulatorInfo().isSingleSimulator())
{
// emulated driver with NO info yet
CLogMessage(this).info(u"Plugin '%1' with no simulator info yet, hope it will be set later") << pluginInfo.getIdentifier();
}
else
{
// NORMAL CASE or plugin with info already set
this->setNewPluginInfo(pluginInfo, m_multiSettings.getSettings(pluginInfo.getSimulatorInfo()));
}
// info data
m_simulatorInternals.setSimulatorName(this->getSimulatorName());
m_simulatorInternals.setSwiftPluginName(this->getSimulatorPluginInfo().toQString());
// model changed
connect(this, &ISimulator::ownAircraftModelChanged, this, &ISimulator::onOwnModelChanged, Qt::QueuedConnection);
// info
CLogMessage(this).info(u"Initialized simulator driver: '%1'") <<
(this->getSimulatorInfo().isUnspecified() ?
this->getSimulatorPluginInfo().toQString() :
this->getSimulatorInfo().toQString());
}
void ISimulator::onRecalculatedRenderedAircraft(const CAirspaceAircraftSnapshot &snapshot)
{
if (!snapshot.isValidSnapshot()) { return; }
// for unrestricted values all add/remove actions are directly linked
// when changing back from restricted->unrestricted an one time update is required
if (!snapshot.isRestricted() && !snapshot.isRestrictionChanged()) { return; }
Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "Needs to run in object thread");
Q_ASSERT_X(snapshot.generatingThreadName() != QThread::currentThread()->objectName(), Q_FUNC_INFO, "Expect snapshot from background thread");
// restricted snapshot values?
bool changed = false;
if (snapshot.isRenderingEnabled())
{
// make sure not to add aircraft again which are no longer in range
const CCallsignSet callsignsInRange = this->getAircraftInRangeCallsigns();
const CCallsignSet callsignsEnabledAndStillInRange = snapshot.getEnabledAircraftCallsignsByDistance().intersection(callsignsInRange);
const CCallsignSet callsignsInSimulator(this->physicallyRenderedAircraft()); // state in simulator
const CCallsignSet callsignsToBeRemoved(callsignsInSimulator.difference(callsignsEnabledAndStillInRange));
const CCallsignSet callsignsToBeAdded(callsignsEnabledAndStillInRange.difference(callsignsInSimulator));
if (!callsignsToBeRemoved.isEmpty())
{
const int r = this->physicallyRemoveMultipleRemoteAircraft(callsignsToBeRemoved);
changed = r > 0;
}
if (!callsignsToBeAdded.isEmpty())
{
CSimulatedAircraftList aircraftToBeAdded(this->getAircraftInRange().findByCallsigns(callsignsToBeAdded)); // thread safe copy
for (const CSimulatedAircraft &aircraft : aircraftToBeAdded)
{
Q_ASSERT_X(aircraft.isEnabled(), Q_FUNC_INFO, "Disabled aircraft detected as to be added");
Q_ASSERT_X(aircraft.hasModelString(), Q_FUNC_INFO, "Missing model string");
this->callPhysicallyAddRemoteAircraft(aircraft); // recalcuate snapshot
changed = true;
}
}
}
else
{
// no rendering at all, we remove everything
const int r = this->physicallyRemoveAllRemoteAircraft();
changed = r > 0;
}
// we have handled snapshot
if (changed)
{
emit this->airspaceSnapshotHandled();
}
}
bool ISimulator::physicallyRemoveRemoteAircraft(const CCallsign &callsign)
{
this->resetLastSentValues(callsign);
return true;
}
int ISimulator::physicallyRemoveMultipleRemoteAircraft(const CCallsignSet &callsigns)
{
if (callsigns.isEmpty()) { return 0; }
this->stopHighlighting();
int removed = 0;
for (const CCallsign &callsign : callsigns)
{
this->callPhysicallyRemoveRemoteAircraft(callsign);
removed++;
}
return removed;
}
int ISimulator::physicallyRemoveAllRemoteAircraft()
{
// a default implementation, but normally overridden by the sims
const CCallsignSet callsigns = this->getAircraftInRangeCallsigns();
// normally that would be already done in the specializied implementation
const int r = this->physicallyRemoveMultipleRemoteAircraft(callsigns);
// leave no trash
this->clearAllRemoteAircraftData(); // remove all aircraft
return r;
}
CAirportList ISimulator::getWebServiceAirports() const
{
if (this->isShuttingDown()) { return CAirportList(); }
if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAirportList(); }
return sApp->getWebDataServices()->getAirports();
}
CAirport ISimulator::getWebServiceAirport(const CAirportIcaoCode &icao) const
{
if (this->isShuttingDown()) { return CAirport(); }
if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return CAirport(); }
return sApp->getWebDataServices()->getAirports().findFirstByIcao(icao);
}
int ISimulator::maxAirportsInRange() const
{
// might change in future or become a setting or such
return 20;
}
void ISimulator::onSwiftDbAllDataRead()
{
// void, can be overridden in specialized drivers
}
void ISimulator::onSwiftDbModelMatchingEntitiesRead()
{
// void, can be overridden in specialized drivers
}
void ISimulator::onSwiftDbAirportsRead()
{
// void, can be overridden in specialized drivers
}
void ISimulator::initSimulatorInternals()
{
const CSpecializedSimulatorSettings s = this->getSimulatorSettings();
m_simulatorInternals.setSimulatorName(this->getSimulatorName());
m_simulatorInternals.setSwiftPluginName(this->getSimulatorPluginInfo().toQString());
m_simulatorInternals.setSimulatorInstallationDirectory(s.getSimulatorDirectoryOrDefault());
}
void ISimulator::rememberElevationAndSimulatorCG(const CCallsign &callsign, const CAircraftModel &model, const CElevationPlane &elevation, const CLength &simulatorCG)
{
if (callsign.isEmpty()) { return; }
if (!elevation.isNull())
{
const int aircraftCount = this->getAircraftInRangeCount();
this->setMaxElevationsRemembered(aircraftCount * 3); // at least 3 elevations per aircraft, even better as not all are requesting elevations
this->rememberGroundElevation(callsign, elevation);
}
const QString modelString = model.getModelString();
if (modelString.isEmpty()) { return; }
const CLength cgOvr = this->overriddenCGorDefault(simulatorCG, modelString);
if (!cgOvr.isNull() && !this->hasSameSimulatorCG(cgOvr, callsign))
{
const CSimulatorSettings::CGSource source = this->getSimulatorSettings().getSimulatorSettings().getCGSource();
if (source != CSimulatorSettings::CGFromDBOnly)
{
this->insertCG(cgOvr, modelString, callsign); // per model string and CG
}
// here we know we have a valid model and CG did change
const CSimulatorInfo sim = this->getSimulatorInfo();
m_autoPublishing.insert(modelString, simulatorCG); // still using simulator CG here, not the overridden value
// if simulator did change, add as well
if (!model.getSimulator().matchesAll(sim))
{
m_autoPublishing.insert(modelString, this->getSimulatorInfo());
}
}
}
void ISimulator::emitSimulatorCombinedStatus(SimulatorStatus oldStatus)
{
const SimulatorStatus newStatus = this->getSimulatorStatus();
if (oldStatus != newStatus)
{
// decouple, follow up of signal can include unloading
// simulator so this should happen strictly asyncronously (which is like forcing Qt::QueuedConnection)
QPointer<ISimulator> myself(this);
QTimer::singleShot(0, this, [ = ]
{
if (!myself || !sApp || sApp->isShuttingDown()) { return; }
// now simulating
if (newStatus.testFlag(Simulating))
{
this->setUpdateAllRemoteAircraft(); // force an update of every remote aircraft
}
emit this->simulatorStatusChanged(newStatus); // only place where we should emit the signal, use emitSimulatorCombinedStatus to emit
});
}
}
void ISimulator::emitInterpolationSetupChanged()
{
QPointer<ISimulator> myself(this);
QTimer::singleShot(0, this, [ = ]
{
if (!myself) { return; }
emit this->interpolationAndRenderingSetupChanged();
});
}
bool ISimulator::setInterpolationSetupGlobal(const CInterpolationAndRenderingSetupGlobal &setup)
{
if (!IInterpolationSetupProvider::setInterpolationSetupGlobal(setup)) { return false; }
const bool r = setup.isRenderingRestricted();
const bool e = setup.isRenderingEnabled();
if (sApp && !sApp->isShuttingDown()) { CCrashHandler::instance()->crashAndLogAppendInfo(u"Rendering setup: " % setup.toQString(true)); }
emit this->renderRestrictionsChanged(r, e, setup.getMaxRenderedAircraft(), setup.getMaxRenderedDistance());
return true;
}
CAircraftSituationList ISimulator::getLoopbackSituations(const CCallsign &callsign) const
{
return m_loopbackSituations.value(callsign);
}
CAirportList ISimulator::getAirportsInRange(bool recalculateDistance) const
{
// default implementation
if (this->isShuttingDown()) { return CAirportList(); }
if (!sApp || !sApp->hasWebDataServices()) { return CAirportList(); }
const CAirportList airports = sApp->getWebDataServices()->getAirports();
if (airports.isEmpty()) { return airports; }
const CCoordinateGeodetic ownPosition = this->getOwnAircraftPosition();
CAirportList airportsInRange = airports.findClosest(maxAirportsInRange(), ownPosition);
if (recalculateDistance) { airportsInRange.calculcateAndUpdateRelativeDistanceAndBearing(this->getOwnAircraftPosition()); }
return airportsInRange;
}
CAircraftModel ISimulator::reverseLookupModel(const CAircraftModel &model)
{
bool modified = false;
const CAircraftModel reverseModel = CDatabaseUtils::consolidateOwnAircraftModelWithDbData(model, false, &modified);
return reverseModel;
}
bool ISimulator::isUpdateAircraftLimited(qint64 timestamp)
{
if (!m_limitUpdateAircraft) { return false; }
const bool hasToken = m_limitUpdateAircraftBucket.tryConsume(1, timestamp);
return !hasToken;
}
bool ISimulator::isUpdateAircraftLimitedWithStats(qint64 startTime)
{
const bool limited = this->isUpdateAircraftLimited(startTime);
return limited;
}
bool ISimulator::limitToUpdatesPerSecond(int numberPerSecond)
{
if (numberPerSecond < 1)
{
m_limitUpdateAircraft = false;
return false;
}
int tokens = qRound(0.1 * numberPerSecond); // 100ms
do
{
if (tokens >= 3) { m_limitUpdateAircraftBucket.setInterval(100); break; }
tokens = qRound(0.25 * numberPerSecond); // 250ms
if (tokens >= 3) { m_limitUpdateAircraftBucket.setInterval(250); break; }
tokens = qRound(0.5 * numberPerSecond); // 500ms
if (tokens >= 3) { m_limitUpdateAircraftBucket.setInterval(500); break; }
tokens = numberPerSecond;
m_limitUpdateAircraftBucket.setInterval(1000);
}
while (false);
m_limitUpdateAircraftBucket.setCapacityAndTokensToRefill(tokens);
m_limitUpdateAircraft = true;
return true;
}
QString ISimulator::updateAircraftLimitationInfo() const
{
if (!m_limitUpdateAircraft) { return QStringLiteral("not limited"); }
static const QString limInfo("Limited %1 times with %2/secs.");
return limInfo.arg(m_statsUpdateAircraftLimited).arg(m_limitUpdateAircraftBucket.getTokensPerSecond());
}
void ISimulator::resetLastSentValues()
{
m_lastSentParts.clear();
m_lastSentSituations.clear();
}
void ISimulator::resetLastSentValues(const CCallsign &callsign)
{
m_lastSentParts.remove(callsign);
m_lastSentSituations.remove(callsign);
}
void ISimulator::unload()
{
this->disconnectFrom(); // disconnect from simulator
const bool saved = m_autoPublishing.writeJsonToFile(); // empty data are ignored
if (saved) { emit this->autoPublishDataWritten(this->getSimulatorInfo()); }
m_autoPublishing.clear();
m_remoteAircraftProviderConnections.disconnectAll(); // disconnect signals from provider
}
bool ISimulator::isAircraftInRangeOrTestMode(const CCallsign &callsign) const
{
return this->isTestMode() || this->isAircraftInRange(callsign);
}
bool ISimulator::disconnectFrom()
{
// supposed to be overridden
return true;
}
bool ISimulator::logicallyReAddRemoteAircraft(const CCallsign &callsign)
{
if (this->isShuttingDown()) { return false; }
if (callsign.isEmpty()) { return false; }
this->stopHighlighting();
this->logicallyRemoveRemoteAircraft(callsign);
if (!this->isAircraftInRange(callsign)) { return false; }
const QPointer<ISimulator> myself(this);
QTimer::singleShot(2500, this, [ = ]
{
if (myself.isNull()) { return; }
if (this->isShuttingDown()) { return; }
if (!this->isAircraftInRange(callsign)) { return; }
const CSimulatedAircraft aircraft = this->getAircraftInRangeForCallsign(callsign);
if (aircraft.isEnabled() && aircraft.hasModelString())
{
this->logicallyAddRemoteAircraft(aircraft);
}
});
return true;
}
CCallsignSet ISimulator::unrenderedEnabledAircraft() const
{
const CSimulatedAircraftList aircraft = this->getAircraftInRange().findByEnabled(true);
if (aircraft.isEmpty()) { return CCallsignSet(); }
CCallsignSet enabledOnes = aircraft.getCallsigns();
const CCallsignSet renderedOnes = this->physicallyRenderedAircraft();
enabledOnes.remove(renderedOnes);
return enabledOnes;
}
CCallsignSet ISimulator::renderedDisabledAircraft() const
{
const CSimulatedAircraftList aircraft = this->getAircraftInRange().findByEnabled(false);
if (aircraft.isEmpty()) { return CCallsignSet(); }
const CCallsignSet disabledOnes = aircraft.getCallsigns();
const CCallsignSet renderedOnes = this->physicallyRenderedAircraft();
return renderedOnes.intersection(disabledOnes);
}
bool ISimulator::changeRemoteAircraftEnabled(const CSimulatedAircraft &aircraft)
{
if (this->isShuttingDown()) { return false; }
return aircraft.isEnabled() ?
this->physicallyAddRemoteAircraft(aircraft) :
this->physicallyRemoveRemoteAircraft(aircraft.getCallsign());
}
bool ISimulator::changeRemoteAircraftModel(const CSimulatedAircraft &aircraft)
{
// we expect the new model "in aircraft"
// remove upfront, and then enable / disable again
if (this->isShuttingDown()) { return false; }
const CCallsign callsign = aircraft.getCallsign();
if (!this->isPhysicallyRenderedAircraft(callsign)) { return false; }
this->physicallyRemoveRemoteAircraft(callsign);
// return this->changeRemoteAircraftEnabled(aircraft);
const QPointer<ISimulator> myself(this);
QTimer::singleShot(1000, this, [ = ]
{
if (!myself) { return; }
if (this->isAircraftInRange(callsign))
{
this->changeRemoteAircraftEnabled(aircraft);
}
});
return true;
}
CStatusMessageList ISimulator::debugVerifyStateAfterAllAircraftRemoved() const
{
CStatusMessageList msgs;
if (!CBuildConfig::isLocalDeveloperDebugBuild()) { return msgs; }
if (!m_addAgainAircraftWhenRemoved.isEmpty()) { msgs.push_back(CStatusMessage(this).error(u"m_addAgainAircraftWhenRemoved not empty: '%1'") << m_addAgainAircraftWhenRemoved.getCallsignStrings(true).join(", ")); }
return msgs;
}
QString ISimulator::getInvalidSituationLogMessage(const CCallsign &callsign, const CInterpolationStatus &status, const QString &details) const
{
static const QString msg("Interpolation ('%1'): '%2'");
const QString m = msg.arg(callsign.asString(), status.toQString());
if (details.isEmpty()) { return m; }
static const QString addDetails(" details: '%1'");
return m % addDetails.arg(details);
}
void ISimulator::finishUpdateRemoteAircraftAndSetStatistics(qint64 startTime, bool limited)
{
const qint64 now = QDateTime::currentMSecsSinceEpoch();
const qint64 dt = now - startTime;
m_statsCurrentUpdateTimeMs = dt;
m_statsUpdateAircraftTimeTotalMs += dt;
m_statsUpdateAircraftRuns++;
m_statsUpdateAircraftTimeAvgMs = static_cast<double>(m_statsUpdateAircraftTimeTotalMs) / static_cast<double>(m_statsUpdateAircraftRuns);
m_updateRemoteAircraftInProgress = false;
m_statsLastUpdateAircraftRequestedMs = startTime;
if (!this->isUpdateAllRemoteAircraft(startTime)) { this->resetUpdateAllRemoteAircraft(); }
if (m_statsMaxUpdateTimeMs < dt) { m_statsMaxUpdateTimeMs = dt; }
if (m_statsLastUpdateAircraftRequestedMs > 0) { m_statsUpdateAircraftRequestedDeltaMs = startTime - m_statsLastUpdateAircraftRequestedMs; }
if (limited) { m_statsUpdateAircraftLimited++; }
}
void ISimulator::onOwnModelChanged(const CAircraftModel &newModel)
{
Q_UNUSED(newModel);
// can be overridden
}
bool ISimulator::updateOwnSituationAndGroundElevation(const CAircraftSituation &situation)
{
const bool updated = this->updateOwnSituation(situation);
// do not use every situation, but every deltaMs and only on ground
constexpr qint64 deltaMs = 5000;
if (situation.isOnGround() && situation.getTimeDifferenceAbsMs(m_lastRecordedGndElevationMs) > deltaMs)
{
m_lastRecordedGndElevationMs = situation.getMSecsSinceEpoch();
const CSimulatorSettings settings = m_multiSettings.getSettings(this->getSimulatorInfo());
if (settings.isRecordOwnAircraftGnd())
{
const CSimulatedAircraft ownAircraft = this->getOwnAircraft();
CAltitude elevation = situation.getGroundElevation();
if (elevation.isNull())
{
const CLength cg = ownAircraft.getModel().getCG();
elevation = (cg.isNull() || situation.getAltitude().isNull()) ? CAltitude::null() : (situation.getAltitude().withOffset(cg * -1.0));
}
if (!elevation.isNull())
{
const CCallsign cs = situation.hasCallsign() ? situation.getCallsign() : ownAircraft.getCallsign();
const CLength radius = settings.getRecordedGndRadius().isNull() ? CElevationPlane::singlePointRadius() : settings.getRecordedGndRadius();
const CElevationPlane ep(situation, radius);
const bool remembered = this->rememberGroundElevation(cs, ep, radius);
Q_UNUSED(remembered); // false means it was already in that cache, or something else is wrong
}
}
}
return updated;
}
CAircraftModelList ISimulator::getModelSet() const
{
const CSimulatorInfo simulator = this->getSimulatorInfo();
if (!simulator.isSingleSimulator()) { return CAircraftModelList(); }
CCentralMultiSimulatorModelSetCachesProvider::modelCachesInstance().synchronizeCache(simulator);
return CCentralMultiSimulatorModelSetCachesProvider::modelCachesInstance().getCachedModels(simulator);
}
bool ISimulator::validateModelOfAircraft(const CSimulatedAircraft &aircraft) const
{
const CAircraftModel model = aircraft.getModel();
if (!aircraft.hasCallsign())
{
CLogMessage(this).warning(u"Missing callsign for '%1'") << aircraft.getModelString();
return false;
}
if (!model.hasModelString())
{
CLogMessage(this).warning(u"No model string for callsign '%1'") << aircraft.getCallsign();
return false;
}
if (model.isCallsignEmpty())
{
CLogMessage(this).warning(u"No callsign for model of aircraft '%1'") << aircraft.getCallsign();
return false;
}
return true;
}
void ISimulator::logAddingAircraftModel(const CSimulatedAircraft &aircraft) const
{
CLogMessage(this).info(u"Adding '%1' '%2' to '%3'") << aircraft.getCallsign() << aircraft.getModel().getModelStringAndDbKey() << this->getSimulatorInfo().toQString(true);
}
QString ISimulator::latestLoggedDataFormatted(const CCallsign &cs) const
{
const SituationLog s = m_interpolationLogger.getLastSituationLog(cs);
const PartsLog p = m_interpolationLogger.getLastPartsLog(cs);
static const QString sep("\n------\n");
QString dm;
if (s.tsCurrent > 0)
{
dm = u"Setup: " % s.usedSetup.toQString(true) %
u"\n\n" %
u"Situation: " % s.toQString(false, true, true, true, true, sep);
}
if (p.tsCurrent > 0) { dm += (dm.isEmpty() ? u"Parts: " : u"\n\nParts: ") % p.toQString(sep); }
return dm;
}
void ISimulator::rapOnRecalculatedRenderedAircraft(const CAirspaceAircraftSnapshot &snapshot)
{
if (!this->isConnected()) { return; }
if (this->isShuttingDown()) { return; }
this->onRecalculatedRenderedAircraft(snapshot);
}
void ISimulator::rapOnRemoteProviderRemovedAircraft(const CCallsign &callsign)
{
Q_UNUSED(callsign);
// currently not used, the calls are handled by context call logicallyRemoveRemoteAircraft
}
void ISimulator::callPhysicallyAddRemoteAircraft(const CSimulatedAircraft &remoteAircraft)
{
m_statsPhysicallyAddedAircraft++;
this->physicallyAddRemoteAircraft(remoteAircraft);
}
void ISimulator::callPhysicallyRemoveRemoteAircraft(const CCallsign &remoteCallsign, bool blinking)
{
if (!blinking) { this->clearData(remoteCallsign); }
this->physicallyRemoveRemoteAircraft(remoteCallsign);
}
void ISimulator::displayLoggedSituationInSimulator(const CCallsign &cs, bool stopLogging, int times)
{
if (cs.isEmpty()) { return; }
if (this->isShuttingDown()) { return; }
const CInterpolationAndRenderingSetupPerCallsign setup = this->getInterpolationSetupPerCallsignOrDefault(cs);
const bool logsCs = setup.logInterpolation();
if (!logsCs) { return; }
stopLogging = stopLogging || !this->isSimulating(); // stop when sim was stopped
stopLogging = stopLogging && logsCs;
if (!stopLogging && times < 1) { return; }
const bool inRange = this->getAircraftInRangeCallsigns().contains(cs);
if (!stopLogging && !inRange) { return; }
if (stopLogging && (times < 1 || !inRange))
{
this->setLogCallsign(false, cs);
return;
}
const QString dm = this->latestLoggedDataFormatted(cs);
if (!dm.isEmpty())
{
this->displayStatusMessage(CStatusMessage(this).info(dm));
emit this->requestUiConsoleMessage(dm, true);
}
const int t = CMathUtils::randomInteger(4500, 5500); // makes sure not always using the same time difference
const QPointer<ISimulator> myself(this);
QTimer::singleShot(t, this, [ = ]
{
if (myself.isNull() || myself->isShuttingDown()) { return; }
this->displayLoggedSituationInSimulator(cs, stopLogging, times - 1);
});
}
void ISimulator::reverseLookupAndUpdateOwnAircraftModel(const QString &modelString)
{
CAircraftModel model = this->getOwnAircraftModel();
model.setModelString(modelString);
this->reverseLookupAndUpdateOwnAircraftModel(model);
}
void ISimulator::reverseLookupAndUpdateOwnAircraftModel(const CAircraftModel &model)
{
if (!model.hasModelString()) { return; }
if (this->isShuttingDown()) { return; }
Q_ASSERT_X(sApp->hasWebDataServices(), Q_FUNC_INFO, "Missing web services");
if (this->getOwnAircraftModel() != model)
{
if (CDatabaseUtils::hasDbAircraftData())
{
const CAircraftModel newModel = this->reverseLookupModel(model);
const bool updated = this->updateOwnModel(newModel); // update in provider (normally the context)
if (updated)
{
emit this->ownAircraftModelChanged(this->getOwnAircraftModel());
}
}
else
{
// we wait for the data
connect(sApp->getWebDataServices(), &CWebDataServices::swiftDbModelMatchingEntitiesRead, this, [ = ]
{
this->reverseLookupAndUpdateOwnAircraftModel(model);
});
}
}
}
ISimulatorListener::ISimulatorListener(const CSimulatorPluginInfo &info) :
QObject(), m_info(info)
{
this->setObjectName("ISimulatorListener:" + info.toQString());
// stop listener after it reports simulator ready
const bool s = connect(this, &ISimulatorListener::simulatorStarted, this, &ISimulatorListener::stop, Qt::QueuedConnection);
Q_ASSERT_X(s, Q_FUNC_INFO, "connect failed");
Q_UNUSED(s)
}
QString ISimulatorListener::backendInfo() const
{
return m_info.toQString();
}
bool ISimulatorListener::isShuttingDown() const
{
return (!sApp || sApp->isShuttingDown());
}
void ISimulatorListener::start()
{
if (m_isRunning) { return; }
if (!CThreadUtils::isCurrentThreadObjectThread(this))
{
// call in correct thread
QPointer<ISimulatorListener> myself(this);
QTimer::singleShot(0, this, [ = ] { if (myself) { this->start(); }});
return;
}
m_isRunning = true;
this->startImpl();
}
void ISimulatorListener::stop()
{
if (!m_isRunning) { return; }
if (!CThreadUtils::isCurrentThreadObjectThread(this))
{
// call in correct thread
QPointer<ISimulatorListener> myself(this);
QTimer::singleShot(0, this, [ = ] { if (myself) { this->stop(); }});
return;
}
this->stopImpl();
m_isRunning = false;
}
void ISimulatorListener::check()
{
if (!m_isRunning) { return; }
if (!CThreadUtils::isCurrentThreadObjectThread(this))
{
// call in correct thread
QPointer<ISimulatorListener> myself(this);
QTimer::singleShot(0, this, [ = ] { if (myself) { this->check(); }});
return;
}
this->checkImpl();
}
} // namespace