#include "ResourceMgr.h" #include "Factories/ResourceLoader.h" #include "spdlog/spdlog.h" #include "File.h" #include "Archive.h" #include "GameVersions.h" #include #include "StormLib.h" namespace Ship { ResourceMgr::ResourceMgr(std::shared_ptr Context, const std::string& MainPath, const std::string& PatchesPath) : Context(Context), bIsRunning(false), FileLoadThread(nullptr) { OTR = std::make_shared(MainPath, PatchesPath, false); gameVersion = OOT_UNKNOWN; if (OTR->IsMainMPQValid()) Start(); } ResourceMgr::~ResourceMgr() { SPDLOG_INFO("destruct ResourceMgr"); Stop(); FileCache.clear(); ResourceCache.clear(); } void ResourceMgr::Start() { const std::lock_guard FileLock(FileLoadMutex); const std::lock_guard ResLock(ResourceLoadMutex); if (!IsRunning()) { bIsRunning = true; FileLoadThread = std::make_shared(&ResourceMgr::LoadFileThread, this); ResourceLoadThread = std::make_shared(&ResourceMgr::LoadResourceThread, this); } } void ResourceMgr::Stop() { if (IsRunning()) { { const std::lock_guard FileLock(FileLoadMutex); const std::lock_guard ResLock(ResourceLoadMutex); bIsRunning = false; } FileLoadNotifier.notify_all(); ResourceLoadNotifier.notify_all(); FileLoadThread->join(); ResourceLoadThread->join(); if (!FileLoadQueue.empty()) { SPDLOG_INFO("Resource manager stopped, but has {} Files left to load.", FileLoadQueue.size()); } if (!ResourceLoadQueue.empty()) { SPDLOG_INFO("Resource manager stopped, but has {} Resources left to load.", FileLoadQueue.size()); } } } bool ResourceMgr::IsRunning() { return bIsRunning && FileLoadThread != nullptr; } bool ResourceMgr::DidLoadSuccessfully() { return OTR != nullptr && OTR->IsMainMPQValid(); } void ResourceMgr::LoadFileThread() { SPDLOG_INFO("Resource Manager LoadFileThread started"); while (true) { std::unique_lock Lock(FileLoadMutex); while (bIsRunning && FileLoadQueue.empty()) { FileLoadNotifier.wait(Lock); } if (!bIsRunning) { break; } std::shared_ptr ToLoad = FileLoadQueue.front(); FileLoadQueue.pop(); OTR->LoadFile(ToLoad->path, true, ToLoad); if (!ToLoad->bHasLoadError) FileCache[ToLoad->path] = ToLoad->bIsLoaded && !ToLoad->bHasLoadError ? ToLoad : nullptr; SPDLOG_DEBUG("Loaded File {} on ResourceMgr thread", ToLoad->path); ToLoad->FileLoadNotifier.notify_all(); } SPDLOG_INFO("Resource Manager LoadFileThread ended"); } void ResourceMgr::LoadResourceThread() { SPDLOG_INFO("Resource Manager LoadResourceThread started"); while (true) { std::unique_lock ResLock(ResourceLoadMutex); while (bIsRunning && ResourceLoadQueue.empty()) { ResourceLoadNotifier.wait(ResLock); } if (!bIsRunning) { break; } std::shared_ptr ToLoad = nullptr; ToLoad = ResourceLoadQueue.front(); ResourceLoadQueue.pop(); // Wait for the underlying File to complete loading { std::unique_lock FileLock(ToLoad->file->FileLoadMutex); while (!ToLoad->file->bIsLoaded && !ToLoad->file->bHasLoadError) { ToLoad->file->FileLoadNotifier.wait(FileLock); } } if (!ToLoad->file->bHasLoadError) { auto UnmanagedRes = ResourceLoader::LoadResource(ToLoad->file); if (UnmanagedRes != nullptr) { UnmanagedRes->resMgr = this; auto Res = std::shared_ptr(UnmanagedRes); if (Res != nullptr) { std::unique_lock Lock(ToLoad->resourceLoadMutex); ToLoad->bHasResourceLoaded = true; ToLoad->resource = Res; ResourceCache[Res->file->path] = Res; SPDLOG_DEBUG("Loaded Resource {} on ResourceMgr thread", ToLoad->file->path); Res->file = nullptr; } else { ToLoad->bHasResourceLoaded = false; ToLoad->resource = nullptr; SPDLOG_ERROR("Resource load FAILED {} on ResourceMgr thread", ToLoad->file->path); } } } else { ToLoad->bHasResourceLoaded = false; ToLoad->resource = nullptr; } ToLoad->resourceLoadNotifier.notify_all(); } SPDLOG_INFO("Resource Manager LoadResourceThread ended"); } uint32_t ResourceMgr::GetGameVersion() { return gameVersion; } void ResourceMgr::SetGameVersion(uint32_t newGameVersion) { gameVersion = newGameVersion; } std::shared_ptr ResourceMgr::LoadFileAsync(const std::string& FilePath) { const std::lock_guard Lock(FileLoadMutex); // File NOT already loaded...? auto fileCacheFind = FileCache.find(FilePath); if (fileCacheFind == FileCache.end()) { SPDLOG_TRACE("Cache miss on File load: {}", FilePath.c_str()); std::shared_ptr ToLoad = std::make_shared(); ToLoad->path = FilePath; FileLoadQueue.push(ToLoad); FileLoadNotifier.notify_all(); return ToLoad; } return fileCacheFind->second; } std::shared_ptr ResourceMgr::LoadFile(const std::string& FilePath) { auto ToLoad = LoadFileAsync(FilePath); // Wait for the File to actually be loaded if we are told to block. std::unique_lock Lock(ToLoad->FileLoadMutex); while (!ToLoad->bIsLoaded && !ToLoad->bHasLoadError) { ToLoad->FileLoadNotifier.wait(Lock); } return ToLoad; } std::shared_ptr ResourceMgr::GetCachedFile(const char* FilePath) const { auto resCacheFind = ResourceCache.find(FilePath); if (resCacheFind != ResourceCache.end() && resCacheFind->second.use_count() > 0) { return resCacheFind->second; } else return nullptr; } std::shared_ptr ResourceMgr::LoadResource(const char* FilePath) { auto Res = LoadResourceAsync(FilePath); if (std::holds_alternative>(Res)) return std::get>(Res); auto& Promise = std::get>(Res); if (!Promise->bHasResourceLoaded) { std::unique_lock Lock(Promise->resourceLoadMutex); while (!Promise->bHasResourceLoaded) { Promise->resourceLoadNotifier.wait(Lock); } } return Promise->resource; } std::variant, std::shared_ptr> ResourceMgr::LoadResourceAsync(const char* FilePath) { if (FilePath[0] == '_' && FilePath[1] == '_' && FilePath[2] == 'O' && FilePath[3] == 'T' && FilePath[4] == 'R' && FilePath[5] == '_' && FilePath[6] == '_') FilePath += 7; const std::lock_guard ResLock(ResourceLoadMutex); auto resCacheFind = ResourceCache.find(FilePath); if (resCacheFind == ResourceCache.end() || resCacheFind->second->isDirty/* || !FileData->bIsLoaded*/) { if (resCacheFind == ResourceCache.end()) { SPDLOG_TRACE("Cache miss on Resource load: {}", FilePath); } std::shared_ptr Promise = std::make_shared(); std::shared_ptr FileData = LoadFile(FilePath); Promise->file = FileData; if (Promise->file->bHasLoadError) { Promise->bHasResourceLoaded = true; } else { Promise->bHasResourceLoaded = false; ResourceLoadQueue.push(Promise); ResourceLoadNotifier.notify_all(); } return Promise; } else { return resCacheFind->second; } } std::shared_ptr>> ResourceMgr::CacheDirectoryAsync(const std::string& SearchMask) { auto loadedList = std::make_shared>>(); auto fileList = OTR->ListFiles(SearchMask); for (DWORD i = 0; i < fileList.size(); i++) { auto resource = LoadResourceAsync(fileList.operator[](i).cFileName); if (std::holds_alternative>(resource)) { auto promise = std::make_shared(); promise->bHasResourceLoaded = true; promise->resource = std::get>(resource); resource = promise; } loadedList->push_back(std::get>(resource)); } return loadedList; } std::shared_ptr>> ResourceMgr::CacheDirectory(const std::string& SearchMask) { auto PromiseList = CacheDirectoryAsync(SearchMask); auto LoadedList = std::make_shared>>(); for (size_t i = 0; i < PromiseList->size(); i++) { auto Promise = PromiseList->at(i); std::unique_lock Lock(Promise->resourceLoadMutex); while (!Promise->bHasResourceLoaded) { Promise->resourceLoadNotifier.wait(Lock); } LoadedList->push_back(Promise->resource); } return LoadedList; } std::shared_ptr>> ResourceMgr::DirtyDirectory(std::string SearchMask) { auto PromiseList = CacheDirectoryAsync(SearchMask); auto LoadedList = std::make_shared>>(); for (size_t i = 0; i < PromiseList->size(); i++) { auto Promise = PromiseList->at(i); std::unique_lock Lock(Promise->resourceLoadMutex); while (!Promise->bHasResourceLoaded) { Promise->resourceLoadNotifier.wait(Lock); } if (Promise->resource != nullptr) Promise->resource->isDirty = true; LoadedList->push_back(Promise->resource); } return LoadedList; } std::shared_ptr> ResourceMgr::ListFiles(std::string SearchMask) { auto result = std::make_shared>(); auto fileList = OTR->ListFiles(SearchMask); for (DWORD i = 0; i < fileList.size(); i++) { result->push_back(fileList[i].cFileName); } return result; } void ResourceMgr::InvalidateResourceCache() { ResourceCache.clear(); } const std::string* ResourceMgr::HashToString(uint64_t Hash) const { return OTR->HashToString(Hash); } }