Performance optimizations: parallelized startup, MetaData indexed storage, XML buffer loading

Cherry-picked and adapted from ROCKNIX badfc993b:

- ThreadPool: Add WorkItem/WorkItemPtr with waitAllExcept pattern for
  fine-grained task synchronization
- main.cpp: Parallelize startup (VLC, IP, MetaData, MameNames, Genres,
  HttpReq cookies) using ThreadPool - significant boot time improvement
- MetaData: Replace std::map<MetaDataId,string> with int8_t indices +
  vector - eliminates 48+ bytes/node red-black tree overhead per game entry,
  O(1) lookups instead of O(log n) - critical for 1GB RAM with large collections
- Gamelist/SystemData: Use readAllBytes + load_buffer_inplace for XML
  parsing instead of load_file - avoids kernel file handle overhead
- ThemeData: Cache raw XML strings instead of parsed DOM trees - less memory
  usage; Element::properties from map to unordered_map for faster lookups
- ThemeVariables: Iterative resolvePlaceholders instead of recursive -
  prevents stack overflow on deeply nested variables, uses string_view
- StringUtil::trim: Skip substr allocation when string needs no trimming
- FileSystemUtil: Add readAllBytes utility

Excluded from original commit: packGamelist, BuildMultiDiskContentCache,
Win32 changes, reloadAllGames parameter, GuiMenu changes (branding conflict)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Douglas Teles
2026-04-17 17:23:29 -03:00
parent d83b57c464
commit a2181ff44e
14 changed files with 330 additions and 125 deletions
+15 -1
View File
@@ -113,7 +113,21 @@ std::vector<FileData*> loadGamelistFile(const std::string xmlpath, SystemData* s
LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"...";
pugi::xml_document doc;
pugi::xml_parse_result result = fromFile ? doc.load_file(WINSTRINGW(xmlpath).c_str()) : doc.load_string(xmlpath.c_str());
pugi::xml_parse_result result;
std::vector<char> buffer;
if (fromFile)
{
buffer = Utils::FileSystem::readAllBytes(xmlpath);
if (!buffer.size())
{
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
return ret;
}
result = doc.load_buffer_inplace(buffer.data(), buffer.size(), pugi::parse_default);
}
else
result = doc.load_string(xmlpath.c_str());
if (!result)
{
+45 -21
View File
@@ -143,7 +143,7 @@ MetaDataId MetaDataList::getId(const std::string& key) const
MetaDataList::MetaDataList(MetaDataListType type) : mType(type), mWasChanged(false), mRelativeTo(nullptr)
{
memset(mIndices, -1, sizeof(mIndices));
}
void MetaDataList::loadFromXML(MetaDataListType type, pugi::xml_node& node, SystemData* system)
@@ -291,16 +291,18 @@ void MetaDataList::appendToXML(pugi::xml_node& parent, bool ignoreDefaults, cons
if (mddIter->id == MetaDataId::GenreIds)
continue;
auto mapIter = mMap.find(mddIter->id);
if(mapIter != mMap.cend())
auto idx = mIndices[mddIter->id];
//auto mapIter = mMap.find(mddIter->id);
//if(mapIter != mMap.cend())
if (idx >= 0)
{
// we have this value!
// if it's just the default (and we ignore defaults), don't write it
if (ignoreDefaults && mapIter->second == mddIter->defaultValue)
if (ignoreDefaults && mValues[idx] == mddIter->defaultValue) // mapIter->second
continue;
// try and make paths relative if we can
std::string value = mapIter->second;
std::string value = mValues[idx]; // mapIter->second;
if (mddIter->type == MD_PATH)
{
if (fullPaths && mRelativeTo != nullptr)
@@ -362,21 +364,40 @@ void MetaDataList::set(MetaDataId id, const std::string& value)
return;
}
// Players -> remove "1-"
// if (mType == GAME_METADATA && id == 12 && Utils::String::startsWith(value, "1-")) // "players"
// {
// mMap[id] = Utils::String::replace(value, "1-", "");
// return;
// }
auto prev = mMap.find(id);
if (prev != mMap.cend() && prev->second == value)
auto idx = mIndices[id];
if (idx >= 0 && mValues[idx] == value)
// auto prev = mMap.find(id);
// if (prev != mMap.cend() && prev->second == value)
return;
if (mGameTypeMap[id] == MD_PATH && mRelativeTo != nullptr) // if it's a path, resolve relative paths
mMap[id] = Utils::FileSystem::createRelativePath(value, mRelativeTo->getStartPath(), true);
#define IS_TRIMCHAR(c) (c == ' ' || c == '\t' || c == '\r' || c == '\n')
if (mGameTypeMap[id] == MD_PATH && mRelativeTo != nullptr) // if it's a path, resolve relative paths
{
if (idx < 0)
{
if (value.size())
{
mIndices[id] = (int8_t)mValues.size();
mValues.push_back(value[0] == '.' && value[1] == '/' ? value : Utils::FileSystem::createRelativePath(value, mRelativeTo->getStartPath(), true));
}
}
else
mValues[idx] = value[0] == '.' && value[1] == '/' ? value : Utils::FileSystem::createRelativePath(value, mRelativeTo->getStartPath(), true);
}
else
mMap[id] = Utils::String::trim(value);
{
if (idx < 0)
{
if (value.size())
{
mIndices[id] = (int8_t)mValues.size();
mValues.push_back(value.size() && IS_TRIMCHAR(value[0]) && IS_TRIMCHAR(value.back()) ? Utils::String::trim(value) : value);
}
}
else
mValues[idx] = value.size() && IS_TRIMCHAR(value[0]) && IS_TRIMCHAR(value.back()) ? Utils::String::trim(value) : value;
}
mWasChanged = true;
}
@@ -386,13 +407,16 @@ const std::string MetaDataList::get(MetaDataId id, bool resolveRelativePaths) co
if (id == MetaDataId::Name)
return mName;
auto it = mMap.find(id);
if (it != mMap.end())
auto idx = mIndices[id];
if (idx >= 0)
// auto it = mMap.find(id);
// if (it != mMap.end())
{
if (resolveRelativePaths && mGameTypeMap[id] == MD_PATH && mRelativeTo != nullptr) // if it's a path, resolve relative paths
return Utils::FileSystem::resolveRelativePath(it->second, mRelativeTo->getStartPath(), true);
return Utils::FileSystem::resolveRelativePath(mValues[idx]/*it->second*/, mRelativeTo->getStartPath(), true);
return it->second;
return mValues[idx]; // it->second;
}
return mDefaultGameMap[id];
+8 -2
View File
@@ -76,7 +76,10 @@ enum MetaDataId
GenreIds = 39,
Family = 40,
Bezel = 41,
Tags = 42
Tags = 42,
// Important : Control value
MetaDataIdCount
};
namespace MetaDataImportType
@@ -182,9 +185,12 @@ private:
std::string mName;
MetaDataListType mType;
std::map<MetaDataId, std::string> mMap;
// std::map<MetaDataId, std::string> mMap;
bool mWasChanged;
SystemData* mRelativeTo;
int8_t mIndices[MetaDataIdCount];
std::vector<std::string> mValues;
static std::vector<MetaDataDecl> mMetaDataDecls;
+16 -3
View File
@@ -110,9 +110,14 @@ SystemData::SystemData(const SystemMetadata& meta, SystemEnvironmentData* envDat
}
if (!Settings::IgnoreGamelist())
parseGamelist(this, fileMap);
{
if (!mHidden && Settings::PackGamelists())
packGamelist(this);
parseGamelist(this, fileMap);
}
if (Settings::RemoveMultiDiskContent())
if (Settings::RemoveMultiDiskContent() || Settings::BuildMultiDiskContentCache())
removeMultiDiskContent(fileMap);
}
else
@@ -820,8 +825,16 @@ bool SystemData::loadConfig(Window* window)
}
pugi::xml_document doc;
pugi::xml_parse_result res = doc.load_file(WINSTRINGW(path).c_str());
auto buffer = Utils::FileSystem::readAllBytes(path);
if (!buffer.size())
{
LOG(LogError) << "Could not open es_systems.cfg file!";
return false;
}
// pugi::xml_parse_result res = doc.load_file(WINSTRINGW(path).c_str());
pugi::xml_parse_result res = doc.load_buffer_inplace(buffer.data(), buffer.size(), pugi::parse_default);
if (!res)
{
LOG(LogError) << "Could not parse es_systems.cfg file!";
+31 -34
View File
@@ -43,6 +43,7 @@
#include "HttpReq.h"
#include <thread>
#include "ZaparooSupport.h"
#include "utils/ThreadPool.h"
#ifdef WIN32
#include <Windows.h>
@@ -444,11 +445,11 @@ void launchStartupGame()
}
}
#include "utils/MathExpr.h"
// #include "utils/MathExpr.h"
int main(int argc, char* argv[])
{
Utils::MathExpr::performUnitTests();
// Utils::MathExpr::performUnitTests();
// signal(SIGABRT, signalHandler);
signal(SIGFPE, signalHandler);
@@ -530,55 +531,54 @@ int main(int argc, char* argv[])
}
#endif
Scripting::fireEvent("start");
// metadata init
HttpReq::resetCookies();
Genres::init();
MetaDataList::initMetadata();
Zaparoo::checkZaparooEnabledAsync();
// Threaded initializations
auto threadPool = new Utils::ThreadPool();
auto vlcInit = threadPool->queueWorkItem([] { VideoVlcComponent::init(); });
threadPool->queueWorkItem([] { ApiSystem::getInstance()->getIpAddress(); });
threadPool->queueWorkItem([] { MetaDataList::initMetadata(); });
threadPool->queueWorkItem([] { MameNames::init(); });
threadPool->queueWorkItem([] { Genres::init(); });
threadPool->queueWorkItem([] { HttpReq::resetCookies(); });
threadPool->start();
Window window;
SystemScreenSaver screensaver(&window);
ViewController::init(&window);
CollectionSystemManager::init(&window);
window.setReloadGamelistsCallback([&window] {
ViewController::reloadAllGames(&window, true, true);
});
VideoVlcComponent::init();
window.setReloadGamelistsCallback([&window] { ViewController::reloadAllGames(&window, true, true); });
window.pushGui(ViewController::get());
if(!window.init(true, false))
if (!window.init(true, false))
{
LOG(LogError) << "Window failed to initialize!";
return 1;
}
Renderer::setWindowResizable(false);
PowerSaver::init();
bool splashScreen = Settings::getInstance()->getBool("SplashScreen");
bool splashScreenProgress = Settings::getInstance()->getBool("SplashScreenProgress");
if (splashScreen)
{
std::string progressText = _("Loading...");
if (splashScreenProgress)
progressText = _("Loading system config...");
window.renderSplashScreen(splashScreenProgress ? _("Loading system config...") : _("Loading..."));
window.renderSplashScreen(progressText);
}
Scripting::fireEvent("start");
MameNames::init();
SystemScreenSaver screensaver(&window);
CollectionSystemManager::init(&window);
Zaparoo::checkZaparooEnabledAsync();
PowerSaver::init();
InputConfig::AssignActionButtons();
if (ApiSystem::getInstance()->isScriptingSupported(ApiSystem::PDFEXTRACTION))
TextureData::PdfHandler = ApiSystem::getInstance();
threadPool->waitAllExcept(vlcInit); // Wait for what's necessary for loadSystemConfigFile
const char* errorMsg = NULL;
if(!loadSystemConfigFile(splashScreen && splashScreenProgress ? &window : nullptr, &errorMsg))
if (!loadSystemConfigFile(splashScreen && splashScreenProgress ? &window : nullptr, &errorMsg))
{
// something went terribly wrong
if(errorMsg == NULL)
if (errorMsg == NULL)
{
LOG(LogError) << "Unknown error occured while parsing system config file.";
Renderer::deinit();
@@ -607,17 +607,11 @@ int main(int argc, char* argv[])
}
#endif
if (ApiSystem::getInstance()->isScriptingSupported(ApiSystem::PDFEXTRACTION))
TextureData::PdfHandler = ApiSystem::getInstance();
ApiSystem::getInstance()->getIpAddress();
// preload what we can right away instead of waiting for the user to select it
// this makes for no delays when accessing content, but a longer startup time
ViewController::get()->preload();
// Initialize input
InputConfig::AssignActionButtons();
InputManager::getInstance()->init();
SDL_StopTextInput();
@@ -635,6 +629,9 @@ int main(int argc, char* argv[])
ViewController::get()->goToStart(true);
}
threadPool->wait();
delete threadPool;
window.closeSplashScreen();
// Check if the device serial number is the same as stored in system.cfg.
+13 -12
View File
@@ -711,14 +711,14 @@ ThemeData::ThemeData(bool temporary)
ThemeFileCache* ThemeFileCache::_instance;
pugi::xml_document& ThemeFileCache::getXmlDocument(const std::string& path)
std::string& ThemeFileCache::getXmlDocument(const std::string& path)
{
std::unique_lock<std::mutex> lock(_lock);
auto it = _cache.find(path);
if (it != _cache.cend())
return *it->second;
return it->second;
/*
pugi::xml_document* doc = new pugi::xml_document();
pugi::xml_parse_result res = doc->load_file(WINSTRINGW(path).c_str());
if (!res)
@@ -726,17 +726,18 @@ pugi::xml_document& ThemeFileCache::getXmlDocument(const std::string& path)
ThemeException error;
throw error << "XML parsing error: \n " << res.description();
}
_cache[path] = doc;
return *doc;
*/
_cache[path] = Utils::FileSystem::readAllText(path);
std::string& buffer = _cache[path];
return buffer;
}
void ThemeFileCache::clear()
{
std::unique_lock<std::mutex> lock(_lock);
for (auto& item : _cache)
delete item.second;
// for (auto& item : _cache)
// delete item.second;
_cache.clear();
}
@@ -805,8 +806,8 @@ void ThemeData::loadFile(const std::string& system, const std::map<std::string,
if (fromFile)
{
pugi::xml_document& cached = ThemeFileCache::getInstance().getXmlDocument(path);
doc.reset(cached);
const std::string& xmlData = ThemeFileCache::getInstance().getXmlDocument(path);
doc.load_buffer(xmlData.c_str(), xmlData.size());
}
else
{
@@ -2777,8 +2778,8 @@ bool ThemeData::appendFile(const std::string& path, bool perGameOverride)
try
{
pugi::xml_document& cached = ThemeFileCache::getInstance().getXmlDocument(path);
includeDoc.reset(cached);
const std::string& xmlData = ThemeFileCache::getInstance().getXmlDocument(path);
includeDoc.load_buffer(xmlData.c_str(), xmlData.size());
}
catch (ThemeException& e)
{
+3 -3
View File
@@ -271,7 +271,7 @@ public:
};
std::map<std::string, Property> properties;
std::unordered_map<std::string, Property> properties;
template<typename T, typename std::enable_if<std::is_same<T, float>::value, int>::type = 0>
const T get(const std::string& prop) const
@@ -585,11 +585,11 @@ public:
}
public:
pugi::xml_document& getXmlDocument(const std::string& path);
std::string& getXmlDocument(const std::string& path);
void clear();
private:
std::map<std::string, pugi::xml_document*> _cache;
std::unordered_map<std::string, std::string> _cache;
std::mutex _lock;
static ThemeFileCache* _instance;
+31 -19
View File
@@ -4,29 +4,41 @@
std::string ThemeVariables::resolvePlaceholders(const char* in) const
{
if (in == nullptr || in[0] == 0)
return in;
if (in == nullptr || in[0] == 0)
return "";
auto begin = strstr(in, "${");
if (begin == nullptr)
return in;
std::string result;
result.reserve(strlen(in));
auto end = strstr(begin, "}");
if (end == nullptr)
return in;
std::string_view view(in);
size_t pos = 0;
std::string inStr(in);
while (true)
{
size_t start = view.find("${", pos);
if (start == std::string_view::npos)
{
result.append(view.substr(pos));
break;
}
const size_t variableBegin = begin - in;
const size_t variableEnd = end - in;
result.append(view.substr(pos, start - pos));
std::string prefix = inStr.substr(0, variableBegin);
std::string replace = inStr.substr(variableBegin + 2, variableEnd - (variableBegin + 2));
std::string suffix = resolvePlaceholders(end + 1);
size_t end = view.find('}', start + 2);
if (end == std::string_view::npos)
{
result.append(view.substr(start));
break;
}
auto it = this->find(replace);
if (it != this->cend())
return prefix + it->second + suffix;
return prefix + "" + suffix;
std::string_view varName = view.substr(start + 2, end - (start + 2));
auto it = this->find(varName);
if (it != this->cend())
result.append(it->second);
pos = end + 1;
}
return result;
}
+1 -1
View File
@@ -6,7 +6,7 @@
#include <string>
#include <map>
class ThemeVariables : public std::map<std::string, std::string>
class ThemeVariables : public std::map<std::string, std::string, std::less<>>
{
public:
std::string resolvePlaceholders(const char* in) const;
+36 -20
View File
@@ -229,32 +229,31 @@ namespace Utils
FINDEX_INFO_LEVELS::FindExInfoBasic, &findData, FINDEX_SEARCH_OPS::FindExSearchNameMatch
, NULL, FIND_FIRST_EX_LARGE_FETCH);
if(hFind != INVALID_HANDLE_VALUE)
if (hFind != INVALID_HANDLE_VALUE)
{
std::string pathPrefix = path + "/";
// loop over all files in the directory
do
{
std::string name = Utils::String::convertFromWideString(findData.cFileName);
// ignore "." and ".."
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && name == "." || name == "..")
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
(findData.cFileName[0] == L'.' && (findData.cFileName[1] == L'\0' || (findData.cFileName[1] == L'.' && findData.cFileName[2] == L'\0'))))
continue;
std::string fullName(getGenericPath(path + "/" + name));
std::string fullName = getGenericPath(pathPrefix + Utils::String::convertFromWideString(findData.cFileName));
FileCache::add(fullName, std::move(FileCache((DWORD)findData.dwFileAttributes)));
if (!includeHidden && (findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN)
continue;
contentList.push_back(fullName);
contentList.push_back(fullName);
if (_recursive && (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
{
for (auto item : getDirContent(fullName, true, includeHidden))
contentList.push_back(item);
}
}
while(FindNextFileW(hFind, &findData));
} while (FindNextFileW(hFind, &findData));
FindClose(hFind);
}
@@ -338,11 +337,11 @@ namespace Utils
{
if (uDriveMask & 1)
{
FileInfo fi;
contentList.emplace_back();
FileInfo& fi = contentList.back();
fi.path = std::string(1, drive) + ":";
fi.hidden = false;
fi.directory = true;
contentList.push_back(std::move(fi));
fi.directory = true;
}
drive++;
@@ -352,9 +351,6 @@ namespace Utils
return contentList;
}
WIN32_FIND_DATAW findData;
std::string wildcard = path + "/*";
@@ -364,6 +360,8 @@ namespace Utils
if (hFind != INVALID_HANDLE_VALUE)
{
std::string pathPrefix = path + "/";
// loop over all files in the directory
do
{
@@ -374,15 +372,14 @@ namespace Utils
(findData.cFileName[0] == '.' && (findData.cFileName[1] == '.' || findData.cFileName[1] == 0)))
continue;
FileInfo fi;
fi.path = path + "/" + Utils::String::convertFromWideString(findData.cFileName);
contentList.emplace_back();
FileInfo& fi = contentList.back();
fi.path = pathPrefix + Utils::String::convertFromWideString(findData.cFileName);
fi.hidden = (findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN;
fi.directory = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;
fi.lastWriteTime = to_time_t(findData.ftLastWriteTime);
FileCache::add(fi.path, std::move(FileCache((DWORD)findData.dwFileAttributes)));
contentList.push_back(std::move(fi));
FileCache::add(fi.path, std::move(FileCache((DWORD)findData.dwFileAttributes)));
}
while (FindNextFileW(hFind, &findData));
@@ -1222,6 +1219,25 @@ namespace Utils
file.seekg(0);
}
std::vector<char> readAllBytes(const std::string& fileName)
{
std::ifstream file(WINSTRINGW(fileName), std::ios::binary | std::ios::ate);
if (!file.is_open())
return {};
std::streamsize size = file.tellg();
if (size <= 0)
return {};
file.seekg(0, std::ios::beg);
std::vector<char> buffer(size);
if (!file.read(buffer.data(), size))
return {};
return buffer;
}
std::list<std::string> readAllLines(const std::string& fileName)
{
std::list<std::string> lines;
+2
View File
@@ -66,6 +66,8 @@ namespace Utils
std::string readAllText(const std::string& fileName);
stringList readAllLines(const std::string& fileName);
std::vector<char> readAllBytes(const std::string& fileName);
void writeAllText(const std::string& fileName, const std::string& text);
bool copyFile(const std::string src, const std::string dst);
void deleteDirectoryFiles(const std::string path, bool deleteDirectory = false);
+6 -1
View File
@@ -413,7 +413,12 @@ namespace Utils
if(strBegin == std::string::npos)
return "";
const size_t strEnd = _string.find_last_not_of(" \t\r\n");
const size_t strEnd = _string.find_last_not_of(" \t\r\n");
const size_t len = strEnd - strBegin + 1;
if (strBegin == 0 && len == _string.length())
return _string;
return _string.substr(strBegin, strEnd - strBegin + 1);
} // trim
+90 -6
View File
@@ -22,6 +22,8 @@ namespace Utils
auto doWork = [&](size_t id)
{
#if WIN32
SetThreadDescription(GetCurrentThread(), L"ThreadPool::thread");
auto mask = (static_cast<DWORD_PTR>(1) << id);
SetThreadAffinityMask(GetCurrentThread(), mask);
#endif
@@ -31,16 +33,17 @@ namespace Utils
_mutex.lock();
if (!mWorkQueue.empty())
{
auto work = mWorkQueue.front();
auto entry = mWorkQueue.front();
mWorkQueue.pop();
_mutex.unlock();
try
{
work();
entry.fn();
}
catch (...) {}
entry.item->mDone.store(true);
mNumWork--;
}
else
@@ -72,14 +75,19 @@ namespace Utils
t.join();
}
void ThreadPool::queueWorkItem(work_function work)
WorkItemPtr ThreadPool::queueWorkItem(work_function work)
{
auto item = std::make_shared<WorkItem>();
_mutex.lock();
mWorkQueue.push(work);
mWorkQueue.push({ work, item });
mAllItems.push_back(item);
mNumWork++;
_mutex.unlock();
}
return item;
}
void ThreadPool::wait()
{
if (!mRunning)
@@ -88,6 +96,10 @@ namespace Utils
mWaiting = true;
while (mNumWork.load() > 0)
std::this_thread::yield();
_mutex.lock();
mAllItems.clear();
_mutex.unlock();
}
void ThreadPool::wait(work_function work, int delay)
@@ -103,9 +115,81 @@ namespace Utils
std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
}
_mutex.lock();
mAllItems.clear();
_mutex.unlock();
}
void WorkItem::wait() const
{
while (!mDone.load())
{
std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
void ThreadPool::cleanupDoneItems()
{
_mutex.lock();
mAllItems.erase(
std::remove_if(mAllItems.begin(), mAllItems.end(), [](const std::shared_ptr<WorkItem>& item) { return item->isDone(); }),
mAllItems.end());
_mutex.unlock();
}
void ThreadPool::waitAll(std::initializer_list<WorkItemPtr> items)
{
if (!mRunning)
start();
for (const auto& item : items)
item->wait();
cleanupDoneItems();
}
void ThreadPool::waitAllExcept(WorkItemPtr excluded)
{
waitAllExcept({ excluded });
}
void ThreadPool::waitAllExcept(std::initializer_list<WorkItemPtr> excluded)
{
if (!mRunning)
start();
std::vector<WorkItemPtr> toWait;
_mutex.lock();
for (auto& item : mAllItems)
{
bool isExcluded = false;
for (const auto& ex : excluded)
{
if (ex == item)
{
isExcluded = true;
break;
}
}
if (!isExcluded)
toWait.push_back(item);
}
_mutex.unlock();
for (const auto& item : toWait)
item->wait();
cleanupDoneItems();
}
void ThreadPool::stop()
{
_mutex.lock();
+33 -2
View File
@@ -9,6 +9,21 @@
namespace Utils
{
class WorkItem
{
public:
WorkItem() : mDone(false) {}
bool isDone() const { return mDone.load(); }
void wait() const;
private:
friend class ThreadPool;
std::atomic<bool> mDone;
};
using WorkItemPtr = std::shared_ptr<WorkItem>;
class ThreadPool
{
public:
@@ -18,9 +33,16 @@ namespace Utils
~ThreadPool();
void start();
void queueWorkItem(work_function work);
WorkItemPtr queueWorkItem(work_function work);
void wait();
void wait(work_function work, int delay = 50);
void waitAll(std::initializer_list<WorkItemPtr> items);
void waitAllExcept(WorkItemPtr excluded);
void waitAllExcept(std::initializer_list<WorkItemPtr> excluded);
void cancel() { mRunning = false; }
void stop();
@@ -29,12 +51,21 @@ namespace Utils
private:
bool mRunning;
bool mWaiting;
std::queue<work_function> mWorkQueue;
struct WorkEntry
{
work_function fn;
WorkItemPtr item;
};
std::queue<WorkEntry> mWorkQueue;
std::atomic<size_t> mNumWork;
std::mutex _mutex;
std::vector<std::thread> mThreads;
int mThreadByCore;
std::vector<std::shared_ptr<WorkItem>> mAllItems;
void cleanupDoneItems();
};
}