Files
pilotclient/src/core/corefacade.cpp
2024-12-23 15:17:06 +01:00

529 lines
23 KiB
C++

// SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
#include "core/corefacade.h"
#include <QDBusConnection>
#include <QElapsedTimer>
#include <QMap>
#include <QObject>
#include <QString>
#include <QStringBuilder>
#include <QtGlobal>
#include "core/airspacemonitor.h"
#include "core/context/contextapplication.h"
#include "core/context/contextapplicationimpl.h"
#include "core/context/contextapplicationproxy.h"
#include "core/context/contextaudio.h"
#include "core/context/contextaudioimpl.h"
#include "core/context/contextnetwork.h"
#include "core/context/contextnetworkimpl.h"
#include "core/context/contextownaircraft.h"
#include "core/context/contextownaircraftimpl.h"
#include "core/context/contextsimulator.h"
#include "core/context/contextsimulatorimpl.h"
#include "core/corefacadeconfig.h"
#include "core/data/launchersetup.h"
#include "core/registermetadata.h"
#include "misc/dbusserver.h"
#include "misc/identifier.h"
#include "misc/loghistory.h"
#include "misc/logmessage.h"
#include "misc/registermetadata.h"
#include "misc/sharedstate/datalinkdbus.h"
#include "misc/statusmessage.h"
#include "misc/stringutils.h"
#include "misc/verify.h"
using namespace swift::misc;
using namespace swift::misc::aviation;
using namespace swift::misc::simulation;
using namespace swift::core::data;
using namespace swift::core::context;
namespace swift::core
{
CCoreFacade::CCoreFacade(const CCoreFacadeConfig &config, QObject *parent) : QObject(parent), m_config(config)
{
this->init();
}
CStatusMessage CCoreFacade::tryToReconnectWithDBus()
{
if (m_shuttingDown) { return CStatusMessage(this, CStatusMessage::SeverityInfo, u"Shutdown"); }
if (!m_config.requiresDBusConnection())
{
return CStatusMessage(this, CStatusMessage::SeverityInfo, u"Not DBus based");
}
const QString dBusAddress = this->getDBusAddress();
if (dBusAddress.isEmpty())
{
return CStatusMessage(this, CStatusMessage::SeverityInfo, u"Not DBus based, no address");
}
QString connectMsg;
if (!CContextApplicationProxy::isContextResponsive(dBusAddress, connectMsg))
{
return CStatusMessage(this, CStatusMessage::SeverityError,
u"Cannot connect DBus at '" % dBusAddress % u"', reason: " % connectMsg);
}
// re-init
m_initalized = false;
this->init();
// success
return CStatusMessage(this, CStatusMessage::SeverityInfo, u"Re-initialized via '%1'") << dBusAddress;
}
void CCoreFacade::init()
{
if (m_initalized || m_shuttingDown) { return; }
QMap<QString, qint64> times;
QElapsedTimer time;
CCoreFacade::registerMetadata();
// either use explicit setting or last value
const QString dbusAddress = this->getDBusAddress();
m_launcherSetup.setProperty(CLauncherSetup::IndexDBusAddress, dbusAddress);
// DBus
time.start();
if (m_config.requiresDBusSever()) { this->initDBusServer(dbusAddress); }
if (m_config.requiresDBusConnection())
{
this->initDBusConnection(dbusAddress);
if (!m_dbusConnection.isConnected())
{
const QString e = m_dbusConnection.lastError().message();
SWIFT_VERIFY_X(false, "CRuntime::init DBus problem", e.toUtf8().constData());
CLogMessage(this).error(u"DBus connection failed: '%1'") << e;
return;
}
}
times.insert("DBus", time.restart());
// shared state infrastructure
m_dataLinkDBus = new shared_state::CDataLinkDBus(this);
switch (m_config.getMode())
{
case CCoreFacadeConfig::NotUsed:
case CCoreFacadeConfig::Local: m_dataLinkDBus->initializeLocal(nullptr); break;
case CCoreFacadeConfig::LocalInDBusServer: m_dataLinkDBus->initializeLocal(m_dbusServer); break;
case CCoreFacadeConfig::Remote:
m_dataLinkDBus->initializeRemote(m_dbusConnection, CDBusServer::coreServiceName(m_dbusConnection));
break;
default: qFatal("Invalid application context mode");
}
// shared log history
m_logHistorySource = new CLogHistorySource(this);
m_logHistorySource->initialize(m_dataLinkDBus);
if (m_config.getMode() == CCoreFacadeConfig::Local ||
m_config.getMode() == CCoreFacadeConfig::LocalInDBusServer)
{
m_logHistory = new CLogHistory(this);
m_logHistory->initialize(m_dataLinkDBus);
}
if (m_config.getMode() == CCoreFacadeConfig::NotUsed)
{
m_initalized = true;
return;
}
// contexts
if (m_contextApplication) { m_contextApplication->deleteLater(); }
m_contextApplication = IContextApplication::create(this, m_config.getMode(), m_dbusServer, m_dbusConnection);
times.insert("Application", time.restart());
if (m_contextAudio) { m_contextAudio->deleteLater(); }
m_contextAudio = qobject_cast<CContextAudioBase *>(
IContextAudio::create(this, m_config.getMode(), m_dbusServer, m_dbusConnection));
times.insert("Audio", time.restart());
if (m_contextOwnAircraft) { m_contextOwnAircraft->deleteLater(); }
m_contextOwnAircraft = IContextOwnAircraft::create(this, m_config.getMode(), m_dbusServer, m_dbusConnection);
times.insert("Own aircraft", time.restart());
if (m_contextSimulator) { m_contextSimulator->deleteLater(); }
m_contextSimulator = IContextSimulator::create(this, m_config.getMode(), m_dbusServer, m_dbusConnection);
times.insert("Simulator", time.restart());
// depends on own aircraft and simulator context, which is bad style
if (m_contextNetwork) { m_contextNetwork->deleteLater(); }
m_contextNetwork = IContextNetwork::create(this, m_config.getMode(), m_dbusServer, m_dbusConnection);
times.insert("Network", time.restart());
// checks --------------
// 1. own aircraft and simulator should reside in same location
Q_ASSERT(!m_contextSimulator || (m_contextOwnAircraft->isUsingImplementingObject() ==
m_contextSimulator->isUsingImplementingObject()));
// 2. own aircraft and network should reside in same location
Q_ASSERT(!m_contextNetwork ||
(m_contextOwnAircraft->isUsingImplementingObject() == m_contextNetwork->isUsingImplementingObject()));
// post inits, wiring things among context (e.g. signal slots)
time.restart();
this->initPostSetup(times);
times.insert("Post setup", time.restart());
CLogMessage(this).info(u"Init times: %1") << qmapToString(times);
// flag
m_initalized = true;
}
void CCoreFacade::registerMetadata()
{
swift::misc::registerMetadata();
swift::core::registerMetadata();
}
bool CCoreFacade::parseCommandLine(const QString &commandLine, const CIdentifier &originator)
{
bool handled = false;
// audio can be empty depending on whre it runs
if (this->getIContextAudio() && !this->getIContextAudio()->isEmptyObject())
{
handled = handled || this->getIContextAudio()->parseCommandLine(commandLine, originator);
}
if (this->getIContextNetwork())
{
handled = handled || this->getIContextNetwork()->parseCommandLine(commandLine, originator);
}
if (this->getIContextOwnAircraft())
{
handled = handled || this->getIContextOwnAircraft()->parseCommandLine(commandLine, originator);
}
if (this->getIContextSimulator())
{
handled = handled || this->getIContextSimulator()->parseCommandLine(commandLine, originator);
}
return handled;
}
void CCoreFacade::initDBusServer(const QString &dBusAddress)
{
Q_ASSERT(!dBusAddress.isEmpty());
if (m_dbusServer) { m_dbusServer->deleteLater(); } // delete if there was an existing one
m_dbusServer = new CDBusServer(dBusAddress, this);
CLogMessage(this).info(u"DBus server on address: '%1'") << dBusAddress;
}
void CCoreFacade::initPostSetup(QMap<QString, qint64> &times)
{
bool c = false;
Q_UNUSED(c) // for release version
QElapsedTimer time;
time.start();
times.insert("Post setup, connects first", time.restart());
// local simulator?
if (m_contextSimulator && m_contextSimulator->isUsingImplementingObject())
{
// only connect if network runs locally, no round trips
// remark: from a design perspective it would be nice if those could be avoided (seperation of concerns)
// those connects here reprent cross context dependencies
if (m_contextNetwork && m_contextNetwork->isUsingImplementingObject())
{
Q_ASSERT_X(this->getCContextNetwork(), Q_FUNC_INFO, "No local network object");
Q_ASSERT_X(this->getCContextNetwork()->airspace(), Q_FUNC_INFO, "No airspace object");
c = connect(m_contextNetwork, &IContextNetwork::textMessagesReceived, this->getCContextSimulator(),
&CContextSimulator::xCtxTextMessagesReceived, Qt::QueuedConnection);
Q_ASSERT(c);
// use readyForModelMatching instead of CAirspaceMonitor::addedAircraft, as it contains client
// information ready for model matching is sent delayed when all information are available
c = connect(m_contextNetwork, &IContextNetwork::readyForModelMatching, this->getCContextSimulator(),
&CContextSimulator::xCtxAddedRemoteAircraftReadyForModelMatching, Qt::QueuedConnection);
Q_ASSERT(c);
c = connect(m_contextNetwork, &IContextNetwork::removedAircraft, this->getCContextSimulator(),
&CContextSimulator::xCtxRemovedRemoteAircraft, Qt::QueuedConnection);
Q_ASSERT(c);
c = connect(m_contextNetwork, &IContextNetwork::changedRemoteAircraftModel,
this->getCContextSimulator(), &CContextSimulator::xCtxChangedRemoteAircraftModel,
Qt::QueuedConnection);
Q_ASSERT(c);
c = connect(m_contextNetwork, &IContextNetwork::changedRemoteAircraftEnabled,
this->getCContextSimulator(), &CContextSimulator::xCtxChangedRemoteAircraftEnabled,
Qt::QueuedConnection);
Q_ASSERT(c);
c = connect(m_contextNetwork, &IContextNetwork::connectionStatusChanged, this->getCContextSimulator(),
&CContextSimulator::xCtxNetworkConnectionStatusChanged, Qt::QueuedConnection);
Q_ASSERT(c);
c = connect(this->getCContextNetwork()->airspace(), &CAirspaceMonitor::requestedNewAircraft,
this->getCContextSimulator(), &CContextSimulator::xCtxNetworkRequestedNewAircraft,
Qt::QueuedConnection);
Q_ASSERT(c);
c = connect(this->getCContextSimulator(), &CContextSimulator::renderRestrictionsChanged,
this->getCContextNetwork(), &CContextNetwork::xCtxSimulatorRenderRestrictionsChanged,
Qt::QueuedConnection);
Q_ASSERT(c);
c = connect(this->getCContextSimulator(), &CContextSimulator::simulatorStatusChanged,
this->getCContextNetwork(), &CContextNetwork::xCtxSimulatorStatusChanged,
Qt::QueuedConnection);
Q_ASSERT(c);
// set provider
this->getCContextNetwork()->setSimulationEnvironmentProvider(this->getCContextSimulator()->simulator());
}
// only if own aircraft runs locally
if (m_contextOwnAircraft && m_contextOwnAircraft->isUsingImplementingObject())
{
c = connect(m_contextOwnAircraft, &IContextOwnAircraft::changedAircraftCockpit,
this->getCContextSimulator(), &CContextSimulator::xCtxUpdateSimulatorCockpitFromContext);
Q_ASSERT(c);
c = connect(m_contextOwnAircraft, &IContextOwnAircraft::changedSelcal, this->getCContextSimulator(),
&CContextSimulator::xCtxUpdateSimulatorSelcalFromContext);
Q_ASSERT(c);
// relay changed aircraft to own aircraft provider but with identifier
// identifier is needed because own aircraft context also reports changed aircraft to
// xCtxChangedOwnAircraftModel and we avoid roundtrips
c = connect(this->getCContextSimulator(), &CContextSimulator::ownAircraftModelChanged,
this->getCContextOwnAircraft(), [=](const CAircraftModel &changedModel) {
if (!this->getIContextOwnAircraft()) { return; }
if (!this->getCContextSimulator()) { return; }
this->getCContextOwnAircraft()->xCtxChangedSimulatorModel(
changedModel, this->getCContextSimulator()->identifier());
});
Q_ASSERT(c);
c = connect(this->getCContextSimulator(), &CContextSimulator::simulatorStatusChanged,
this->getCContextOwnAircraft(), &CContextOwnAircraft::xCtxChangedSimulatorStatus);
Q_ASSERT(c);
// this is used if the value in own aircraft is changed, to callback simulator
c = connect(this->getCContextOwnAircraft(), &CContextOwnAircraft::ps_changedModel,
this->getCContextSimulator(), &CContextSimulator::xCtxChangedOwnAircraftModel);
Q_ASSERT(c);
}
// times
times.insert("Post setup, sim.connects", time.restart());
}
// connection status of network changed
// with AFV no longer use m_contextAudio->isUsingImplementingObject() as audio can run on both sides
if (this->getCContextAudioBase() && m_contextNetwork)
{
Q_ASSERT(m_contextApplication);
c = connect(m_contextNetwork, &IContextNetwork::connectionStatusChanged, this->getCContextAudioBase(),
&CContextAudio::xCtxNetworkConnectionStatusChanged, Qt::QueuedConnection);
Q_ASSERT(c);
times.insert("Post setup, connects audio", time.restart());
}
}
QString CCoreFacade::getDBusAddress() const
{
QString dbusAddress;
if (m_config.hasDBusAddress()) { dbusAddress = m_config.getDBusAddress(); }
else
{
const CLauncherSetup setup = m_launcherSetup.get();
dbusAddress = setup.getDBusAddress();
}
return dbusAddress;
}
void CCoreFacade::gracefulShutdown()
{
if (!m_initalized) { return; }
if (m_shuttingDown) { return; }
m_shuttingDown = true;
// disable all signals towards runtime
disconnect(this);
// tear down shared state infrastructure
delete m_logHistory;
m_logHistory = nullptr;
delete m_logHistorySource;
m_logHistorySource = nullptr;
delete m_dataLinkDBus;
m_dataLinkDBus = nullptr;
// unregister all from DBus
if (m_dbusServer) { m_dbusServer->removeAllObjects(); }
// handle contexts
if (this->getCContextAudioBase())
{
// there is no empty audio context since AFV
disconnect(this->getCContextAudioBase());
this->getCContextAudioBase()->gracefulShutdown();
this->getIContextAudio()->deleteLater();
m_contextAudio = nullptr;
}
// log off from network, if connected
if (this->getIContextNetwork())
{
disconnect(this->getIContextNetwork());
this->getIContextNetwork()->disconnectFromNetwork();
if (m_contextNetwork->isUsingImplementingObject())
{
this->getCContextNetwork()->gracefulShutdown(); // for threads
}
this->getIContextNetwork()->deleteLater();
// replace by dummy object avoiding nullptr issues during shutdown phase
QDBusConnection defaultConnection("default");
m_contextNetwork = IContextNetwork::create(this, CCoreFacadeConfig::NotUsed, nullptr, defaultConnection);
}
if (this->getIContextSimulator())
{
disconnect(this->getIContextSimulator());
if (this->getIContextSimulator()->isUsingImplementingObject())
{
// shutdown the plugins
this->getCContextSimulator()->gracefulShutdown();
}
this->getIContextSimulator()->deleteLater();
QDBusConnection defaultConnection("default");
m_contextSimulator =
IContextSimulator::create(this, CCoreFacadeConfig::NotUsed, nullptr, defaultConnection);
}
if (this->getIContextOwnAircraft())
{
disconnect(this->getIContextOwnAircraft());
this->getIContextOwnAircraft()->deleteLater();
QDBusConnection defaultConnection("default");
m_contextOwnAircraft =
IContextOwnAircraft::create(this, CCoreFacadeConfig::NotUsed, nullptr, defaultConnection);
}
if (this->getIContextApplication())
{
disconnect(this->getIContextApplication());
this->getIContextApplication()->deleteLater();
QDBusConnection defaultConnection("default");
m_contextApplication =
IContextApplication::create(this, CCoreFacadeConfig::NotUsed, nullptr, defaultConnection);
}
}
void CCoreFacade::initDBusConnection(const QString &address)
{
if (m_initDBusConnection) { return; }
if (address.isEmpty() || address == CDBusServer::sessionBusAddress())
{
QDBusConnection::disconnectFromBus(m_dbusConnection.name());
m_dbusConnection = QDBusConnection::sessionBus();
}
else if (address == CDBusServer::systemBusAddress())
{
QDBusConnection::disconnectFromBus(m_dbusConnection.name());
m_dbusConnection = QDBusConnection::systemBus();
}
else
{
const QString name(CDBusServer::p2pConnectionName() + " " + address);
QDBusConnection::disconnectFromPeer(name);
m_dbusConnection = QDBusConnection::connectToPeer(address, name);
}
}
const IContextApplication *CCoreFacade::getIContextApplication() const { return m_contextApplication; }
IContextApplication *CCoreFacade::getIContextApplication() { return m_contextApplication; }
IContextAudio *CCoreFacade::getIContextAudio() { return m_contextAudio; }
const IContextAudio *CCoreFacade::getIContextAudio() const { return m_contextAudio; }
CContextAudioBase *CCoreFacade::getCContextAudioBase() { return m_contextAudio; }
const CContextAudioBase *CCoreFacade::getCContextAudioBase() const { return m_contextAudio; }
IContextNetwork *CCoreFacade::getIContextNetwork() { return m_contextNetwork; }
const IContextNetwork *CCoreFacade::getIContextNetwork() const { return m_contextNetwork; }
IContextOwnAircraft *CCoreFacade::getIContextOwnAircraft() { return m_contextOwnAircraft; }
const IContextOwnAircraft *CCoreFacade::getIContextOwnAircraft() const { return m_contextOwnAircraft; }
const IContextSimulator *CCoreFacade::getIContextSimulator() const { return m_contextSimulator; }
IContextSimulator *CCoreFacade::getIContextSimulator() { return m_contextSimulator; }
CContextAudio *CCoreFacade::getCContextAudio()
{
Q_ASSERT_X(m_contextAudio && m_contextAudio->isUsingImplementingObject(), "CCoreRuntime",
"Cannot downcast to local object");
return static_cast<CContextAudio *>(m_contextAudio);
}
const CContextAudio *CCoreFacade::getCContextAudio() const
{
Q_ASSERT_X(m_contextAudio && m_contextAudio->isUsingImplementingObject(), "CCoreRuntime",
"Cannot downcast to local object");
return static_cast<CContextAudio *>(m_contextAudio);
}
CContextApplication *CCoreFacade::getCContextApplication()
{
Q_ASSERT_X(m_contextApplication && m_contextApplication->isUsingImplementingObject(), "CCoreRuntime",
"Cannot downcast to local object");
return static_cast<CContextApplication *>(m_contextApplication);
}
const CContextApplication *CCoreFacade::getCContextApplication() const
{
Q_ASSERT_X(m_contextApplication && m_contextApplication->isUsingImplementingObject(), "CCoreRuntime",
"Cannot downcast to local object");
return static_cast<CContextApplication *>(m_contextApplication);
}
CContextNetwork *CCoreFacade::getCContextNetwork()
{
Q_ASSERT_X(m_contextNetwork && m_contextNetwork->isUsingImplementingObject(), "CCoreRuntime",
"Cannot downcast to local object");
return static_cast<CContextNetwork *>(m_contextNetwork);
}
const CContextNetwork *CCoreFacade::getCContextNetwork() const
{
Q_ASSERT_X(m_contextNetwork && m_contextNetwork->isUsingImplementingObject(), "CCoreRuntime",
"Cannot downcast to local object");
return static_cast<CContextNetwork *>(m_contextNetwork);
}
CContextOwnAircraft *CCoreFacade::getCContextOwnAircraft()
{
Q_ASSERT_X(m_contextOwnAircraft && m_contextOwnAircraft->isUsingImplementingObject(), "CCoreRuntime",
"Cannot downcast to local object");
return static_cast<CContextOwnAircraft *>(m_contextOwnAircraft);
}
const CContextOwnAircraft *CCoreFacade::getCContextOwnAircraft() const
{
Q_ASSERT_X(m_contextOwnAircraft && m_contextOwnAircraft->isUsingImplementingObject(), "CCoreRuntime",
"Cannot downcast to local object");
return static_cast<CContextOwnAircraft *>(m_contextOwnAircraft);
}
CContextSimulator *CCoreFacade::getCContextSimulator()
{
Q_ASSERT_X(m_contextSimulator && m_contextSimulator->isUsingImplementingObject(), "CCoreRuntime",
"Cannot downcast to local object");
return static_cast<CContextSimulator *>(m_contextSimulator);
}
const CContextSimulator *CCoreFacade::getCContextSimulator() const
{
Q_ASSERT_X(m_contextSimulator && m_contextSimulator->isUsingImplementingObject(), "CCoreRuntime",
"Cannot downcast to local object");
return static_cast<CContextSimulator *>(m_contextSimulator);
}
} // namespace swift::core