diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp index 043bdba74d1..83f21201678 100644 --- a/src/plugins/coreplugin/coreplugin.cpp +++ b/src/plugins/coreplugin/coreplugin.cpp @@ -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; diff --git a/src/plugins/coreplugin/coreplugin.h b/src/plugins/coreplugin/coreplugin.h index 7153c5415b8..b415ba146e3 100644 --- a/src/plugins/coreplugin/coreplugin.h +++ b/src/plugins/coreplugin/coreplugin.h @@ -8,6 +8,7 @@ #include #include +#include QT_BEGIN_NAMESPACE class QMenu; @@ -46,13 +47,14 @@ 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); static QString msgCrashpadInformation(); public slots: - void fileOpenRequest(const QString&); + void fileOpenRequest(const QString &); #if defined(WITH_TESTS) private slots: @@ -77,6 +79,7 @@ private: FolderNavigationWidgetFactory *m_folderNavigationWidgetFactory = nullptr; Utils::Environment m_startupSystemEnvironment; Utils::EnvironmentItems m_environmentChanges; + Utils::FutureSynchronizer m_futureSynchronizer; }; } // namespace Internal diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.cpp b/src/plugins/coreplugin/locator/ilocatorfilter.cpp index f529aa0285a..35c934fbc49 100644 --- a/src/plugins/coreplugin/locator/ilocatorfilter.cpp +++ b/src/plugins/coreplugin/locator/ilocatorfilter.cpp @@ -3,9 +3,11 @@ #include "ilocatorfilter.h" +#include "../coreplugin.h" #include "../coreplugintr.h" #include "../editormanager/editormanager.h" +#include #include #include @@ -14,12 +16,15 @@ #include #include #include +#include #include #include #include #include #include +#include + 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) { + mergeWith(entries, state); + } + LocatorMatcherTask::OutputData mergeWith(const LocatorMatcherTask::OutputData &entries, + std::atomic &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 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 &promise) + { + QList> data; + QList> 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({}); + 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> *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 m_state = State::Awaiting; + QList> 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> m_watcher; + std::shared_ptr 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); + 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 &promise, + const std::shared_ptr &dataProvider) { + dataProvider->run(promise); + }; + m_watcher->setFuture(Utils::asyncRun(filter, m_dataProvider)); +} + +class OutputFilterAdapter : public Tasking::TaskAdapter +{ +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 m_tasks; + LocatorMatcherTask::Storage m_storage; + int m_parallelLimit = 0; + std::unique_ptr m_taskTree; +}; + +LocatorMatcher::LocatorMatcher() + : d(new LocatorMatcherPrivate) {} + +LocatorMatcher::~LocatorMatcher() = default; + +void LocatorMatcher::setTasks(const QList &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; + + 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 parallelTasks { ParallelLimit(d->m_parallelLimit) }; + + const auto onGroupSetup = [this](const TreeStorage &storage) { + return [this, storage] { storage->input = d->m_storage.input; }; + }; + const auto onGroupDone = [filterStorage] + (const TreeStorage &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 &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 g_locatorFilters; /*! @@ -669,3 +1050,5 @@ bool ILocatorFilter::isOldSetting(const QByteArray &state) */ } // Core + +#include "ilocatorfilter.moc" diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.h b/src/plugins/coreplugin/locator/ilocatorfilter.h index ca7a0dff763..888d7b391e1 100644 --- a/src/plugins/coreplugin/locator/ilocatorfilter.h +++ b/src/plugins/coreplugin/locator/ilocatorfilter.h @@ -109,6 +109,57 @@ public: } }; +class CORE_EXPORT LocatorMatcherTask final +{ +public: + using InputData = QString; + using OutputData = QList; + 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; +}; + +class LocatorMatcherPrivate; + +class CORE_EXPORT LocatorMatcher final : public QObject +{ + Q_OBJECT + +public: + LocatorMatcher(); + ~LocatorMatcher(); + void setTasks(const QList &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 &tasks, + const LocatorMatcherTask::InputData &input, + int parallelLimit = 0); +signals: + void serialOutputDataReady(const LocatorMatcherTask::OutputData &serialOutputData); + void done(bool success); + +private: + std::unique_ptr d; +}; + class CORE_EXPORT ILocatorFilter : public QObject { Q_OBJECT