Add deferred async scene asset loading
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user