Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mapsme/omim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/search
diff options
context:
space:
mode:
authorMaxim Pimenov <m@maps.me>2019-03-22 20:00:01 +0300
committerTatiana Yan <tatiana.kondakova@gmail.com>2019-03-25 17:11:23 +0300
commited850339be2de77569b5675bd818da9f077676ba (patch)
tree6adcc6fed3ed4f0af8a814827f8eef0ed9837b02 /search
parentaff6a26b56e91d4b117d98adaec51ef2f62d29cc (diff)
[search] [assessment-tool] Implemented searching in the background.
Diffstat (limited to 'search')
-rw-r--r--search/search_quality/assessment_tool/CMakeLists.txt2
-rw-r--r--search/search_quality/assessment_tool/context.hpp14
-rw-r--r--search/search_quality/assessment_tool/main_model.cpp134
-rw-r--r--search/search_quality/assessment_tool/main_model.hpp15
-rw-r--r--search/search_quality/assessment_tool/main_view.cpp77
-rw-r--r--search/search_quality/assessment_tool/main_view.hpp3
-rw-r--r--search/search_quality/assessment_tool/model.hpp8
-rw-r--r--search/search_quality/assessment_tool/samples_view.cpp9
-rw-r--r--search/search_quality/assessment_tool/search_request_runner.cpp207
-rw-r--r--search/search_quality/assessment_tool/search_request_runner.hpp68
-rw-r--r--search/search_quality/assessment_tool/view.hpp1
-rw-r--r--search/search_quality/matcher.hpp6
12 files changed, 437 insertions, 107 deletions
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<search::Sample::Result> 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<boost::optional<Edits::Relevance>> relevances;
- vector<size_t> goldenMatching;
- vector<size_t> 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<search::Result> 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,80 +306,32 @@ 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<boost::optional<Edits::Relevance>> const & relevances,
- vector<size_t> const & goldenMatching,
- vector<size_t> 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<boost::optional<Edits::Relevance>> 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->OnSearchCompleted();
-}
-
-void MainModel::ResetSearch()
-{
- ++m_queryTimestamp;
- if (auto handle = m_queryHandle.lock())
- handle->Cancel();
-}
+ 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());
-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());
+ m_view->SetEdits(m_selectedSample, context.m_foundResultsEdits, context.m_nonFoundResultsEdits);
+ m_view->OnSearchCompleted();
}
void MainModel::OnChangeAllRelevancesClicked(Edits::Relevance relevance)
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 OnUpdate(View::ResultType type, size_t sampleIndex, Edits::Update const & update);
+ void InitiateForegroundSearch(size_t index);
- void OnResults(uint64_t timestamp, size_t sampleIndex, search::Results const & results,
- std::vector<boost::optional<Edits::Relevance>> const & relevances,
- std::vector<size_t> const & goldenMatching,
- std::vector<size_t> const & actualMatching);
+ void OnUpdate(View::ResultType type, size_t sampleIndex, Edits::Update const & update);
- 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<search::ProcessorHandle> 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 <limits>
+
#include <QtCore/Qt>
#include <QtGui/QCloseEvent>
+#include <QtGui/QIntValidator>
+#include <QtGui/QKeySequence>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
+#include <QtWidgets/QDialog>
+#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QDockWidget>
#include <QtWidgets/QFileDialog>
+#include <QtWidgets/QFormLayout>
#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QToolBar>
@@ -125,6 +135,13 @@ void MainView::ShowNonFoundResults(std::vector<search::Sample::Result> 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<int>::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<size_t>(from),
+ base::checked_cast<size_t>(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<search::Sample::Result> const & results,
std::vector<Edits::Entry> const & entries) override;
+ void ShowMarks(Context const & context) override;
void ShowFoundResultsMarks(search::Results::ConstIter begin,
search::Results::ConstIter end) override;
void ShowNonFoundResultsMarks(std::vector<search::Sample::Result> 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 <utility>
+
+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<boost::optional<Edits::Relevance>> relevances;
+ vector<size_t> goldenMatching;
+ vector<size_t> 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<search::Result> 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<boost::optional<Edits::Relevance>> 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<size_t>().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 <cstddef>
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <memory>
+#include <queue>
+#include <vector>
+
+// 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<void(search::Results const & results)>;
+ using UpdateSampleSearchState = std::function<void(size_t index)>;
+
+ 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<size_t>::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<search::ProcessorHandle> m_backgroundQueryHandle;
+ std::weak_ptr<search::ProcessorHandle> m_foregroundQueryHandle;
+
+ size_t m_foregroundTimestamp = 0;
+ size_t m_backgroundTimestamp = 0;
+
+ size_t m_backgroundFirstIndex = kInvalidIndex;
+ size_t m_backgroundLastIndex = kInvalidIndex;
+
+ std::queue<size_t> 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<search::Sample::Result> const & results,
std::vector<Edits::Entry> 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<search::Sample::Result> 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<Sample::Result> const & golden, std::vector<Result> const & actual,
std::vector<size_t> & goldenMatching, std::vector<size_t> & actualMatching);