diff --git a/search/integration_tests/retrieval_test.cpp b/search/integration_tests/retrieval_test.cpp index fc1361c128..479cd644fa 100644 --- a/search/integration_tests/retrieval_test.cpp +++ b/search/integration_tests/retrieval_test.cpp @@ -38,12 +38,12 @@ public: TestCallback(MwmSet::MwmId const & id) : m_id(id), m_triggered(false) {} // search::Retrieval::Callback overrides: - void OnMwmProcessed(MwmSet::MwmId const & id, vector const & offsets) override + void OnFeaturesRetrieved(MwmSet::MwmId const & id, double scale, + vector const & offsets) override { - TEST(!m_triggered, ("Callback must be triggered only once.")); TEST_EQUAL(m_id, id, ()); m_triggered = true; - m_offsets = offsets; + m_offsets.insert(m_offsets.end(), offsets.begin(), offsets.end()); } bool WasTriggered() const { return m_triggered; } @@ -63,14 +63,12 @@ public: MultiMwmCallback(vector const & ids) : m_ids(ids), m_numFeatures(0) {} // search::Retrieval::Callback overrides: - void OnMwmProcessed(MwmSet::MwmId const & id, vector const & offsets) override + void OnFeaturesRetrieved(MwmSet::MwmId const & id, double /* scale */, + vector const & offsets) override { auto const it = find(m_ids.cbegin(), m_ids.cend(), id); TEST(it != m_ids.cend(), ("Unknown mwm:", id)); - auto const rt = m_retrieved.find(id); - TEST(rt == m_retrieved.cend(), ("For", id, "callback must be triggered only once.")); - m_retrieved.insert(id); m_numFeatures += offsets.size(); } @@ -93,7 +91,7 @@ UNIT_TEST(Retrieval_Smoke) platform::LocalCountryFile file(platform.WritableDir(), platform::CountryFile("WhiskeyTown"), 0); MY_SCOPE_GUARD(deleteFile, [&]() - { + { file.DeleteFromDisk(MapOptions::Map); }); @@ -119,11 +117,14 @@ UNIT_TEST(Retrieval_Smoke) search::Retrieval retrieval; + vector> infos; + index.GetMwmsInfo(infos); + // Retrieve all (100) whiskey bars from the mwm. { TestCallback callback(handle.GetId()); - retrieval.Init(index, m2::RectD(m2::PointD(0, 0), m2::PointD(1, 1)), params, + retrieval.Init(index, infos, m2::RectD(m2::PointD(0, 0), m2::PointD(1, 1)), params, search::Retrieval::Limits()); retrieval.Go(callback); TEST(callback.WasTriggered(), ()); @@ -138,9 +139,9 @@ UNIT_TEST(Retrieval_Smoke) { TestCallback callback(handle.GetId()); search::Retrieval::Limits limits; - limits.SetMaxViewportScale(5.0); + limits.SetMaxViewportScale(9.0); - retrieval.Init(index, m2::RectD(m2::PointD(0, 0), m2::PointD(1, 1)), params, limits); + retrieval.Init(index, infos, m2::RectD(m2::PointD(0, 0), m2::PointD(1, 1)), params, limits); retrieval.Go(callback); TEST(callback.WasTriggered(), ()); TEST_EQUAL(36 /* number of whiskey bars in a 5 x 5 square (border is counted) */, @@ -153,7 +154,8 @@ UNIT_TEST(Retrieval_Smoke) search::Retrieval::Limits limits; limits.SetMaxNumFeatures(8); - retrieval.Init(index, m2::RectD(m2::PointD(4.9, 4.9), m2::PointD(5.1, 5.1)), params, limits); + retrieval.Init(index, infos, m2::RectD(m2::PointD(4.9, 4.9), m2::PointD(5.1, 5.1)), params, + limits); retrieval.Go(callback); TEST(callback.WasTriggered(), ()); TEST_EQUAL(callback.Offsets().size(), 8, ()); @@ -169,7 +171,7 @@ UNIT_TEST(Retrieval_3Mwms) platform::LocalCountryFile mtv(platform.WritableDir(), platform::CountryFile("mtv"), 0); platform::LocalCountryFile zrh(platform.WritableDir(), platform::CountryFile("zrh"), 0); MY_SCOPE_GUARD(deleteFiles, [&]() - { + { msk.DeleteFromDisk(MapOptions::Map); mtv.DeleteFromDisk(MapOptions::Map); zrh.DeleteFromDisk(MapOptions::Map); @@ -205,6 +207,9 @@ UNIT_TEST(Retrieval_3Mwms) search::SearchQueryParams params; InitParams("mtv", params); + vector> infos; + index.GetMwmsInfo(infos); + search::Retrieval retrieval; { @@ -212,7 +217,8 @@ UNIT_TEST(Retrieval_3Mwms) search::Retrieval::Limits limits; limits.SetMaxNumFeatures(1); - retrieval.Init(index, m2::RectD(m2::PointD(-1.0, -1.0), m2::PointD(1.0, 1.0)), params, limits); + retrieval.Init(index, infos, m2::RectD(m2::PointD(-1.0, -1.0), m2::PointD(1.0, 1.0)), params, + limits); retrieval.Go(callback); TEST(callback.WasTriggered(), ()); TEST_EQUAL(callback.Offsets().size(), 1, ()); @@ -223,7 +229,8 @@ UNIT_TEST(Retrieval_3Mwms) search::Retrieval::Limits limits; limits.SetMaxNumFeatures(10 /* more than total number of features in all these mwms */); - retrieval.Init(index, m2::RectD(m2::PointD(-1.0, -1.0), m2::PointD(1.0, 1.0)), params, limits); + retrieval.Init(index, infos, m2::RectD(m2::PointD(-1.0, -1.0), m2::PointD(1.0, 1.0)), params, + limits); retrieval.Go(callback); TEST_EQUAL(3 /* total number of mwms */, callback.GetNumMwms(), ()); TEST_EQUAL(3 /* total number of features in all these mwms */, callback.GetNumFeatures(), ()); @@ -233,7 +240,8 @@ UNIT_TEST(Retrieval_3Mwms) MultiMwmCallback callback({mskHandle.GetId(), mtvHandle.GetId(), zrhHandle.GetId()}); search::Retrieval::Limits limits; - retrieval.Init(index, m2::RectD(m2::PointD(-1.0, -1.0), m2::PointD(1.0, 1.0)), params, limits); + retrieval.Init(index, infos, m2::RectD(m2::PointD(-1.0, -1.0), m2::PointD(1.0, 1.0)), params, + limits); retrieval.Go(callback); TEST_EQUAL(3, callback.GetNumMwms(), ()); TEST_EQUAL(3, callback.GetNumFeatures(), ()); diff --git a/search/retrieval.cpp b/search/retrieval.cpp index 37aee8a6b6..fa8bfcacb5 100644 --- a/search/retrieval.cpp +++ b/search/retrieval.cpp @@ -2,6 +2,7 @@ #include "search/feature_offset_match.hpp" +#include "indexer/feature.hpp" #include "indexer/index.hpp" #include "indexer/search_trie.hpp" @@ -16,11 +17,17 @@ namespace search { namespace { +// Upper bound on a number of features when fast path is used. +// Otherwise, slow path is used. +uint64_t constexpr kFastPathThreshold = 100; + struct EmptyFilter { inline bool operator()(uint32_t /* featureId */) const { return true; } }; +// Retrieves from the search index corresponding to |handle| all +// features matching to |params|. void RetrieveAddressFeatures(MwmSet::MwmHandle const & handle, SearchQueryParams const & params, vector & featureIds) { @@ -32,7 +39,6 @@ void RetrieveAddressFeatures(MwmSet::MwmHandle const & handle, SearchQueryParams trie::ReadTrie(SubReaderWrapper(searchReader.GetPtr()), trie::ValueReader(codingParams), trie::TEdgeValueReader())); - featureIds.clear(); auto collector = [&](trie::ValueReader::ValueType const & value) { featureIds.push_back(value.m_featureId); @@ -40,13 +46,19 @@ void RetrieveAddressFeatures(MwmSet::MwmHandle const & handle, SearchQueryParams MatchFeaturesInTrie(params, *trieRoot, EmptyFilter(), collector); } -void RetrieveGeometryFeatures(MwmSet::MwmHandle const & handle, m2::RectD const & viewport, - SearchQueryParams const & params, vector & featureIds) +// Retrieves from the geomery index corresponding to handle all +// features in (and, possibly, around) viewport and executes |toDo| on +// them. +template +void RetrieveGeometryFeatures(MwmSet::MwmHandle const & handle, m2::RectD viewport, + SearchQueryParams const & params, ToDo && toDo) { auto * value = handle.GetValue(); ASSERT(value, ()); feature::DataHeader const & header = value->GetHeader(); - ASSERT(viewport.IsIntersect(header.GetBounds()), ()); + + if (!viewport.Intersect(header.GetBounds())) + return; auto const scaleRange = header.GetScaleRange(); int const scale = min(max(params.m_scale, scaleRange.first), scaleRange.second); @@ -55,20 +67,151 @@ void RetrieveGeometryFeatures(MwmSet::MwmHandle const & handle, m2::RectD const covering::IntervalsT const & intervals = covering.Get(scale); ScaleIndex index(value->m_cont.GetReader(INDEX_FILE_TAG), value->m_factory); - featureIds.clear(); - auto collector = MakeBackInsertFunctor(featureIds); for (auto const & interval : intervals) - { - index.ForEachInIntervalAndScale(collector, interval.first, interval.second, scale); - } + index.ForEachInIntervalAndScale(toDo, interval.first, interval.second, scale); } + +// This class represents a fast retrieval strategy. When number of +// matching features in an mwm is small, it is worth to compute their +// centers explicitly, by loading geometry from mwm. +class FastPathStrategy : public Retrieval::Strategy +{ +public: + FastPathStrategy(Index const & index, MwmSet::MwmHandle & handle, m2::RectD const & viewport, + vector const & addressFeatures) + : m_handle(handle), m_viewport(viewport), m_lastReported(0) + { + m2::PointD const center = m_viewport.Center(); + + Index::FeaturesLoaderGuard loader(index, m_handle.GetId()); + for (auto const & featureId : addressFeatures) + { + FeatureType feature; + loader.GetFeatureByIndex(featureId, feature); + m_features.emplace_back(featureId, feature.GetCenter()); + } + sort(m_features.begin(), m_features.end(), + [¢er](pair const & lhs, pair const & rhs) + { + return lhs.second.SquareLength(center) < rhs.second.SquareLength(center); + }); + } + + // Retrieval::Strategy overrides: + bool Retrieve(double scale, my::Cancellable const & /* cancellable */, + TCallback const & callback) override + { + m2::RectD viewport = m_viewport; + viewport.Scale(scale); + + vector features; + + ASSERT_LESS_OR_EQUAL(m_lastReported, m_features.size(), ()); + while (m_lastReported < m_features.size() && + viewport.IsPointInside(m_features[m_lastReported].second)) + { + features.push_back(m_features[m_lastReported].first); + ++m_lastReported; + } + + callback(features); + + return true; + } + +private: + MwmSet::MwmHandle & m_handle; + + vector> m_features; + m2::RectD m_viewport; + size_t m_lastReported; +}; + +// This class represents a slow retrieval strategy. It starts with +// initial viewport and iteratively scales it until whole mwm is +// covered by a scaled viewport. On each scale it retrieves features +// for a scaled viewport from a geometry index and then intersects +// them with features retrieved from search index. +class SlowPathStrategy : public Retrieval::Strategy +{ +public: + SlowPathStrategy(MwmSet::MwmHandle & handle, SearchQueryParams const & params, + m2::RectD const & viewport, vector const & addressFeatures) + : m_handle(handle), m_params(params), m_viewport(viewport), m_prevScale(-1.0) + { + if (addressFeatures.empty()) + return; + + m_nonReported.resize(*max_element(addressFeatures.begin(), addressFeatures.end()) + 1); + for (auto const & featureId : addressFeatures) + m_nonReported[featureId] = true; + } + + // Retrieval::Strategy overrides: + bool Retrieve(double scale, my::Cancellable const & cancellable, + TCallback const & callback) override + { +#define LONG_OP(op) \ + { \ + if (cancellable.IsCancelled()) \ + return false; \ + op; \ + } + m2::RectD currViewport = m_viewport; + currViewport.Scale(scale); + + vector geometryFeatures; + auto collector = [&](uint32_t feature) + { + if (feature < m_nonReported.size() && m_nonReported[feature]) + { + geometryFeatures.push_back(feature); + m_nonReported[feature] = false; + } + }; + + if (m_prevScale < 0) + { + LONG_OP(RetrieveGeometryFeatures(m_handle, currViewport, m_params, collector)); + } + else + { + m2::RectD prevViewport = m_viewport; + prevViewport.Scale(m_prevScale); + + m2::RectD a(currViewport.LeftTop(), prevViewport.RightTop()); + m2::RectD c(currViewport.RightBottom(), prevViewport.LeftBottom()); + m2::RectD b(a.RightTop(), c.RightTop()); + m2::RectD d(a.LeftBottom(), c.LeftBottom()); + + LONG_OP(RetrieveGeometryFeatures(m_handle, a, m_params, collector)); + LONG_OP(RetrieveGeometryFeatures(m_handle, b, m_params, collector)); + LONG_OP(RetrieveGeometryFeatures(m_handle, c, m_params, collector)); + LONG_OP(RetrieveGeometryFeatures(m_handle, d, m_params, collector)); + } + + m_prevScale = scale; + callback(geometryFeatures); + return true; + } + +private: + MwmSet::MwmHandle & m_handle; + SearchQueryParams const & m_params; + m2::RectD m_viewport; + double m_prevScale; + + vector m_nonReported; +}; } // namespace +// Retrieval::Limits ------------------------------------------------------------------------------- Retrieval::Limits::Limits() - : m_maxNumFeatures(0), - m_maxViewportScale(0.0), - m_maxNumFeaturesSet(false), - m_maxViewportScaleSet(false) + : m_maxNumFeatures(0) + , m_maxViewportScale(0.0) + , m_maxNumFeaturesSet(false) + , m_maxViewportScaleSet(false) + , m_searchInWorld(false) { } @@ -96,11 +239,12 @@ double Retrieval::Limits::GetMaxViewportScale() const return m_maxViewportScale; } +// Retrieval::Bucket ------------------------------------------------------------------------------- Retrieval::Bucket::Bucket(MwmSet::MwmHandle && handle) - : m_handle(move(handle)), - m_intersectsWithViewport(false), - m_coveredByViewport(false), - m_finished(false) + : m_handle(move(handle)) + , m_featuresReported(0) + , m_intersectsWithViewport(false) + , m_finished(false) { auto * value = m_handle.GetValue(); ASSERT(value, ()); @@ -108,9 +252,11 @@ Retrieval::Bucket::Bucket(MwmSet::MwmHandle && handle) m_bounds = header.GetBounds(); } +// Retrieval --------------------------------------------------------------------------------------- Retrieval::Retrieval() : m_index(nullptr), m_featuresReported(0) {} -void Retrieval::Init(Index & index, m2::RectD const & viewport, SearchQueryParams const & params, +void Retrieval::Init(Index & index, vector> const & infos, + m2::RectD const & viewport, SearchQueryParams const & params, Limits const & limits) { m_index = &index; @@ -119,9 +265,6 @@ void Retrieval::Init(Index & index, m2::RectD const & viewport, SearchQueryParam m_limits = limits; m_featuresReported = 0; - vector> infos; - index.GetMwmsInfo(infos); - m_buckets.clear(); for (auto const & info : infos) { @@ -129,11 +272,15 @@ void Retrieval::Init(Index & index, m2::RectD const & viewport, SearchQueryParam if (!handle.IsAlive()) continue; auto * value = handle.GetValue(); - if (value && value->m_cont.IsExist(SEARCH_INDEX_FILE_TAG) && - value->m_cont.IsExist(INDEX_FILE_TAG)) + if (!value || !value->m_cont.IsExist(SEARCH_INDEX_FILE_TAG) || + !value->m_cont.IsExist(SEARCH_INDEX_FILE_TAG)) { - m_buckets.emplace_back(move(handle)); + continue; } + bool const isWorld = value->GetHeader().GetType() == feature::DataHeader::world; + if (isWorld && !m_limits.GetSearchInWorld()) + continue; + m_buckets.emplace_back(move(handle)); } } @@ -141,97 +288,110 @@ void Retrieval::Go(Callback & callback) { static double const kViewportScaleMul = sqrt(2.0); - for (double viewportScale = 1.0;; viewportScale *= kViewportScaleMul) + double currScale = 1.0; + while (true) { - double scale = viewportScale; - if (m_limits.IsMaxViewportScaleSet() && scale >= m_limits.GetMaxViewportScale()) - scale = m_limits.GetMaxViewportScale(); - - m2::RectD viewport = m_viewport; - viewport.Scale(scale); - RetrieveForViewport(viewport, callback); - - if (ViewportCoversAllMwms()) + if (IsCancelled()) break; - if (m_limits.IsMaxViewportScaleSet() && scale >= m_limits.GetMaxViewportScale()) + + double reducedScale = currScale; + if (m_limits.IsMaxViewportScaleSet() && reducedScale >= m_limits.GetMaxViewportScale()) + reducedScale = m_limits.GetMaxViewportScale(); + + if (!RetrieveForScale(reducedScale, callback)) + break; + + if (Finished()) + break; + if (m_limits.IsMaxViewportScaleSet() && reducedScale >= m_limits.GetMaxViewportScale()) break; if (m_limits.IsMaxNumFeaturesSet() && m_featuresReported >= m_limits.GetMaxNumFeatures()) break; - } - for (auto & bucket : m_buckets) - { - if (bucket.m_finished) - continue; - // The bucket is not covered by viewport, thus all matching - // features were not reported. - bucket.m_finished = true; - ReportFeatures(bucket, callback); + currScale *= kViewportScaleMul; } } -void Retrieval::RetrieveForViewport(m2::RectD const & viewport, Callback & callback) +bool Retrieval::RetrieveForScale(double scale, Callback & callback) { + m2::RectD viewport = m_viewport; + viewport.Scale(scale); + for (auto & bucket : m_buckets) { - if (bucket.m_coveredByViewport || bucket.m_finished || !viewport.IsIntersect(bucket.m_bounds)) + if (IsCancelled()) + return false; + + if (bucket.m_finished || !viewport.IsIntersect(bucket.m_bounds)) continue; if (!bucket.m_intersectsWithViewport) { // This is the first time viewport intersects with mwm. Retrieve - // all matching features from search index. + // all matching features from the search index. + ASSERT(!bucket.m_strategy, ()); RetrieveAddressFeatures(bucket.m_handle, m_params, bucket.m_addressFeatures); - sort(bucket.m_addressFeatures.begin(), bucket.m_addressFeatures.end()); + if (IsCancelled()) + return false; + if (bucket.m_addressFeatures.size() < kFastPathThreshold) + { + bucket.m_strategy.reset( + new FastPathStrategy(*m_index, bucket.m_handle, m_viewport, bucket.m_addressFeatures)); + } + else + { + bucket.m_strategy.reset( + new SlowPathStrategy(bucket.m_handle, m_params, m_viewport, bucket.m_addressFeatures)); + } + bucket.m_intersectsWithViewport = true; } - // Mwm is still not covered by expanding viewport. - if (!bucket.m_coveredByViewport) + ASSERT_LESS_OR_EQUAL(bucket.m_featuresReported, bucket.m_addressFeatures.size(), ()); + if (bucket.m_featuresReported == bucket.m_addressFeatures.size()) { - RetrieveGeometryFeatures(bucket.m_handle, viewport, m_params, bucket.m_geometryFeatures); - sort(bucket.m_geometryFeatures.begin(), bucket.m_geometryFeatures.end()); - - bucket.m_intersection.clear(); - set_intersection(bucket.m_addressFeatures.begin(), bucket.m_addressFeatures.end(), - bucket.m_geometryFeatures.begin(), bucket.m_geometryFeatures.end(), - back_inserter(bucket.m_intersection)); - } - - if (!bucket.m_coveredByViewport && viewport.IsRectInside(bucket.m_bounds)) - { - // Next time we will skip the bucket, so it's better to report - // all its features now. - bucket.m_coveredByViewport = true; + ASSERT(bucket.m_intersectsWithViewport, ()); + // All features were reported for the bucket. bucket.m_finished = true; - ReportFeatures(bucket, callback); + continue; } + + Strategy::TCallback wrapper = [&](vector & features) + { + ReportFeatures(bucket, features, scale, callback); + }; + if (!bucket.m_strategy->Retrieve(scale, *this /* cancellable */, wrapper)) + return false; } + + return true; } -bool Retrieval::ViewportCoversAllMwms() const +bool Retrieval::Finished() const { for (auto const & bucket : m_buckets) { - if (!bucket.m_coveredByViewport) + if (!bucket.m_finished) return false; } return true; } -void Retrieval::ReportFeatures(Bucket & bucket, Callback & callback) +void Retrieval::ReportFeatures(Bucket & bucket, vector & featureIds, double scale, + Callback & callback) { ASSERT(!m_limits.IsMaxNumFeaturesSet() || m_featuresReported <= m_limits.GetMaxNumFeatures(), ()); if (m_limits.IsMaxNumFeaturesSet()) { uint64_t const delta = m_limits.GetMaxNumFeatures() - m_featuresReported; - if (bucket.m_intersection.size() > delta) - bucket.m_intersection.resize(delta); + if (featureIds.size() > delta) + featureIds.resize(delta); } - if (!bucket.m_intersection.empty()) + if (!featureIds.empty()) { - callback.OnMwmProcessed(bucket.m_handle.GetId(), bucket.m_intersection); - m_featuresReported += bucket.m_intersection.size(); + callback.OnFeaturesRetrieved(bucket.m_handle.GetId(), scale, featureIds); + bucket.m_featuresReported += featureIds.size(); + m_featuresReported += featureIds.size(); } } } // namespace search diff --git a/search/retrieval.hpp b/search/retrieval.hpp index 3750e5adba..aacfe096c3 100644 --- a/search/retrieval.hpp +++ b/search/retrieval.hpp @@ -6,14 +6,18 @@ #include "geometry/rect2d.hpp" +#include "base/cancellable.hpp" +#include "base/macros.hpp" + #include "std/function.hpp" +#include "std/unique_ptr.hpp" #include "std/vector.hpp" class Index; namespace search { -class Retrieval +class Retrieval : public my::Cancellable { public: class Callback @@ -21,10 +25,15 @@ public: public: virtual ~Callback() = default; - virtual void OnMwmProcessed(MwmSet::MwmId const & id, vector const & featureIds) = 0; + // Called each time a bunch of features for an mwm is retrieved. + // This method may be called several times for the same mwm, + // reporting disjoint sets of features. + virtual void OnFeaturesRetrieved(MwmSet::MwmId const & id, double scale, + vector const & featureIds) = 0; }; - // Following class represents a set of retrieval's limits. + // This class wraps a set of retrieval's limits like number of + // features to be retrieved, maximum viewport scale, etc. struct Limits { public: @@ -34,51 +43,85 @@ public: // retrieved. void SetMaxNumFeatures(uint64_t minNumFeatures); uint64_t GetMaxNumFeatures() const; + inline bool IsMaxNumFeaturesSet() const { return m_maxNumFeaturesSet; } // Sets upper bound on a maximum viewport's scale. void SetMaxViewportScale(double maxViewportScale); double GetMaxViewportScale() const; - - inline bool IsMaxNumFeaturesSet() const { return m_maxNumFeaturesSet; } inline bool IsMaxViewportScaleSet() const { return m_maxViewportScaleSet; } + // Sets whether retrieval should/should not skip World.mwm. + inline void SetSearchInWorld(bool searchInWorld) { m_searchInWorld = searchInWorld; } + inline bool GetSearchInWorld() const { return m_searchInWorld; } + private: uint64_t m_maxNumFeatures; double m_maxViewportScale; bool m_maxNumFeaturesSet : 1; bool m_maxViewportScaleSet : 1; + bool m_searchInWorld : 1; + }; + + // This class represents a retrieval's strategy. + class Strategy + { + public: + using TCallback = function &)>; + + virtual ~Strategy() = default; + + WARN_UNUSED_RESULT virtual bool Retrieve(double scale, my::Cancellable const & cancellable, + TCallback const & callback) = 0; }; Retrieval(); - void Init(Index & index, m2::RectD const & viewport, SearchQueryParams const & params, - Limits const & limits); + void Init(Index & index, vector> const & infos, m2::RectD const & viewport, + SearchQueryParams const & params, Limits const & limits); + // Start retrieval process. + // + // *NOTE* Retrieval may report features not belonging to viewport + // (even scaled by maximum allowed scale). The reason is the current + // geomerty index algorithm - when it asked for features in a + // rectangle, it reports all features from cells that cover (not + // covered by) a rectangle. void Go(Callback & callback); private: + // This class is a wrapper around single mwm during retrieval + // process. struct Bucket { Bucket(MwmSet::MwmHandle && handle); MwmSet::MwmHandle m_handle; - vector m_addressFeatures; - vector m_geometryFeatures; - vector m_intersection; m2::RectD m_bounds; + vector m_addressFeatures; + + // The order matters here - strategy may contain references to the + // fields above, thus it must be destructed before them. + unique_ptr m_strategy; + + size_t m_featuresReported; bool m_intersectsWithViewport : 1; - bool m_coveredByViewport : 1; bool m_finished : 1; }; - // *NOTE* arguments of successive calls of this method should be - // *non-decreasing. - void RetrieveForViewport(m2::RectD const & viewport, Callback & callback); + // Retrieves features for the viewport scaled by |scale| and + // invokes callback on retrieved features. + // + // *NOTE* |scale| of successive calls of this method should be + // non-decreasing. + WARN_UNUSED_RESULT bool RetrieveForScale(double scale, Callback & callback); - bool ViewportCoversAllMwms() const; + // Returns true when all buckets are marked as finished. + bool Finished() const; - void ReportFeatures(Bucket & bucket, Callback & callback); + // Reports features, updates bucket's stats. + void ReportFeatures(Bucket & bucket, vector & featureIds, double scale, + Callback & callback); Index * m_index; m2::RectD m_viewport;