ILocatorFilter: Introduce LocatorMatcher

This machinery is going to replace
ILocatorFilter::matchesFor() and filter usages outside
of locator scope, e.g. in find unused functions.

In contrary to LocatorWidget, which calls
Core::Internal::runSearch() in a separate thread
and the latter executes matchesFor() sequentially for all filters,
this patch offers a possibility to run all filters in parallel.

Change-Id: Ia59463c95294299090173f3d510d57c9f8c7f993
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Jarek Kobus
2023-03-21 21:15:26 +01:00
parent d8de6c88e9
commit 2eba3584a1
4 changed files with 444 additions and 1 deletions

View File

@@ -313,6 +313,12 @@ QObject *CorePlugin::remoteCommand(const QStringList & /* options */,
return res;
}
FutureSynchronizer *CorePlugin::futureSynchronizer()
{
QTC_ASSERT(m_instance, return nullptr);
return &m_instance->m_futureSynchronizer;
}
Environment CorePlugin::startupSystemEnvironment()
{
return m_instance->m_startupSystemEnvironment;

View File

@@ -8,6 +8,7 @@
#include <extensionsystem/iplugin.h>
#include <utils/environment.h>
#include <utils/futuresynchronizer.h>
QT_BEGIN_NAMESPACE
class QMenu;
@@ -46,6 +47,7 @@ public:
const QString &workingDirectory,
const QStringList &args) override;
static Utils::FutureSynchronizer *futureSynchronizer();
static Utils::Environment startupSystemEnvironment();
static Utils::EnvironmentItems environmentChanges();
static void setEnvironmentChanges(const Utils::EnvironmentItems &changes);
@@ -77,6 +79,7 @@ private:
FolderNavigationWidgetFactory *m_folderNavigationWidgetFactory = nullptr;
Utils::Environment m_startupSystemEnvironment;
Utils::EnvironmentItems m_environmentChanges;
Utils::FutureSynchronizer m_futureSynchronizer;
};
} // namespace Internal

View File

@@ -3,9 +3,11 @@
#include "ilocatorfilter.h"
#include "../coreplugin.h"
#include "../coreplugintr.h"
#include "../editormanager/editormanager.h"
#include <utils/asynctask.h>
#include <utils/fuzzymatcher.h>
#include <utils/tasktree.h>
@@ -14,12 +16,15 @@
#include <QCoreApplication>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFutureWatcher>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QLineEdit>
#include <QRegularExpression>
#include <unordered_set>
using namespace Utils;
/*!
@@ -46,6 +51,382 @@ using namespace Utils;
namespace Core {
class OutputDataProvider
{
enum class State {
Awaiting,
NewData,
Canceled
};
struct WorkingData {
WorkingData() = default;
WorkingData(const LocatorMatcherTask::OutputData &entries, std::atomic<State> &state) {
mergeWith(entries, state);
}
LocatorMatcherTask::OutputData mergeWith(const LocatorMatcherTask::OutputData &entries,
std::atomic<State> &state) {
LocatorMatcherTask::OutputData results;
results.reserve(entries.size());
for (const LocatorFilterEntry &entry : entries) {
if (state == State::Canceled)
return LocatorMatcherTask::OutputData();
const auto &link = entry.linkForEditor;
if (!link || m_cache.emplace(*link).second)
results.append(entry);
}
if (state == State::Canceled)
return LocatorMatcherTask::OutputData();
m_data += results;
return results;
}
LocatorMatcherTask::OutputData entries() const { return m_data; }
private:
LocatorMatcherTask::OutputData m_data;
std::unordered_set<Utils::Link> m_cache;
};
public:
OutputDataProvider(int filterCount)
: m_filterCount(filterCount)
, m_outputData(filterCount, {})
{}
void addOutputData(int index, const LocatorMatcherTask::OutputData &outputData)
{
QTC_ASSERT(index >= 0, return);
QMutexLocker locker(&m_mutex);
QTC_ASSERT(index < m_filterCount, return);
QTC_ASSERT(m_state != State::Canceled, return);
QTC_ASSERT(!m_outputData.at(index).has_value(), return);
m_outputData[index] = outputData;
m_state = State::NewData;
m_waitCondition.wakeOne();
}
void cancel()
{
QMutexLocker locker(&m_mutex);
m_state = State::Canceled;
m_waitCondition.wakeOne();
}
// Called from separate thread (OutputFilter's thread)
void run(QPromise<LocatorMatcherTask::OutputData> &promise)
{
QList<std::optional<LocatorMatcherTask::OutputData>> data;
QList<std::optional<WorkingData>> workingList;
while (waitForData(&data)) {
// Emit new results only when new data is reachable from the beginning (i.e. no gaps)
int currentIndex = 0;
int mergeToIndex = 0;
bool hasGap = false;
while (currentIndex < m_filterCount) {
if (m_state == State::Canceled)
return;
const auto &outputData = data.at(currentIndex);
if (!outputData.has_value()) {
++currentIndex;
mergeToIndex = currentIndex;
hasGap = true;
continue;
}
const auto &workingData = workingList.at(currentIndex);
if (!workingData.has_value()) {
const bool mergeToCurrent = currentIndex == mergeToIndex;
const LocatorMatcherTask::OutputData dataForIndex = mergeToCurrent
? *outputData : LocatorMatcherTask::OutputData();
workingList[currentIndex] = std::make_optional(WorkingData(dataForIndex,
m_state));
if (m_state == State::Canceled)
return;
const LocatorMatcherTask::OutputData newData = mergeToCurrent
? workingList[currentIndex]->entries()
: workingList[mergeToIndex]->mergeWith(*outputData, m_state);
if (m_state == State::Canceled)
return;
if (!hasGap && !newData.isEmpty())
promise.addResult(newData);
} else if (currentIndex != mergeToIndex) {
const LocatorMatcherTask::OutputData newData
= workingList[mergeToIndex]->mergeWith(workingData->entries(), m_state);
workingList[currentIndex] = std::make_optional<WorkingData>({});
if (m_state == State::Canceled)
return;
if (!hasGap && !newData.isEmpty())
promise.addResult(newData);
}
++currentIndex;
}
// All data arrived (no gap), so finish here
if (!hasGap)
return;
}
}
private:
bool waitForData(QList<std::optional<LocatorMatcherTask::OutputData>> *data)
{
QMutexLocker locker(&m_mutex);
if (m_state == State::Canceled)
return false;
if (m_state == State::NewData) {
m_state = State::Awaiting;
*data = m_outputData;
return true;
}
m_waitCondition.wait(&m_mutex);
QTC_ASSERT(m_state != State::Awaiting, return false);
if (m_state == State::Canceled)
return false;
m_state = State::Awaiting;
*data = m_outputData;
return true;
}
QMutex m_mutex;
QWaitCondition m_waitCondition;
const int m_filterCount = 0;
std::atomic<State> m_state = State::Awaiting;
QList<std::optional<LocatorMatcherTask::OutputData>> m_outputData;
};
class OutputFilter : public QObject
{
Q_OBJECT
public:
~OutputFilter();
void setFilterCount(int count);
// When last index is added it ends automatically (asynchronously)
void addOutputData(int index, const LocatorMatcherTask::OutputData &outputData);
void start();
bool isRunning() const { return m_watcher.get(); }
signals:
void serialOutputDataReady(const LocatorMatcherTask::OutputData &serialOutputData);
void done();
private:
int m_filterCount = 0;
std::unique_ptr<QFutureWatcher<LocatorMatcherTask::OutputData>> m_watcher;
std::shared_ptr<OutputDataProvider> m_dataProvider;
};
OutputFilter::~OutputFilter()
{
if (!isRunning())
return;
m_dataProvider->cancel();
Internal::CorePlugin::futureSynchronizer()->addFuture(m_watcher->future());
}
void OutputFilter::setFilterCount(int count)
{
QTC_ASSERT(!isRunning(), return);
QTC_ASSERT(count >= 0, return);
m_filterCount = count;
}
void OutputFilter::addOutputData(int index, const LocatorMatcherTask::OutputData &outputData)
{
QTC_ASSERT(isRunning(), return);
m_dataProvider->addOutputData(index, outputData);
}
void OutputFilter::start()
{
QTC_ASSERT(!m_watcher, return);
QTC_ASSERT(!isRunning(), return);
if (m_filterCount == 0) {
emit done();
return;
}
m_dataProvider.reset(new OutputDataProvider(m_filterCount));
m_watcher.reset(new QFutureWatcher<LocatorMatcherTask::OutputData>);
connect(m_watcher.get(), &QFutureWatcherBase::resultReadyAt, this, [this](int index) {
emit serialOutputDataReady(m_watcher->resultAt(index));
});
connect(m_watcher.get(), &QFutureWatcherBase::finished, this, [this] {
emit done();
m_watcher.release()->deleteLater();
m_dataProvider.reset();
});
// TODO: When filterCount == 1, deliver results directly and finish?
auto filter = [](QPromise<LocatorMatcherTask::OutputData> &promise,
const std::shared_ptr<OutputDataProvider> &dataProvider) {
dataProvider->run(promise);
};
m_watcher->setFuture(Utils::asyncRun(filter, m_dataProvider));
}
class OutputFilterAdapter : public Tasking::TaskAdapter<OutputFilter>
{
public:
OutputFilterAdapter() {
connect(task(), &OutputFilter::done, this, [this] { emit done(true); });
}
void start() final { task()->start(); }
};
} // namespace Core
QTC_DECLARE_CUSTOM_TASK(Filter, Core::OutputFilterAdapter);
namespace Core {
class LocatorMatcherPrivate
{
public:
QList<LocatorMatcherTask> m_tasks;
LocatorMatcherTask::Storage m_storage;
int m_parallelLimit = 0;
std::unique_ptr<TaskTree> m_taskTree;
};
LocatorMatcher::LocatorMatcher()
: d(new LocatorMatcherPrivate) {}
LocatorMatcher::~LocatorMatcher() = default;
void LocatorMatcher::setTasks(const QList<LocatorMatcherTask> &tasks)
{
d->m_tasks = tasks;
}
void LocatorMatcher::setInputData(const LocatorMatcherTask::InputData &inputData)
{
d->m_storage.input = inputData;
}
void LocatorMatcher::setParallelLimit(int limit)
{
d->m_parallelLimit = limit;
}
void LocatorMatcher::start()
{
QTC_ASSERT(!isRunning(), return);
d->m_storage.output = {};
d->m_taskTree.reset(new TaskTree);
using namespace Tasking;
struct FilterStorage
{
OutputFilter *m_filter = nullptr;
};
TreeStorage<FilterStorage> filterStorage;
const auto onFilterSetup = [this, filterCount = d->m_tasks.size(), filterStorage](OutputFilter &filter) {
filterStorage->m_filter = &filter;
filter.setFilterCount(filterCount);
connect(&filter, &OutputFilter::serialOutputDataReady,
this, [this](const LocatorMatcherTask::OutputData &serialOutputData) {
d->m_storage.output += serialOutputData;
emit serialOutputDataReady(serialOutputData);
});
};
const auto onFilterDone = [filterStorage](const OutputFilter &filter) {
Q_UNUSED(filter)
filterStorage->m_filter = nullptr;
};
QList<TaskItem> parallelTasks { ParallelLimit(d->m_parallelLimit) };
const auto onGroupSetup = [this](const TreeStorage<LocatorMatcherTask::Storage> &storage) {
return [this, storage] { storage->input = d->m_storage.input; };
};
const auto onGroupDone = [filterStorage]
(const TreeStorage<LocatorMatcherTask::Storage> &storage, int index) {
return [filterStorage, storage, index] {
OutputFilter *outputFilter = filterStorage->m_filter;
QTC_ASSERT(outputFilter, return);
outputFilter->addOutputData(index, storage->output);
};
};
int index = 0;
for (const LocatorMatcherTask &task : std::as_const(d->m_tasks)) {
const auto storage = task.storage;
const Group group {
optional,
Storage(storage),
OnGroupSetup(onGroupSetup(storage)),
OnGroupDone(onGroupDone(storage, index)),
OnGroupError(onGroupDone(storage, index)),
task.task
};
parallelTasks << group;
++index;
}
const Group root {
parallel,
Storage(filterStorage),
Filter(onFilterSetup, onFilterDone, onFilterDone),
Group {
parallelTasks
}
};
d->m_taskTree->setupRoot(root);
const auto onFinish = [this](bool success) {
return [this, success] {
emit done(success);
d->m_taskTree.release()->deleteLater();
};
};
connect(d->m_taskTree.get(), &TaskTree::done, this, onFinish(true));
connect(d->m_taskTree.get(), &TaskTree::errorOccurred, this, onFinish(false));
d->m_taskTree->start();
}
void LocatorMatcher::stop()
{
if (!isRunning())
return;
d->m_taskTree->stop();
d->m_taskTree.reset();
}
bool LocatorMatcher::isRunning() const
{
return d->m_taskTree.get() && d->m_taskTree->isRunning();
}
LocatorMatcherTask::OutputData LocatorMatcher::outputData() const
{
return d->m_storage.output;
}
LocatorMatcherTask::OutputData LocatorMatcher::runBlocking(const QList<LocatorMatcherTask> &tasks,
const LocatorMatcherTask::InputData &input, int parallelLimit)
{
LocatorMatcher tree;
tree.setTasks(tasks);
tree.setInputData(input);
tree.setParallelLimit(parallelLimit);
QEventLoop loop;
connect(&tree, &LocatorMatcher::done, &loop, [&loop] { loop.quit(); });
tree.start();
if (tree.isRunning())
loop.exec(QEventLoop::ExcludeUserInputEvents);
return tree.outputData();
}
static QList<ILocatorFilter *> g_locatorFilters;
/*!
@@ -669,3 +1050,5 @@ bool ILocatorFilter::isOldSetting(const QByteArray &state)
*/
} // Core
#include "ilocatorfilter.moc"

View File

@@ -109,6 +109,57 @@ public:
}
};
class CORE_EXPORT LocatorMatcherTask final
{
public:
using InputData = QString;
using OutputData = QList<LocatorFilterEntry>;
class Storage
{
public:
InputData input;
OutputData output;
};
// The main task. Initial data taken from storage.input field.
// Results reporting is done through the storage.output field.
Utils::Tasking::TaskItem task = Utils::Tasking::Group{};
// When setting up the task, take the input data from storage.input field.
// When task is done, report results by updating the storage.output field.
// When constructing the task, don't place the storage inside the task above.
Utils::Tasking::TreeStorage<Storage> storage;
};
class LocatorMatcherPrivate;
class CORE_EXPORT LocatorMatcher final : public QObject
{
Q_OBJECT
public:
LocatorMatcher();
~LocatorMatcher();
void setTasks(const QList<LocatorMatcherTask> &tasks);
void setInputData(const LocatorMatcherTask::InputData &inputData);
void setParallelLimit(int limit); // by default 0 = parallel
void start();
void stop();
bool isRunning() const;
// Total data collected so far, even when running.
LocatorMatcherTask::OutputData outputData() const;
// Note: Starts internal event loop.
static LocatorMatcherTask::OutputData runBlocking(const QList<LocatorMatcherTask> &tasks,
const LocatorMatcherTask::InputData &input,
int parallelLimit = 0);
signals:
void serialOutputDataReady(const LocatorMatcherTask::OutputData &serialOutputData);
void done(bool success);
private:
std::unique_ptr<LocatorMatcherPrivate> d;
};
class CORE_EXPORT ILocatorFilter : public QObject
{
Q_OBJECT