diff --git a/search/search_quality/assessment_tool/CMakeLists.txt b/search/search_quality/assessment_tool/CMakeLists.txt index 914ee7be56..700f038d73 100644 --- a/search/search_quality/assessment_tool/CMakeLists.txt +++ b/search/search_quality/assessment_tool/CMakeLists.txt @@ -30,6 +30,8 @@ set( sample_view.hpp samples_view.cpp samples_view.hpp + search_request_runner.cpp + search_request_runner.hpp view.hpp ) diff --git a/search/search_quality/assessment_tool/context.hpp b/search/search_quality/assessment_tool/context.hpp index 26cd3c03d2..35d111771d 100644 --- a/search/search_quality/assessment_tool/context.hpp +++ b/search/search_quality/assessment_tool/context.hpp @@ -19,6 +19,13 @@ class FeatureLoader; struct Context { + enum class SearchState + { + Untouched, + InQueue, + Completed + }; + Context(Edits::OnUpdate onFoundResultsUpdate, Edits::OnUpdate onNonFoundResultsUpdate) : m_foundResultsEdits(onFoundResultsUpdate), m_nonFoundResultsEdits(onNonFoundResultsUpdate) { @@ -60,6 +67,8 @@ struct Context std::vector m_nonFoundResults; Edits m_nonFoundResultsEdits; + SearchState m_searchState = SearchState::Untouched; + bool m_initialized = false; }; @@ -81,6 +90,11 @@ public: bool IsChanged(size_t index) const { return (*m_contexts)[index].HasChanges(); } + Context::SearchState GetSearchState(size_t index) const + { + return (*m_contexts)[index].m_searchState; + } + size_t Size() const { return m_contexts->Size(); } private: diff --git a/search/search_quality/assessment_tool/main_model.cpp b/search/search_quality/assessment_tool/main_model.cpp index 77163047e0..1b2587b263 100644 --- a/search/search_quality/assessment_tool/main_model.cpp +++ b/search/search_quality/assessment_tool/main_model.cpp @@ -37,6 +37,12 @@ MainModel::MainModel(Framework & framework) [this](size_t sampleIndex, Edits::Update const & update) { OnUpdate(View::ResultType::NonFound, sampleIndex, update); }) + , m_runner(m_framework, m_dataSource, m_contexts, + [this](search::Results const & results) { UpdateViewOnResults(results); }, + [this](size_t index) { + // The second parameter does not matter, we only change SearchStatus. + m_view->OnSampleChanged(index, false /* hasEdits */); + }) { } @@ -63,7 +69,8 @@ void MainModel::Open(string const & path) return; } - ResetSearch(); + m_runner.ResetForegroundSearch(); + m_runner.ResetBackgroundSearch(); m_view->Clear(); @@ -108,6 +115,11 @@ void MainModel::SaveAs(string const & path) m_path = path; } +void MainModel::InitiateBackgroundSearch(size_t from, size_t to) +{ + m_runner.InitiateBackgroundSearch(from, to); +} + void MainModel::OnSampleSelected(int index) { CHECK(m_threadChecker.CalledOnOriginalThread(), ()); @@ -120,55 +132,19 @@ void MainModel::OnSampleSelected(int index) auto & context = m_contexts[index]; auto const & sample = context.m_sample; + m_view->ShowSample(index, sample, sample.m_pos, context.HasChanges()); - ResetSearch(); - auto const timestamp = m_queryTimestamp; + m_runner.ResetForegroundSearch(); m_numShownResults = 0; if (context.m_initialized) { - OnResults(timestamp, index, context.m_foundResults, context.m_foundResultsEdits.GetRelevances(), - context.m_goldenMatching, context.m_actualMatching); + UpdateViewOnResults(context.m_foundResults); return; } - auto & engine = m_framework.GetSearchAPI().GetEngine(); - { - search::SearchParams params; - sample.FillSearchParams(params); - params.m_onResults = [this, index, sample, timestamp](search::Results const & results) { - vector> relevances; - vector goldenMatching; - vector actualMatching; - - if (results.IsEndedNormal()) - { - // Can't use m_loader here due to thread-safety issues. - search::FeatureLoader loader(m_dataSource); - search::Matcher matcher(loader); - - vector const actual(results.begin(), results.end()); - matcher.Match(sample.m_results, actual, goldenMatching, actualMatching); - relevances.resize(actual.size()); - for (size_t i = 0; i < goldenMatching.size(); ++i) - { - auto const j = goldenMatching[i]; - if (j != search::Matcher::kInvalidId) - { - CHECK_LESS(j, relevances.size(), ()); - relevances[j] = sample.m_results[i].m_relevance; - } - } - } - - GetPlatform().RunTask(Platform::Thread::Gui, bind(&MainModel::OnResults, this, timestamp, index, results, - relevances, goldenMatching, actualMatching)); - }; - - m_queryHandle = engine.Search(params); - m_view->OnSearchStarted(); - } + InitiateForegroundSearch(index); } void MainModel::OnResultSelected(int index) @@ -294,6 +270,16 @@ void MainModel::AddNonFoundResult(FeatureID const & id) context.AddNonFoundResult(result); } +void MainModel::InitiateForegroundSearch(size_t index) +{ + auto & context = m_contexts[index]; + auto const & sample = context.m_sample; + + m_view->ShowSample(index, sample, sample.m_pos, context.HasChanges()); + m_runner.InitiateForegroundSearch(index); + m_view->OnSearchStarted(); +} + void MainModel::OnUpdate(View::ResultType type, size_t sampleIndex, Edits::Update const & update) { using Type = Edits::Update::Type; @@ -320,82 +306,34 @@ void MainModel::OnUpdate(View::ResultType type, size_t sampleIndex, Edits::Updat { CHECK(context.m_initialized, ()); CHECK_EQUAL(type, View::ResultType::NonFound, ()); - ShowMarks(context); + m_view->ShowMarks(context); } } -void MainModel::OnResults(uint64_t timestamp, size_t sampleIndex, search::Results const & results, - vector> const & relevances, - vector const & goldenMatching, - vector const & actualMatching) +void MainModel::UpdateViewOnResults(search::Results const & results) { CHECK(m_threadChecker.CalledOnOriginalThread(), ()); - if (timestamp != m_queryTimestamp) - return; - CHECK_LESS_OR_EQUAL(m_numShownResults, results.GetCount(), ()); m_view->AddFoundResults(results.begin() + m_numShownResults, results.end()); m_numShownResults = results.GetCount(); - auto & context = m_contexts[sampleIndex]; - context.m_foundResults = results; - if (!results.IsEndedNormal()) return; - if (!context.m_initialized) - { - context.m_foundResultsEdits.Reset(relevances); - context.m_goldenMatching = goldenMatching; - context.m_actualMatching = actualMatching; - - { - vector> relevances; - - auto & nonFound = context.m_nonFoundResults; - CHECK(nonFound.empty(), ()); - for (size_t i = 0; i < context.m_goldenMatching.size(); ++i) - { - auto const j = context.m_goldenMatching[i]; - if (j != search::Matcher::kInvalidId) - continue; - nonFound.push_back(context.m_sample.m_results[i]); - relevances.emplace_back(nonFound.back().m_relevance); - } - context.m_nonFoundResultsEdits.Reset(relevances); - } - - context.m_initialized = true; - } + auto & context = m_contexts[m_selectedSample]; m_view->ShowNonFoundResults(context.m_nonFoundResults, context.m_nonFoundResultsEdits.GetEntries()); - ShowMarks(context); - m_view->OnResultChanged(sampleIndex, View::ResultType::Found, - Edits::Update::MakeAll()); - m_view->OnResultChanged(sampleIndex, View::ResultType::NonFound, - Edits::Update::MakeAll()); - m_view->OnSampleChanged(sampleIndex, context.HasChanges()); - m_view->SetEdits(sampleIndex, context.m_foundResultsEdits, context.m_nonFoundResultsEdits); + m_view->ShowMarks(context); + m_view->OnResultChanged(m_selectedSample, View::ResultType::Found, Edits::Update::MakeAll()); + m_view->OnResultChanged(m_selectedSample, View::ResultType::NonFound, Edits::Update::MakeAll()); + m_view->OnSampleChanged(m_selectedSample, context.HasChanges()); + + m_view->SetEdits(m_selectedSample, context.m_foundResultsEdits, context.m_nonFoundResultsEdits); m_view->OnSearchCompleted(); } -void MainModel::ResetSearch() -{ - ++m_queryTimestamp; - if (auto handle = m_queryHandle.lock()) - handle->Cancel(); -} - -void MainModel::ShowMarks(Context const & context) -{ - m_view->ClearSearchResultMarks(); - m_view->ShowFoundResultsMarks(context.m_foundResults.begin(), context.m_foundResults.end()); - m_view->ShowNonFoundResultsMarks(context.m_nonFoundResults, - context.m_nonFoundResultsEdits.GetEntries()); -} - void MainModel::OnChangeAllRelevancesClicked(Edits::Relevance relevance) { CHECK_GREATER_OR_EQUAL(m_selectedSample, 0, ()); diff --git a/search/search_quality/assessment_tool/main_model.hpp b/search/search_quality/assessment_tool/main_model.hpp index b2e7d2469e..78079c4b35 100644 --- a/search/search_quality/assessment_tool/main_model.hpp +++ b/search/search_quality/assessment_tool/main_model.hpp @@ -5,6 +5,7 @@ #include "search/search_quality/assessment_tool/context.hpp" #include "search/search_quality/assessment_tool/edits.hpp" #include "search/search_quality/assessment_tool/model.hpp" +#include "search/search_quality/assessment_tool/search_request_runner.hpp" #include "search/search_quality/assessment_tool/view.hpp" #include "search/search_quality/sample.hpp" @@ -34,6 +35,7 @@ public: void Open(std::string const & path) override; void Save() override; void SaveAs(std::string const & path) override; + void InitiateBackgroundSearch(size_t const from, size_t const to) override; void OnSampleSelected(int index) override; void OnResultSelected(int index) override; @@ -49,14 +51,11 @@ public: private: static int constexpr kInvalidIndex = -1; + void InitiateForegroundSearch(size_t index); + void OnUpdate(View::ResultType type, size_t sampleIndex, Edits::Update const & update); - void OnResults(uint64_t timestamp, size_t sampleIndex, search::Results const & results, - std::vector> const & relevances, - std::vector const & goldenMatching, - std::vector const & actualMatching); - - void ResetSearch(); + void UpdateViewOnResults(search::Results const & results); void ShowMarks(Context const & context); void OnChangeAllRelevancesClicked(Edits::Relevance relevance); @@ -73,10 +72,10 @@ private: // Path to the last file search samples were loaded from or saved to. std::string m_path; - std::weak_ptr m_queryHandle; - uint64_t m_queryTimestamp = 0; int m_selectedSample = kInvalidIndex; size_t m_numShownResults = 0; + SearchRequestRunner m_runner; + ThreadChecker m_threadChecker; }; diff --git a/search/search_quality/assessment_tool/main_view.cpp b/search/search_quality/assessment_tool/main_view.cpp index 66090ccd96..73758306a3 100644 --- a/search/search_quality/assessment_tool/main_view.cpp +++ b/search/search_quality/assessment_tool/main_view.cpp @@ -18,15 +18,25 @@ #include "geometry/mercator.hpp" #include "base/assert.hpp" +#include "base/checked_cast.hpp" #include "base/string_utils.hpp" +#include + #include #include +#include +#include #include #include +#include +#include #include #include +#include #include +#include +#include #include #include #include @@ -125,6 +135,13 @@ void MainView::ShowNonFoundResults(std::vector const & r m_sampleView->ShowNonFoundResults(results, entries); } +void MainView::ShowMarks(Context const & context) +{ + ClearSearchResultMarks(); + ShowFoundResultsMarks(context.m_foundResults.begin(), context.m_foundResults.end()); + ShowNonFoundResultsMarks(context.m_nonFoundResults, context.m_nonFoundResultsEdits.GetEntries()); +} + void MainView::ShowFoundResultsMarks(search::Results::ConstIter begin, search::Results::ConstIter end) @@ -165,6 +182,7 @@ void MainView::OnResultChanged(size_t sampleIndex, ResultType type, Edits::Updat if (!m_samplesView->IsSelected(sampleIndex)) return; + switch (type) { case ResultType::Found: m_sampleView->GetFoundResultsView().Update(update); break; @@ -174,6 +192,7 @@ void MainView::OnResultChanged(size_t sampleIndex, ResultType type, Edits::Updat void MainView::OnSampleChanged(size_t sampleIndex, bool hasEdits) { + m_samplesView->OnUpdate(sampleIndex); if (!m_samplesView->IsSelected(sampleIndex)) return; SetSampleDockTitle(hasEdits); @@ -276,6 +295,17 @@ void MainView::InitMenuBar() fileMenu->addAction(m_saveAs); } + { + m_initiateBackgroundSearch = new QAction(tr("Initiate background search"), this /* parent */); + m_initiateBackgroundSearch->setShortcut(Qt::CTRL | Qt::Key_I); + m_initiateBackgroundSearch->setStatusTip( + tr("Search in the background for the queries from a selected range")); + m_initiateBackgroundSearch->setEnabled(false); + connect(m_initiateBackgroundSearch, &QAction::triggered, this, + &MainView::InitiateBackgroundSearch); + fileMenu->addAction(m_initiateBackgroundSearch); + } + fileMenu->addSeparator(); { @@ -379,6 +409,7 @@ void MainView::Open() return; m_model->Open(file); + m_initiateBackgroundSearch->setEnabled(true); } void MainView::Save() { m_model->Save(); } @@ -392,6 +423,52 @@ void MainView::SaveAs() m_model->SaveAs(file); } +void MainView::InitiateBackgroundSearch() +{ + QDialog dialog(this); + QFormLayout form(&dialog); + + form.addRow(new QLabel("Queries range")); + + QValidator * validator = new QIntValidator(0, std::numeric_limits::max(), this); + + QLineEdit * lineEditFrom = new QLineEdit(&dialog); + form.addRow(new QLabel("First"), lineEditFrom); + lineEditFrom->setValidator(validator); + + QLineEdit * lineEditTo = new QLineEdit(&dialog); + form.addRow(new QLabel("Last"), lineEditTo); + lineEditTo->setValidator(validator); + + QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, + &dialog); + form.addRow(&buttonBox); + + connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + if (dialog.exec() != QDialog::Accepted) + return; + + std::string const strFrom = lineEditFrom->text().toStdString(); + std::string const strTo = lineEditTo->text().toStdString(); + uint64_t from = 0; + uint64_t to = 0; + if (!strings::to_uint64(strFrom, from)) + { + LOG(LERROR, ("Could not parse number from", strFrom)); + return; + } + if (!strings::to_uint64(strTo, to)) + { + LOG(LERROR, ("Could not parse number from", strTo)); + return; + } + + m_model->InitiateBackgroundSearch(base::checked_cast(from), + base::checked_cast(to)); +} + void MainView::SetSamplesDockTitle(bool hasEdits) { CHECK(m_samplesDock, ()); diff --git a/search/search_quality/assessment_tool/main_view.hpp b/search/search_quality/assessment_tool/main_view.hpp index 62b5715bc2..6ec9491162 100644 --- a/search/search_quality/assessment_tool/main_view.hpp +++ b/search/search_quality/assessment_tool/main_view.hpp @@ -39,6 +39,7 @@ public: void ShowNonFoundResults(std::vector const & results, std::vector const & entries) override; + void ShowMarks(Context const & context) override; void ShowFoundResultsMarks(search::Results::ConstIter begin, search::Results::ConstIter end) override; void ShowNonFoundResultsMarks(std::vector const & results, @@ -101,6 +102,7 @@ private: void Open(); void Save(); void SaveAs(); + void InitiateBackgroundSearch(); void SetSamplesDockTitle(bool hasEdits); void SetSampleDockTitle(bool hasEdits); @@ -120,6 +122,7 @@ private: QAction * m_save = nullptr; QAction * m_saveAs = nullptr; + QAction * m_initiateBackgroundSearch = nullptr; State m_state = State::BeforeSearch; FeatureID m_selectedFeature; diff --git a/search/search_quality/assessment_tool/model.hpp b/search/search_quality/assessment_tool/model.hpp index 4b44f4010c..3b98b75dd0 100644 --- a/search/search_quality/assessment_tool/model.hpp +++ b/search/search_quality/assessment_tool/model.hpp @@ -16,6 +16,14 @@ public: virtual void Save() = 0; virtual void SaveAs(std::string const & path) = 0; + // Initiates the search in the background on all samples + // in the 1-based range [|from|, |to|], both ends included. + // Another background search that may currently be running will be cancelled + // but the results for already completed requests will not be discarded. + // + // Does nothing if the range is invalid. + virtual void InitiateBackgroundSearch(size_t from, size_t to) = 0; + virtual void OnSampleSelected(int index) = 0; virtual void OnResultSelected(int index) = 0; virtual void OnNonFoundResultSelected(int index) = 0; diff --git a/search/search_quality/assessment_tool/samples_view.cpp b/search/search_quality/assessment_tool/samples_view.cpp index f35a9b8d3d..26d8719f37 100644 --- a/search/search_quality/assessment_tool/samples_view.cpp +++ b/search/search_quality/assessment_tool/samples_view.cpp @@ -22,7 +22,14 @@ QVariant SamplesView::Model::data(QModelIndex const & index, int role) const if (role == Qt::BackgroundRole && m_samples.IsValid()) { if (m_samples.IsChanged(row)) - return QBrush(QColor(255, 255, 200)); + return QBrush(QColor(0xFF, 0xFF, 0xC8)); + + if (m_samples.GetSearchState(row) == Context::SearchState::InQueue) + return QBrush(QColor(0xFF, 0xCC, 0x66)); + + if (m_samples.GetSearchState(row) == Context::SearchState::Completed) + return QBrush(QColor(0xCA, 0xFE, 0xDB)); + return QBrush(Qt::transparent); } return QStandardItemModel::data(index, role); diff --git a/search/search_quality/assessment_tool/search_request_runner.cpp b/search/search_quality/assessment_tool/search_request_runner.cpp new file mode 100644 index 0000000000..1977f51080 --- /dev/null +++ b/search/search_quality/assessment_tool/search_request_runner.cpp @@ -0,0 +1,207 @@ +#include "search/search_quality/assessment_tool/search_request_runner.hpp" + +#include "search/feature_loader.hpp" + +#include "base/assert.hpp" +#include "base/logging.hpp" + +#include + +using namespace std; + +SearchRequestRunner::SearchRequestRunner(Framework & framework, DataSource const & dataSource, + ContextList & contexts, + UpdateViewOnResults && updateViewOnResults, + UpdateSampleSearchState && updateSampleSearchState) + : m_framework(framework) + , m_dataSource(dataSource) + , m_contexts(contexts) + , m_updateViewOnResults(move(updateViewOnResults)) + , m_updateSampleSearchState(move(updateSampleSearchState)) +{ +} + +void SearchRequestRunner::InitiateForegroundSearch(size_t index) +{ + RunRequest(index, false /* background */, m_foregroundTimestamp); +} + +void SearchRequestRunner::InitiateBackgroundSearch(size_t from, size_t to) +{ + // 1 <= from <= to <= m_contexts.Size(). + if (from < 1 || from > to || to > m_contexts.Size()) + { + LOG(LINFO, + ("Could not initiate search in the range", from, to, "Total samples:", m_contexts.Size())); + return; + } + + ResetBackgroundSearch(); + + // Convert to 0-based. + --from; + --to; + m_backgroundFirstIndex = from; + m_backgroundLastIndex = to; + m_numProcessedRequests = 0; + + for (size_t index = from; index <= to; ++index) + { + if (m_contexts[index].m_searchState == Context::SearchState::Untouched) + { + m_contexts[index].m_searchState = Context::SearchState::InQueue; + m_backgroundQueue.push(index); + m_updateSampleSearchState(index); + } + else + { + CHECK(m_contexts[index].m_searchState == Context::SearchState::Completed, ()); + } + } + + RunNextBackgroundRequest(m_backgroundTimestamp); +} + +void SearchRequestRunner::RunNextBackgroundRequest(size_t timestamp) +{ + // todo(@m) Process in batches instead? + if (m_backgroundQueue.empty()) + { + LOG(LINFO, ("All requests from", m_backgroundFirstIndex + 1, "to", m_backgroundLastIndex + 1, + "have been processed")); + return; + } + size_t index = m_backgroundQueue.front(); + m_backgroundQueue.pop(); + + RunRequest(index, true /* background */, timestamp); +} + +void SearchRequestRunner::RunRequest(size_t index, bool background, size_t timestamp) +{ + CHECK_THREAD_CHECKER(m_threadChecker, ()); + + auto const & context = m_contexts[index]; + auto const & sample = context.m_sample; + + // todo(@m) What if we want multiple threads in engine? + auto & engine = m_framework.GetSearchAPI().GetEngine(); + + search::SearchParams params; + sample.FillSearchParams(params); + params.m_onResults = [=](search::Results const & results) { + vector> relevances; + vector goldenMatching; + vector actualMatching; + + if (results.IsEndedNormal()) + { + // Can't use MainModel's m_loader here due to thread-safety issues. + search::FeatureLoader loader(m_dataSource); + search::Matcher matcher(loader); + + vector const actual(results.begin(), results.end()); + matcher.Match(sample.m_results, actual, goldenMatching, actualMatching); + relevances.resize(actual.size()); + for (size_t i = 0; i < goldenMatching.size(); ++i) + { + auto const j = goldenMatching[i]; + if (j != search::Matcher::kInvalidId) + { + CHECK_LESS(j, relevances.size(), ()); + relevances[j] = sample.m_results[i].m_relevance; + } + } + + LOG(LINFO, ("Request number", index + 1, "has been processed in the", + background ? "background" : "foreground")); + } + + GetPlatform().RunTask(Platform::Thread::Gui, [this, background, timestamp, index, results, + relevances, goldenMatching, actualMatching] { + size_t const latestTimestamp = background ? m_backgroundTimestamp : m_foregroundTimestamp; + if (timestamp != latestTimestamp) + return; + + auto & context = m_contexts[index]; + + context.m_foundResults = results; + + if (results.IsEndMarker()) + { + if (results.IsEndedNormal()) + context.m_searchState = Context::SearchState::Completed; + else + context.m_searchState = Context::SearchState::Untouched; + m_updateSampleSearchState(index); + } + + if (results.IsEndedNormal() && !context.m_initialized) + { + context.m_foundResultsEdits.Reset(relevances); + context.m_goldenMatching = goldenMatching; + context.m_actualMatching = actualMatching; + + { + vector> relevances; + + auto & nonFound = context.m_nonFoundResults; + CHECK(nonFound.empty(), ()); + for (size_t i = 0; i < context.m_goldenMatching.size(); ++i) + { + auto const j = context.m_goldenMatching[i]; + if (j != search::Matcher::kInvalidId) + continue; + nonFound.push_back(context.m_sample.m_results[i]); + relevances.emplace_back(nonFound.back().m_relevance); + } + context.m_nonFoundResultsEdits.Reset(relevances); + } + + context.m_initialized = true; + } + + if (background) + RunNextBackgroundRequest(timestamp); + else + m_updateViewOnResults(results); + }); + }; + + if (background) + m_backgroundQueryHandle = engine.Search(params); + else + m_foregroundQueryHandle = engine.Search(params); +} + +void SearchRequestRunner::ResetForegroundSearch() +{ + CHECK_THREAD_CHECKER(m_threadChecker, ()); + + ++m_foregroundTimestamp; + if (auto handle = m_foregroundQueryHandle.lock()) + handle->Cancel(); +} + +void SearchRequestRunner::ResetBackgroundSearch() +{ + CHECK_THREAD_CHECKER(m_threadChecker, ()); + + ++m_backgroundTimestamp; + auto handle = m_backgroundQueryHandle.lock(); + if (!handle) + return; + + handle->Cancel(); + + for (size_t index = m_backgroundFirstIndex; index <= m_backgroundLastIndex; ++index) + { + if (m_contexts[index].m_searchState == Context::SearchState::InQueue) + { + m_contexts[index].m_searchState = Context::SearchState::Untouched; + m_updateSampleSearchState(index); + } + } + + queue().swap(m_backgroundQueue); +} diff --git a/search/search_quality/assessment_tool/search_request_runner.hpp b/search/search_quality/assessment_tool/search_request_runner.hpp new file mode 100644 index 0000000000..1ea5b82c8b --- /dev/null +++ b/search/search_quality/assessment_tool/search_request_runner.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "search/search_quality/assessment_tool/context.hpp" + +#include "map/framework.hpp" + +#include "base/thread_checker.hpp" + +#include +#include +#include +#include +#include +#include +#include + +// A proxy for SearchAPI/SearchEngine. +// This class updates the Model's |m_contexts| directly (from the main thread) and updates +// the View via the |m_updateViewOnResults| and |m_updateSampleSearchState| callbacks. +class SearchRequestRunner +{ +public: + using UpdateViewOnResults = std::function; + using UpdateSampleSearchState = std::function; + + SearchRequestRunner(Framework & framework, DataSource const & dataSource, ContextList & contexts, + UpdateViewOnResults && updateViewOnResults, + UpdateSampleSearchState && updateSampleSearchState); + + void InitiateForegroundSearch(size_t index); + + void InitiateBackgroundSearch(size_t from, size_t to); + + void ResetForegroundSearch(); + + void ResetBackgroundSearch(); + +private: + static size_t constexpr kInvalidIndex = std::numeric_limits::max(); + + void RunNextBackgroundRequest(size_t timestamp); + + void RunRequest(size_t index, bool background, size_t timestamp); + + Framework & m_framework; + + DataSource const & m_dataSource; + + ContextList & m_contexts; + + UpdateViewOnResults m_updateViewOnResults; + UpdateSampleSearchState m_updateSampleSearchState; + + std::weak_ptr m_backgroundQueryHandle; + std::weak_ptr m_foregroundQueryHandle; + + size_t m_foregroundTimestamp = 0; + size_t m_backgroundTimestamp = 0; + + size_t m_backgroundFirstIndex = kInvalidIndex; + size_t m_backgroundLastIndex = kInvalidIndex; + + std::queue m_backgroundQueue; + + size_t m_numProcessedRequests = 0; + + ThreadChecker m_threadChecker; +}; diff --git a/search/search_quality/assessment_tool/view.hpp b/search/search_quality/assessment_tool/view.hpp index ca2d6efdf7..6abf635ca3 100644 --- a/search/search_quality/assessment_tool/view.hpp +++ b/search/search_quality/assessment_tool/view.hpp @@ -41,6 +41,7 @@ public: virtual void ShowNonFoundResults(std::vector const & results, std::vector const & entries) = 0; + virtual void ShowMarks(Context const & context) = 0; virtual void ShowFoundResultsMarks(search::Results::ConstIter begin, search::Results::ConstIter end) = 0; virtual void ShowNonFoundResultsMarks(std::vector const & results, diff --git a/search/search_quality/matcher.hpp b/search/search_quality/matcher.hpp index 09dcc04d41..741e042692 100644 --- a/search/search_quality/matcher.hpp +++ b/search/search_quality/matcher.hpp @@ -20,6 +20,12 @@ public: explicit Matcher(FeatureLoader & loader); + // Matches the |golden| results loaded from a Sample with |actual| results + // found by the search engine using the params from the Sample. + // goldenMatching[i] is the index of the result in |actual| that matches + // the sample result number i. + // actualMatching[j] is the index of the sample in |golden| that matches + // the golden result number j. void Match(std::vector const & golden, std::vector const & actual, std::vector & goldenMatching, std::vector & actualMatching);