From c5564559cc01821e97e10f7253933ac5ca7f24c6 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Thu, 30 May 2024 10:19:10 +0200 Subject: [PATCH] ProjectExplorer: use multiple threads to scan for files recursively Change-Id: I845d2e2eaffd8d6a4e85d3186435d57846789506 Reviewed-by: Eike Ziller Reviewed-by: Jarek Kobus --- .../projectexplorer/projectnodeshelper.h | 125 ++++++++++++------ 1 file changed, 88 insertions(+), 37 deletions(-) diff --git a/src/plugins/projectexplorer/projectnodeshelper.h b/src/plugins/projectexplorer/projectnodeshelper.h index b9e3cc27aa0..6809622af5b 100644 --- a/src/plugins/projectexplorer/projectnodeshelper.h +++ b/src/plugins/projectexplorer/projectnodeshelper.h @@ -8,67 +8,121 @@ #include #include +#include + #include +#include #include +#include #include namespace ProjectExplorer { namespace Internal { +struct DirectoryScanResult +{ + QList nodes; + Utils::FilePaths subDirectories; +}; + template -QList scanForFilesRecursively( +DirectoryScanResult scanForFiles( QPromise &promise, - double progressStart, - double progressRange, const Utils::FilePath &directory, const QDir::Filters &filter, const std::function factory, - QSet &visited, const QList &versionControls) { - QList result; - - // Do not follow directory loops: - if (!Utils::insert(visited, directory.canonicalPath())) - return result; + DirectoryScanResult result; const Utils::FilePaths entries = directory.dirEntries(filter); - double progress = 0; - const double progressIncrement = progressRange / static_cast(entries.count()); - int lastIntProgress = 0; for (const Utils::FilePath &entry : entries) { if (promise.isCanceled()) return result; - if (!Utils::contains(versionControls, [entry](const Core::IVersionControl *vc) { + if (Utils::anyOf(versionControls, [entry](const Core::IVersionControl *vc) { return vc->isVcsFileOrDirectory(entry); })) { - if (entry.isDir()) { - result.append(scanForFilesRecursively(promise, - progress, - progressIncrement, - entry, - filter, - factory, - visited, - versionControls)); - } else if (FileNode *node = factory(entry)) { - result.append(node); - } - } - progress += progressIncrement; - const int intProgress = std::min(static_cast(progressStart + progress), - promise.future().progressMaximum()); - if (lastIntProgress < intProgress) { - promise.setProgressValue(intProgress); - lastIntProgress = intProgress; + continue; } + + if (entry.isDir()) + result.subDirectories.append(entry); + else if (FileNode *node = factory(entry)) + result.nodes.append(node); } - promise.setProgressValue(std::min(static_cast(progressStart + progressRange), - promise.future().progressMaximum())); + return result; } + +template +QList scanForFilesRecursively( + QPromise &promise, + double progressRange, + const Utils::FilePath &directory, + const QDir::Filters &filter, + const std::function factory, + const QList &versionControls) +{ + QSet visited; + const DirectoryScanResult result + = scanForFiles(promise, directory, filter, factory, versionControls); + QList fileNodes = result.nodes; + const double progressIncrement = progressRange + / static_cast( + fileNodes.count() + result.subDirectories.count()); + promise.setProgressValue(fileNodes.count() * progressIncrement); + QList> subDirectories; + auto addSubDirectories = [&](const Utils::FilePaths &subdirs, int progressIncrement) { + for (const Utils::FilePath &subdir : subdirs) { + if (Utils::insert(visited, subdir.canonicalPath())) + subDirectories.append(qMakePair(subdir, progressIncrement)); + else + promise.setProgressValue(promise.future().progressValue() + progressIncrement); + } + }; + addSubDirectories(result.subDirectories, progressIncrement); + + while (!subDirectories.isEmpty()) { + using namespace Tasking; + LoopList iterator(subDirectories); + subDirectories.clear(); + + auto onSetup = [&, iterator](Utils::Async &task) { + task.setConcurrentCallData( + [&filter, &factory, &promise, &versionControls, subdir = iterator->first]( + QPromise &p) { + p.addResult(scanForFiles(promise, subdir, filter, factory, versionControls)); + }); + }; + + auto onDone = [&, iterator](const Utils::Async &task) { + const int progressRange = iterator->second; + const DirectoryScanResult result = task.result(); + fileNodes.append(result.nodes); + const int subDirCount = result.subDirectories.count(); + if (subDirCount == 0) { + promise.setProgressValue(promise.future().progressValue() + progressRange); + } else { + const int fileCount = result.nodes.count(); + const int increment = progressRange / static_cast(fileCount + subDirCount); + promise.setProgressValue( + promise.future().progressValue() + increment * fileCount); + addSubDirectories(result.subDirectories, increment); + } + }; + + const Group group{ + Utils::HostOsInfo::isLinuxHost() ? parallelLimit(2) : parallelIdealThreadCountLimit, + iterator, + Utils::AsyncTask(onSetup, onDone) + }; + TaskTree::runBlocking(group); + } + return fileNodes; +} + } // namespace Internal template @@ -78,15 +132,12 @@ QList scanForFiles( const QDir::Filters &filter, const std::function factory) { - QSet visited; promise.setProgressRange(0, 1000000); return Internal::scanForFilesRecursively(promise, - 0.0, 1000000.0, directory, filter, factory, - visited, Core::VcsManager::versionControls()); }