You've already forked emulationstation-next
mirror of
https://github.com/archr-linux/emulationstation-next.git
synced 2026-07-13 03:19:12 -07:00
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:
+15
-1
@@ -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
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user