diff --git a/src/blackcore/context/contextsimulatorimpl.cpp b/src/blackcore/context/contextsimulatorimpl.cpp index 671c7e920..df5f8421a 100644 --- a/src/blackcore/context/contextsimulatorimpl.cpp +++ b/src/blackcore/context/contextsimulatorimpl.cpp @@ -18,6 +18,7 @@ #include "blackcore/application.h" #include "blackcore/pluginmanagersimulator.h" #include "blackcore/simulator.h" +#include "blackmisc/verify.h" #include "blackmisc/aviation/callsign.h" #include "blackmisc/compare.h" #include "blackmisc/dbusserver.h" @@ -494,9 +495,10 @@ namespace BlackCore void CContextSimulator::ps_addedRemoteAircraft(const CSimulatedAircraft &remoteAircraft) { if (!isSimulatorSimulating()) { return; } - Q_ASSERT(!remoteAircraft.getCallsign().isEmpty()); - const CCallsign callsign = remoteAircraft.getCallsign(); + BLACK_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Remote aircrft with empty callsign"); + if (callsign.isEmpty()) { return; } + CStatusMessageList matchingMessages; CStatusMessageList *pMatchingMessages = m_enableMatchingMessages ? &matchingMessages : nullptr; const CAircraftModel aircraftModel = m_modelMatcher.getClosestMatch(remoteAircraft, pMatchingMessages); diff --git a/src/blackcore/simulatorcommon.cpp b/src/blackcore/simulatorcommon.cpp index 315753d33..bea4911ac 100644 --- a/src/blackcore/simulatorcommon.cpp +++ b/src/blackcore/simulatorcommon.cpp @@ -80,12 +80,14 @@ namespace BlackCore bool CSimulatorCommon::logicallyAddRemoteAircraft(const CSimulatedAircraft &remoteAircraft) { + Q_ASSERT_X(remoteAircraft.hasModelString(), Q_FUNC_INFO, "Missing model string"); + Q_ASSERT_X(remoteAircraft.hasCallsign(), Q_FUNC_INFO, "Missing callsign"); if (!remoteAircraft.isEnabled()) { return false; } // if not restriced, directly change if (!isRenderingRestricted()) { this->physicallyAddRemoteAircraft(remoteAircraft); return true; } - // will be added with next snapshot + // will be added with next snapshot ps_recalculateRenderedAircraft return false; } @@ -94,7 +96,7 @@ namespace BlackCore // if not restriced, directly change if (!isRenderingRestricted()) { this->physicallyRemoveRemoteAircraft(callsign); return true; } - // will be added with next snapshot + // will be added with next snapshot ps_recalculateRenderedAircraft return false; } @@ -322,11 +324,11 @@ namespace BlackCore void CSimulatorCommon::highlightAircraft(const BlackMisc::Simulation::CSimulatedAircraft &aircraftToHighlight, bool enableHighlight, const BlackMisc::PhysicalQuantities::CTime &displayTime) { - CCallsign cs(aircraftToHighlight.getCallsign()); + const CCallsign cs(aircraftToHighlight.getCallsign()); this->m_highlightedAircraft.removeByCallsign(cs); if (enableHighlight) { - qint64 deltaT = displayTime.valueRounded(CTimeUnit::ms(), 0); + const qint64 deltaT = displayTime.valueRounded(CTimeUnit::ms(), 0); this->m_highlightEndTimeMsEpoch = QDateTime::currentMSecsSinceEpoch() + deltaT; this->m_highlightedAircraft.push_back(aircraftToHighlight); } @@ -432,4 +434,36 @@ namespace BlackCore { Q_UNUSED(callsign); } + + void CSimulatorCommon::ps_queueForAdding(const CSimulatedAircraft &aircraft) + { + m_pendingAircraftToAdd.replaceOrAddByCallsign(aircraft); + QTimer::singleShot(500, this, [ = ] + { + this->physicallyAddNextQueuedAircraft(); + }); + } + + void CSimulatorCommon::physicallyAddNextQueuedAircraft() + { + if (m_pendingAircraftToAdd.isEmpty()) { return; } // delete in meantime + CSimulatedAircraft nextAircraft(m_pendingAircraftToAdd.front()); // normally it should always find a value + m_pendingAircraftToAdd.pop_front(); + this->physicallyAddRemoteAircraft(nextAircraft); + } + + void CSimulatorCommon::reset() + { + m_statsUpdateAircraftCountMs = 0; + m_statsUpdateAircraftTimeAvgMs = 0; + m_statsUpdateAircraftTimeTotalMs = 0; + this->clearAllAircraft(); + } + + void CSimulatorCommon::clearAllAircraft() + { + m_aircraftToAddAgainWhenRemoved.clear(); + m_pendingAircraftToAdd.clear(); + } + } // namespace diff --git a/src/blackcore/simulatorcommon.h b/src/blackcore/simulatorcommon.h index b454eb128..cb68d48cd 100644 --- a/src/blackcore/simulatorcommon.h +++ b/src/blackcore/simulatorcommon.h @@ -88,6 +88,8 @@ namespace BlackCore //! @} protected slots: + + //! \name Connected with remote aircraft provider signals { //! Slow timer used to highlight aircraft, can be used for other things too virtual void ps_oneSecondTimer(); @@ -102,6 +104,11 @@ namespace BlackCore //! Provider removed aircraft virtual void ps_remoteProviderRemovedAircraft(const BlackMisc::Aviation::CCallsign &callsign); + //! @} + + //! Add when pending aircraft is added + //! \remark no need to use this if multiple models can be added to simulator at once + void ps_queueForAdding(const BlackMisc::Simulation::CSimulatedAircraft &aircraft); protected: //! Constructor @@ -117,6 +124,12 @@ namespace BlackCore //! \copydoc ISimulator::logicallyRemoveRemoteAircraft virtual bool logicallyRemoveRemoteAircraft(const BlackMisc::Aviation::CCallsign &callsign) override; + //! Reset state + virtual void reset(); + + //! Clear all aircraft related data + virtual void clearAllAircraft(); + //! Blink the highlighted aircraft void blinkHighlightedAircraft(); @@ -132,15 +145,22 @@ namespace BlackCore //! Set own model void reverseLookupAndUpdateOwnAircraftModel(const QString &modelString); - protected: + //! Add the next qeueud aircraft + void physicallyAddNextQueuedAircraft(); + BlackMisc::IInterpolator *m_interpolator = nullptr; //!< interpolator instance bool m_pausedSimFreezesInterpolation = false; //!< paused simulator will also pause interpolation (so AI aircraft will hold) BlackMisc::Simulation::CSimulatorSetup m_simulatorSetup; //!< setup object + BlackMisc::CInterpolationAndRenderingSetup m_interpolationRenderingSetup; //!< debug messages, rendering etc. BlackMisc::Simulation::CAircraftModel m_defaultModel; //!< default model qint64 m_statsUpdateAircraftTimeTotalMs = 0; //!< statistics update time qint64 m_statsUpdateAircraftTimeAvgMs = 0; //!< statistics update time int m_statsUpdateAircraftCountMs = 0; //!< statistics update time + // some optional functionality which can be used by the sims as needed + BlackMisc::Simulation::CSimulatedAircraftList m_aircraftToAddAgainWhenRemoved; //!< add this model again when removed, normally used to change model + BlackMisc::Simulation::CSimulatedAircraftList m_pendingAircraftToAdd; //!< used with qeued adding, here only one model is added add a time and only after it is confirmed by the sim. the next model is added + //! Lookup against DB data static BlackMisc::Simulation::CAircraftModel reverseLookupModel(const BlackMisc::Simulation::CAircraftModel &model); diff --git a/src/plugins/simulator/fs9/simulatorfs9.cpp b/src/plugins/simulator/fs9/simulatorfs9.cpp index bf86ffeb7..a0b34319c 100644 --- a/src/plugins/simulator/fs9/simulatorfs9.cpp +++ b/src/plugins/simulator/fs9/simulatorfs9.cpp @@ -106,7 +106,8 @@ namespace BlackSimPlugin { connect(lobbyClient.data(), &CLobbyClient::disconnected, this, std::bind(&CSimulatorFs9::simulatorStatusChanged, this, 0)); this->m_interpolator = new BlackMisc::CInterpolatorLinear(remoteAircraftProvider, this); - m_defaultModel = { + m_defaultModel = + { "Boeing 737-400", CAircraftModel::TypeModelMatchingDefaultModel, "B737-400 default model", @@ -170,10 +171,13 @@ namespace BlackSimPlugin client->start(); m_hashFs9Clients.insert(callsign, client); - updateAircraftRendered(callsign, rendered); + bool updated = updateAircraftRendered(callsign, rendered); CSimulatedAircraft remoteAircraftCopy(newRemoteAircraft); remoteAircraftCopy.setRendered(rendered); - emit aircraftRenderingChanged(remoteAircraftCopy); + if (updated) + { + emit aircraftRenderingChanged(remoteAircraftCopy); + } CLogMessage(this).info("FS9: Added aircraft %1") << callsign.toQString(); return true; } diff --git a/src/plugins/simulator/fsx/simconnectdatadefinition.h b/src/plugins/simulator/fsx/simconnectdatadefinition.h index 0e373bc82..f47008fc0 100644 --- a/src/plugins/simulator/fsx/simconnectdatadefinition.h +++ b/src/plugins/simulator/fsx/simconnectdatadefinition.h @@ -26,7 +26,7 @@ namespace BlackSimPlugin { namespace Fsx { - //! \brief Data struct of our own aircraft + //! Data struct of our own aircraft //! \sa SimConnect variables http://msdn.microsoft.com/en-us/library/cc526981.aspx //! \sa SimConnect events http://msdn.microsoft.com/en-us/library/cc526980.aspx struct DataDefinitionOwnAircraft @@ -40,12 +40,12 @@ namespace BlackSimPlugin double velocity; //!< Ground velocity double simOnGround; //!< Is aircraft on ground? - double lightStrobe; //!< Is strobe light on? - double lightLanding; //!< Is landing light on? - double lightTaxi; //!< Is taxi light on? - double lightBeacon; //!< Is beacon light on? - double lightNav; //!< Is nav light on? - double lightLogo; //!< Is logo light on? + double lightStrobe; //!< Is strobe light on? + double lightLanding; //!< Is landing light on? + double lightTaxi; //!< Is taxi light on? + double lightBeacon; //!< Is beacon light on? + double lightNav; //!< Is nav light on? + double lightLogo; //!< Is logo light on? double transponderCode; //!< Transponder Code double com1ActiveMHz; //!< COM1 active frequency @@ -73,22 +73,22 @@ namespace BlackSimPlugin //! Data struct of remote aircraft parts struct DataDefinitionRemoteAircraftParts { - double lightStrobe; //!< Is strobe light on? - double lightLanding; //!< Is landing light on? - // double lightTaxi; //!< Is taxi light on? - double lightBeacon; //!< Is beacon light on? - double lightNav; //!< Is nav light on? - double lightLogo; //!< Is logo light on? - double flapsLeadingEdgeLeftPercent; //!< Leading edge left in percent - double flapsLeadingEdgeRightPercent; //!< Leading edge right in percent - double flapsTrailingEdgeLeftPercent; //!< Trailing edge left in percent - double flapsTrailingEdgeRightPercent; //!< Trailing edge right in percent - double gearHandlePosition; //!< Gear handle position - double spoilersHandlePosition; //!< Spoilers out? - double engine1Combustion; //!< Engine 1 combustion flag - double engine2Combustion; //!< Engine 2 combustion flag - double engine3Combustion; //!< Engine 3 combustion flag - double engine4Combustion; //!< Engine 4 combustion flag + double lightStrobe; //!< Is strobe light on? + double lightLanding; //!< Is landing light on? + // double lightTaxi; //!< Is taxi light on? + double lightBeacon; //!< Is beacon light on? + double lightNav; //!< Is nav light on? + double lightLogo; //!< Is logo light on? + double flapsLeadingEdgeLeftPercent; //!< Leading edge left in percent + double flapsLeadingEdgeRightPercent; //!< Leading edge right in percent + double flapsTrailingEdgeLeftPercent; //!< Trailing edge left in percent + double flapsTrailingEdgeRightPercent; //!< Trailing edge right in percent + double gearHandlePosition; //!< Gear handle position + double spoilersHandlePosition; //!< Spoilers out? + double engine1Combustion; //!< Engine 1 combustion flag + double engine2Combustion; //!< Engine 2 combustion flag + double engine3Combustion; //!< Engine 3 combustion flag + double engine4Combustion; //!< Engine 4 combustion flag }; //! Data struct simulator environment @@ -134,7 +134,6 @@ namespace BlackSimPlugin class CSimConnectDefinitions { public: - //! SimConnect definiton IDs enum DataDefiniton { @@ -168,7 +167,6 @@ namespace BlackSimPlugin static QString getLogCategory() { return "swift.fsx.simconnect"; } private: - //! Initialize data definition for our own aircraft static HRESULT initOwnAircraft(const HANDLE hSimConnect); diff --git a/src/plugins/simulator/fsx/simconnectobject.h b/src/plugins/simulator/fsx/simconnectobject.h index 0a0358ed4..4f958b4ba 100644 --- a/src/plugins/simulator/fsx/simconnectobject.h +++ b/src/plugins/simulator/fsx/simconnectobject.h @@ -40,6 +40,9 @@ namespace BlackSimPlugin //! Simulated aircraft (as added) const BlackMisc::Simulation::CSimulatedAircraft &getAircraft() const { return m_aircraft; } + //! Simulated aircraft model string + const QString &getAircraftModelString() const { return m_aircraft.getModelString(); } + //! Set Simconnect request id void setRequestId(int id) { m_requestId = id; } diff --git a/src/plugins/simulator/fsx/simulatorfsx.cpp b/src/plugins/simulator/fsx/simulatorfsx.cpp index 534b47c1c..14c771fed 100644 --- a/src/plugins/simulator/fsx/simulatorfsx.cpp +++ b/src/plugins/simulator/fsx/simulatorfsx.cpp @@ -19,6 +19,7 @@ #include "blackmisc/aviation/airportlist.h" #include "blackmisc/logmessage.h" #include "blackmisc/threadutils.h" +#include "blackmisc/verify.h" #include "blackmisc/simulation/fscommon/fscommonutil.h" #include @@ -81,12 +82,10 @@ namespace BlackSimPlugin bool CSimulatorFsx::connectTo() { if (this->isConnected()) { return true; } + this->reset(); if (FAILED(SimConnect_Open(&m_hSimConnect, sApp->swiftVersionChar(), nullptr, 0, 0, 0))) { // reset state as expected for unconnected - if (m_simconnectTimerId >= 0) { killTimer(m_simconnectTimerId); } - m_simConnected = false; - m_simSimulating = false; return false; } if (m_useFsuipc) { this->m_fsuipc->connect(); } // FSUIPC too @@ -102,25 +101,14 @@ namespace BlackSimPlugin bool CSimulatorFsx::disconnectFrom() { if (!m_simConnected) { return true; } - - // stop mapper init - //! \todo mapper shutdown in FSX, review keep it? - // mapperInstance()->gracefulShutdown(); - - if (m_simconnectTimerId) - { - killTimer(m_simconnectTimerId); - } - + if (m_simconnectTimerId) { killTimer(m_simconnectTimerId); } if (m_hSimConnect) { SimConnect_Close(m_hSimConnect); m_hSimConnect = nullptr; } - m_simConnected = false; - m_simSimulating = false; - m_simconnectTimerId = -1; + reset(); // emit status and disconnect FSUIPC CSimulatorFsCommon::disconnectFrom(); @@ -136,8 +124,26 @@ namespace BlackSimPlugin Q_ASSERT_X(newRemoteAircraft.hasModelString(), Q_FUNC_INFO, "missing model string"); if (callsign.isEmpty()) { return false; } - const bool aircraftAlreadyExistsInSim = this->m_simConnectObjects.contains(callsign); - if (aircraftAlreadyExistsInSim) + // check if we have to do something + if (m_simConnectObjects.contains(callsign)) + { + const CSimConnectObject simObj = m_simConnectObjects[callsign]; + if (simObj.isPendingAdded()) + { + return true; // already pending + } + else + { + // same model, nothing will change, otherwise add again when removed + if (simObj.getAircraft().getModel() != newRemoteAircraft.getModel()) + { + m_aircraftToAddAgainWhenRemoved.push_back(newRemoteAircraft); + } + return false; + } + } + + if (!m_pendingAircraftToAdd.isEmpty() || m_simConnectObjects.containsPendingAdd()) { const CSimConnectObject simObj = m_simConnectObjects[callsign]; if (simObj.isPendingAdded()) @@ -164,11 +170,17 @@ namespace BlackSimPlugin // initial position setInitialAircraftSituation(addedAircraft); // set interpolated data/parts if available - SIMCONNECT_DATA_INITPOSITION initialPosition = aircraftSituationToFsxInitPosition(addedAircraft.getSituation()); - const QByteArray m = aircraftModel.getModelString().toLocal8Bit(); + const int requestId = m_requestId++; + SIMCONNECT_DATA_INITPOSITION initialPosition = aircraftSituationToFsxPosition(addedAircraft.getSituation()); + const QString modelString(addedAircraft.getModelString()); + const QByteArray m(modelString.toLocal8Bit()); + + if (m_interpolationRenderingSetup.showSimulatorDebugMessages()) + { + CLogMessage(this).debug() << "physicallyAddRemoteAircraft" << callsign.toQString() << "request" << requestId << "model" << modelString; + CLogMessage(this).debug() << "initial position" << fsxPositionToString(initialPosition); + } - const int requestId = m_requestId; - ++m_requestId; HRESULT hr = SimConnect_AICreateNonATCAircraft(m_hSimConnect, m.constData(), qPrintable(callsign.toQString().left(12)), initialPosition, static_cast(requestId)); if (hr != S_OK) { @@ -275,22 +287,13 @@ namespace BlackSimPlugin SIMCONNECT_TEXT_TYPE type = SIMCONNECT_TEXT_TYPE_PRINT_BLACK; switch (message.getSeverity()) { - case CStatusMessage::SeverityDebug: - return; - case CStatusMessage::SeverityInfo: - type = SIMCONNECT_TEXT_TYPE_PRINT_GREEN; - break; - case CStatusMessage::SeverityWarning: - type = SIMCONNECT_TEXT_TYPE_PRINT_YELLOW; - break; - case CStatusMessage::SeverityError: - type = SIMCONNECT_TEXT_TYPE_PRINT_RED; - break; + case CStatusMessage::SeverityDebug: return; + case CStatusMessage::SeverityInfo: type = SIMCONNECT_TEXT_TYPE_PRINT_GREEN; break; + case CStatusMessage::SeverityWarning: type = SIMCONNECT_TEXT_TYPE_PRINT_YELLOW; break; + case CStatusMessage::SeverityError: type = SIMCONNECT_TEXT_TYPE_PRINT_RED; break; } - HRESULT hr = SimConnect_Text( - m_hSimConnect, type, 7.5, EventTextMessage, static_cast(m.size()), - m.data() - ); + HRESULT hr = SimConnect_Text(m_hSimConnect, type, 7.5, EventTextMessage, + static_cast(m.size()), m.data()); Q_UNUSED(hr); } @@ -312,6 +315,12 @@ namespace BlackSimPlugin return CCallsignSet(this->m_simConnectObjects.keys()); } + bool CSimulatorFsx::stillDisplayReceiveExceptions() + { + m_receiveExceptionCount++; + return m_receiveExceptionCount < IgnoreReceiveExceptions; + } + void CSimulatorFsx::setSimConnected() { m_simConnected = true; @@ -404,8 +413,7 @@ namespace BlackSimPlugin simulatorOwnAircraft.lightLogo); QList helperList {simulatorOwnAircraft.engine1Combustion != 0, simulatorOwnAircraft.engine2Combustion != 0, - simulatorOwnAircraft.engine3Combustion != 0, simulatorOwnAircraft.engine4Combustion != 0 - }; + simulatorOwnAircraft.engine3Combustion != 0, simulatorOwnAircraft.engine4Combustion != 0 }; CAircraftEngineList engines; for (int index = 0; index < simulatorOwnAircraft.numberOfEngines; ++index) @@ -436,16 +444,16 @@ namespace BlackSimPlugin // updates com1.setFrequencyActive(CFrequency(simulatorOwnAircraft.com1ActiveMHz, CFrequencyUnit::MHz())); com1.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com1StandbyMHz, CFrequencyUnit::MHz())); - bool changedCom1 = myAircraft.getCom1System() != com1; + const bool changedCom1 = myAircraft.getCom1System() != com1; this->m_simCom1 = com1; com2.setFrequencyActive(CFrequency(simulatorOwnAircraft.com2ActiveMHz, CFrequencyUnit::MHz())); com2.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com2StandbyMHz, CFrequencyUnit::MHz())); - bool changedCom2 = myAircraft.getCom2System() != com2; + const bool changedCom2 = myAircraft.getCom2System() != com2; this->m_simCom2 = com2; transponder.setTransponderCode(simulatorOwnAircraft.transponderCode); - bool changedXpr = (myAircraft.getTransponderCode() != transponder.getTransponderCode()); + const bool changedXpr = (myAircraft.getTransponderCode() != transponder.getTransponderCode()); if (changedCom1 || changedCom2 || changedXpr) { @@ -486,60 +494,86 @@ namespace BlackSimPlugin this->updateCockpit(myAircraft.getCom1System(), myAircraft.getCom2System(), xpdr, this->identifier()); } - bool CSimulatorFsx::aiAircraftWasAddedInSimulator(DWORD requestID, DWORD objectID) + bool CSimulatorFsx::simulatorReportedObjectAdded(DWORD objectID) { - if (!this->setSimConnectObjectID(requestID, objectID)) { return false; } - const CSimConnectObject simObject = this->getSimObjectForObjectId(objectID); + const CSimConnectObject simObject = this->m_simConnectObjects.getSimObjectForObjectId(objectID); + const CCallsign callsign(simObject.getCallsign()); + if (!simObject.hasValidRequestAndObjectId() || callsign.isEmpty()) { return false; } - bool ok = false; - if (simObject.hasValidRequestAndObjectId() && !simObject.getCallsign().isEmpty()) + Q_ASSERT_X(simObject.isPendingAdded(), Q_FUNC_INFO, "already confirmed"); + m_simConnectObjects[callsign].setConfirmedAdded(true); + if (m_interpolationRenderingSetup.showSimulatorDebugMessages()) { + CLogMessage(this).debug() << "Adding AI" << callsign.toQString() << "confirmed" << "id" << static_cast(objectID) << "model" << simObject.getAircraftModelString(); + } - SimConnect_AIReleaseControl(m_hSimConnect, objectID, requestID); + // P3D also has SimConnect_AIReleaseControlEx; + const int requestId = m_requestId++; + HRESULT hr = SimConnect_AIReleaseControl(m_hSimConnect, objectID, static_cast(requestId)); + if (hr == S_OK) + { SimConnect_TransmitClientEvent(m_hSimConnect, objectID, EventFreezeLat, 1, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); SimConnect_TransmitClientEvent(m_hSimConnect, objectID, EventFreezeAlt, 1, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); SimConnect_TransmitClientEvent(m_hSimConnect, objectID, EventFreezeAtt, 1, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + } + else + { + CLogMessage(this).error("Adding AI %1 failed") << callsign.toQString(); + } - // optimistic approach, as the aircraft is not yet really in the SIM - // its generation has been just requested - this->updateAircraftRendered(simObject.getCallsign(), true); + const bool updated = this->updateAircraftRendered(simObject.getCallsign(), true); + if (updated) + { emit aircraftRenderingChanged(simObject.getAircraft()); - ok = true; + } + return true; + } + + bool CSimulatorFsx::simulatorReportedObjectRemoved(DWORD objectID) + { + const CSimConnectObject simObject = this->m_simConnectObjects.getSimObjectForObjectId(objectID); + if (!simObject.hasValidRequestAndObjectId()) { return false; } // object id from somewhere else + const CCallsign callsign(simObject.getCallsign()); + Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "missing callsign"); + + bool ok = false; + if (simObject.isPendingRemoved()) + { + // good case, we can remove object + } + else + { + // object was removed but not requested by us + CLogMessage(this).warning("Removed %1 from simulator, but was not initiated by us: %1 '%2' object id %3") << callsign.toQString() << simObject.getAircraftModelString() << static_cast(objectID); + } + + // in all cases we remove + const int c = m_simConnectObjects.remove(callsign); + m_pendingAircraftToAdd.removeByCallsign(callsign); + ok = c > 0; + CLogMessage(this).info("FSX: Removed aircraft '%1'") << simObject.getCallsign().toQString(); + + const bool updated = this->updateAircraftRendered(simObject.getCallsign(), false); + if (updated) + { + emit aircraftRenderingChanged(simObject.getAircraft()); + } + + // models we have to add again after removing + if (m_aircraftToAddAgainWhenRemoved.containsCallsign(callsign)) + { + const CSimulatedAircraft aircraftAddAgain = m_aircraftToAddAgainWhenRemoved.findFirstByCallsign(callsign); + QTimer::singleShot(1000, this, [ = ] { this->physicallyAddRemoteAircraft(aircraftAddAgain); }); } return ok; } - bool CSimulatorFsx::setSimConnectObjectID(DWORD requestID, DWORD objectID) + bool CSimulatorFsx::setSimConnectObjectId(DWORD requestID, DWORD objectID) { - // First check, if this request id belongs to us - const int requestIntId = static_cast(requestID); - auto it = std::find_if(m_simConnectObjects.begin(), m_simConnectObjects.end(), [requestIntId](const CSimConnectObject & obj) { return obj.getRequestId() == requestIntId; }); - if (it == m_simConnectObjects.end()) { return false; } - - // belongs to us - it->setObjectId(static_cast(objectID)); - return true; - } - - CCallsign CSimulatorFsx::getCallsignForObjectId(DWORD objectID) const - { - return getSimObjectForObjectId(objectID).getCallsign(); - } - - CSimConnectObject CSimulatorFsx::getSimObjectForObjectId(DWORD objectID) const - { - const int objectIntId = static_cast(objectID); - for (const CSimConnectObject &simObject : m_simConnectObjects.values()) - { - if (simObject.getObjectId() == objectIntId) - { - return simObject; - } - } - return CSimConnectObject(); + return this->m_simConnectObjects.setSimConnectObjectId(static_cast(requestID), static_cast(objectID)); } void CSimulatorFsx::timerEvent(QTimerEvent *event) @@ -571,7 +605,7 @@ namespace BlackSimPlugin if (m_useFsuipc && m_fsuipc) { CSimulatedAircraft fsuipcAircraft(getOwnAircraft()); - //! \todo split in high / low frequency reads + //! \fixme split in high / low frequency reads bool ok = m_fsuipc->read(fsuipcAircraft, true, true, true); if (ok) { @@ -584,34 +618,58 @@ namespace BlackSimPlugin bool CSimulatorFsx::physicallyRemoveRemoteAircraft(const CCallsign &callsign) { // only remove from sim - Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "wrong thred"); - if (!m_simConnectObjects.contains(callsign)) { return false; } - this->physicallyRemoveRemoteAircraft(m_simConnectObjects.value(callsign)); + Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "wrong thread"); + if (callsign.isEmpty()) { return false; } // can happen if an object is not an aircraft + + if (m_pendingAircraftToAdd.removeByCallsign(callsign) > 0) { return true; } // was in queue to be added + if (!m_simConnectObjects.contains(callsign)) { return false; } // already fully removed or not yet added + + CSimConnectObject &simObject = m_simConnectObjects[callsign]; + if (simObject.isPendingRemoved()) { return true; } + if (simObject.isPendingAdded()) + { + // problem: we try to delete an aircraft just requested to be added + return false; //! \todo improve + } + + simObject.setPendingRemoved(true); + if (m_interpolationRenderingSetup.showSimulatorDebugMessages()) + { + CLogMessage(this).debug() << "physicallyRemoveRemoteAircraft" << callsign.toQString(); + } + + // call in SIM + SimConnect_AIRemoveObject(m_hSimConnect, static_cast(simObject.getObjectId()), static_cast(m_requestId++)); + + // mark in provider + bool updated = updateAircraftRendered(callsign, false); + if (updated) + { + CSimulatedAircraft aircraft(simObject.getAircraft()); + aircraft.setRendered(false); + emit aircraftRenderingChanged(aircraft); + } + + // cleanup function, actually this should not be needed + QTimer::singleShot(100, this, &CSimulatorFsx::ps_physicallyRemoveAircraftNotInProvider); + + // bye return true; } int CSimulatorFsx::physicallyRemoveAllRemoteAircraft() { if (m_simConnectObjects.isEmpty()) { return 0; } - QList callsigns(m_simConnectObjects.keys()); + const QList callsigns(m_simConnectObjects.keys()); int r = 0; for (const CCallsign &cs : callsigns) { if (physicallyRemoveRemoteAircraft(cs)) { r++; } } + clearAllAircraft(); return r; } - bool CSimulatorFsx::physicallyRemoveRemoteAircraft(const CSimConnectObject &simObject) - { - CCallsign callsign(simObject.getCallsign()); - m_simConnectObjects.remove(callsign); - SimConnect_AIRemoveObject(m_hSimConnect, static_cast(simObject.getObjectId()), static_cast(simObject.getRequestId())); - updateAircraftRendered(callsign, false); - CLogMessage(this).info("FSX: Removed aircraft '%1'") << simObject.getCallsign().toQString(); - return true; - } - HRESULT CSimulatorFsx::initEvents() { HRESULT hr = S_OK; @@ -706,25 +764,29 @@ namespace BlackSimPlugin // values used for position and parts bool isOnGround = false; - qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch(); - CCallsignSet aircraftWithParts(this->remoteAircraftSupportingParts()); // optimization, fetch all parts supporting aircraft in one step (one lock) + const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch(); + const CCallsignSet aircraftWithParts(this->remoteAircraftSupportingParts()); // optimization, fetch all parts supporting aircraft in one step (one lock) const QList simObjects(m_simConnectObjects.values()); for (const CSimConnectObject &simObj : simObjects) { - if (simObj.getObjectId() < 1) { continue; } + // happending if aircraft is not yet added to SIM or to be deleted + if (simObj.isPendingAdded()) { continue; } + if (simObj.isPendingRemoved()) { continue; } const CCallsign callsign(simObj.getCallsign()); Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "missing callsign"); + Q_ASSERT_X(simObj.hasValidRequestAndObjectId(), Q_FUNC_INFO, "Missing ids"); + IInterpolator::InterpolationStatus interpolatorStatus; - CAircraftSituation interpolatedSituation = this->m_interpolator->getInterpolatedSituation(callsign, currentTimestamp, simObj.isVtol(), interpolatorStatus); + const CAircraftSituation interpolatedSituation = this->m_interpolator->getInterpolatedSituation(callsign, currentTimestamp, simObj.isVtol(), interpolatorStatus); // having the onGround flag in parts forces me to obtain parts here // which is not the smartest thing regarding performance IInterpolator::PartsStatus partsStatus; - partsStatus.supportsParts = aircraftWithParts.contains(callsign); + partsStatus.setSupportsParts(aircraftWithParts.contains(callsign)); CAircraftPartsList parts; - if (partsStatus.supportsParts) + if (partsStatus.allTrue()) { parts = this->m_interpolator->getPartsBeforeTime(callsign, currentTimestamp, partsStatus); } @@ -732,13 +794,12 @@ namespace BlackSimPlugin if (interpolatorStatus.allTrue()) { // update situation - SIMCONNECT_DATA_INITPOSITION position = aircraftSituationToFsxInitPosition(interpolatedSituation); + SIMCONNECT_DATA_INITPOSITION position = aircraftSituationToFsxPosition(interpolatedSituation); - //! \todo The onGround in parts is nuts, as already mentioned in the discussion - // Currently ignored here, only guessing which is faster as aircraft without parts can just be + //! \fixme The onGround in parts is no ideal, as already mentioned in the discussion // a) I am forced to read parts even if i just want to update position // b) Unlike the other values it is not a fire and forget value, as I need it again in the next cycle - if (partsStatus.supportsParts && !parts.isEmpty()) + if (partsStatus.isSupportingParts() && !parts.isEmpty()) { // we have parts, and use the closest ground isOnGround = parts.front().isOnGround(); @@ -753,11 +814,14 @@ namespace BlackSimPlugin hr += SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftPosition, static_cast(simObj.getObjectId()), 0, 0, sizeof(SIMCONNECT_DATA_INITPOSITION), &position); - if (hr != S_OK) { CLogMessage(this).warning("Failed so set position on SimObject '%1' callsign: '%2'") << simObj.getObjectId() << callsign; } + if (hr != S_OK) + { + CLogMessage(this).warning("Failed so set position on SimObject '%1' callsign: '%2'") << simObj.getObjectId() << callsign; + } } // interpolation data - if (interpolatorStatus.interpolationSucceeded) + if (interpolatorStatus.didInterpolationSucceed()) { // aircraft parts // inside "interpolator if", as no parts can be sent without position @@ -765,7 +829,7 @@ namespace BlackSimPlugin } } // all callsigns - qint64 dt = QDateTime::currentMSecsSinceEpoch() - currentTimestamp; + const qint64 dt = QDateTime::currentMSecsSinceEpoch() - currentTimestamp; m_statsUpdateAircraftTimeTotalMs += dt; m_statsUpdateAircraftCountMs++; m_statsUpdateAircraftTimeAvgMs = m_statsUpdateAircraftTimeTotalMs / m_statsUpdateAircraftCountMs; @@ -773,9 +837,11 @@ namespace BlackSimPlugin bool CSimulatorFsx::updateRemoteAircraftParts(const CSimConnectObject &simObj, const CAircraftPartsList &parts, IInterpolator::PartsStatus partsStatus, const CAircraftSituation &interpolatedSituation, bool isOnGround) const { + if (!simObj.hasValidRequestAndObjectId()) { return false; } + // set parts DataDefinitionRemoteAircraftParts ddRemoteAircraftParts; - if (partsStatus.supportsParts) + if (partsStatus.isSupportingParts()) { // parts is supported, but do we need to update? if (parts.isEmpty()) { return false; } @@ -928,6 +994,47 @@ namespace BlackSimPlugin } } + void CSimulatorFsx::reset() + { + if (m_simconnectTimerId >= 0) { killTimer(m_simconnectTimerId); } + m_simConnected = false; + m_simSimulating = false; + m_simconnectTimerId = -1; + CSimulatorFsCommon::reset(); + } + + void CSimulatorFsx::clearAllAircraft() + { + m_simConnectObjects.clear(); + CSimulatorFsCommon::clearAllAircraft(); + } + + QString CSimulatorFsx::fsxPositionToString(const SIMCONNECT_DATA_INITPOSITION &position) + { + const QString positionStr("Lat: %1 lng: %2 alt: %3ft pitch: %4 bank: %5 hdg: %6 airspeed: %7kts onGround: %8"); + return positionStr. + arg(position.Latitude).arg(position.Longitude).arg(position.Altitude). + arg(position.Pitch).arg(position.Bank).arg(position.Heading).arg(position.Airspeed).arg(position.OnGround); + } + + CCallsignSet CSimulatorFsx::getCallsignsMissingInProvider() const + { + CCallsignSet simObjectCallsigns(m_simConnectObjects.keys()); + CCallsignSet providerCallsigns(this->getAircraftInRangeCallsigns()); + return simObjectCallsigns.difference(providerCallsigns); + } + + CCallsignSet CSimulatorFsx::ps_physicallyRemoveAircraftNotInProvider() + { + const CCallsignSet toBeRemoved(getCallsignsMissingInProvider()); + if (toBeRemoved.isEmpty()) { return toBeRemoved; } + for (const CCallsign &callsign : toBeRemoved) + { + physicallyRemoveRemoteAircraft(callsign); + } + return toBeRemoved; + } + CSimulatorFsxListener::CSimulatorFsxListener(const CSimulatorPluginInfo &info) : ISimulatorListener(info), m_timer(new QTimer(this)) diff --git a/src/plugins/simulator/fsx/simulatorfsx.h b/src/plugins/simulator/fsx/simulatorfsx.h index 4c40d51b2..e9a4b9ce0 100644 --- a/src/plugins/simulator/fsx/simulatorfsx.h +++ b/src/plugins/simulator/fsx/simulatorfsx.h @@ -102,6 +102,9 @@ namespace BlackSimPlugin virtual BlackMisc::Aviation::CCallsignSet physicallyRenderedAircraft() const override; //! @} + //! Display receive exceptions? + bool stillDisplayReceiveExceptions(); + //! Called when data about our own aircraft are received void updateOwnAircraftFromSimulator(DataDefinitionOwnAircraft simulatorOwnAircraft); @@ -109,17 +112,16 @@ namespace BlackSimPlugin void updateOwnAircraftFromSimulator(DataDefinitionClientAreaSb sbDataArea); //! An AI aircraft was added in the simulator - //! \remark that AI aircraft was previously request by requestID - bool aiAircraftWasAddedInSimulator(DWORD requestID, DWORD objectID); + bool simulatorReportedObjectAdded(DWORD objectID); + + //! Simulator reported that AI aircraft was removed + bool simulatorReportedObjectRemoved(DWORD objectID); //! Set ID of a SimConnect object, so far we only have an request id in the object - bool setSimConnectObjectID(DWORD requestID, DWORD objectID); + bool setSimConnectObjectId(DWORD requestID, DWORD objectID); - //! Find which callsign belongs to the object id - BlackMisc::Aviation::CCallsign getCallsignForObjectId(DWORD objectID) const; - - //! Find which callsign belongs to the object id - CSimConnectObject getSimObjectForObjectId(DWORD objectID) const; + //! The simconnect related objects + const CSimConnectObjects &getSimConnectObjects() const { return m_simConnectObjects; } protected: //! \copydoc BlackCore::ISimulator::isConnected() @@ -136,6 +138,10 @@ namespace BlackSimPlugin //! Dispatch SimConnect messages void ps_dispatch(); + //! Remove aircraft not in provider anymore + //! \remark kind of cleanup function, in an ideal this should never need to cleanup something + BlackMisc::Aviation::CCallsignSet ps_physicallyRemoveAircraftNotInProvider(); + private: //! Call this method to declare the simulator connected void setSimConnected(); @@ -152,9 +158,6 @@ namespace BlackSimPlugin //! Simulator is going down void onSimExit(); - //! Remove a remote aircraft - bool physicallyRemoveRemoteAircraft(const CSimConnectObject &simObject); - //! Init when connected HRESULT initWhenConnected(); @@ -172,7 +175,7 @@ namespace BlackSimPlugin BlackMisc::IInterpolator::PartsStatus partsStatus, const BlackMisc::Aviation::CAircraftSituation &interpolatedSituation, bool isOnGround) const; //! Format conversion - SIMCONNECT_DATA_INITPOSITION aircraftSituationToFsxInitPosition(const BlackMisc::Aviation::CAircraftSituation &situation); + SIMCONNECT_DATA_INITPOSITION aircraftSituationToFsxPosition(const BlackMisc::Aviation::CAircraftSituation &situation, bool guessOnGround = true); //! Sync time with user's computer void synchronizeTime(const BlackMisc::PhysicalQuantities::CTime &zuluTimeSim, const BlackMisc::PhysicalQuantities::CTime &localTimeSim); @@ -183,9 +186,22 @@ namespace BlackSimPlugin //! Reload weather settings void reloadWeatherSettings(); + //! Reset values when restarted + virtual void reset() override; + + //! Clear all aircraft lists + virtual void clearAllAircraft() override; + + //! FSX position as string + static QString fsxPositionToString(const SIMCONNECT_DATA_INITPOSITION &position); + + //! Get the callsigns which are no longer in the provider, but still in m_simConnectObjects + BlackMisc::Aviation::CCallsignSet getCallsignsMissingInProvider() const; + static constexpr int SkipUpdateCyclesForCockpit = 10; //!< skip x cycles before updating cockpit again + static constexpr int IgnoreReceiveExceptions = 10; //!< skip exceptions when displayed more than x times bool m_simConnected = false; //!< Is simulator connected? - bool m_simSimulating = false; //!< Simulator running? + bool m_simSimulating = false; //!< Simulator running? bool m_useSbOffsets = true; //!< with SB offsets int m_syncDeferredCounter = 0; //!< Set when synchronized, used to wait some time int m_simconnectTimerId = -1; //!< Timer identifier @@ -193,10 +209,10 @@ namespace BlackSimPlugin int m_interpolationRequest = 0; //!< current interpolation request int m_interpolationsSkipped = 0; //!< number of skipped interpolation request int m_requestId = 1; //!< request id - int m_dispatchErrors = 0; //!< numer of dispatched failed, \sa ps_dispatch + int m_dispatchErrors = 0; //!< number of dispatched failed, \sa ps_dispatch + int m_receiveExceptionCount = 0; //!< exceptions HANDLE m_hSimConnect = nullptr; //!< Handle to SimConnect object - QHash m_simConnectObjects; - + CSimConnectObjects m_simConnectObjects; //!< AI objects and their object / request ids BlackMisc::Geo::CCoordinateGeodetic m_lastWeatherPosition; //!< Own aircraft position at which weather was fetched and injected last BlackMisc::CSetting m_weatherScenarioSettings { this, &CSimulatorFsx::reloadWeatherSettings }; }; diff --git a/src/plugins/simulator/fsx/simulatorfsxsimconnectproc.cpp b/src/plugins/simulator/fsx/simulatorfsxsimconnectproc.cpp index 8a69d3447..0118983a3 100644 --- a/src/plugins/simulator/fsx/simulatorfsxsimconnectproc.cpp +++ b/src/plugins/simulator/fsx/simulatorfsxsimconnectproc.cpp @@ -50,6 +50,11 @@ namespace BlackSimPlugin } case SIMCONNECT_RECV_ID_EXCEPTION: { + if (!simulatorFsx->stillDisplayReceiveExceptions()) + { + break; + } + SIMCONNECT_RECV_EXCEPTION *exception = (SIMCONNECT_RECV_EXCEPTION *)pData; QString ex; const int exceptionId = static_cast(exception->dwException); @@ -57,17 +62,19 @@ namespace BlackSimPlugin const int index = static_cast(exception->dwIndex); const int data = static_cast(cbData); ex.sprintf("Exception=%d SendID=%d Index=%d cbData=%d", exceptionId, sendId, index, data); - if (exceptionId == SIMCONNECT_EXCEPTION_OPERATION_INVALID_FOR_OBJECT_TYPE) + + switch (exceptionId) { - // means we have problems with an AI aircraft - //! \todo find out how to obtain the id here so I can get callsign - CCallsign cs = simulatorFsx->getCallsignForObjectId(data); - if (!cs.isEmpty()) + case SIMCONNECT_EXCEPTION_OPERATION_INVALID_FOR_OBJECT_TYPE: + case SIMCONNECT_EXCEPTION_UNRECOGNIZED_ID: { - ex += " callsign: "; - ex += cs.toQString(); + //! \fixme do not know how to obtain the object id which failed. Can I get it? } + break; + default: + break; } + CLogMessage(simulatorFsx).error("Caught FSX simConnect exception: %1 %2") << CSimConnectUtilities::simConnectExceptionToString((SIMCONNECT_EXCEPTION)exception->dwException) << ex; @@ -114,11 +121,24 @@ namespace BlackSimPlugin case SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE: { SIMCONNECT_RECV_EVENT_OBJECT_ADDREMOVE *event = static_cast(pData); + const DWORD objectID = event->dwData; + const SIMCONNECT_SIMOBJECT_TYPE objectType = event->eObjType; + if (objectType != SIMCONNECT_SIMOBJECT_TYPE_AIRCRAFT && objectType != SIMCONNECT_SIMOBJECT_TYPE_HELICOPTER) + { + break; + } + + // such an object is not necessarily one of ours + // for instance, I always see object 5 when I start the simulator + if (!simulatorFsx->getSimConnectObjects().isKnownSimObjectId(objectID)) break; switch (event->uEventID) { case SystemEventObjectAdded: + // added in SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID + // adding here cause trouble break; case SystemEventObjectRemoved: + simulatorFsx->simulatorReportedObjectRemoved(objectID); break; default: break; @@ -131,6 +151,7 @@ namespace BlackSimPlugin switch (event->uEventID) { case SystemEventFrame: + // doing interpolation simulatorFsx->onSimFrame(); break; default: @@ -143,13 +164,16 @@ namespace BlackSimPlugin SIMCONNECT_RECV_ASSIGNED_OBJECT_ID *event = static_cast(pData); const DWORD requestID = event->dwRequestID; const DWORD objectID = event->dwObjectID; - const bool success = simulatorFsx->aiAircraftWasAddedInSimulator(requestID, objectID); + bool success = simulatorFsx->setSimConnectObjectId(requestID, objectID); + if (!success) { break; } // not an request ID of ours + + success = simulatorFsx->simulatorReportedObjectAdded(objectID); if (!success) { - CLogMessage(simulatorFsx).warning("Cannot find CSimConnectObject for request %1") << requestID; - const CSimulatedAircraft remoteAircraft(simulatorFsx->getSimObjectForObjectId(objectID).getAircraft()); - const QString msg("Object id for request " + QString::number(requestID) + " not avialable"); - emit simulatorFsx->physicallyAddingRemoteModelFailed(remoteAircraft, CStatusMessage(simulatorFsx, CStatusMessage::SeverityError, msg)); + const CSimulatedAircraft remoteAircraft(simulatorFsx->getSimConnectObjects().getSimObjectForObjectId(objectID).getAircraft()); + const CStatusMessage msg = CStatusMessage(simulatorFsx).error("Cannot add object %1") << objectID; + CLogMessage::preformatted(msg); + emit simulatorFsx->physicallyAddingRemoteModelFailed(remoteAircraft, msg); } break; }