From 9020c65681f49d3c14ba787abe3ff79116d59732 Mon Sep 17 00:00:00 2001 From: Roland Winklmeier Date: Sat, 26 Jul 2014 19:06:21 +0200 Subject: [PATCH] refs #241 FS9 implementation of the ISimulator interface Covers most important interface methods. Still TODO: - getAirportsInRange - Time sync - Updating COM units from context --- src/plugins/simulator/fs9/simulator_fs9.cpp | 325 ++++++++++++++++++++ src/plugins/simulator/fs9/simulator_fs9.h | 166 ++++++++++ 2 files changed, 491 insertions(+) create mode 100644 src/plugins/simulator/fs9/simulator_fs9.cpp create mode 100644 src/plugins/simulator/fs9/simulator_fs9.h diff --git a/src/plugins/simulator/fs9/simulator_fs9.cpp b/src/plugins/simulator/fs9/simulator_fs9.cpp new file mode 100644 index 000000000..de8a687ae --- /dev/null +++ b/src/plugins/simulator/fs9/simulator_fs9.cpp @@ -0,0 +1,325 @@ +/* Copyright (C) 2014 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of Swift Project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "fs9.h" +#include "blacksimplugin_freefunctions.h" +#include "simulator_fs9.h" +#include "fs9_host.h" +#include "fs9_client.h" +#include "multiplayer_packets.h" +#include "multiplayer_packet_parser.h" +#include "blacksim/simulatorinfo.h" +#include "blackmisc/project.h" +#include +#include + +using namespace BlackMisc::Aviation; +using namespace BlackMisc::PhysicalQuantities; +using namespace BlackMisc::Geo; +using namespace BlackSim; +using namespace BlackSimPlugin::Fs9; + +namespace BlackSimPlugin +{ + namespace Fs9 + { + BlackCore::ISimulator *CSimulatorFs9Factory::create(QObject *parent) + { + registerMetadata(); + return new Fs9::CSimulatorFs9(parent); + } + + BlackSim::CSimulatorInfo CSimulatorFs9Factory::getSimulatorInfo() const + { + return CSimulatorInfo::FS9(); + } + + CSimulatorFs9::CSimulatorFs9(QObject *parent) : + ISimulator(parent), + m_fs9Host(new CFs9Host), + m_hostThread(this), + m_simulatorInfo(CSimulatorInfo::FS9()), + m_fsuipc(new FsCommon::CFsuipc()) + { + // We move the host thread already in the constructor + m_fs9Host->moveToThread(&m_hostThread); + connect(&m_hostThread, &QThread::started, m_fs9Host, &CFs9Host::init); + connect(m_fs9Host, &CFs9Host::customPacketReceived, this, &CSimulatorFs9::processFs9Message); + connect(m_fs9Host, &CFs9Host::statusChanged, this, &CSimulatorFs9::changeHostStatus); + connect(&m_hostThread, &QThread::finished, m_fs9Host, &CFs9Host::deleteLater); + connect(&m_hostThread, &QThread::finished, &m_hostThread, &QThread::deleteLater); + m_hostThread.start(); + } + + CSimulatorFs9::~CSimulatorFs9() + { + m_hostThread.quit(); + m_hostThread.wait(1000); + } + + bool CSimulatorFs9::isConnected() const + { + return m_fs9Host->isConnected(); + } + + bool CSimulatorFs9::connectTo() + { + m_fsuipc->connect(); // connect FSUIPC too + + // FIXME: This does start hosting only. Add lobby connection here. + return true; + } + + void CSimulatorFs9::asyncConnectTo() + { + // Since we are running the host in its own thread, it is async anyway + connectTo(); + } + + bool CSimulatorFs9::disconnectFrom() + { + disconnectAllClients(); + + // We tell the host to terminate and stop the thread afterwards + QMetaObject::invokeMethod(m_fs9Host, "stopHosting"); + emit statusChanged(ISimulator::Disconnected); + m_fsuipc->disconnect(); + + return false; + } + + bool CSimulatorFs9::canConnect() + { + return true; + } + + void CSimulatorFs9::addRemoteAircraft(const CCallsign &callsign, const QString & /* type */, const CAircraftSituation & /*initialSituation*/ ) + { + // Create a new client thread, set update frequency to 25 ms and start it + QThread *clientThread = new QThread(this); + CFs9Client *client = new CFs9Client(callsign.toQString(), CTime(25, CTimeUnit::ms())); + client->setPlayerUserId(m_fs9Host->getPlayerUserId()); + client->moveToThread(clientThread); + + connect(clientThread, &QThread::started, client, &CFs9Client::init); + connect(client, &CFs9Client::clientTimedOut, this, &CSimulatorFs9::removeAircraft); + m_fs9ClientThreads.insert(client, clientThread); + m_hashFs9Clients.insert(callsign, client); + clientThread->start(); + } + + void CSimulatorFs9::addAircraftSituation(const CCallsign &callsign, const CAircraftSituation &situation) + { + // If this is a new guy, add him to the session + if (!m_hashFs9Clients.contains(callsign)) + { + // Only add a maximum number of 20 clients. + // FIXME: We need a smart method to get the 20 nearest aircrafts. If someone logs in + // nearby we need to kick out the one with max distance. + + if (m_hashFs9Clients.size() < 20) + addRemoteAircraft(callsign, "Boeing 737-400 Paint1", situation); + } + + // otherwise just add the new position + CFs9Client *client = m_hashFs9Clients.value(callsign); + if (!client) + return; + + client->addAircraftSituation(situation); + } + + void CSimulatorFs9::removeRemoteAircraft(const CCallsign &callsign) + { + if(!m_hashFs9Clients.contains(callsign)) + return; + + CFs9Client *fs9Client = m_hashFs9Clients.value(callsign); + + Q_ASSERT(m_fs9ClientThreads.contains(fs9Client)); + QThread *fs9ClientThread = m_fs9ClientThreads.value(fs9Client); + + QMetaObject::invokeMethod(fs9Client, "disconnectFrom"); + + m_fs9ClientThreads.remove(fs9Client); + m_hashFs9Clients.remove(callsign); + + fs9ClientThread->wait(100); + + /*fs9ClientThread->deleteLater(); + fs9Client->deleteLater();*/ + } + + bool CSimulatorFs9::updateOwnSimulatorCockpit(const CAircraft &ownAircraft) + { + CComSystem newCom1 = ownAircraft.getCom1System(); + CComSystem newCom2 = ownAircraft.getCom2System(); + CTransponder newTransponder = ownAircraft.getTransponder(); + + bool changed = false; + if (newCom1 != this->m_ownAircraft.getCom1System()) + { + if (newCom1.getFrequencyActive() != this->m_ownAircraft.getCom1System().getFrequencyActive()) + { + + } + if (newCom1.getFrequencyStandby() != this->m_ownAircraft.getCom1System().getFrequencyStandby()) + { + + } + + this->m_ownAircraft.setCom1System(newCom1); + changed = true; + } + + if (newCom2 != this->m_ownAircraft.getCom2System()) + { + if (newCom2.getFrequencyActive() != this->m_ownAircraft.getCom2System().getFrequencyActive()) + { + + } + + if (newCom2.getFrequencyStandby() != this->m_ownAircraft.getCom2System().getFrequencyStandby()) + { + + } + + this->m_ownAircraft.setCom2System(newCom2); + changed = true; + } + + if (newTransponder != this->m_ownAircraft.getTransponder()) + { + if (newTransponder.getTransponderCode() != this->m_ownAircraft.getTransponder().getTransponderCode()) + { + changed = true; + } + this->m_ownAircraft.setTransponder(newTransponder); + } + + // bye + return changed; + } + + CSimulatorInfo CSimulatorFs9::getSimulatorInfo() const + { + return this->m_simulatorInfo; + } + + void CSimulatorFs9::displayStatusMessage(const BlackMisc::CStatusMessage &message) const + { + QMetaObject::invokeMethod(m_fs9Host, "sendTextMessage", Q_ARG(QString, message.toQString())); + } + + CAirportList CSimulatorFs9::getAirportsInRange() const + { + return this->m_airportsInRange; + } + + void CSimulatorFs9::setTimeSynchronization(bool enable, BlackMisc::PhysicalQuantities::CTime offset) + { + this->m_syncTime = enable; + this->m_syncTimeOffset = offset; + } + + void CSimulatorFs9::timerEvent(QTimerEvent * /* event */) + { + ps_dispatch(); + } + + void CSimulatorFs9::ps_dispatch() + { + if (m_fsuipc) m_fsuipc->process(); + updateOwnAircraftFromSim(m_fsuipc->getOwnAircraft()); + } + + void CSimulatorFs9::processFs9Message(const QByteArray &message) + { + CFs9Sdk::MULTIPLAYER_PACKET_ID messageType = MultiPlayerPacketParser::readType(message); + + switch (messageType) + { + case CFs9Sdk::MULTIPLAYER_PACKET_ID_CHANGE_PLAYER_PLANE: + { + MPChangePlayerPlane mpChangePlayerPlane; + MultiPlayerPacketParser::readMessage(message, mpChangePlayerPlane); + changeOwnAircraftModel(mpChangePlayerPlane.aircraft_name); + break; + } + case CFs9Sdk::MULTIPLAYER_PACKET_ID_POSITION_VELOCITY: + { + MPPositionVelocity mpPositionVelocity; + MultiPlayerPacketParser::readMessage(message, mpPositionVelocity); + m_ownAircraft.setSituation(aircraftSituationfromFS9(mpPositionVelocity)); + break; + } + case CFs9Sdk::MPCHAT_PACKET_ID_CHAT_TEXT_SEND: + { + MPChatText mpChatText; + MultiPlayerPacketParser::readMessage(message, mpChatText); + break; + } + + default: + break; + } + } + + void CSimulatorFs9::changeOwnAircraftModel(const QString &modelname) + { + m_aircraftModel.setQueriedModelString(modelname); + emit aircraftModelChanged(m_aircraftModel); + } + + void CSimulatorFs9::changeHostStatus(CFs9Host::HostStatus status) + { + switch (status) + { + case CFs9Host::Hosting: + { + m_isHosting = true; + startTimer(50); + emit statusChanged(Connected); + break; + } + case CFs9Host::Terminated: + { + qDebug() << "Quitting thread"; + m_hostThread.quit(); + m_isHosting = false; + emit statusChanged(Disconnected); + break; + } + default: + break; + } + } + + void CSimulatorFs9::removeAircraft(const QString &callsign) + { + removeRemoteAircraft(callsign); + } + + void CSimulatorFs9::updateOwnAircraftFromSim(const CAircraft &ownAircraft) + { + m_ownAircraft.setCom1System(ownAircraft.getCom1System()); + m_ownAircraft.setCom2System(ownAircraft.getCom2System()); + m_ownAircraft.setTransponder(ownAircraft.getTransponder()); + } + + void CSimulatorFs9::disconnectAllClients() + { + // Stop all FS9 client threads + for (auto fs9Client : m_hashFs9Clients.keys()) + { + removeRemoteAircraft(fs9Client); + } + } + } +} diff --git a/src/plugins/simulator/fs9/simulator_fs9.h b/src/plugins/simulator/fs9/simulator_fs9.h new file mode 100644 index 000000000..b6cc95705 --- /dev/null +++ b/src/plugins/simulator/fs9/simulator_fs9.h @@ -0,0 +1,166 @@ +/* Copyright (C) 2014 + * swift project community / contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of Swift Project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#ifndef BLACKSIMPLUGIN_SIMULATOR_FS9_H +#define BLACKSIMPLUGIN_SIMULATOR_FS9_H + +#include "fs9_host.h" +#include "fs9_client.h" +#include "../fscommon/fsuipc.h" +#include "blackcore/simulator.h" +#include "blackcore/interpolator_linear.h" +#include "blackmisc/avaircraft.h" +#include "blackmisc/nwaircraftmodel.h" +#include "blacksim/simulatorinfo.h" +#include +#include +#include +#include +#include + +//! \file + +namespace BlackSimPlugin +{ + namespace Fs9 + { + //! Factory implementation to create CSimulatorFs9 instances + class Q_DECL_EXPORT CSimulatorFs9Factory : public QObject, public BlackCore::ISimulatorFactory + { + Q_OBJECT + Q_PLUGIN_METADATA(IID "net.vatsim.PilotClient.BlackCore.SimulatorInterface") + Q_INTERFACES(BlackCore::ISimulatorFactory) + + public: + //! \copydoc BlackCore::ISimulatorFactory::create() + virtual BlackCore::ISimulator *create(QObject *parent) override; + + //! Simulator info + virtual BlackSim::CSimulatorInfo getSimulatorInfo() const override; + }; + + //! \brief FSX Simulator Implementation + class CSimulatorFs9 : public BlackCore::ISimulator + { + Q_OBJECT + public: + //! \brief Constructor + CSimulatorFs9(QObject *parent = nullptr); + + virtual ~CSimulatorFs9(); + + //! \copydoc ISimulator::isConnected() + virtual bool isConnected() const override; + + //! \copydoc ISimulator::canConnect() + virtual bool canConnect() override; + + public slots: + + //! \copydoc ISimulator::connectTo() + virtual bool connectTo() override; + + //! \copydoc ISimulator::connectTo() + virtual void asyncConnectTo() override; + + //! \copydoc ISimulator::disconnectFrom() + virtual bool disconnectFrom() override; + + //! \copydoc ISimulator::getOwnAircraft() + virtual BlackMisc::Aviation::CAircraft getOwnAircraft() const override { return m_ownAircraft; } + + //! \copydoc ISimulator::addRemoteAircraft() + virtual void addRemoteAircraft(const BlackMisc::Aviation::CCallsign &callsign, const QString &type, const BlackMisc::Aviation::CAircraftSituation &initialSituation) override; + + //! \copydoc ISimulator::addAircraftSituation() + virtual void addAircraftSituation(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftSituation &initialSituation) override; + + //! \copydoc ISimulator::removeRemoteAircraft() + virtual void removeRemoteAircraft(const BlackMisc::Aviation::CCallsign &callsign) override; + + //! \copydoc ISimulator::updateOwnSimulatorCockpit() + virtual bool updateOwnSimulatorCockpit(const BlackMisc::Aviation::CAircraft &ownAircraft) override; + + //! \copydoc ISimulator::getSimulatorInfo() + virtual BlackSim::CSimulatorInfo getSimulatorInfo() const override; + + //! \copydoc ISimulator::displayStatusMessage() + virtual void displayStatusMessage(const BlackMisc::CStatusMessage &message) const override; + + //! \copydoc ISimulator::getAircraftModel() + virtual BlackMisc::Network::CAircraftModel getAircraftModel() const override { return m_aircraftModel; } + + //! Airports in range + virtual BlackMisc::Aviation::CAirportList getAirportsInRange() const override; + + //! Set time synchronization between simulator and user's computer time + //! \remarks not all drivers implement this, e.g. if it is an intrinsic simulator feature + virtual void setTimeSynchronization(bool enable, BlackMisc::PhysicalQuantities::CTime offset) override; + + //! Is time synchronization on? + virtual bool isTimeSynchronized() const override { return m_syncTime; } + + //! Time synchronization offset + virtual BlackMisc::PhysicalQuantities::CTime getTimeSynchronizationOffset() const override { return m_syncTimeOffset; } + + //! Simulator paused? + virtual bool isSimPaused() const override { return m_simPaused; } + + protected: + //! Timer event + virtual void timerEvent(QTimerEvent *event); + + private slots: + + //! Dispatch SimConnect messages + void ps_dispatch(); + + //! Process incoming FS9 message + void processFs9Message(const QByteArray &message); + + //! Change own aircraft model string + void changeOwnAircraftModel(const QString &modelname); + + //! Change DirectPlay host status + void changeHostStatus(CFs9Host::HostStatus status); + + //! Remove client by callsign QString + void removeAircraft(const QString &callsign); + + private: + + //! Called when data about our own aircraft are received + void updateOwnAircraftFromSim(const BlackMisc::Aviation::CAircraft &ownAircraft); + + void disconnectAllClients(); + + // DirectPlay object handling + CFs9Host *m_fs9Host = nullptr; + QThread m_hostThread; + bool m_isHosting = false; //!< Is sim connected + bool m_syncTime = false; //!< Time synchronized? + int m_syncDeferredCounter = 0; //!< Set when synchronized, used to wait some time + bool m_simPaused = false; //!< Simulator paused? + + QHash m_hashFs9Clients; + QHash m_fs9ClientThreads; + + BlackSim::CSimulatorInfo m_simulatorInfo; + BlackMisc::Aviation::CAircraft m_ownAircraft; //!< Object representing our own aircraft from simulator + BlackMisc::Aviation::CAirportList m_airportsInRange; + BlackMisc::Network::CAircraftModel m_aircraftModel; + BlackMisc::PhysicalQuantities::CTime m_syncTimeOffset; + + QScopedPointer m_fsuipc; + }; + } + +} // namespace BlackCore + +#endif // guard