diff --git a/source/RobotAPI/libraries/armem/server/query_proc/base/EntityQueryProcessorBase.h b/source/RobotAPI/libraries/armem/server/query_proc/base/EntityQueryProcessorBase.h index 5633dc1fe9e50290b1a655e9c1bc89b5ab1be21f..57cf3dc4ac8979bd466475745d7102dd49658a27 100644 --- a/source/RobotAPI/libraries/armem/server/query_proc/base/EntityQueryProcessorBase.h +++ b/source/RobotAPI/libraries/armem/server/query_proc/base/EntityQueryProcessorBase.h @@ -184,6 +184,61 @@ namespace armarx::armem::base::query_proc } } + inline auto lastElementBeforeOrAt(const auto& history, const auto timestamp) const + { + // first element equal or greater + typename std::map<Time, EntitySnapshotT>::const_iterator refItFwd = history.upper_bound(timestamp); + + // last element less than + typename std::map<Time, EntitySnapshotT>::const_iterator refItFwdLt = std::prev(refItFwd); + + // last element not greater than => if this is invalid, we have no suitable elements + typename std::map<Time, EntitySnapshotT>::const_reverse_iterator refIt(refItFwd); + if (refIt == history.rend()) + { + return history.end(); + } + + // now either refItFwd is a perfect match ... + if (refItFwd->first == timestamp) + { + return refItFwd; + } + + // ... or we return the element before if possible + if (refItFwdLt != history.begin()) + { + return refItFwdLt; + } + + return history.end(); + } + + inline auto lastElementBefore(const auto& history, const auto& timestamp) const + { + // first element equal or greater + typename std::map<Time, EntitySnapshotT>::const_iterator refItFwd = history.upper_bound(timestamp); + + // last element less than + typename std::map<Time, EntitySnapshotT>::const_iterator refItFwdLt = std::prev(refItFwd); + + // last element not greater than => if this is invalid, we have no suitable elements + typename std::map<Time, EntitySnapshotT>::const_reverse_iterator refIt(refItFwd); + if (refIt == history.rend()) + { + return history.end(); + } + + // we return the element before if possible + if (refItFwdLt != history.begin()) + { + return refItFwdLt; + } + + return history.end(); + } + + void process(_EntityT& result, const armem::query::data::entity::BeforeTime& query, const _EntityT& entity) const @@ -193,20 +248,14 @@ namespace armarx::armem::base::query_proc const auto maxEntries = fromIce<std::int64_t>(query.maxEntries); - // first element equal or greater - typename std::map<Time, EntitySnapshotT>::const_iterator refItFwd = entity.history().upper_bound(referenceTimestamp); - // last element not greater than - typename std::map<Time, EntitySnapshotT>::const_iterator refItFwdLt = std::prev(refItFwd); - - // last element not greater than - typename std::map<Time, EntitySnapshotT>::const_reverse_iterator refIt(refItFwd); - if (refIt == entity.history().rend()) + const auto itBefore = lastElementBefore(entity.history(), referenceTimestamp); + if (itBefore == entity.history().end()) { - ARMARX_WARNING << "No valid entities found."; + ARMARX_WARNING << "No suitable element found"; return; } - const auto nEntriesBefore = std::distance(entity.history().begin(), refItFwdLt); + const auto nEntriesBefore = std::distance(entity.history().begin(), itBefore); const int nEntries = [&]() { @@ -220,7 +269,7 @@ namespace armarx::armem::base::query_proc } (); - auto it = refItFwdLt; + auto it = itBefore; for (std::int64_t i = 0; i < nEntries; i++, --it) { addResultSnapshot(result, it); @@ -231,6 +280,7 @@ namespace armarx::armem::base::query_proc const armem::query::data::entity::TimeApprox& query, const _EntityT& entity) const { + const auto referenceTimestamp = fromIce<Time>(query.timestamp); ARMARX_CHECK(referenceTimestamp.toMicroSeconds() >= 0) << "Reference timestamp is negative!"; @@ -248,24 +298,34 @@ namespace armarx::armem::base::query_proc return std::abs(t.toMicroSeconds() - referenceTimestampMicroSeconds) <= epsDuration; }; + // first element before or at timestamp + const auto beforeOrAt = lastElementBeforeOrAt(entity.history(), referenceTimestamp); + const bool isBeforeOrAtValid = beforeOrAt != entity.history().end(); + const auto isPerfectMatch = isBeforeOrAtValid && beforeOrAt->first == referenceTimestamp; - const auto beforeOrAt = entity.history().lower_bound(referenceTimestamp); + // first element greater const auto after = entity.history().upper_bound(referenceTimestamp); + const bool isAfterValid = after != entity.history().end(); - const bool isBeforeOrAtValid = beforeOrAt != entity.history().end(); - const bool isAfterValid = isBeforeOrAtValid && after != entity.history().end(); - - // if 'before' is already invalid, there is nothing to be gained here. - if (not isBeforeOrAtValid) + // if both are invalid, there is nothing to be gained here. + if ((not isBeforeOrAtValid) and (not isAfterValid)) { const armem::Time dt = referenceTimestamp - entity.getLatestTimestamp(); ARMARX_WARNING << "Lookup " << dt.toMilliSeconds() << " ms into the future."; return; + } + // -> now one or both are valid ... + + // is 'before' a perfect match? + if(isPerfectMatch) + { + addResultSnapshot(result, beforeOrAt); + return; } - // only 'before' valid? or is 'before' perfect match? - if ((not isAfterValid) or (beforeOrAt->first == referenceTimestamp)) + // only 'before' valid? + if (not isAfterValid) { if (isInRange(beforeOrAt->first)) { @@ -273,26 +333,29 @@ namespace armarx::armem::base::query_proc } return; } + + // only 'after' valid? + if (not isBeforeOrAtValid) + { + if (isInRange(after->first)) + { + addResultSnapshot(result, after); + } + return; + } // -> now both are valid - // return both => user can interpolate + // return both (if in range) => user can interpolate if (isInRange(beforeOrAt->first)) { addResultSnapshot(result, beforeOrAt); } - else - { - ARMARX_WARNING << "No valid entity before timestamp"; - } if (isInRange(after->first)) { addResultSnapshot(result, after); } - else - { - ARMARX_WARNING << "No valid entity after timestamp"; - } + } diff --git a/source/RobotAPI/libraries/armem/test/ArMemQueryTest.cpp b/source/RobotAPI/libraries/armem/test/ArMemQueryTest.cpp index 15e23b3d942d1e6363ed7ff2d3ab63f140757ff9..4a14aa7fce29f5262f3d4eeb7080f2d6986c27f2 100644 --- a/source/RobotAPI/libraries/armem/test/ArMemQueryTest.cpp +++ b/source/RobotAPI/libraries/armem/test/ArMemQueryTest.cpp @@ -20,6 +20,7 @@ * GNU General Public License */ +#include <RobotAPI/interface/armem/query.h> #define BOOST_TEST_MODULE RobotAPI::ArmarXLibraries::armem #define ARMARX_BOOST_TEST @@ -294,7 +295,7 @@ BOOST_AUTO_TEST_CASE(test_entity_TimeRange_to_end) } -/* beforeTime */ +/* BeforeTime */ BOOST_AUTO_TEST_CASE(test_entity_BeforeTime_1) { @@ -312,8 +313,6 @@ BOOST_AUTO_TEST_CASE(test_entity_BeforeTime_1) } -/* beforeTime */ - BOOST_AUTO_TEST_CASE(test_entity_BeforeTime_2) { BOOST_REQUIRE_EQUAL(entity.size(), 5); @@ -336,6 +335,110 @@ BOOST_AUTO_TEST_CASE(test_entity_BeforeTime_2) } +/* TimeApprox */ + +BOOST_AUTO_TEST_CASE(test_entity_TimeApprox_no_limit) +{ + BOOST_REQUIRE_EQUAL(entity.size(), 5); + addResults(query::entity::TimeApprox{ 3500, -1}); + BOOST_REQUIRE_EQUAL(results.size(), 2); + + for (const auto& result : results) + { + std::vector<armem::Time> times = simox::alg::get_keys(result.history()); + BOOST_REQUIRE_EQUAL(times.size(), 2); + + std::vector<armem::Time> expected + { + armem::Time::microSeconds(3000), armem::Time::microSeconds(4000) + }; + + BOOST_CHECK_EQUAL_COLLECTIONS(times.begin(), times.end(), expected.begin(), expected.end()); + } + +} + + +BOOST_AUTO_TEST_CASE(test_entity_TimeApprox_limit_600) +{ + BOOST_REQUIRE_EQUAL(entity.size(), 5); + addResults(query::entity::TimeApprox{ 3500, 600}); + BOOST_REQUIRE_EQUAL(results.size(), 2); + + for (const auto& result : results) + { + std::vector<armem::Time> times = simox::alg::get_keys(result.history()); + BOOST_REQUIRE_EQUAL(times.size(), 2); + + std::vector<armem::Time> expected + { + armem::Time::microSeconds(3000), armem::Time::microSeconds(4000) + }; + + BOOST_CHECK_EQUAL_COLLECTIONS(times.begin(), times.end(), expected.begin(), expected.end()); + } + +} + + +BOOST_AUTO_TEST_CASE(test_entity_TimeApprox_limit_too_small) +{ + BOOST_REQUIRE_EQUAL(entity.size(), 5); + addResults(query::entity::TimeApprox{ 3500, 100}); + BOOST_REQUIRE_EQUAL(results.size(), 2); + + for (const auto& result : results) + { + std::vector<armem::Time> times = simox::alg::get_keys(result.history()); + BOOST_REQUIRE_EQUAL(times.size(), 0); + } + +} + + +BOOST_AUTO_TEST_CASE(test_entity_TimeApprox_limit_only_next) +{ + BOOST_REQUIRE_EQUAL(entity.size(), 5); + addResults(query::entity::TimeApprox{ 3700, 400}); + BOOST_REQUIRE_EQUAL(results.size(), 2); + + for (const auto& result : results) + { + std::vector<armem::Time> times = simox::alg::get_keys(result.history()); + BOOST_REQUIRE_EQUAL(times.size(), 1); + + std::vector<armem::Time> expected + { + armem::Time::microSeconds(4000) + }; + + BOOST_CHECK_EQUAL_COLLECTIONS(times.begin(), times.end(), expected.begin(), expected.end()); + } + +} + +BOOST_AUTO_TEST_CASE(test_entity_TimeApprox_limit_only_previous) +{ + BOOST_REQUIRE_EQUAL(entity.size(), 5); + addResults(query::entity::TimeApprox{ 3300, 400}); + BOOST_REQUIRE_EQUAL(results.size(), 2); + + for (const auto& result : results) + { + std::vector<armem::Time> times = simox::alg::get_keys(result.history()); + BOOST_REQUIRE_EQUAL(times.size(), 1); + + std::vector<armem::Time> expected + { + armem::Time::microSeconds(3000) + }; + + BOOST_CHECK_EQUAL_COLLECTIONS(times.begin(), times.end(), expected.begin(), expected.end()); + } + +} + + BOOST_AUTO_TEST_CASE(test_negative_index_semantics) {