diff --git a/src/blackcore/airspacemonitor.cpp b/src/blackcore/airspacemonitor.cpp index 192e1c9bd..e1eb0e9df 100644 --- a/src/blackcore/airspacemonitor.cpp +++ b/src/blackcore/airspacemonitor.cpp @@ -1288,7 +1288,7 @@ namespace BlackCore // list from new to old QWriteLocker lock(&m_lockSituations); CAircraftSituationList &situationList = m_situationsByCallsign[callsign]; - situationList.push_frontMaxElements(situation, IRemoteAircraftProvider::MaxSituationsPerCallsign); + situationList.push_frontKeepLatestFirstAdjustOffset(situation, IRemoteAircraftProvider::MaxSituationsPerCallsign); // check sort order Q_ASSERT_X(situationList.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "wrong sort order"); @@ -1303,7 +1303,7 @@ namespace BlackCore // list sorted from new to old QWriteLocker lock(&m_lockParts); CAircraftPartsList &partsList = m_partsByCallsign[callsign]; - partsList.push_frontKeepLatestAdjustedFirst(parts, IRemoteAircraftProvider::MaxPartsPerCallsign); + partsList.push_frontKeepLatestFirstAdjustOffset(parts, IRemoteAircraftProvider::MaxPartsPerCallsign); // remove outdated parts (but never remove the most recent one) IRemoteAircraftProvider::removeOutdatedParts(partsList); diff --git a/src/blackmisc/simulation/interpolator.cpp b/src/blackmisc/simulation/interpolator.cpp index f7d00372c..9b7b44ce8 100644 --- a/src/blackmisc/simulation/interpolator.cpp +++ b/src/blackmisc/simulation/interpolator.cpp @@ -238,11 +238,7 @@ namespace BlackMisc return emptyParts; } - // with the latest updates of T243 the order and the offsets are supposed to be correct - // so even mixing fast/slow updates shall work - Q_ASSERT_X(m_aircraftParts.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "Wrong sort order"); - - // Ref T243, KB 2018-02, can be removed in future, we verify parts above + // Ref T243, KB 2018-02, can be removed in future, we verify parts when we add parts // Parts are supposed to be in correct order // const auto end = std::is_sorted_until(m_aircraftParts.begin(), m_aircraftParts.end(), [](auto && a, auto && b) { return b.getAdjustedMSecsSinceEpoch() < a.getAdjustedMSecsSinceEpoch(); }); // const auto validParts = makeRange(m_aircraftParts.begin(), end); @@ -328,36 +324,54 @@ namespace BlackMisc } // we add new situations at front and keep the latest values (real time) first - m_aircraftSituations.push_frontKeepLatestFirst(situation, IRemoteAircraftProvider::MaxSituationsPerCallsign); + m_aircraftSituations.push_frontKeepLatestFirstAdjustOffset(situation, IRemoteAircraftProvider::MaxSituationsPerCallsign); + + + // with the latest updates of T243 the order and the offsets are supposed to be correct + // so even mixing fast/slow updates shall work + Q_ASSERT_X(!m_aircraftSituations.containsZeroOrNegativeOffsetTime(), Q_FUNC_INFO, "Missing offset time"); + Q_ASSERT_X(m_aircraftSituations.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "Wrong sort order"); } template void CInterpolator::addAircraftParts(const CAircraftParts &parts) { const bool hasOffset = parts.hasOffsetTime(); - const qint64 offset = - hasOffset ? - parts.getTimeOffsetMs() : - m_aircraftSituations.isEmpty() ? IRemoteAircraftProvider::DefaultOffsetTimeMs : m_aircraftSituations.front().getTimeOffsetMs(); + if (!hasOffset) + { + const qint64 offset = + hasOffset ? + parts.getTimeOffsetMs() : + m_aircraftSituations.isEmpty() ? IRemoteAircraftProvider::DefaultOffsetTimeMs : m_aircraftSituations.front().getTimeOffsetMs(); + CAircraftParts copy(parts); + copy.setTimeOffsetMs(offset); + CInterpolator::addAircraftParts(copy); + return; + } + // here we have an offset + Q_ASSERT_X(parts.hasOffsetTime(), Q_FUNC_INFO, "Missing parts offset"); if (m_aircraftParts.isEmpty()) { // make sure we have enough parts to do start interpolating immediately without waiting for more updates // the offsets here (addMSecs) do not really matter CAircraftParts copy(parts); - copy.setTimeOffsetMs(offset); - copy.addMsecs(-2 * offset); + copy.addMsecs(-2 * parts.getTimeOffsetMs()); m_aircraftParts.push_frontKeepLatestFirst(copy); - copy.addMsecs(offset); + copy.addMsecs(parts.getTimeOffsetMs()); m_aircraftParts.push_frontKeepLatestFirst(copy); } // we add new situations at front and keep the latest values (real time) first - m_aircraftParts.push_frontKeepLatestFirst(parts, IRemoteAircraftProvider::MaxSituationsPerCallsign); - if (!hasOffset) { m_aircraftParts.front().setTimeOffsetMs(offset); } + m_aircraftParts.push_frontKeepLatestFirstAdjustOffset(parts, IRemoteAircraftProvider::MaxSituationsPerCallsign); // force remote provider to cleanup IRemoteAircraftProvider::removeOutdatedParts(m_aircraftParts); + + // with the latest updates of T243 the order and the offsets are supposed to be correct + // so even mixing fast/slow updates shall work + Q_ASSERT_X(!m_aircraftParts.containsZeroOrNegativeOffsetTime(), Q_FUNC_INFO, "Missing offset time"); + Q_ASSERT_X(m_aircraftParts.isSortedAdjustedLatestFirst(), Q_FUNC_INFO, "Wrong sort order"); } template diff --git a/src/blackmisc/timestampobjectlist.cpp b/src/blackmisc/timestampobjectlist.cpp index a9ef5db3d..2af700c96 100644 --- a/src/blackmisc/timestampobjectlist.cpp +++ b/src/blackmisc/timestampobjectlist.cpp @@ -232,17 +232,18 @@ namespace BlackMisc { c.truncate(maxElements - 1); } - if (!c.isEmpty() && value.isOlderThan(c.front())) + const bool needSort = !c.isEmpty() && value.isOlderThan(c.front()); + c.push_front(value); + if (needSort) { ITimestampObjectList::sortLatestFirst(); } - c.push_front(value); } template bool ITimestampObjectList::isSortedLatestLast() const { - if (this->container().isEmpty()) { return true; } + if (this->container().size() < 2) { return true; } qint64 max = -1; for (const ITimestampBased &obj : this->container()) { @@ -257,7 +258,7 @@ namespace BlackMisc template bool ITimestampObjectList::isSortedLatestFirst() const { - if (this->container().isEmpty()) { return true; } + if (this->container().size() < 2) { return true; } qint64 min = std::numeric_limits ::max(); for (const ITimestampBased &obj : this->container()) { @@ -282,6 +283,16 @@ namespace BlackMisc this->container().sort(Predicates::MemberLess(&OBJ::getAdjustedMSecsSinceEpoch)); } + template + bool ITimestampWithOffsetObjectList::containsZeroOrNegativeOffsetTime() const + { + for (const ITimestampWithOffsetBased &obj : this->container()) + { + if (obj.getTimeOffsetMs() <= 0) { return true; } + } + return false; + } + template ITimestampWithOffsetObjectList::ITimestampWithOffsetObjectList() : ITimestampObjectList() { @@ -297,17 +308,41 @@ namespace BlackMisc { c.truncate(maxElements - 1); } - if (!c.isEmpty() && value.isOlderThanAdjusted(c.front())) + const bool needSort = !c.isEmpty() && value.isOlderThanAdjusted(c.front()); + c.push_front(value); + if (needSort) { ITimestampWithOffsetObjectList::sortAdjustedLatestFirst(); } - c.push_front(value); + } + + template + void ITimestampWithOffsetObjectList::push_frontKeepLatestFirstAdjustOffset(const OBJ &value, int maxElements) + { + ITimestampObjectList::push_frontKeepLatestFirst(value, maxElements); + + // now sorted by timestamp + // this reflects normally the incoming order + CONTAINER &c = this->container(); + if (c.size() < 2) { return; } + const ITimestampWithOffsetBased &front = c.front(); + ITimestampWithOffsetBased &second = c[1]; + if (!front.isOlderThanAdjusted(second)) { return; } + const qint64 maxOs = front.getAdjustedMSecsSinceEpoch() - second.getMSecsSinceEpoch(); + const qint64 avgOs = (maxOs + qMax(front.getTimeOffsetMs(), maxOs)) / 2; + second.setTimeOffsetMs(avgOs); + + // ts + // 8: os 2 adj 10 + // 6: os 2 adj 8 + // 5: os 5 adj 10 => max os 3 + // 0: os 5 adj 5 } template bool ITimestampWithOffsetObjectList::isSortedAdjustedLatestLast() const { - if (this->container().isEmpty()) { return true; } + if (this->container().size() < 2) { return true; } qint64 max = -1; for (const ITimestampWithOffsetBased &obj : this->container()) { @@ -321,14 +356,13 @@ namespace BlackMisc template bool ITimestampWithOffsetObjectList::isSortedAdjustedLatestFirst() const { - if (this->container().isEmpty()) { return true; } + if (this->container().size() < 2) { return true; } qint64 min = std::numeric_limits ::max(); for (const ITimestampWithOffsetBased &obj : this->container()) { if (!obj.hasValidTimestamp()) { return false; } if (obj.getAdjustedMSecsSinceEpoch() > min) { return false; } min = obj.getAdjustedMSecsSinceEpoch(); - continue; } return true; } diff --git a/src/blackmisc/timestampobjectlist.h b/src/blackmisc/timestampobjectlist.h index 6bc44f6e7..abfe95319 100644 --- a/src/blackmisc/timestampobjectlist.h +++ b/src/blackmisc/timestampobjectlist.h @@ -125,9 +125,16 @@ namespace BlackMisc //! Sort by adjusted timestamp void sortAdjustedOldestFirst(); + //! Any negative or zero offset time? + bool containsZeroOrNegativeOffsetTime() const; + //! Insert as first element by keeping maxElements and the latest first void push_frontKeepLatestAdjustedFirst(const OBJ &value, int maxElements = -1); + //! Insert as first element by keeping maxElements and the latest first + //! \remark adjust offset so adjusted values are sorted + void push_frontKeepLatestFirstAdjustOffset(const OBJ &value, int maxElements = -1); + //! Is completely sorted: latest last //! \remark all object must have a valid timestamp bool isSortedAdjustedLatestLast() const;