Add deferred async scene asset loading

This commit is contained in:
2026-04-02 18:50:41 +08:00
parent dd08d8969e
commit 86144416af
18 changed files with 1806 additions and 97 deletions

View File

@@ -6,12 +6,34 @@
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <XCEngine/Resources/Texture/TextureLoader.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) {
@@ -31,6 +53,17 @@ ResourceManager& ResourceManager::Get() {
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() {
if (m_asyncLoader) {
return;
@@ -51,11 +84,17 @@ void ResourceManager::Shutdown() {
m_asyncLoader->Shutdown();
m_asyncLoader.reset();
}
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
m_assetDatabase.Shutdown();
ResourceFileSystem::Get().Shutdown();
std::lock_guard<std::mutex> inFlightLock(m_inFlightLoadsMutex);
m_inFlightLoads.clear();
}
void ResourceManager::SetResourceRoot(const Containers::String& rootPath) {
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
m_resourceRoot = rootPath;
if (!m_resourceRoot.Empty()) {
ResourceFileSystem::Get().Initialize(rootPath);
@@ -139,31 +178,48 @@ void ResourceManager::AddToCache(ResourceGUID guid, IResource* resource) {
}
void ResourceManager::Unload(ResourceGUID guid) {
std::lock_guard lock(m_mutex);
auto* it = m_resourceCache.Find(guid);
if (it != nullptr) {
IResource* resource = *it;
m_resourceCache.Erase(guid);
m_guidToPath.Erase(guid);
m_memoryUsage -= resource->GetMemorySize();
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_guidToPath.Erase(guid);
m_memoryUsage -= resource->GetMemorySize();
}
}
if (resource != nullptr) {
resource->Release();
}
}
void ResourceManager::UnloadAll() {
std::lock_guard lock(m_mutex);
Containers::Array<IResource*> resourcesToRelease;
{
std::lock_guard lock(m_mutex);
const auto cachedResources = m_resourceCache.GetPairs();
for (const auto& pair : cachedResources) {
if (pair.second != nullptr) {
pair.second->Release();
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_refCounts.Clear();
m_guidToPath.Clear();
m_memoryUsage = 0;
}
for (IResource* resource : resourcesToRelease) {
if (resource != nullptr) {
resource->Release();
}
}
m_resourceCache.Clear();
m_refCounts.Clear();
m_guidToPath.Clear();
m_memoryUsage = 0;
}
void ResourceManager::SetMemoryBudget(size_t bytes) {
@@ -210,7 +266,28 @@ void ResourceManager::LoadAsync(const Containers::String& path, ResourceType typ
void ResourceManager::LoadAsync(const Containers::String& path, ResourceType type,
ImportSettings* settings,
std::function<void(LoadResult)> callback) {
m_asyncLoader->Submit(path, type, settings, callback);
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) {
@@ -245,14 +322,24 @@ Containers::Array<Containers::String> ResourceManager::GetResourcePaths() const
}
void ResourceManager::UnloadGroup(const Containers::Array<ResourceGUID>& guids) {
std::lock_guard lock(m_mutex);
for (const auto& guid : guids) {
auto* it = m_resourceCache.Find(guid);
if (it != nullptr) {
IResource* resource = *it;
m_resourceCache.Erase(guid);
m_guidToPath.Erase(guid);
m_memoryUsage -= resource->GetMemorySize();
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_guidToPath.Erase(guid);
m_memoryUsage -= resource->GetMemorySize();
resourcesToRelease.PushBack(resource);
}
}
}
for (IResource* resource : resourcesToRelease) {
if (resource != nullptr) {
resource->Release();
}
}
@@ -260,12 +347,62 @@ void ResourceManager::UnloadGroup(const Containers::Array<ResourceGUID>& guids)
void ResourceManager::RefreshAssetDatabase() {
if (!m_resourceRoot.Empty()) {
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
m_assetDatabase.Refresh();
}
}
bool ResourceManager::TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const {
return m_assetDatabase.TryGetAssetRef(path, resourceType, outRef);
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
const bool resolved = m_assetDatabase.TryGetAssetRef(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 {
if (!assetRef.IsValid()) {
return false;
}
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
const bool resolved = m_assetDatabase.TryGetPrimaryAssetPath(assetRef.assetGuid, 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,
@@ -273,7 +410,23 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
ImportSettings* settings) {
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);
}
@@ -285,24 +438,125 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
return LoadResult(false, "Loader not found");
}
Containers::String loadPath = path;
AssetDatabase::ResolvedAsset resolvedAsset;
if (!m_resourceRoot.Empty() &&
m_assetDatabase.EnsureArtifact(path, type, resolvedAsset) &&
resolvedAsset.artifactReady) {
loadPath = resolvedAsset.artifactMainPath;
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;
{
std::lock_guard<std::recursive_mutex> ioLock(m_ioMutex);
AssetDatabase::ResolvedAsset resolvedAsset;
if (!m_resourceRoot.Empty() &&
m_assetDatabase.EnsureArtifact(path, type, resolvedAsset) &&
resolvedAsset.artifactReady) {
loadPath = resolvedAsset.artifactMainPath;
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");
}
LoadResult result = loader->Load(loadPath, settings);
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);
m_guidToPath.Insert(guid, path);
{
std::lock_guard lock(m_mutex);
m_guidToPath.Insert(guid, path);
}
completeInFlightLoad(result);
return result;
}