Reflecting discussed changes for interpolation performance refs #386

(based on FSX testing)
* Only send changed situation to SIM
* Split sending of parts / situations
* Only send parts with a reduced frequency (means slower as positions)
* Mark geodetic height as null for default values (the value is usually unavailable)
* Fixed altitude to MSL for network data
* Trace which aircrafts support aircraft parts via network
* Renamed insert_fron push_front (as proposed by Roland)

Status quo / lessons learnt
* On slower PCs jitter is still noticed for interpolated aircraft.
* Running interpolation in an independent process (aka core, not in GUI) reduced load dependencies
  => it seems to make sense to run driver in own thread
* The onGround flag in parts seems clumsy as it required to retrieve parts for position updates
* In interpolation performance really matters
This commit is contained in:
Klaus Basan
2015-02-26 21:47:28 +01:00
parent ca6cd9c063
commit eca8c5b637
21 changed files with 376 additions and 320 deletions

View File

@@ -120,9 +120,13 @@ namespace BlackCore
std::function<void(const CCallsign &)> removedAircraftSlot std::function<void(const CCallsign &)> removedAircraftSlot
) )
{ {
bool s1 = connect(this, &CAirspaceMonitor::addedRemoteAircraftSituation, situationSlot); // bool s1 = connect(this, &CAirspaceMonitor::addedRemoteAircraftSituation, situationSlot);
bool s1 = connect(this->m_network, &INetwork::aircraftPositionUpdate, situationSlot);
Q_ASSERT(s1);
bool s2 = connect(this, &CAirspaceMonitor::addedRemoteAircraftParts, partsSlot); bool s2 = connect(this, &CAirspaceMonitor::addedRemoteAircraftParts, partsSlot);
Q_ASSERT(s2);
bool s3 = connect(this, &CAirspaceMonitor::removedRemoteAircraft, removedAircraftSlot); bool s3 = connect(this, &CAirspaceMonitor::removedRemoteAircraft, removedAircraftSlot);
Q_ASSERT(s3);
return s1 && s2 && s3; return s1 && s2 && s3;
} }
@@ -747,16 +751,18 @@ namespace BlackCore
if (c > 0) { ps_sendReadyForModelMatching(callsign, 1); } if (c > 0) { ps_sendReadyForModelMatching(callsign, 1); }
} }
void CAirspaceMonitor::ps_aircraftUpdateReceived(const CCallsign &callsign, const CAircraftSituation &situation, const CTransponder &transponder) void CAirspaceMonitor::ps_aircraftUpdateReceived(const CAircraftSituation &situation, const CTransponder &transponder)
{ {
Q_ASSERT(BlackCore::isCurrentThreadCreatingThread(this)); Q_ASSERT(BlackCore::isCurrentThreadCreatingThread(this));
if (!this->m_connected) { return; } if (!this->m_connected) { return; }
CCallsign callsign(situation.getCallsign());
Q_ASSERT(!callsign.isEmpty());
// store situation history // store situation history
CAircraftSituation situationWithCallsign(situation); // this->m_aircraftSituations.insert_front(situation);
situationWithCallsign.setCallsign(callsign);
// this->m_aircraftSituations.insert_front(situationWithCallsign);
// this->m_aircraftSituations.removeOlderThanNowMinusOffset(AircraftSituationsRemovedOffsetMs); // this->m_aircraftSituations.removeOlderThanNowMinusOffset(AircraftSituationsRemovedOffsetMs);
emit this->addedRemoteAircraftSituation(situation);
bool exists = this->m_aircraftInRange.containsCallsign(callsign); bool exists = this->m_aircraftInRange.containsCallsign(callsign);
if (!exists) if (!exists)
@@ -829,7 +835,6 @@ namespace BlackCore
this->m_aircraftWatchdog.resetCallsign(callsign); this->m_aircraftWatchdog.resetCallsign(callsign);
} }
emit this->addedRemoteAircraftSituation(situationWithCallsign);
emit this->changedAircraftInRange(); emit this->changedAircraftInRange();
} }

View File

@@ -201,7 +201,7 @@ namespace BlackCore
private slots: private slots:
//! Create aircraft in range, this is the only place where a new aircraft should be added //! Create aircraft in range, this is the only place where a new aircraft should be added
void ps_aircraftUpdateReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftSituation &situation, const BlackMisc::Aviation::CTransponder &transponder); void ps_aircraftUpdateReceived(const BlackMisc::Aviation::CAircraftSituation &situation, const BlackMisc::Aviation::CTransponder &transponder);
void ps_realNameReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &realname); void ps_realNameReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &realname);
void ps_capabilitiesReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, quint32 flags); void ps_capabilitiesReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, quint32 flags);

View File

@@ -35,48 +35,19 @@ namespace BlackCore
Q_UNUSED(c); Q_UNUSED(c);
} }
QList<CAircraftSituationList> IInterpolator::getSituationsTimeSplit(const CCallsign &callsign, qint64 splitTimeMsSinceEpoch) const CAircraftSituationList IInterpolator::getInterpolatedSituations(qint64 currentTimeMsSinceEpoch)
{ {
QReadLocker l(&m_situationsLock); QReadLocker l(&m_lockSituations);
static const QList<CAircraftSituationList> empty({ CAircraftSituationList(), CAircraftSituationList() }); const CSituationsPerCallsign situationsCopy(m_situationsByCallsign);
if (!this->m_situationsByCallsign.contains(callsign)) { return empty; }
return this->m_situationsByCallsign[callsign].splitByTime(splitTimeMsSinceEpoch);
}
bool IInterpolator::hasEnoughAircraftSituations(const CCallsign &callsign) const
{
QReadLocker l(&m_situationsLock);
if (!this->m_situationsByCallsign.contains(callsign)) { return false; }
return this->m_situationsByCallsign[callsign].findBeforeNowMinusOffset(TimeOffsetMs).size() > 0;
}
CAircraftParts IInterpolator::getLatestPartsBeforeOffset(const CCallsign &callsign, qint64 timeOffset, bool *ok) const
{
QReadLocker l(&m_partsLock);
static const CAircraftParts empty;
if (ok) { *ok = false; }
if (this->m_partsByCallsign.contains(callsign)) { return empty; }
CAircraftPartsList partsList = this->m_partsByCallsign[callsign].findBeforeNowMinusOffset(timeOffset);
l.unlock();
if (partsList.isEmpty()) { return empty; }
if (ok) { *ok = true; }
return partsList.latestValue();
}
void IInterpolator::syncRequestSituationsCalculationsForAllCallsigns(int requestId, qint64 currentTimeMsSinceEpoch)
{
QReadLocker l(&m_situationsLock);
Q_ASSERT(requestId >= 0);
const QHash<BlackMisc::Aviation::CCallsign, BlackMisc::Aviation::CAircraftSituationList> situationsCopy(m_situationsByCallsign);
l.unlock(); l.unlock();
CAircraftSituationList latestInterpolations; CAircraftSituationList latestInterpolations;
if (currentTimeMsSinceEpoch < 0) { currentTimeMsSinceEpoch = QDateTime::currentMSecsSinceEpoch(); } if (currentTimeMsSinceEpoch < 0) { currentTimeMsSinceEpoch = QDateTime::currentMSecsSinceEpoch(); }
for (const CCallsign &cs : situationsCopy.keys()) for (const CCallsign &cs : situationsCopy.keys())
{ {
bool ok = false; InterpolationStatus status;
CAircraftSituation situation = getCurrentInterpolatedSituation(situationsCopy, cs, currentTimeMsSinceEpoch, &ok); CAircraftSituation situation = getInterpolatedSituation(cs, currentTimeMsSinceEpoch, status, &situationsCopy);
if (ok) if (status.allTrue())
{ {
latestInterpolations.push_back(situation); latestInterpolations.push_back(situation);
} }
@@ -86,71 +57,51 @@ namespace BlackCore
// further logging could go here // further logging could go here
} }
} }
return latestInterpolations;
QWriteLocker wl(&m_requestedInterpolationsLock);
while (m_requestedInterpolations.size() >= MaxKeptInterpolationRequests - 1)
{
m_requestedInterpolations.erase(--m_requestedInterpolations.end());
}
m_requestedInterpolations.insert(requestId, latestInterpolations); // new to old
if (m_withDebugMsg)
{
CLogMessage(this).debug() << "Added request" << requestId << "with" << latestInterpolations.size() << "interpolation(s)";
}
} }
void IInterpolator::asyncRequestSituationsCalculationsForAllCallsigns(int requestId, qint64 currentTimeMsSinceEpoch) IInterpolator::CSituationsPerCallsign IInterpolator::getSituationsByCallsign() const
{ {
Q_ASSERT(requestId >= 0); QReadLocker l(&m_lockSituations);
QMetaObject::invokeMethod(this, "syncRequestSituationsCalculationsForAllCallsigns",
Qt::QueuedConnection, Q_ARG(int, requestId), Q_ARG(qint64, currentTimeMsSinceEpoch));
}
QHash<CCallsign, CAircraftSituationList> IInterpolator::getSituationsByCallsign() const
{
QReadLocker l(&m_situationsLock);
return m_situationsByCallsign; return m_situationsByCallsign;
} }
CAircraftPartsList IInterpolator::getAndRemovePartsBeforeOffset(const CCallsign &callsign, qint64 cutoffTime, BlackCore::IInterpolator::PartsStatus &partsStatus)
{
static const CAircraftPartsList empty;
partsStatus.reset();
QWriteLocker l(&m_lockParts);
if (this->m_partsByCallsign.contains(callsign))
{
partsStatus.supportsParts = true;
return this->m_partsByCallsign[callsign].findBeforeAndRemove(cutoffTime);
}
else
{
partsStatus.supportsParts = m_aircraftSupportingParts.contains(callsign);
return empty;
}
}
void IInterpolator::clear()
{
QWriteLocker s(&m_lockSituations);
QWriteLocker p(&m_lockParts);
m_situationsByCallsign.clear();
m_partsByCallsign.clear();
}
CAircraftSituationList IInterpolator::getSituationsForCallsign(const CCallsign &callsign) const CAircraftSituationList IInterpolator::getSituationsForCallsign(const CCallsign &callsign) const
{ {
QReadLocker l(&m_situationsLock); QReadLocker l(&m_lockSituations);
static const CAircraftSituationList empty; static const CAircraftSituationList empty;
if (!m_situationsByCallsign.contains(callsign)) { return empty; } if (!m_situationsByCallsign.contains(callsign)) { return empty; }
return m_situationsByCallsign[callsign]; return m_situationsByCallsign[callsign];
} }
int IInterpolator::latestFinishedRequestId() const
{
QReadLocker l(&m_requestedInterpolationsLock);
if (m_requestedInterpolations.isEmpty()) { return -1; }
return m_requestedInterpolations.keys().first();
}
CAircraftSituationList IInterpolator::latestFinishedRequest() const
{
QReadLocker l(&m_requestedInterpolationsLock);
static const CAircraftSituationList empty;
if (m_requestedInterpolations.isEmpty()) { return empty; }
return m_requestedInterpolations.values().first();
}
CAircraftSituationList IInterpolator::getRequest(int requestId, bool *ok) const
{
QReadLocker l(&m_requestedInterpolationsLock);
static const CAircraftSituationList empty;
if (!m_requestedInterpolations.contains(requestId))
{
if (ok) { *ok = false; }
return empty;
}
if (ok) { *ok = true; }
return m_requestedInterpolations[requestId];
}
void IInterpolator::ps_onAddedAircraftSituation(const CAircraftSituation &situation) void IInterpolator::ps_onAddedAircraftSituation(const CAircraftSituation &situation)
{ {
QWriteLocker lock(&m_situationsLock); QWriteLocker lock(&m_lockSituations);
const CCallsign callsign(situation.getCallsign()); const CCallsign callsign(situation.getCallsign());
Q_ASSERT(!callsign.isEmpty()); Q_ASSERT(!callsign.isEmpty());
if (callsign.isEmpty()) { return; } if (callsign.isEmpty()) { return; }
@@ -158,32 +109,57 @@ namespace BlackCore
// list from new to old // list from new to old
CAircraftSituationList &l = this->m_situationsByCallsign[callsign]; CAircraftSituationList &l = this->m_situationsByCallsign[callsign];
l.insertTimestampObject(situation, MaxSituationsPerCallsign); l.push_frontMaxElements(situation, MaxSituationsPerCallsign);
} }
void IInterpolator::ps_onAddedAircraftParts(const CAircraftParts &parts) void IInterpolator::ps_onAddedAircraftParts(const CAircraftParts &parts)
{ {
QWriteLocker lock(&m_partsLock); QWriteLocker lock(&m_lockParts);
const CCallsign callsign(parts.getCallsign()); const CCallsign callsign(parts.getCallsign());
Q_ASSERT(!callsign.isEmpty()); Q_ASSERT(!callsign.isEmpty());
if (callsign.isEmpty()) { return; } if (callsign.isEmpty()) { return; }
if (this->m_withDebugMsg) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << parts.getCallsign() << parts.getMSecsSinceEpoch(); } if (this->m_withDebugMsg) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << parts.getCallsign() << parts.getMSecsSinceEpoch(); }
// list from new to old // list sorted from new to old
CAircraftPartsList &l = this->m_partsByCallsign[callsign]; CAircraftPartsList &l = this->m_partsByCallsign[callsign];
l.insertTimestampObject(parts, MaxPartsPerCallsign); l.push_frontMaxElements(parts, MaxPartsPerCallsign);
if (m_aircraftSupportingParts.contains(callsign)) { return; }
m_aircraftSupportingParts.push_back(callsign);
} }
void IInterpolator::ps_onRemovedAircraft(const CCallsign &callsign) void IInterpolator::ps_onRemovedAircraft(const CCallsign &callsign)
{ {
QWriteLocker ls(&m_situationsLock); QWriteLocker ls(&m_lockSituations);
QWriteLocker lp(&m_partsLock); QWriteLocker lp(&m_lockParts);
Q_ASSERT(!callsign.isEmpty()); Q_ASSERT(!callsign.isEmpty());
if (callsign.isEmpty()) { return; } if (callsign.isEmpty()) { return; }
if (this->m_withDebugMsg) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << callsign; } if (this->m_withDebugMsg) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << callsign; }
this->m_partsByCallsign.remove(callsign); this->m_partsByCallsign.remove(callsign);
this->m_situationsByCallsign.remove(callsign); this->m_situationsByCallsign.remove(callsign);
this->m_aircraftSupportingParts.remove(callsign);
}
bool IInterpolator::InterpolationStatus::allTrue() const
{
return interpolationSucceeded && changedPosition;
}
void IInterpolator::InterpolationStatus::reset()
{
changedPosition = false;
interpolationSucceeded = false;
}
bool IInterpolator::PartsStatus::allTrue() const
{
return supportsParts;
}
void IInterpolator::PartsStatus::reset()
{
supportsParts = false;
} }
} // namespace } // namespace

View File

@@ -29,45 +29,72 @@ namespace BlackCore
Q_OBJECT Q_OBJECT
public: public:
//! Situations per callsign
typedef QHash<BlackMisc::Aviation::CCallsign, BlackMisc::Aviation::CAircraftSituationList> CSituationsPerCallsign;
//! Parts per callsign
typedef QHash<BlackMisc::Aviation::CCallsign, BlackMisc::Aviation::CAircraftPartsList> CPartsPerCallsign;
//! Virtual destructor //! Virtual destructor
virtual ~IInterpolator() {} virtual ~IInterpolator() {}
//! Log category //! Log category
static QString getMessageCategory() { return "swift.iinterpolator"; } static QString getMessageCategory() { return "swift.iinterpolator"; }
//! Has situations? //! Status of interpolation
//! \deprecated Try not to use, it would be more efficient to directly getting the values and decide then struct InterpolationStatus
//! \threadsafe {
virtual bool hasEnoughAircraftSituations(const BlackMisc::Aviation::CCallsign &callsign) const; public:
bool changedPosition = false; //!< position was changed
bool interpolationSucceeded = false; //!< interpolation succeeded (means enough values, etc.)
//! all OK
bool allTrue() const;
//! Reset to default values
void reset();
};
//! Status regarding parts
struct PartsStatus
{
bool supportsParts = false; //!< supports parts for given callsign
//! all OK
bool allTrue() const;
//! Reset to default values
void reset();
};
//! Current interpolated situation //! Current interpolated situation
//! \threadsafe //! \threadsafe
virtual BlackMisc::Aviation::CAircraftSituation getCurrentInterpolatedSituation(const QHash<BlackMisc::Aviation::CCallsign, BlackMisc::Aviation::CAircraftSituationList> &allSituations, const BlackMisc::Aviation::CCallsign &callsign, qint64 currentTimeSinceEpoc = -1, bool *ok = nullptr) const = 0; virtual BlackMisc::Aviation::CAircraftSituation getInterpolatedSituation(
const BlackMisc::Aviation::CCallsign &callsign, qint64 currentTimeSinceEpoc,
InterpolationStatus &status, const CSituationsPerCallsign *situationsPerCallsign = nullptr) const = 0;
//! Latest parts before time - offset //! Do a complete calculation for all know callsigns.
//! \param currentTimeMsSinceEpoch if no value is passed current time is used
//! \threadsafe //! \threadsafe
BlackMisc::Aviation::CAircraftParts getLatestPartsBeforeOffset(const BlackMisc::Aviation::CCallsign &callsign, qint64 timeOffset = TimeOffsetMs, bool *ok = nullptr) const; //!
virtual BlackMisc::Aviation::CAircraftSituationList getInterpolatedSituations(qint64 currentTimeMsSinceEpoch = -1);
//! The situations per callsign //! The situations per callsign
//! \threadsafe //! \threadsafe
QHash<BlackMisc::Aviation::CCallsign, BlackMisc::Aviation::CAircraftSituationList> getSituationsByCallsign() const; CSituationsPerCallsign getSituationsByCallsign() const;
//! Parts before given offset time (aka pending parts)
//! \threadsafe
virtual BlackMisc::Aviation::CAircraftPartsList getAndRemovePartsBeforeOffset(const BlackMisc::Aviation::CCallsign &callsign, qint64 timeOffset, PartsStatus &partsStatus);
//! Clear all data
//! \threadsafe
virtual void clear();
//! Situations for given callsign //! Situations for given callsign
//! \threadsafe //! \threadsafe
BlackMisc::Aviation::CAircraftSituationList getSituationsForCallsign(const BlackMisc::Aviation::CCallsign &callsign) const; BlackMisc::Aviation::CAircraftSituationList getSituationsForCallsign(const BlackMisc::Aviation::CCallsign &callsign) const;
//! Last finished request id, -1 means none
//! \threadsafe
int latestFinishedRequestId() const;
//! Latest calculation
//! \threadsafe
BlackMisc::Aviation::CAircraftSituationList latestFinishedRequest() const;
//! Calculation by id
//! \threadsafe
BlackMisc::Aviation::CAircraftSituationList getRequest(int requestId, bool *ok = nullptr) const;
//! Enable debug messages //! Enable debug messages
void enableDebugMessages(bool enabled); void enableDebugMessages(bool enabled);
@@ -76,20 +103,6 @@ namespace BlackCore
static const int MaxPartsPerCallsign = 3; //!< How many parts per callsign static const int MaxPartsPerCallsign = 3; //!< How many parts per callsign
static const int MaxKeptInterpolationRequests = 3; //!< How many requests are stored static const int MaxKeptInterpolationRequests = 3; //!< How many requests are stored
public slots:
//! Do a complete calculation for all know callsigns in background.
//! Only use positive numbers.
//! \param requestId
//! \param currentTimeMsSinceEpoch if no value is passed current time is used
//! \threadsafe
//!
void syncRequestSituationsCalculationsForAllCallsigns(int requestId, qint64 currentTimeMsSinceEpoch = -1);
//! Do a complete calculation for all know callsigns in background.
//! Non blocking call of \sa syncRequestSituationsCalculationsForAllCallsigns
//! \threadsafe
void asyncRequestSituationsCalculationsForAllCallsigns(int requestId, qint64 currentTimeMsSinceEpoch = -1);
private slots: private slots:
//! New situation got added //! New situation got added
//! \threadsafe //! \threadsafe
@@ -107,23 +120,17 @@ namespace BlackCore
//! Constructor //! Constructor
IInterpolator(BlackMisc::Simulation::IRemoteAircraftProviderReadOnly *provider, const QString &workerName, QObject *parent = nullptr); IInterpolator(BlackMisc::Simulation::IRemoteAircraftProviderReadOnly *provider, const QString &workerName, QObject *parent = nullptr);
//! Situations for times before / after
//! \sa ITimestampObjectList::splitByTime
//! \threadsafe
//! \deprecated For first version
QList<BlackMisc::Aviation::CAircraftSituationList> getSituationsTimeSplit(const BlackMisc::Aviation::CCallsign &callsign, qint64 splitTimeMsSinceEpoch) const;
bool m_withDebugMsg = false; //!< allows to disable debug messages bool m_withDebugMsg = false; //!< allows to disable debug messages
BlackMisc::Aviation::CCallsignList m_aircraftSupportingParts; //!< aircraft supporting parts
private:
mutable QReadWriteLock m_situationsLock;
mutable QReadWriteLock m_partsLock;
mutable QReadWriteLock m_requestedInterpolationsLock;
// hashs, because not sorted by key but keeping order // hashs, because not sorted by key but keeping order
QHash<BlackMisc::Aviation::CCallsign, BlackMisc::Aviation::CAircraftSituationList> m_situationsByCallsign; CSituationsPerCallsign m_situationsByCallsign; //!< situations
QHash<BlackMisc::Aviation::CCallsign, BlackMisc::Aviation::CAircraftPartsList> m_partsByCallsign; CPartsPerCallsign m_partsByCallsign; //!< parts
QHash<int, BlackMisc::Aviation::CAircraftSituationList> m_requestedInterpolations;
// locks
mutable QReadWriteLock m_lockSituations; //!< lock for situations
mutable QReadWriteLock m_lockParts; //!< lock for parts
}; };
} // namespace } // namespace

View File

@@ -20,18 +20,31 @@ using namespace BlackMisc::Aviation;
namespace BlackCore namespace BlackCore
{ {
CAircraftSituation CInterpolatorLinear::getCurrentInterpolatedSituation(const QHash<CCallsign, CAircraftSituationList> &allSituations, const CCallsign &callsign, qint64 currentTimeMsSinceEpoc, bool *ok) const CAircraftSituation CInterpolatorLinear::getInterpolatedSituation(const CCallsign &callsign, qint64 currentTimeMsSinceEpoc, InterpolationStatus &status, const CSituationsPerCallsign *situationsPerCallsign) const
{ {
const static CAircraftSituation empty; const static CAircraftSituation empty;
if (ok) { *ok = false; } status.reset();
if (!allSituations.contains(callsign)) { return empty; } QList<CAircraftSituationList> splitSituations;
if (allSituations[callsign].isEmpty()) { return empty; }
if (currentTimeMsSinceEpoc < 0) { currentTimeMsSinceEpoc = QDateTime::currentMSecsSinceEpoch(); } if (currentTimeMsSinceEpoc < 0) { currentTimeMsSinceEpoc = QDateTime::currentMSecsSinceEpoch(); }
qint64 splitTimeMsSinceEpoch = currentTimeMsSinceEpoc - TimeOffsetMs; qint64 splitTimeMsSinceEpoch = currentTimeMsSinceEpoc - TimeOffsetMs;
QList<CAircraftSituationList> splitSituations = allSituations[callsign].splitByTime(splitTimeMsSinceEpoch);
CAircraftSituationList &situationsNewer = splitSituations[0]; // latest first if (situationsPerCallsign)
CAircraftSituationList &situationsOlder = splitSituations[1]; // latest first {
if (!situationsPerCallsign->contains(callsign)) { return empty; }
if ((*situationsPerCallsign)[callsign].isEmpty()) { return empty; }
splitSituations = (*situationsPerCallsign)[callsign].splitByTime(splitTimeMsSinceEpoch);
}
else
{
// only part where it is locked
QReadLocker lock(&m_lockSituations);
if (!m_situationsByCallsign.contains(callsign)) { return empty; }
if (m_situationsByCallsign[callsign].isEmpty()) { return empty; }
splitSituations = m_situationsByCallsign[callsign].splitByTime(splitTimeMsSinceEpoch);
}
CAircraftSituationList &situationsNewer = splitSituations[0]; // newer part
CAircraftSituationList &situationsOlder = splitSituations[1]; // older part
// interpolation situations // interpolation situations
CAircraftSituation oldSituation; CAircraftSituation oldSituation;
@@ -52,7 +65,6 @@ namespace BlackCore
// We just place at he last position until we get before / after situations // We just place at he last position until we get before / after situations
if (situationsOlderNo < 1 || situationsNewerNo < 1) if (situationsOlderNo < 1 || situationsNewerNo < 1)
{ {
if (ok) { *ok = true; }
// no after situations // no after situations
if (situationsOlderNo < 1) { return situationsNewer.back(); } // oldest newest if (situationsOlderNo < 1) { return situationsNewer.back(); } // oldest newest
@@ -73,6 +85,7 @@ namespace BlackCore
CAircraftSituation currentSituation(oldSituation); CAircraftSituation currentSituation(oldSituation);
CCoordinateGeodetic currentPosition; CCoordinateGeodetic currentPosition;
status.interpolationSucceeded = true;
// Time between start and end packet // Time between start and end packet
double deltaTime = oldSituation.absMsecsTo(newSituation); double deltaTime = oldSituation.absMsecsTo(newSituation);
@@ -82,7 +95,7 @@ namespace BlackCore
// 1) values > 1 mean extrapolation // 1) values > 1 mean extrapolation
// 2) values > 2 mean no new situations coming in // 2) values > 2 mean no new situations coming in
double simulationTimeFraction = 1 - ((newSituation.getMSecsSinceEpoch() - splitTimeMsSinceEpoch) / deltaTime); double simulationTimeFraction = 1 - ((newSituation.getMSecsSinceEpoch() - splitTimeMsSinceEpoch) / deltaTime);
if (simulationTimeFraction > 1.5) if (simulationTimeFraction > 2.0)
{ {
if (this->m_withDebugMsg) if (this->m_withDebugMsg)
{ {
@@ -91,21 +104,34 @@ namespace BlackCore
} }
// Interpolate latitude: Lat = (LatB - LatA) * t + LatA // Interpolate latitude: Lat = (LatB - LatA) * t + LatA
currentPosition.setLatitude((newSituation.getPosition().latitude() - oldSituation.getPosition().latitude()) const CLatitude oldLat(oldSituation.latitude());
const CLatitude newLat(newSituation.latitude());
const CLongitude oldLng(oldSituation.longitude());
const CLongitude newLng(newSituation.longitude());
currentPosition.setLatitude((newLat - oldLat)
* simulationTimeFraction * simulationTimeFraction
+ oldSituation.getPosition().latitude()); + oldLat);
// Interpolate latitude: Lon = (LonB - LonA) * t + LonA // Interpolate latitude: Lon = (LonB - LonA) * t + LonA
currentPosition.setLongitude((newSituation.getPosition().longitude() - oldSituation.getPosition().longitude()) currentPosition.setLongitude((newLng - oldLng)
* simulationTimeFraction * simulationTimeFraction
+ oldSituation.getPosition().longitude()); + oldLng);
currentSituation.setPosition(currentPosition); currentSituation.setPosition(currentPosition);
// Interpolate altitude: Alt = (AltB - AltA) * t + AltA // Interpolate altitude: Alt = (AltB - AltA) * t + AltA
currentSituation.setAltitude(CAltitude((newSituation.getAltitude() - oldSituation.getAltitude()) const CAltitude oldAlt(oldSituation.getAltitude());
const CAltitude newAlt(newSituation.getAltitude());
Q_ASSERT(oldAlt.getReferenceDatum() == newAlt.getReferenceDatum()); // otherwise no calculation is possible
currentSituation.setAltitude(CAltitude((newAlt - oldAlt)
* simulationTimeFraction * simulationTimeFraction
+ oldSituation.getAltitude(), + oldAlt,
oldSituation.getAltitude().getReferenceDatum())); oldAlt.getReferenceDatum()));
if (newLat == oldLat && newLng == oldLng && oldAlt == newAlt)
{
return currentSituation;
}
// Interpolate heading: HDG = (HdgB - HdgA) * t + HdgA // Interpolate heading: HDG = (HdgB - HdgA) * t + HdgA
CHeading headingBegin = oldSituation.getHeading(); CHeading headingBegin = oldSituation.getHeading();
@@ -143,13 +169,13 @@ namespace BlackCore
// TODO: According to the specification, banks to the right should be negative. // TODO: According to the specification, banks to the right should be negative.
// But somehow we get positive banks from the network. // But somehow we get positive banks from the network.
bank *= -1; bank *= -1.0;
currentSituation.setBank(bank); currentSituation.setBank(bank);
currentSituation.setGroundspeed((newSituation.getGroundSpeed() - oldSituation.getGroundSpeed()) currentSituation.setGroundspeed((newSituation.getGroundSpeed() - oldSituation.getGroundSpeed())
* simulationTimeFraction * simulationTimeFraction
+ oldSituation.getGroundSpeed()); + oldSituation.getGroundSpeed());
if (ok) { *ok = true; } status.changedPosition = true;
Q_ASSERT(currentSituation.getCallsign() == callsign); Q_ASSERT(currentSituation.getCallsign() == callsign);
return currentSituation; return currentSituation;
} }

View File

@@ -30,7 +30,7 @@ namespace BlackCore
{} {}
//! \copydoc IInterpolator::getCurrentInterpolatedSituation //! \copydoc IInterpolator::getCurrentInterpolatedSituation
virtual BlackMisc::Aviation::CAircraftSituation getCurrentInterpolatedSituation(const QHash<BlackMisc::Aviation::CCallsign, BlackMisc::Aviation::CAircraftSituationList> &allSituations, const BlackMisc::Aviation::CCallsign &callsign, qint64 currentTimeSinceEpoc = -1, bool *ok = nullptr) const override; virtual BlackMisc::Aviation::CAircraftSituation getInterpolatedSituation(const BlackMisc::Aviation::CCallsign &callsign, qint64 currentTimeSinceEpoc, InterpolationStatus &status, const CSituationsPerCallsign *situationsPerCallsign = nullptr) const override;
//! Log category //! Log category
static QString getMessageCategory() { return "swift.interpolatorlinear"; } static QString getMessageCategory() { return "swift.interpolatorlinear"; }

View File

@@ -430,8 +430,9 @@ namespace BlackCore
/*! /*!
* We received a notification of the state of another aircraft on the network. * We received a notification of the state of another aircraft on the network.
* Corresponding callsign in \sa CAircraftSituation::getCallsign .
*/ */
void aircraftPositionUpdate(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftSituation &situation, void aircraftPositionUpdate(const BlackMisc::Aviation::CAircraftSituation &situation,
const BlackMisc::Aviation::CTransponder &transponder); const BlackMisc::Aviation::CTransponder &transponder);
/*! /*!

View File

@@ -648,16 +648,17 @@ namespace BlackCore
{ {
const CCallsign callsign(callsignChar); const CCallsign callsign(callsignChar);
const CAircraftSituation situation( const CAircraftSituation situation(
callsign,
CCoordinateGeodetic(position->latitude, position->longitude, 0.0), CCoordinateGeodetic(position->latitude, position->longitude, 0.0),
CAltitude(position->altitudeTrue, CAltitude::AboveGround, CLengthUnit::ft()), CAltitude(position->altitudeTrue, CAltitude::MeanSeaLevel, CLengthUnit::ft()),
CHeading(position->heading, CHeading::True, CAngleUnit::deg()), CHeading(position->heading, CHeading::True, CAngleUnit::deg()),
CAngle(position->pitch, CAngleUnit::deg()), CAngle(position->pitch, CAngleUnit::deg()),
CAngle(position->bank, CAngleUnit::deg()), CAngle(position->bank, CAngleUnit::deg()),
CSpeed(position->groundSpeed, CSpeedUnit::kts()) CSpeed(position->groundSpeed, CSpeedUnit::kts())
); );
QString tn("transponder "); QString transponderName("transponder ");
tn.append(callsign.asString()); transponderName.append(callsign.asString());
CTransponder::TransponderMode mode = CTransponder::StateStandby; CTransponder::TransponderMode mode = CTransponder::StateStandby;
switch (position->transponderMode) switch (position->transponderMode)
{ {
@@ -675,19 +676,21 @@ namespace BlackCore
break; break;
} }
// I did have a situation where I got wrong transponger codes (KB) // I did have a situation where I got wrong transponder codes (KB)
// So I now check for a valid code in order to detect such codes // So I now check for a valid code in order to detect such codes
CTransponder transponder(tn, 0, mode); CTransponder transponder;
if (CTransponder::isValidTransponderCode(position->transponderCode)) if (CTransponder::isValidTransponderCode(position->transponderCode))
{ {
transponder = CTransponder(tn, position->transponderCode, mode); transponder = CTransponder(transponderName, position->transponderCode, mode);
} }
else else
{ {
// TODO: how do with log this CLogMessage(static_cast<CNetworkVatlib *>(nullptr)).warning("Wrong transponder code %1 for %2") << position->transponderCode << callsign;
qDebug() << "Wrong transponder code" << position->transponderMode << callsign;
// default
transponder = CTransponder(transponderName, 7000, mode);
} }
emit cbvar_cast(cbvar)->aircraftPositionUpdate(callsign, situation, transponder); emit cbvar_cast(cbvar)->aircraftPositionUpdate(situation, transponder);
} }
void CNetworkVatlib::onAircraftConfigReceived(VatSessionID, const char *callsign, const char *aircraftConfig, void *cbvar) void CNetworkVatlib::onAircraftConfigReceived(VatSessionID, const char *callsign, const char *aircraftConfig, void *cbvar)

View File

@@ -27,7 +27,9 @@ namespace BlackCore
/*! /*!
* Implementation of INetwork using the vatlib shim * Implementation of INetwork using the vatlib shim
*/ */
class CNetworkVatlib : public INetwork, public BlackMisc::Simulation::COwnAircraftProviderSupport class CNetworkVatlib :
public INetwork,
public BlackMisc::Simulation::COwnAircraftProviderSupport // network vatlib consumes own aircraft data and sets ICAO/callsign data
{ {
Q_OBJECT Q_OBJECT

View File

@@ -111,7 +111,7 @@ namespace BlackMisc
CLength heightAboveGround(this->getHeightAboveGround()); CLength heightAboveGround(this->getHeightAboveGround());
if (!heightAboveGround.isNull()) if (!heightAboveGround.isNull())
{ {
return heightAboveGround.value(CLengthUnit::m()) < 2.0; return heightAboveGround.value(CLengthUnit::m()) < 1.0;
} }
// we guess on pitch an bank // we guess on pitch an bank
@@ -120,7 +120,7 @@ namespace BlackMisc
if (this->getGroundSpeed().value(CSpeedUnit::km_h()) > 80) { return false; } if (this->getGroundSpeed().value(CSpeedUnit::km_h()) > 80) { return false; }
// not sure, but his is a guess // not sure, but this is a guess
return true; return true;
} }

View File

@@ -183,7 +183,7 @@ namespace BlackMisc
BLACK_ENABLE_TUPLE_CONVERSION(CCoordinateGeodetic) BLACK_ENABLE_TUPLE_CONVERSION(CCoordinateGeodetic)
BlackMisc::Geo::CLatitude m_latitude; //!< Latitude BlackMisc::Geo::CLatitude m_latitude; //!< Latitude
BlackMisc::Geo::CLongitude m_longitude; //!< Longitude BlackMisc::Geo::CLongitude m_longitude; //!< Longitude
BlackMisc::PhysicalQuantities::CLength m_geodeticHeight; //!< height, ellipsoidal or geodetic height BlackMisc::PhysicalQuantities::CLength m_geodeticHeight { 0, BlackMisc::PhysicalQuantities::CLengthUnit::nullUnit() }; //!< height, ellipsoidal or geodetic height
}; };
} // namespace } // namespace

View File

@@ -234,6 +234,12 @@ namespace BlackMisc
*/ */
void push_back(const T &value) { Q_ASSERT(pimpl()); pimpl()->push_back(value); } void push_back(const T &value) { Q_ASSERT(pimpl()); pimpl()->push_back(value); }
/*!
* \brief Insert as first element.
* \pre The sequence must be initialized.
*/
void push_front(const T &value) { insert(begin(), value); }
/*! /*!
* \brief Move-appends an element at the end of the sequence. * \brief Move-appends an element at the end of the sequence.
* \pre The sequence must be initialized. * \pre The sequence must be initialized.
@@ -272,12 +278,6 @@ namespace BlackMisc
*/ */
void insert(T &&value) { push_back(std::move(value)); } void insert(T &&value) { push_back(std::move(value)); }
/*!
* \brief Insert as first element.
* \pre The sequence must be initialized.
*/
void insert_front(const T &value) { insert(begin(), value); }
/*! /*!
* \brief Synonym for push_back. * \brief Synonym for push_back.
* \pre The sequence must be initialized. * \pre The sequence must be initialized.

View File

@@ -75,7 +75,7 @@ namespace BlackMisc
void CRemoteAircraftProviderDummy::insertNewSituation(const CAircraftSituation &situation) void CRemoteAircraftProviderDummy::insertNewSituation(const CAircraftSituation &situation)
{ {
this->m_situations.insertTimestampObject(situation, 20); this->m_situations.push_frontMaxElements(situation, 20);
emit addedRemoteAircraftSituation(situation); emit addedRemoteAircraftSituation(situation);
} }

View File

@@ -55,6 +55,17 @@ namespace BlackMisc
return this->findBy(Predicates::MemberValid(&CSimulatedAircraft::getPilot)).transform(Predicates::MemberTransform(&CSimulatedAircraft::getPilot)); return this->findBy(Predicates::MemberValid(&CSimulatedAircraft::getPilot)).transform(Predicates::MemberTransform(&CSimulatedAircraft::getPilot));
} }
CCallsignList CSimulatedAircraftList::getCallsignsWithSyncronizedParts() const
{
CCallsignList csl;
for (const CSimulatedAircraft &aircraft : (*this))
{
if (!aircraft.isPartsSynchronized()) { continue; }
csl.push_back(aircraft.getCallsign());
}
return csl;
}
CAircraftList CSimulatedAircraftList::toAircraftList() const CAircraftList CSimulatedAircraftList::toAircraftList() const
{ {
CAircraftList al; CAircraftList al;

View File

@@ -43,6 +43,9 @@ namespace BlackMisc
//! All pilots (with valid data) //! All pilots (with valid data)
BlackMisc::Network::CUserList getPilots() const; BlackMisc::Network::CUserList getPilots() const;
//! Callsigns of aircraft with synchronized parts
BlackMisc::Aviation::CCallsignList getCallsignsWithSyncronizedParts() const;
//! \copydoc CValueObject::toQVariant //! \copydoc CValueObject::toQVariant
virtual QVariant toQVariant() const override { return QVariant::fromValue(*this); } virtual QVariant toQVariant() const override { return QVariant::fromValue(*this); }

View File

@@ -29,6 +29,14 @@ namespace BlackMisc
}); });
} }
template <class OBJ, class CONTAINER>
CONTAINER ITimestampObjectList<OBJ, CONTAINER>::findBeforeAndRemove(qint64 msSinceEpoch)
{
CONTAINER result(findBefore(msSinceEpoch));
this->removeBefore(msSinceEpoch);
return result;
}
template <class OBJ, class CONTAINER> template <class OBJ, class CONTAINER>
CONTAINER ITimestampObjectList<OBJ, CONTAINER>::findBeforeNowMinusOffset(qint64 msOffset) const CONTAINER ITimestampObjectList<OBJ, CONTAINER>::findBeforeNowMinusOffset(qint64 msOffset) const
{ {
@@ -75,18 +83,18 @@ namespace BlackMisc
OBJ ITimestampObjectList<OBJ, CONTAINER>::latestValue() const OBJ ITimestampObjectList<OBJ, CONTAINER>::latestValue() const
{ {
if (this->container().isEmpty()) { return OBJ(); } if (this->container().isEmpty()) { return OBJ(); }
CONTAINER container(container()); // copy CONTAINER copy(container()); // copy
container.sortLatestFirst(); copy.sortLatestFirst();
return container.front(); return copy.front();
} }
template <class OBJ, class CONTAINER> template <class OBJ, class CONTAINER>
OBJ ITimestampObjectList<OBJ, CONTAINER>::oldestValue() const OBJ ITimestampObjectList<OBJ, CONTAINER>::oldestValue() const
{ {
if (this->container().isEmpty()) { return OBJ(); } if (this->container().isEmpty()) { return OBJ(); }
CONTAINER container(container()); // copy CONTAINER copy(container()); // copy
container.sortLatestFirst(); copy.sortLatestFirst();
return container.back(); return copy.back();
} }
template <class OBJ, class CONTAINER> template <class OBJ, class CONTAINER>
@@ -134,14 +142,14 @@ namespace BlackMisc
} }
template <class OBJ, class CONTAINER> template <class OBJ, class CONTAINER>
void ITimestampObjectList<OBJ, CONTAINER>::insertTimestampObject(const OBJ &object, int maxElements) void ITimestampObjectList<OBJ, CONTAINER>::push_frontMaxElements(const OBJ &object, int maxElements)
{ {
Q_ASSERT(maxElements > 1); Q_ASSERT(maxElements > 1);
if (this->container().size() >= (maxElements - 1)) if (this->container().size() >= (maxElements - 1))
{ {
this->container().truncate(maxElements - 1); this->container().truncate(maxElements - 1);
} }
this->container().insert_front(object); this->container().push_front(object);
} }
// see here for the reason of thess forward instantiations // see here for the reason of thess forward instantiations

View File

@@ -32,6 +32,9 @@ namespace BlackMisc
//! List of objects before msSinceEpoch //! List of objects before msSinceEpoch
CONTAINER findBefore(qint64 msSinceEpoch) const; CONTAINER findBefore(qint64 msSinceEpoch) const;
//! Get objects before msSinceEpoch and remove those
CONTAINER findBeforeAndRemove(qint64 msSinceEpoch);
//! List of objects before now - offset //! List of objects before now - offset
CONTAINER findBeforeNowMinusOffset(qint64 msOffset) const; CONTAINER findBeforeNowMinusOffset(qint64 msOffset) const;
@@ -67,7 +70,7 @@ namespace BlackMisc
void sortOldestFirst(); void sortOldestFirst();
//! Inserts as first object by keeping max. elements //! Inserts as first object by keeping max. elements
void insertTimestampObject(const OBJ &object, int maxElements); void push_frontMaxElements(const OBJ &object, int maxElements);
protected: protected:
//! Constructor //! Constructor

View File

@@ -81,31 +81,14 @@ namespace BlackSimPlugin
if (m_clientStatus == Disconnected) { return; } if (m_clientStatus == Disconnected) { return; }
bool ok; IInterpolator::InterpolationStatus status;
CAircraftSituation situation = this->m_interpolator->getCurrentInterpolatedSituation(this->m_interpolator->getSituationsByCallsign(), m_callsign, -1, &ok); CAircraftSituation situation = this->m_interpolator->getInterpolatedSituation(m_callsign, -1, status);
if (!ok) { return; }
MPPositionSlewMode positionSlewMode = aircraftSituationToFS9(situation);
QByteArray positionMessage; // Test only for successful interpolation. FS9 requires constant positions
MultiPlayerPacketParser::writeType(positionMessage, CFs9Sdk::MULTIPLAYER_PACKET_ID_POSITION_SLEWMODE); if (!status.interpolationSucceeded) return;
MultiPlayerPacketParser::writeSize(positionMessage, positionSlewMode.size());
positionSlewMode.packet_index = m_packetIndex;
++m_packetIndex;
positionMessage = MultiPlayerPacketParser::writeMessage(positionMessage, positionSlewMode);
sendMessage(positionMessage);
QByteArray paramMessage;
MPParam param;
MultiPlayerPacketParser::writeType(paramMessage, CFs9Sdk::MULTIPLAYER_PACKET_ID_PARAMS);
MultiPlayerPacketParser::writeSize(paramMessage, param.size());
param.packet_index = m_packetIndex;
++m_packetIndex;
paramMessage = MultiPlayerPacketParser::writeMessage(paramMessage, param);
sendMessage(paramMessage);
m_lastAircraftSituation = situation;
sendMultiplayerPosition(situation);
sendMultiplayerParamaters();
} }
void CFs9Client::initialize() void CFs9Client::initialize()

View File

@@ -71,31 +71,32 @@ namespace BlackSimPlugin
HRESULT CSimConnectDefinitions::initRemoteAircraft(const HANDLE hSimConnect) HRESULT CSimConnectDefinitions::initRemoteAircraft(const HANDLE hSimConnect)
{ {
HRESULT hr = S_OK; HRESULT hr = S_OK;
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "Initial Position", NULL, SIMCONNECT_DATATYPE_INITPOSITION); // Position
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftPosition, "Initial Position", NULL, SIMCONNECT_DATATYPE_INITPOSITION);
// Lights // Lights
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "LIGHT STROBE", "Bool"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "LIGHT STROBE", "Bool");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "LIGHT LANDING", "Bool"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "LIGHT LANDING", "Bool");
// hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "LIGHT TAXI", "Bool"); // hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "LIGHT TAXI", "Bool");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "LIGHT BEACON", "Bool"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "LIGHT BEACON", "Bool");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "LIGHT NAV", "Bool"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "LIGHT NAV", "Bool");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "LIGHT LOGO", "Bool"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "LIGHT LOGO", "Bool");
// Flaps // Flaps
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "LEADING EDGE FLAPS LEFT PERCENT", "Percent Over 100"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "LEADING EDGE FLAPS LEFT PERCENT", "Percent Over 100");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "LEADING EDGE FLAPS RIGHT PERCENT", "Percent Over 100"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "LEADING EDGE FLAPS RIGHT PERCENT", "Percent Over 100");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "TRAILING EDGE FLAPS LEFT PERCENT", "Percent Over 100"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "TRAILING EDGE FLAPS LEFT PERCENT", "Percent Over 100");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "TRAILING EDGE FLAPS RIGHT PERCENT", "Percent Over 100"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "TRAILING EDGE FLAPS RIGHT PERCENT", "Percent Over 100");
// Gear & Spoiler // Gear & Spoiler
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "GEAR HANDLE POSITION", "Bool"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "GEAR HANDLE POSITION", "Bool");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "SPOILERS HANDLE POSITION", "Percent Over 100"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "SPOILERS HANDLE POSITION", "Percent Over 100");
// Engines // Engines
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "GENERAL ENG COMBUSTION:1", "Bool"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "GENERAL ENG COMBUSTION:1", "Bool");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "GENERAL ENG COMBUSTION:2", "Bool"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "GENERAL ENG COMBUSTION:2", "Bool");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "GENERAL ENG COMBUSTION:3", "Bool"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "GENERAL ENG COMBUSTION:3", "Bool");
hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, "GENERAL ENG COMBUSTION:4", "Bool"); hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "GENERAL ENG COMBUSTION:4", "Bool");
if (hr != S_OK) if (hr != S_OK)
{ {

View File

@@ -70,10 +70,9 @@ namespace BlackSimPlugin
char title[256]; //!< Aircraft model string char title[256]; //!< Aircraft model string
}; };
//! Data struct of remote aircraft //! Data struct of remote aircraft parts
struct DataDefinitionRemoteAircraft struct DataDefinitionRemoteAircraftParts
{ {
SIMCONNECT_DATA_INITPOSITION position; //!< Aircrafts position
double lightStrobe; //!< Is strobe light on? double lightStrobe; //!< Is strobe light on?
double lightLanding; //!< Is landing light on? double lightLanding; //!< Is landing light on?
// double lightTaxi; //!< Is taxi light on? // double lightTaxi; //!< Is taxi light on?
@@ -141,7 +140,8 @@ namespace BlackSimPlugin
{ {
DataOwnAircraft, DataOwnAircraft,
DataOwnAircraftTitle, DataOwnAircraftTitle,
DataRemoteAircraft, DataRemoteAircraftParts,
DataRemoteAircraftPosition,
DataSimEnvironment, DataSimEnvironment,
DataClientAreaSb, //!< whole SB area DataClientAreaSb, //!< whole SB area
DataClientAreaSbIdent, //!< ident single value DataClientAreaSbIdent, //!< ident single value

View File

@@ -597,111 +597,138 @@ namespace BlackSimPlugin
void CSimulatorFsx::updateRemoteAircraft() void CSimulatorFsx::updateRemoteAircraft()
{ {
static_assert(sizeof(DataDefinitionRemoteAircraft) == 176, "DataDefinitionRemoteAircraft has an incorrect size."); static_assert(sizeof(DataDefinitionRemoteAircraftParts) == 120, "DataDefinitionRemoteAircraftParts has an incorrect size.");
Q_ASSERT(this->m_interpolator); Q_ASSERT(this->m_interpolator);
Q_ASSERT_X(this->m_interpolator->thread() != this->thread(), "updateOtherAircraft", "interpolator should run in its own thread"); Q_ASSERT_X(this->m_interpolator->thread() != this->thread(), "updateOtherAircraft", "interpolator should run in its own thread");
// nothing to do, reset request id and exit // nothing to do, reset request id and exit
if (this->isPaused()) { return; } // no interpolation while paused
int remoteAircraftNo = this->remoteAircraft().size(); int remoteAircraftNo = this->remoteAircraft().size();
if (remoteAircraftNo < 1) { m_interpolationRequest = 0; return; } if (remoteAircraftNo < 1) { m_interpolationRequest = 0; return; }
// initial request, and bye. First time we have aircraft // interpolate and send to SIM
if (m_interpolationRequest == 0) m_interpolationRequest++;
{
m_interpolator->syncRequestSituationsCalculationsForAllCallsigns(++m_interpolationRequest);
return;
}
// try to get old request // values used for position and parts
bool lastRequestAvailable = false; bool isOnGround = false;
CAircraftSituationList interpolations = m_interpolator->getRequest(m_interpolationRequest, &lastRequestAvailable);
if (!lastRequestAvailable)
{
// warning the 1st and every 10th time
bool warning = m_interpolationsSkipped % 10;
m_interpolationsSkipped++;
if (warning)
{
CLogMessage(this).warning("Skipped interpolation %1 time(s)") << m_interpolationsSkipped;
}
return;
}
// non blocking calculations in background qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
m_interpolator->syncRequestSituationsCalculationsForAllCallsigns(++m_interpolationRequest); for (const CSimConnectObject &simObj : m_simConnectObjects)
// now send to sim
for (const CAircraftSituation &currentSituation : interpolations)
{ {
const CCallsign callsign(currentSituation.getCallsign()); const CCallsign callsign(simObj.getCallsign());
if (!m_simConnectObjects.contains(callsign)) { continue; } // only if aircraft is already available IInterpolator::InterpolationStatus interpolatorStatus;
bool hasParts; IInterpolator::PartsStatus partsStatus;
const CSimConnectObject &simObj = m_simConnectObjects[callsign];
if (simObj.getObjectId() == 0) { continue; } if (simObj.getObjectId() == 0) { continue; }
SIMCONNECT_DATA_INITPOSITION position = aircraftSituationToFsxInitPosition(currentSituation); CAircraftSituation interpolatedSituation = this->m_interpolator->getInterpolatedSituation(callsign, currentTimestamp, interpolatorStatus);
CAircraftParts parts = m_interpolator->getLatestPartsBeforeOffset(callsign, IInterpolator::TimeOffsetMs, &hasParts);
DataDefinitionRemoteAircraft ddRemoteAircraft; // having the on gground flag in parts forces me to obtain parts here
if (hasParts) // which is not hte smartest thing regarding performance
CAircraftPartsList parts = this->m_interpolator->getAndRemovePartsBeforeOffset(callsign, currentTimestamp - IInterpolator::TimeOffsetMs, partsStatus);
if (interpolatorStatus.allTrue())
{ {
// we have parts // update situation
position.OnGround = parts.isOnGround() ? 1 : 0; SIMCONNECT_DATA_INITPOSITION position = aircraftSituationToFsxInitPosition(interpolatedSituation);
ddRemoteAircraft.position = position;
ddRemoteAircraft.lightStrobe = parts.getLights().isStrobeOn() ? 1.0 : 0.0; //! \todo The onGround in parts is nuts, as already mentioned in the discussion
ddRemoteAircraft.lightLanding = parts.getLights().isLandingOn() ? 1.0 : 0.0; // a) I am forced to read parts even if i just want to update position
// ddRemoteAircraft.lightTaxi = parts.getLights().isTaxiOn() ? 1.0 : 0.0; // b) Unlike the other values it is not a fire aforget value, as I need it again in the next cycle
ddRemoteAircraft.lightBeacon = parts.getLights().isBeaconOn() ? 1.0 : 0.0; if (partsStatus.supportsParts && !parts.isEmpty())
ddRemoteAircraft.lightNav = parts.getLights().isNavOn() ? 1.0 : 0.0; {
ddRemoteAircraft.lightLogo = parts.getLights().isLogoOn() ? 1.0 : 0.0; // we have parts, and use the closest ground
ddRemoteAircraft.flapsLeadingEdgeLeftPercent = parts.getFlapsPercent() / 100.0; isOnGround = parts.front().isOnGround();
ddRemoteAircraft.flapsLeadingEdgeRightPercent = parts.getFlapsPercent() / 100.0;
ddRemoteAircraft.flapsTrailingEdgeLeftPercent = parts.getFlapsPercent() / 100.0;
ddRemoteAircraft.flapsTrailingEdgeRightPercent = parts.getFlapsPercent() / 100.0;
ddRemoteAircraft.spoilersHandlePosition = parts.isSpoilersOut() ? 1.0 : 0.0;
ddRemoteAircraft.gearHandlePosition = parts.isGearDown() ? 1 : 0;
ddRemoteAircraft.engine1Combustion = parts.isEngineOn(1) ? 1 : 0;
ddRemoteAircraft.engine2Combustion = parts.isEngineOn(2) ? 1 : 0;;
ddRemoteAircraft.engine3Combustion = parts.isEngineOn(3) ? 1 : 0;
ddRemoteAircraft.engine4Combustion = parts.isEngineOn(4) ? 1 : 0;
} }
else else
{ {
//! \todo interpolator, set data without parts by educated guessing whatsoever isOnGround = interpolatedSituation.isOnGroundGuessed();
bool onGround = currentSituation.isOnGroundGuessed(); }
position.OnGround = onGround ? 1 : 0;
ddRemoteAircraft.position = position; position.OnGround = isOnGround ? 1 : 0;
ddRemoteAircraft.gearHandlePosition = onGround ? 1 : 0; HRESULT hr = S_OK;
hr += SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftPosition,
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; }
}
// set parts
DataDefinitionRemoteAircraftParts ddRemoteAircraftParts;
if (partsStatus.supportsParts)
{
// parts is supported, but do we need to update?
if (parts.isEmpty()) { continue; }
// we have parts
CAircraftParts newestParts = parts.front();
ddRemoteAircraftParts.lightStrobe = newestParts.getLights().isStrobeOn() ? 1.0 : 0.0;
ddRemoteAircraftParts.lightLanding = newestParts.getLights().isLandingOn() ? 1.0 : 0.0;
// ddRemoteAircraftParts.lightTaxi = newestParts.getLights().isTaxiOn() ? 1.0 : 0.0;
ddRemoteAircraftParts.lightBeacon = newestParts.getLights().isBeaconOn() ? 1.0 : 0.0;
ddRemoteAircraftParts.lightNav = newestParts.getLights().isNavOn() ? 1.0 : 0.0;
ddRemoteAircraftParts.lightLogo = newestParts.getLights().isLogoOn() ? 1.0 : 0.0;
ddRemoteAircraftParts.flapsLeadingEdgeLeftPercent = newestParts.getFlapsPercent() / 100.0;
ddRemoteAircraftParts.flapsLeadingEdgeRightPercent = newestParts.getFlapsPercent() / 100.0;
ddRemoteAircraftParts.flapsTrailingEdgeLeftPercent = newestParts.getFlapsPercent() / 100.0;
ddRemoteAircraftParts.flapsTrailingEdgeRightPercent = newestParts.getFlapsPercent() / 100.0;
ddRemoteAircraftParts.spoilersHandlePosition = newestParts.isSpoilersOut() ? 1.0 : 0.0;
ddRemoteAircraftParts.gearHandlePosition = newestParts.isGearDown() ? 1 : 0;
ddRemoteAircraftParts.engine1Combustion = newestParts.isEngineOn(1) ? 1 : 0;
ddRemoteAircraftParts.engine2Combustion = newestParts.isEngineOn(2) ? 1 : 0;;
ddRemoteAircraftParts.engine3Combustion = newestParts.isEngineOn(3) ? 1 : 0;
ddRemoteAircraftParts.engine4Combustion = newestParts.isEngineOn(4) ? 1 : 0;
}
else
{
// mode is guessing parts
if (this->m_interpolationRequest % 20 != 0) { continue; } // only update every 20th cycle
if (!interpolatorStatus.allTrue()) { continue; } // no position, no really guess possible
ddRemoteAircraftParts.gearHandlePosition = isOnGround ? 1 : 0;
// when first detected moving, lights on // when first detected moving, lights on
if (onGround && currentSituation.getGroundSpeed().value(CSpeedUnit::km_h()) > 15) if (isOnGround)
{ {
// ddRemoteAircraft.light = 1.0; // ddRemoteAircraftParts.lightTaxi = 1.0;
ddRemoteAircraft.lightBeacon = 1.0; ddRemoteAircraftParts.lightBeacon = 1.0;
ddRemoteAircraft.lightNav = 1.0; ddRemoteAircraftParts.lightNav = 1.0;
ddRemoteAircraft.lightLanding = 0.0;
double gskmh = interpolatedSituation.getGroundSpeed().value(CSpeedUnit::km_h());
if (gskmh > 7.5)
{
// mode taxi
// ddRemoteAircraftParts.lightTaxi = 1.0;
ddRemoteAircraftParts.lightLanding = 0.0;
} }
else if (onGround) else if (gskmh > 25)
{ {
// ddRemoteAircraft.lightTaxi = 0.0; // mode accelaration for takeoff
ddRemoteAircraft.lightBeacon = 1.0; // ddRemoteAircraftParts.lightTaxi = 0.0;
ddRemoteAircraft.lightNav = 1.0; ddRemoteAircraftParts.lightLanding = 1.0;
ddRemoteAircraft.lightLanding = 0.0;
} }
else if (!onGround) else
{ {
// ddRemoteAircraft.lightTaxi = 0.0; // slow movements or parking
ddRemoteAircraft.lightBeacon = 1.0; // ddRemoteAircraftParts.lightTaxi = 0.0;
ddRemoteAircraft.lightNav = 1.0; ddRemoteAircraftParts.lightLanding = 0.0;
}
}
else
{
// ddRemoteAircraftParts.lightTaxi = 0.0;
ddRemoteAircraftParts.lightBeacon = 1.0;
ddRemoteAircraftParts.lightNav = 1.0;
// landing lights for < 10000ft (normally MSL, here ignored)
ddRemoteAircraftParts.lightLanding = (interpolatedSituation.getAltitude().value(CLengthUnit::ft()) < 10000) ? 1.0 : 0;
} }
} }
HRESULT hr = S_OK; HRESULT hr = S_OK;
hr += SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraft, hr += SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts,
simObj.getObjectId(), 0, 0, simObj.getObjectId(), 0, 0,
sizeof(DataDefinitionRemoteAircraft), &ddRemoteAircraft); sizeof(DataDefinitionRemoteAircraftParts), &ddRemoteAircraftParts);
if (hr != S_OK) { CLogMessage(this).warning("Failed so set parts on SimObject %1 callsign: %2") << simObj.getObjectId() << callsign; }
if (hr != S_OK) { CLogMessage(this).warning("Failed so set data on SimObject"); } } // all callsigns
} // all situations
} }
SIMCONNECT_DATA_INITPOSITION CSimulatorFsx::aircraftSituationToFsxInitPosition(const CAircraftSituation &situation) SIMCONNECT_DATA_INITPOSITION CSimulatorFsx::aircraftSituationToFsxInitPosition(const CAircraftSituation &situation)