Files
XCEngine/engine/src/Core/Asset/ResourceManager.cpp

661 lines
20 KiB
C++

#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/ResourceHandle.h>
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Core/IO/ResourceFileSystem.h>
#include <XCEngine/Resources/Material/MaterialLoader.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Resources/UI/UIDocumentLoaders.h>
#include <XCEngine/Resources/Volume/VolumeFieldLoader.h>
#include <exception>
namespace XCEngine {
namespace Resources {
namespace {
std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
bool ShouldTraceResourcePath(const Containers::String& path) {
const std::string text = ToStdString(path);
return text.rfind("builtin://", 0) == 0 ||
text.find("backpack") != std::string::npos ||
text.find("New Material.mat") != std::string::npos;
}
Containers::String EncodeAssetRef(const AssetRef& assetRef) {
if (!assetRef.IsValid()) {
return Containers::String("<invalid>");
}
return assetRef.assetGuid.ToString() + "," +
Containers::String(std::to_string(assetRef.localID).c_str()) + "," +
Containers::String(std::to_string(static_cast<int>(assetRef.resourceType)).c_str());
}
template<typename TLoader>
void RegisterBuiltinLoader(ResourceManager& manager, TLoader& loader) {
if (manager.GetLoader(loader.GetResourceType()) == nullptr) {
manager.RegisterLoader(&loader);
}
}
MaterialLoader g_materialLoader;
MeshLoader g_meshLoader;
ShaderLoader g_shaderLoader;
TextureLoader g_textureLoader;
UIViewLoader g_uiViewLoader;
UIThemeLoader g_uiThemeLoader;
UISchemaLoader g_uiSchemaLoader;
VolumeFieldLoader g_volumeFieldLoader;
} // namespace
ResourceManager& ResourceManager::Get() {
static ResourceManager instance;
return instance;
}
ResourceManager::ScopedDeferredSceneLoad::ScopedDeferredSceneLoad(ResourceManager& manager)
: m_manager(&manager) {
m_manager->BeginDeferredSceneLoad();
}
ResourceManager::ScopedDeferredSceneLoad::~ScopedDeferredSceneLoad() {
if (m_manager != nullptr) {
m_manager->EndDeferredSceneLoad();
}
}
void ResourceManager::Initialize() {
EnsureInitialized();
}
void ResourceManager::EnsureInitialized() {
if (m_asyncLoader) {
return;
}
std::lock_guard<std::mutex> initLock(m_initializeMutex);
if (m_asyncLoader) {
return;
}
Core::UniqueRef<AsyncLoader> asyncLoader = Core::MakeUnique<AsyncLoader>();
asyncLoader->Initialize(2);
RegisterBuiltinLoader(*this, g_materialLoader);
RegisterBuiltinLoader(*this, g_meshLoader);
RegisterBuiltinLoader(*this, g_shaderLoader);
RegisterBuiltinLoader(*this, g_textureLoader);
RegisterBuiltinLoader(*this, g_uiViewLoader);
RegisterBuiltinLoader(*this, g_uiThemeLoader);
RegisterBuiltinLoader(*this, g_uiSchemaLoader);
RegisterBuiltinLoader(*this, g_volumeFieldLoader);
m_assetImportService.Initialize();
m_asyncLoader = std::move(asyncLoader);
}
void ResourceManager::Shutdown() {
UnloadAll();
if (m_asyncLoader) {
m_asyncLoader->Shutdown();
m_asyncLoader.reset();
}
m_assetImportService.Shutdown();
ResourceFileSystem::Get().Shutdown();
m_projectAssetIndex.ResetProjectRoot();
std::lock_guard<std::mutex> inFlightLock(m_inFlightLoadsMutex);
m_inFlightLoads.clear();
}
void ResourceManager::SetResourceRoot(const Containers::String& rootPath) {
EnsureInitialized();
m_resourceRoot = rootPath;
if (!m_resourceRoot.Empty()) {
ResourceFileSystem::Get().Initialize(rootPath);
m_assetImportService.SetProjectRoot(rootPath);
BootstrapProjectAssets();
} else {
m_assetImportService.SetProjectRoot(Containers::String());
ResourceFileSystem::Get().Shutdown();
m_projectAssetIndex.ResetProjectRoot();
}
}
const Containers::String& ResourceManager::GetResourceRoot() const {
return m_resourceRoot;
}
bool ResourceManager::BootstrapProjectAssets() {
if (m_resourceRoot.Empty()) {
return false;
}
const bool bootstrapped = m_assetImportService.BootstrapProject();
m_projectAssetIndex.RefreshFrom(m_assetImportService);
return bootstrapped;
}
void ResourceManager::AddRef(ResourceGUID guid) {
std::lock_guard lock(m_mutex);
auto* it = m_refCounts.Find(guid);
if (it == nullptr) {
m_refCounts.Insert(guid, 1);
} else {
(*it)++;
}
if (!m_resourceCache.Contains(guid)) {
ReloadResource(guid);
}
}
void ResourceManager::Release(ResourceGUID guid) {
std::lock_guard lock(m_mutex);
auto* it = m_refCounts.Find(guid);
if (it != nullptr) {
(*it)--;
if (*it == 0) {
m_refCounts.Erase(guid);
m_cache.OnZeroRefCount(guid);
}
}
}
Core::uint32 ResourceManager::GetRefCount(ResourceGUID guid) const {
auto* it = m_refCounts.Find(guid);
return it != nullptr ? *it : 0;
}
void ResourceManager::RegisterLoader(IResourceLoader* loader) {
std::lock_guard lock(m_mutex);
m_loaders.Insert(loader->GetResourceType(), loader);
}
IResourceLoader* ResourceManager::GetLoader(ResourceType type) const {
auto* it = m_loaders.Find(type);
return it != nullptr ? *it : nullptr;
}
IResourceLoader* ResourceManager::FindLoader(ResourceType type) {
return GetLoader(type);
}
IResource* ResourceManager::FindInCache(ResourceGUID guid) {
std::lock_guard lock(m_mutex);
auto* it = m_resourceCache.Find(guid);
return it != nullptr ? *it : nullptr;
}
void ResourceManager::AddToCache(ResourceGUID guid, IResource* resource) {
std::lock_guard lock(m_mutex);
resource->m_guid = guid;
m_resourceCache.Insert(guid, resource);
m_memoryUsage += resource->GetMemorySize();
m_cache.Add(guid, resource);
if (m_memoryUsage > m_memoryBudget) {
m_cache.OnMemoryPressure(m_memoryUsage - m_memoryBudget);
}
}
void ResourceManager::Unload(ResourceGUID guid) {
IResource* resource = nullptr;
{
std::lock_guard lock(m_mutex);
auto* it = m_resourceCache.Find(guid);
if (it != nullptr) {
resource = *it;
m_resourceCache.Erase(guid);
m_cache.Remove(guid);
m_guidToPath.Erase(guid);
m_memoryUsage -= resource->GetMemorySize();
}
}
if (resource != nullptr) {
resource->Release();
}
}
void ResourceManager::UnloadAll() {
Containers::Array<IResource*> resourcesToRelease;
{
std::lock_guard lock(m_mutex);
const auto cachedResources = m_resourceCache.GetPairs();
resourcesToRelease.Reserve(cachedResources.Size());
for (const auto& pair : cachedResources) {
if (pair.second != nullptr) {
resourcesToRelease.PushBack(pair.second);
}
}
m_resourceCache.Clear();
m_cache.Clear();
m_refCounts.Clear();
m_guidToPath.Clear();
m_memoryUsage = 0;
}
for (IResource* resource : resourcesToRelease) {
if (resource != nullptr) {
resource->Release();
}
}
}
void ResourceManager::SetMemoryBudget(size_t bytes) {
m_memoryBudget = bytes;
}
size_t ResourceManager::GetMemoryUsage() const {
return m_memoryUsage;
}
size_t ResourceManager::GetMemoryBudget() const {
return m_memoryBudget;
}
void ResourceManager::FlushCache() {
m_cache.Flush();
}
IResource* ResourceManager::Find(const Containers::String& path) {
return Find(ResourceGUID::Generate(path));
}
IResource* ResourceManager::Find(ResourceGUID guid) {
return FindInCache(guid);
}
bool ResourceManager::Exists(const Containers::String& path) const {
return Exists(ResourceGUID::Generate(path));
}
bool ResourceManager::Exists(ResourceGUID guid) const {
return m_resourceCache.Contains(guid);
}
Containers::String ResourceManager::ResolvePath(const Containers::String& relativePath) const {
return m_resourceRoot + "/" + relativePath;
}
void ResourceManager::LoadAsync(const Containers::String& path, ResourceType type,
std::function<void(LoadResult)> callback) {
LoadAsync(path, type, nullptr, callback);
}
void ResourceManager::LoadAsync(const Containers::String& path, ResourceType type,
ImportSettings* settings,
std::function<void(LoadResult)> callback) {
EnsureInitialized();
if (!m_asyncLoader) {
if (callback) {
callback(LoadResult("Async loader is not initialized"));
}
return;
}
m_asyncLoader->Submit(path, type, settings, std::move(callback));
}
void ResourceManager::UpdateAsyncLoads() {
if (m_asyncLoader) {
m_asyncLoader->Update();
}
}
bool ResourceManager::IsAsyncLoading() const {
return m_asyncLoader && m_asyncLoader->IsLoading();
}
Core::uint32 ResourceManager::GetAsyncPendingCount() const {
return m_asyncLoader ? m_asyncLoader->GetPendingCount() : 0;
}
void ResourceManager::Unload(const Containers::String& path) {
Unload(ResourceGUID::Generate(path));
}
void ResourceManager::UnloadUnused() {
}
void ResourceManager::UnregisterLoader(ResourceType type) {
m_loaders.Erase(type);
}
void ResourceManager::ReloadResource(ResourceGUID guid) {
auto* pathIt = m_guidToPath.Find(guid);
if (pathIt == nullptr) {
return;
}
const Containers::String path = *pathIt;
auto* typeIt = m_resourceCache.Find(guid);
(void)typeIt;
}
Containers::Array<Containers::String> ResourceManager::GetResourcePaths() const {
Containers::Array<Containers::String> paths;
for (const auto& pair : m_guidToPath) {
paths.PushBack(pair.second);
}
return paths;
}
void ResourceManager::UnloadGroup(const Containers::Array<ResourceGUID>& guids) {
Containers::Array<IResource*> resourcesToRelease;
{
std::lock_guard lock(m_mutex);
resourcesToRelease.Reserve(guids.Size());
for (const auto& guid : guids) {
auto* it = m_resourceCache.Find(guid);
if (it != nullptr) {
IResource* resource = *it;
m_resourceCache.Erase(guid);
m_cache.Remove(guid);
m_guidToPath.Erase(guid);
m_memoryUsage -= resource->GetMemorySize();
resourcesToRelease.PushBack(resource);
}
}
}
for (IResource* resource : resourcesToRelease) {
if (resource != nullptr) {
resource->Release();
}
}
}
void ResourceManager::RefreshProjectAssets() {
if (!m_resourceRoot.Empty()) {
m_assetImportService.Refresh();
m_projectAssetIndex.RefreshFrom(m_assetImportService);
}
}
bool ResourceManager::CanReimportProjectAsset(const Containers::String& path) const {
if (m_resourceRoot.Empty() || path.Empty()) {
return false;
}
ResourceType importType = ResourceType::Unknown;
return m_assetImportService.TryGetImportableResourceType(path, importType);
}
bool ResourceManager::ReimportProjectAsset(const Containers::String& path) {
if (m_resourceRoot.Empty() || path.Empty()) {
return false;
}
UnloadAll();
AssetImportService::ImportedAsset importedAsset;
const bool reimported = m_assetImportService.ReimportAsset(path, importedAsset);
m_projectAssetIndex.RefreshFrom(m_assetImportService);
if (reimported && importedAsset.assetGuid.IsValid() && !importedAsset.relativePath.Empty()) {
m_projectAssetIndex.RememberResolvedPath(importedAsset.assetGuid, importedAsset.relativePath);
}
return reimported;
}
bool ResourceManager::ClearProjectLibraryCache() {
if (m_resourceRoot.Empty()) {
return false;
}
UnloadAll();
const bool cleared = m_assetImportService.ClearLibraryCache();
m_projectAssetIndex.RefreshFrom(m_assetImportService);
return cleared;
}
bool ResourceManager::RebuildProjectAssetCache() {
if (m_resourceRoot.Empty()) {
return false;
}
UnloadAll();
const bool rebuilt = m_assetImportService.RebuildLibraryCache();
m_projectAssetIndex.RefreshFrom(m_assetImportService);
return rebuilt;
}
Containers::String ResourceManager::GetProjectLibraryRoot() const {
return m_assetImportService.GetLibraryRoot();
}
AssetImportService::ImportStatusSnapshot ResourceManager::GetProjectAssetImportStatus() const {
return m_assetImportService.GetLastImportStatus();
}
bool ResourceManager::TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const {
const bool resolved = m_projectAssetIndex.TryGetAssetRef(m_assetImportService, path, resourceType, outRef);
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] TryGetAssetRef path=") +
path +
" type=" +
GetResourceTypeName(resourceType) +
" success=" +
Containers::String(resolved ? "1" : "0") +
" ref=" +
EncodeAssetRef(outRef));
}
return resolved;
}
bool ResourceManager::TryResolveAssetPath(const AssetRef& assetRef, Containers::String& outPath) const {
const bool resolved = m_projectAssetIndex.TryResolveAssetPath(m_assetImportService, assetRef, outPath);
if (resolved && ShouldTraceResourcePath(outPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] TryResolveAssetPath ref=") +
EncodeAssetRef(assetRef) +
" path=" +
outPath);
}
return resolved;
}
void ResourceManager::BeginDeferredSceneLoad() {
++m_deferredSceneLoadDepth;
}
void ResourceManager::EndDeferredSceneLoad() {
const Core::uint32 currentDepth = m_deferredSceneLoadDepth.load();
if (currentDepth == 0) {
return;
}
--m_deferredSceneLoadDepth;
}
bool ResourceManager::IsDeferredSceneLoadEnabled() const {
return m_deferredSceneLoadDepth.load() > 0;
}
LoadResult ResourceManager::LoadResource(const Containers::String& path,
ResourceType type,
ImportSettings* settings) {
EnsureInitialized();
const ResourceGUID guid = ResourceGUID::Generate(path);
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource request path=") +
path +
" type=" +
GetResourceTypeName(type) +
" root=" +
m_resourceRoot);
}
if (IResource* cached = FindInCache(guid)) {
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource cache-hit path=") + path);
}
return LoadResult(cached);
}
IResourceLoader* loader = FindLoader(type);
if (loader == nullptr) {
Debug::Logger::Get().Warning(Debug::LogCategory::FileSystem,
Containers::String("No loader found for resource type: ") +
GetResourceTypeName(type));
return LoadResult(false, "Loader not found");
}
const InFlightLoadKey inFlightKey{ guid, type };
std::shared_ptr<InFlightLoadState> inFlightState;
bool shouldExecuteLoad = false;
{
std::unique_lock<std::mutex> inFlightLock(m_inFlightLoadsMutex);
auto inFlightIt = m_inFlightLoads.find(inFlightKey);
if (inFlightIt == m_inFlightLoads.end()) {
inFlightState = std::make_shared<InFlightLoadState>();
m_inFlightLoads.emplace(inFlightKey, inFlightState);
shouldExecuteLoad = true;
} else {
inFlightState = inFlightIt->second;
}
}
auto completeInFlightLoad = [&](const LoadResult& result) {
{
std::lock_guard<std::mutex> inFlightLock(m_inFlightLoadsMutex);
inFlightState->completed = true;
inFlightState->success = result && result.resource != nullptr;
inFlightState->errorMessage = result.errorMessage;
m_inFlightLoads.erase(inFlightKey);
}
inFlightState->condition.notify_all();
};
if (!shouldExecuteLoad) {
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource wait-inflight path=") +
path +
" type=" +
GetResourceTypeName(type));
}
{
std::unique_lock<std::mutex> inFlightLock(m_inFlightLoadsMutex);
inFlightState->condition.wait(inFlightLock, [&inFlightState]() {
return inFlightState->completed;
});
}
if (IResource* cached = FindInCache(guid)) {
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource in-flight-cache-hit path=") + path);
}
return LoadResult(cached);
}
return LoadResult(
!inFlightState->errorMessage.Empty()
? inFlightState->errorMessage
: Containers::String("In-flight load completed without cached resource"));
}
Containers::String loadPath = path;
AssetImportService::ImportedAsset resolvedAsset;
ResourceType importableType = ResourceType::Unknown;
const bool shouldUseProjectArtifact =
!m_resourceRoot.Empty() &&
m_assetImportService.TryGetImportableResourceType(path, importableType) &&
importableType == type;
if (shouldUseProjectArtifact &&
m_assetImportService.EnsureArtifact(path, type, resolvedAsset) &&
resolvedAsset.artifactReady) {
m_projectAssetIndex.RememberResolvedPath(resolvedAsset.assetGuid, resolvedAsset.relativePath);
loadPath = resolvedAsset.runtimeLoadPath;
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource artifact path=") +
path +
" artifact=" +
loadPath);
}
} else if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource direct path=") +
path +
" loadPath=" +
loadPath);
}
LoadResult result;
try {
result = loader->Load(loadPath, settings);
} catch (const std::exception& exception) {
result = LoadResult(
Containers::String("LoadResource exception: ") +
Containers::String(exception.what()));
} catch (...) {
result = LoadResult("LoadResource exception: unknown");
}
if (!result || result.resource == nullptr) {
Debug::Logger::Get().Error(Debug::LogCategory::FileSystem,
Containers::String("Failed to load resource: ") + path + " - " + result.errorMessage);
completeInFlightLoad(result);
return result;
}
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource success path=") +
path +
" loadPath=" +
loadPath);
}
result.resource->m_path = path;
AddToCache(guid, result.resource);
{
std::lock_guard lock(m_mutex);
m_guidToPath.Insert(guid, path);
}
completeInFlightLoad(result);
return result;
}
} // namespace Resources
} // namespace XCEngine