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

@@ -5,6 +5,7 @@
#include <XCEngine/Core/Asset/ResourceHandle.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <memory>
#include <string>
namespace XCEngine {
@@ -14,8 +15,8 @@ class MeshFilterComponent : public Component {
public:
std::string GetName() const override { return "MeshFilter"; }
Resources::Mesh* GetMesh() const { return m_mesh.Get(); }
const Resources::ResourceHandle<Resources::Mesh>& GetMeshHandle() const { return m_mesh; }
Resources::Mesh* GetMesh() const;
const Resources::ResourceHandle<Resources::Mesh>& GetMeshHandle() const;
const std::string& GetMeshPath() const { return m_meshPath; }
const Resources::AssetRef& GetMeshAssetRef() const { return m_meshRef; }
@@ -28,9 +29,15 @@ public:
void Deserialize(std::istream& is) override;
private:
struct PendingMeshLoadState;
void BeginAsyncMeshLoad(const std::string& meshPath);
void ResolvePendingMesh();
Resources::ResourceHandle<Resources::Mesh> m_mesh;
std::string m_meshPath;
Resources::AssetRef m_meshRef;
std::shared_ptr<PendingMeshLoadState> m_pendingMeshLoad;
};
} // namespace Components

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Resources/Material/Material.h>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
@@ -42,12 +43,17 @@ public:
void Deserialize(std::istream& is) override;
private:
struct PendingMaterialLoadState;
void BeginAsyncMaterialLoad(size_t index, const std::string& materialPath);
void ResolvePendingMaterials();
void EnsureMaterialSlot(size_t index);
static std::string MaterialPathFromHandle(const Resources::ResourceHandle<Resources::Material>& material);
std::vector<Resources::ResourceHandle<Resources::Material>> m_materials;
std::vector<std::string> m_materialPaths;
std::vector<Resources::AssetRef> m_materialRefs;
std::vector<std::shared_ptr<PendingMaterialLoadState>> m_pendingMaterialLoads;
bool m_castShadows = true;
bool m_receiveShadows = true;
uint32_t m_renderLayer = 0;

View File

@@ -2,10 +2,13 @@
#include <XCEngine/Core/IO/IResourceLoader.h>
#include <XCEngine/Core/Asset/ImportSettings.h>
#include <XCEngine/Core/Containers/Array.h>
#include <XCEngine/Threading/Mutex.h>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <functional>
#include <mutex>
#include <thread>
#include <vector>
namespace XCEngine {
namespace Resources {
@@ -50,6 +53,14 @@ private:
static Core::uint64 GenerateRequestId();
};
struct CompletedLoadRequest {
LoadRequest request;
LoadResult result;
CompletedLoadRequest(LoadRequest inRequest, LoadResult inResult)
: request(std::move(inRequest)), result(std::move(inResult)) {}
};
class AsyncLoader {
public:
static AsyncLoader& Get();
@@ -76,18 +87,21 @@ public:
private:
void SubmitInternal(LoadRequest request);
IResourceLoader* FindLoader(ResourceType type) const;
void WorkerThread();
void QueueCompleted(LoadRequest request, LoadResult result);
Threading::Mutex m_queueMutex;
Containers::Array<LoadRequest> m_pendingQueue;
Threading::Mutex m_completedMutex;
Containers::Array<LoadRequest> m_completedQueue;
std::mutex m_queueMutex;
std::condition_variable m_pendingCondition;
std::deque<LoadRequest> m_pendingQueue;
std::mutex m_completedMutex;
std::deque<CompletedLoadRequest> m_completedQueue;
std::vector<std::thread> m_workerThreads;
std::atomic<bool> m_running{false};
std::atomic<Core::uint32> m_pendingCount{0};
std::atomic<Core::uint32> m_completedCount{0};
Core::uint32 m_totalRequested = 0;
std::atomic<Core::uint64> m_totalRequested{0};
};
} // namespace Resources

View File

@@ -11,6 +11,11 @@
#include <XCEngine/Core/Containers/HashMap.h>
#include <XCEngine/Threading/Mutex.h>
#include <XCEngine/Debug/Logger.h>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <type_traits>
namespace XCEngine {
@@ -18,6 +23,17 @@ namespace Resources {
class ResourceManager {
public:
class ScopedDeferredSceneLoad {
public:
explicit ScopedDeferredSceneLoad(ResourceManager& manager = ResourceManager::Get());
ScopedDeferredSceneLoad(const ScopedDeferredSceneLoad&) = delete;
ScopedDeferredSceneLoad& operator=(const ScopedDeferredSceneLoad&) = delete;
~ScopedDeferredSceneLoad();
private:
ResourceManager* m_manager = nullptr;
};
static ResourceManager& Get();
void Initialize();
@@ -43,7 +59,7 @@ public:
static_assert(std::is_base_of_v<IResource, T>, "T must derive from IResource");
Containers::String path;
if (!assetRef.IsValid() || !m_assetDatabase.TryGetPrimaryAssetPath(assetRef.assetGuid, path)) {
if (!TryResolveAssetPath(assetRef, path)) {
return ResourceHandle<T>();
}
@@ -54,6 +70,9 @@ public:
std::function<void(LoadResult)> callback);
void LoadAsync(const Containers::String& path, ResourceType type, ImportSettings* settings,
std::function<void(LoadResult)> callback);
void UpdateAsyncLoads();
bool IsAsyncLoading() const;
Core::uint32 GetAsyncPendingCount() const;
void Unload(const Containers::String& path);
void Unload(ResourceGUID guid);
@@ -98,8 +117,35 @@ public:
void UnloadGroup(const Containers::Array<ResourceGUID>& guids);
void RefreshAssetDatabase();
bool TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const;
bool TryResolveAssetPath(const AssetRef& assetRef, Containers::String& outPath) const;
void BeginDeferredSceneLoad();
void EndDeferredSceneLoad();
bool IsDeferredSceneLoadEnabled() const;
private:
struct InFlightLoadKey {
ResourceGUID guid;
ResourceType type = ResourceType::Unknown;
bool operator==(const InFlightLoadKey& other) const {
return guid == other.guid && type == other.type;
}
};
struct InFlightLoadKeyHasher {
size_t operator()(const InFlightLoadKey& key) const noexcept {
return std::hash<ResourceGUID>{}(key.guid) ^
(static_cast<size_t>(key.type) << 1);
}
};
struct InFlightLoadState {
bool completed = false;
bool success = false;
Containers::String errorMessage;
std::condition_variable condition;
};
ResourceManager() = default;
~ResourceManager() = default;
@@ -122,8 +168,13 @@ private:
ResourceCache m_cache;
Core::UniqueRef<AsyncLoader> m_asyncLoader;
Threading::Mutex m_mutex;
mutable std::recursive_mutex m_ioMutex;
std::mutex m_inFlightLoadsMutex;
std::unordered_map<InFlightLoadKey, std::shared_ptr<InFlightLoadState>, InFlightLoadKeyHasher> m_inFlightLoads;
std::atomic<Core::uint32> m_deferredSceneLoadDepth{0};
friend class ResourceHandleBase;
friend class AsyncLoader;
};
} // namespace Resources

View File

@@ -1,6 +1,8 @@
#include "Components/MeshFilterComponent.h"
#include "Components/GameObject.h"
#include "Core/Asset/ResourceManager.h"
#include "Debug/Logger.h"
#include <sstream>
@@ -13,6 +15,11 @@ std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
bool ShouldTraceMeshPath(const std::string& path) {
return path.rfind("builtin://", 0) == 0 ||
path.find("backpack") != std::string::npos;
}
std::string EncodeAssetRef(const Resources::AssetRef& assetRef) {
if (!assetRef.IsValid()) {
return std::string();
@@ -37,9 +44,40 @@ bool TryDecodeAssetRef(const std::string& value, Resources::AssetRef& outRef) {
return outRef.IsValid();
}
std::string DescribeOwner(const MeshFilterComponent& component) {
const GameObject* owner = component.GetGameObject();
if (owner == nullptr) {
return "<no-owner>";
}
return std::string("id=") + std::to_string(owner->GetID()) + ",name=" + owner->GetName();
}
void TraceMeshFilter(const MeshFilterComponent& component, const std::string& message) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String(("[MeshFilter] owner={" + DescribeOwner(component) + "} " + message).c_str()));
}
} // namespace
struct MeshFilterComponent::PendingMeshLoadState {
Resources::LoadResult result;
bool completed = false;
};
Resources::Mesh* MeshFilterComponent::GetMesh() const {
const_cast<MeshFilterComponent*>(this)->ResolvePendingMesh();
return m_mesh.Get();
}
const Resources::ResourceHandle<Resources::Mesh>& MeshFilterComponent::GetMeshHandle() const {
const_cast<MeshFilterComponent*>(this)->ResolvePendingMesh();
return m_mesh;
}
void MeshFilterComponent::SetMeshPath(const std::string& meshPath) {
m_pendingMeshLoad.reset();
m_meshPath = meshPath;
if (m_meshPath.empty()) {
m_mesh.Reset();
@@ -51,9 +89,18 @@ void MeshFilterComponent::SetMeshPath(const std::string& meshPath) {
if (!Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, m_meshRef)) {
m_meshRef.Reset();
}
if (ShouldTraceMeshPath(m_meshPath)) {
TraceMeshFilter(
*this,
std::string("SetMeshPath path=") + m_meshPath +
" ref=" + EncodeAssetRef(m_meshRef) +
" loaded=" + (m_mesh.Get() != nullptr ? "1" : "0"));
}
}
void MeshFilterComponent::SetMesh(const Resources::ResourceHandle<Resources::Mesh>& mesh) {
m_pendingMeshLoad.reset();
m_mesh = mesh;
m_meshPath = mesh.Get() != nullptr ? ToStdString(mesh->GetPath()) : std::string();
if (m_meshPath.empty()) {
@@ -68,6 +115,7 @@ void MeshFilterComponent::SetMesh(Resources::Mesh* mesh) {
}
void MeshFilterComponent::ClearMesh() {
m_pendingMeshLoad.reset();
m_mesh.Reset();
m_meshPath.clear();
m_meshRef.Reset();
@@ -79,6 +127,7 @@ void MeshFilterComponent::Serialize(std::ostream& os) const {
}
void MeshFilterComponent::Deserialize(std::istream& is) {
m_pendingMeshLoad.reset();
m_mesh.Reset();
m_meshPath.clear();
m_meshRef.Reset();
@@ -106,8 +155,30 @@ void MeshFilterComponent::Deserialize(std::istream& is) {
}
}
if (ShouldTraceMeshPath(pendingMeshPath) || pendingMeshRef.IsValid()) {
TraceMeshFilter(
*this,
std::string("Deserialize path=") + pendingMeshPath +
" ref=" + EncodeAssetRef(pendingMeshRef) +
" deferred=" + (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled() ? "1" : "0"));
}
if (pendingMeshRef.IsValid()) {
m_meshRef = pendingMeshRef;
if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) {
Containers::String resolvedPath;
if (Resources::ResourceManager::Get().TryResolveAssetPath(pendingMeshRef, resolvedPath)) {
m_meshPath = ToStdString(resolvedPath);
if (ShouldTraceMeshPath(m_meshPath)) {
TraceMeshFilter(*this, std::string("Resolved meshRef to path=") + m_meshPath);
}
BeginAsyncMeshLoad(m_meshPath);
return;
}
TraceMeshFilter(*this, std::string("Failed to resolve meshRef, fallback path=") + pendingMeshPath);
}
m_mesh = Resources::ResourceManager::Get().Load<Resources::Mesh>(pendingMeshRef);
if (m_mesh.Get() != nullptr) {
m_meshPath = ToStdString(m_mesh->GetPath());
@@ -115,9 +186,79 @@ void MeshFilterComponent::Deserialize(std::istream& is) {
m_meshPath = pendingMeshPath;
}
} else if (!pendingMeshPath.empty()) {
if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) {
m_meshPath = pendingMeshPath;
if (!Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, m_meshRef)) {
m_meshRef.Reset();
}
BeginAsyncMeshLoad(m_meshPath);
return;
}
SetMeshPath(pendingMeshPath);
}
}
void MeshFilterComponent::BeginAsyncMeshLoad(const std::string& meshPath) {
if (meshPath.empty()) {
m_pendingMeshLoad.reset();
m_mesh.Reset();
return;
}
m_mesh.Reset();
m_pendingMeshLoad = std::make_shared<PendingMeshLoadState>();
if (ShouldTraceMeshPath(meshPath)) {
TraceMeshFilter(*this, std::string("BeginAsyncMeshLoad path=") + meshPath);
}
std::weak_ptr<PendingMeshLoadState> weakState = m_pendingMeshLoad;
Resources::ResourceManager::Get().LoadAsync(meshPath.c_str(),
Resources::ResourceType::Mesh,
[weakState](Resources::LoadResult result) {
if (std::shared_ptr<PendingMeshLoadState> state = weakState.lock()) {
state->result = std::move(result);
state->completed = true;
}
});
}
void MeshFilterComponent::ResolvePendingMesh() {
if (!m_pendingMeshLoad || !m_pendingMeshLoad->completed) {
return;
}
std::shared_ptr<PendingMeshLoadState> completedLoad = std::move(m_pendingMeshLoad);
m_pendingMeshLoad.reset();
if (!completedLoad->result || completedLoad->result.resource == nullptr) {
if (ShouldTraceMeshPath(m_meshPath)) {
TraceMeshFilter(
*this,
std::string("ResolvePendingMesh failed path=") + m_meshPath +
" error=" + ToStdString(completedLoad->result.errorMessage));
}
return;
}
m_mesh = Resources::ResourceHandle<Resources::Mesh>(
static_cast<Resources::Mesh*>(completedLoad->result.resource));
if (m_mesh.Get() == nullptr) {
return;
}
m_meshPath = ToStdString(m_mesh->GetPath());
if (!Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, m_meshRef)) {
m_meshRef.Reset();
}
if (ShouldTraceMeshPath(m_meshPath)) {
TraceMeshFilter(
*this,
std::string("ResolvePendingMesh success path=") + m_meshPath +
" ref=" + EncodeAssetRef(m_meshRef) +
" vertices=" + std::to_string(m_mesh->GetVertexCount()));
}
}
} // namespace Components
} // namespace XCEngine

View File

@@ -1,6 +1,8 @@
#include "Components/MeshRendererComponent.h"
#include "Components/GameObject.h"
#include "Core/Asset/ResourceManager.h"
#include "Debug/Logger.h"
#include <sstream>
@@ -13,6 +15,12 @@ std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
bool ShouldTraceMaterialPath(const std::string& path) {
return path.rfind("builtin://", 0) == 0 ||
path.find("backpack") != std::string::npos ||
path.find("New Material.mat") != std::string::npos;
}
std::string EncodeAssetRef(const Resources::AssetRef& assetRef) {
if (!assetRef.IsValid()) {
return std::string();
@@ -83,13 +91,66 @@ std::vector<Resources::AssetRef> SplitMaterialRefs(const std::string& value) {
return refs;
}
std::string JoinMaterialPaths(const std::vector<std::string>& values) {
std::ostringstream stream;
for (size_t index = 0; index < values.size(); ++index) {
if (index > 0) {
stream << "|";
}
stream << values[index];
}
return stream.str();
}
std::string JoinMaterialRefs(const std::vector<Resources::AssetRef>& values) {
std::ostringstream stream;
for (size_t index = 0; index < values.size(); ++index) {
if (index > 0) {
stream << "|";
}
stream << EncodeAssetRef(values[index]);
}
return stream.str();
}
bool ShouldTraceAnyMaterialPath(const std::vector<std::string>& values) {
for (const std::string& value : values) {
if (ShouldTraceMaterialPath(value)) {
return true;
}
}
return false;
}
std::string DescribeOwner(const MeshRendererComponent& component) {
const GameObject* owner = component.GetGameObject();
if (owner == nullptr) {
return "<no-owner>";
}
return std::string("id=") + std::to_string(owner->GetID()) + ",name=" + owner->GetName();
}
void TraceMeshRenderer(const MeshRendererComponent& component, const std::string& message) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String(("[MeshRenderer] owner={" + DescribeOwner(component) + "} " + message).c_str()));
}
} // namespace
struct MeshRendererComponent::PendingMaterialLoadState {
Resources::LoadResult result;
bool completed = false;
};
Resources::Material* MeshRendererComponent::GetMaterial(size_t index) const {
const_cast<MeshRendererComponent*>(this)->ResolvePendingMaterials();
return index < m_materials.size() ? m_materials[index].Get() : nullptr;
}
const Resources::ResourceHandle<Resources::Material>& MeshRendererComponent::GetMaterialHandle(size_t index) const {
const_cast<MeshRendererComponent*>(this)->ResolvePendingMaterials();
static const Resources::ResourceHandle<Resources::Material> kNullHandle;
return index < m_materials.size() ? m_materials[index] : kNullHandle;
}
@@ -101,6 +162,7 @@ const std::string& MeshRendererComponent::GetMaterialPath(size_t index) const {
void MeshRendererComponent::SetMaterialPath(size_t index, const std::string& materialPath) {
EnsureMaterialSlot(index);
m_pendingMaterialLoads[index].reset();
m_materialPaths[index] = materialPath;
if (materialPath.empty()) {
m_materials[index].Reset();
@@ -112,10 +174,20 @@ void MeshRendererComponent::SetMaterialPath(size_t index, const std::string& mat
if (!Resources::ResourceManager::Get().TryGetAssetRef(materialPath.c_str(), Resources::ResourceType::Material, m_materialRefs[index])) {
m_materialRefs[index].Reset();
}
if (ShouldTraceMaterialPath(materialPath)) {
TraceMeshRenderer(
*this,
std::string("SetMaterialPath slot=") + std::to_string(index) +
" path=" + materialPath +
" ref=" + EncodeAssetRef(m_materialRefs[index]) +
" loaded=" + (m_materials[index].Get() != nullptr ? "1" : "0"));
}
}
void MeshRendererComponent::SetMaterial(size_t index, const Resources::ResourceHandle<Resources::Material>& material) {
EnsureMaterialSlot(index);
m_pendingMaterialLoads[index].reset();
m_materials[index] = material;
m_materialPaths[index] = MaterialPathFromHandle(material);
if (m_materialPaths[index].empty() ||
@@ -132,6 +204,8 @@ void MeshRendererComponent::SetMaterials(const std::vector<Resources::ResourceHa
m_materials = materials;
m_materialPaths.resize(materials.size());
m_materialRefs.resize(materials.size());
m_pendingMaterialLoads.clear();
m_pendingMaterialLoads.resize(materials.size());
for (size_t i = 0; i < materials.size(); ++i) {
m_materialPaths[i] = MaterialPathFromHandle(materials[i]);
if (m_materialPaths[i].empty() ||
@@ -145,6 +219,7 @@ void MeshRendererComponent::ClearMaterials() {
m_materials.clear();
m_materialPaths.clear();
m_materialRefs.clear();
m_pendingMaterialLoads.clear();
}
void MeshRendererComponent::Serialize(std::ostream& os) const {
@@ -194,6 +269,7 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
m_materialPaths = SplitMaterialPaths(value);
m_materials.resize(m_materialPaths.size());
m_materialRefs.resize(m_materialPaths.size());
m_pendingMaterialLoads.resize(m_materialPaths.size());
} else if (key == "materialRefs") {
pendingMaterialRefs = SplitMaterialRefs(value);
} else if (key == "castShadows") {
@@ -209,28 +285,149 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
m_materialPaths.resize(pendingMaterialRefs.size());
m_materials.resize(pendingMaterialRefs.size());
m_materialRefs.resize(pendingMaterialRefs.size());
m_pendingMaterialLoads.resize(pendingMaterialRefs.size());
}
if (ShouldTraceAnyMaterialPath(m_materialPaths)) {
TraceMeshRenderer(
*this,
std::string("Deserialize paths=") + JoinMaterialPaths(m_materialPaths) +
" refs=" + JoinMaterialRefs(pendingMaterialRefs) +
" deferred=" + (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled() ? "1" : "0"));
}
for (size_t i = 0; i < m_materialPaths.size(); ++i) {
bool restoredOrQueued = false;
if (i < pendingMaterialRefs.size() && pendingMaterialRefs[i].IsValid()) {
m_materialRefs[i] = pendingMaterialRefs[i];
m_materials[i] = Resources::ResourceManager::Get().Load<Resources::Material>(pendingMaterialRefs[i]);
if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) {
Containers::String resolvedPath;
if (Resources::ResourceManager::Get().TryResolveAssetPath(pendingMaterialRefs[i], resolvedPath)) {
m_materialPaths[i] = ToStdString(resolvedPath);
if (ShouldTraceMaterialPath(m_materialPaths[i])) {
TraceMeshRenderer(
*this,
std::string("Resolved materialRef slot=") + std::to_string(i) +
" path=" + m_materialPaths[i]);
}
BeginAsyncMaterialLoad(i, m_materialPaths[i]);
restoredOrQueued = true;
}
}
if (!restoredOrQueued) {
m_materials[i] = Resources::ResourceManager::Get().Load<Resources::Material>(pendingMaterialRefs[i]);
}
if (m_materials[i].Get() != nullptr) {
m_materialPaths[i] = MaterialPathFromHandle(m_materials[i]);
restoredOrQueued = true;
}
if (restoredOrQueued) {
continue;
}
}
if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) {
if (!m_materialPaths[i].empty()) {
if (!m_materialRefs[i].IsValid() &&
!Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[i].c_str(),
Resources::ResourceType::Material,
m_materialRefs[i])) {
m_materialRefs[i].Reset();
}
BeginAsyncMaterialLoad(i, m_materialPaths[i]);
}
continue;
}
SetMaterialPath(i, m_materialPaths[i]);
}
}
void MeshRendererComponent::BeginAsyncMaterialLoad(size_t index, const std::string& materialPath) {
EnsureMaterialSlot(index);
if (materialPath.empty()) {
m_pendingMaterialLoads[index].reset();
m_materials[index].Reset();
return;
}
m_materials[index].Reset();
m_pendingMaterialLoads[index] = std::make_shared<PendingMaterialLoadState>();
if (ShouldTraceMaterialPath(materialPath)) {
TraceMeshRenderer(
*this,
std::string("BeginAsyncMaterialLoad slot=") + std::to_string(index) +
" path=" + materialPath);
}
std::weak_ptr<PendingMaterialLoadState> weakState = m_pendingMaterialLoads[index];
Resources::ResourceManager::Get().LoadAsync(materialPath.c_str(),
Resources::ResourceType::Material,
[weakState](Resources::LoadResult result) {
if (std::shared_ptr<PendingMaterialLoadState> state = weakState.lock()) {
state->result = std::move(result);
state->completed = true;
}
});
}
void MeshRendererComponent::ResolvePendingMaterials() {
for (size_t index = 0; index < m_pendingMaterialLoads.size(); ++index) {
if (!m_pendingMaterialLoads[index] || !m_pendingMaterialLoads[index]->completed) {
continue;
}
std::shared_ptr<PendingMaterialLoadState> completedLoad = std::move(m_pendingMaterialLoads[index]);
m_pendingMaterialLoads[index].reset();
if (!completedLoad->result || completedLoad->result.resource == nullptr) {
if (ShouldTraceMaterialPath(m_materialPaths[index])) {
TraceMeshRenderer(
*this,
std::string("ResolvePendingMaterial failed slot=") + std::to_string(index) +
" path=" + m_materialPaths[index] +
" error=" + ToStdString(completedLoad->result.errorMessage));
}
continue;
}
m_materials[index] = Resources::ResourceHandle<Resources::Material>(
static_cast<Resources::Material*>(completedLoad->result.resource));
if (m_materials[index].Get() == nullptr) {
continue;
}
m_materialPaths[index] = MaterialPathFromHandle(m_materials[index]);
if (!Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[index].c_str(),
Resources::ResourceType::Material,
m_materialRefs[index])) {
m_materialRefs[index].Reset();
}
if (ShouldTraceMaterialPath(m_materialPaths[index])) {
TraceMeshRenderer(
*this,
std::string("ResolvePendingMaterial success slot=") + std::to_string(index) +
" path=" + m_materialPaths[index] +
" ref=" + EncodeAssetRef(m_materialRefs[index]));
}
}
}
void MeshRendererComponent::EnsureMaterialSlot(size_t index) {
if (index >= m_materials.size()) {
m_materials.resize(index + 1);
}
if (index >= m_materialPaths.size()) {
m_materialPaths.resize(index + 1);
}
if (index >= m_materialRefs.size()) {
m_materialRefs.resize(index + 1);
}
if (index >= m_pendingMaterialLoads.size()) {
m_pendingMaterialLoads.resize(index + 1);
}
}
std::string MeshRendererComponent::MaterialPathFromHandle(const Resources::ResourceHandle<Resources::Material>& material) {

View File

@@ -1,6 +1,7 @@
#include <XCEngine/Core/Asset/AssetDatabase.h>
#include <XCEngine/Core/Asset/ArtifactFormats.h>
#include <XCEngine/Debug/Logger.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Texture/TextureLoader.h>
@@ -23,6 +24,17 @@ std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
bool ShouldTraceAssetPath(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;
}
bool HasVirtualPathScheme(const Containers::String& value) {
return ToStdString(value).find("://") != std::string::npos;
}
Containers::String ToContainersString(const std::string& value) {
return Containers::String(value.c_str());
}
@@ -368,6 +380,10 @@ bool AssetDatabase::ResolvePath(const Containers::String& requestPath,
return false;
}
if (HasVirtualPathScheme(requestPath)) {
return false;
}
fs::path inputPath(requestPath.CStr());
if (inputPath.is_absolute()) {
outAbsolutePath = NormalizePathString(inputPath);
@@ -863,11 +879,24 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
Containers::String absolutePath;
Containers::String relativePath;
if (!ResolvePath(requestPath, absolutePath, relativePath) || relativePath.Empty()) {
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[AssetDatabase] EnsureArtifact unresolved path=") + requestPath);
}
return false;
}
const fs::path absoluteFsPath(absolutePath.CStr());
if (!fs::exists(absoluteFsPath)) {
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[AssetDatabase] EnsureArtifact missing source path=") +
requestPath +
" absolute=" +
absolutePath);
}
return false;
}
@@ -876,8 +905,31 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
return false;
}
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[AssetDatabase] EnsureArtifact source path=") +
requestPath +
" guid=" +
sourceRecord.guid.ToString() +
" importer=" +
sourceRecord.importerName +
" relative=" +
sourceRecord.relativePath);
}
const ResourceType primaryType = GetPrimaryResourceTypeForImporter(sourceRecord.importerName);
if (primaryType == ResourceType::Unknown || primaryType != requestedType) {
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[AssetDatabase] EnsureArtifact type-mismatch path=") +
requestPath +
" requested=" +
GetResourceTypeName(requestedType) +
" importerType=" +
GetResourceTypeName(primaryType));
}
return false;
}
@@ -888,8 +940,18 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
}
if (ShouldReimport(sourceRecord, artifactRecord)) {
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[AssetDatabase] EnsureArtifact reimport path=") + requestPath);
}
ArtifactRecord rebuiltRecord;
if (!ImportAsset(sourceRecord, rebuiltRecord)) {
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Error(
Debug::LogCategory::FileSystem,
Containers::String("[AssetDatabase] EnsureArtifact reimport failed path=") + requestPath);
}
return false;
}
@@ -914,6 +976,17 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
outAsset.artifactDirectory = NormalizePathString(fs::path(m_projectRoot.CStr()) / artifactRecord->artifactDirectory.CStr());
outAsset.artifactMainPath = NormalizePathString(fs::path(m_projectRoot.CStr()) / artifactRecord->mainArtifactPath.CStr());
outAsset.mainLocalID = artifactRecord->mainLocalID;
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[AssetDatabase] EnsureArtifact ready path=") +
requestPath +
" artifactKey=" +
artifactRecord->artifactKey +
" artifact=" +
outAsset.artifactMainPath);
}
return true;
}

View File

@@ -1,10 +1,30 @@
#include <XCEngine/Core/Asset/AsyncLoader.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Debug/Logger.h>
namespace XCEngine {
namespace Resources {
namespace {
std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
bool ShouldTraceAsyncPath(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;
}
void TraceAsyncLoad(const Containers::String& message) {
Debug::Logger::Get().Info(Debug::LogCategory::FileSystem, message);
}
} // namespace
Core::uint64 LoadRequest::GenerateRequestId() {
static std::atomic<Core::uint64> s_requestId{0};
return ++s_requestId;
@@ -16,11 +36,43 @@ AsyncLoader& AsyncLoader::Get() {
}
void AsyncLoader::Initialize(Core::uint32 workerThreadCount) {
(void)workerThreadCount;
if (m_running.exchange(true)) {
return;
}
if (workerThreadCount == 0) {
workerThreadCount = 1;
}
m_workerThreads.reserve(workerThreadCount);
for (Core::uint32 index = 0; index < workerThreadCount; ++index) {
m_workerThreads.emplace_back(&AsyncLoader::WorkerThread, this);
}
}
void AsyncLoader::Shutdown() {
CancelAll();
{
std::lock_guard<std::mutex> lock(m_queueMutex);
m_running = false;
m_pendingQueue.clear();
m_pendingCount = 0;
}
m_pendingCondition.notify_all();
for (std::thread& workerThread : m_workerThreads) {
if (workerThread.joinable()) {
workerThread.join();
}
}
m_workerThreads.clear();
{
std::lock_guard<std::mutex> lock(m_completedMutex);
m_completedQueue.clear();
}
m_completedCount = 0;
m_totalRequested = 0;
}
void AsyncLoader::Submit(const Containers::String& path, ResourceType type,
@@ -36,7 +88,7 @@ void AsyncLoader::Submit(const Containers::String& path, ResourceType type, Impo
void AsyncLoader::SubmitInternal(LoadRequest request) {
IResourceLoader* loader = FindLoader(request.type);
if (!loader) {
if (request.callback) {
LoadResult result(Containers::String("No loader for type: ") +
@@ -45,58 +97,187 @@ void AsyncLoader::SubmitInternal(LoadRequest request) {
}
return;
}
{
std::lock_guard lock(m_queueMutex);
m_pendingQueue.PushBack(std::move(request));
m_pendingCount++;
m_totalRequested++;
std::lock_guard<std::mutex> lock(m_queueMutex);
if (!m_running) {
if (request.callback) {
request.callback(LoadResult("Async loader is not initialized"));
}
return;
}
m_pendingQueue.emplace_back(std::move(request));
++m_pendingCount;
++m_totalRequested;
const LoadRequest& queuedRequest = m_pendingQueue.back();
if (ShouldTraceAsyncPath(queuedRequest.path)) {
TraceAsyncLoad(
Containers::String("[AsyncLoader] submit id=") +
Containers::String(std::to_string(queuedRequest.requestId).c_str()) +
" type=" +
GetResourceTypeName(queuedRequest.type) +
" path=" +
queuedRequest.path +
" pending=" +
Containers::String(std::to_string(m_pendingCount.load()).c_str()));
}
}
m_pendingCondition.notify_one();
}
void AsyncLoader::Update() {
Containers::Array<LoadRequest> completed;
std::deque<CompletedLoadRequest> completed;
{
std::lock_guard lock(m_completedMutex);
completed = std::move(m_completedQueue);
m_completedQueue.Clear();
std::lock_guard<std::mutex> lock(m_completedMutex);
completed.swap(m_completedQueue);
}
for (auto& request : completed) {
m_pendingCount--;
if (request.callback) {
LoadResult result(true);
request.callback(result);
for (CompletedLoadRequest& entry : completed) {
if (m_pendingCount > 0) {
--m_pendingCount;
}
++m_completedCount;
if (ShouldTraceAsyncPath(entry.request.path)) {
TraceAsyncLoad(
Containers::String("[AsyncLoader] dispatch id=") +
Containers::String(std::to_string(entry.request.requestId).c_str()) +
" type=" +
GetResourceTypeName(entry.request.type) +
" path=" +
entry.request.path +
" success=" +
Containers::String(entry.result && entry.result.resource != nullptr ? "1" : "0") +
" pending=" +
Containers::String(std::to_string(m_pendingCount.load()).c_str()) +
(entry.result.errorMessage.Empty()
? Containers::String()
: Containers::String(" error=") + entry.result.errorMessage));
}
if (entry.request.callback) {
entry.request.callback(std::move(entry.result));
}
}
}
float AsyncLoader::GetProgress() const {
if (m_totalRequested == 0) return 1.0f;
return static_cast<float>(m_totalRequested - m_pendingCount.load()) / m_totalRequested;
const Core::uint64 totalRequested = m_totalRequested.load();
if (totalRequested == 0) {
return 1.0f;
}
return static_cast<float>(totalRequested - m_pendingCount.load()) /
static_cast<float>(totalRequested);
}
void AsyncLoader::CancelAll() {
std::lock_guard lock(m_queueMutex);
m_pendingQueue.Clear();
m_pendingCount = 0;
std::lock_guard<std::mutex> lock(m_queueMutex);
if (!m_pendingQueue.empty()) {
const Core::uint32 queuedCount = static_cast<Core::uint32>(m_pendingQueue.size());
m_pendingQueue.clear();
if (m_pendingCount >= queuedCount) {
m_pendingCount -= queuedCount;
} else {
m_pendingCount = 0;
}
}
}
void AsyncLoader::Cancel(Core::uint64 requestId) {
std::lock_guard lock(m_queueMutex);
(void)requestId;
std::lock_guard<std::mutex> lock(m_queueMutex);
for (auto it = m_pendingQueue.begin(); it != m_pendingQueue.end(); ++it) {
if (it->requestId == requestId) {
m_pendingQueue.erase(it);
if (m_pendingCount > 0) {
--m_pendingCount;
}
return;
}
}
}
IResourceLoader* AsyncLoader::FindLoader(ResourceType type) const {
return ResourceManager::Get().GetLoader(type);
}
void AsyncLoader::WorkerThread() {
for (;;) {
LoadRequest request;
{
std::unique_lock<std::mutex> lock(m_queueMutex);
m_pendingCondition.wait(lock, [this]() {
return !m_running || !m_pendingQueue.empty();
});
if (!m_running && m_pendingQueue.empty()) {
return;
}
if (m_pendingQueue.empty()) {
continue;
}
request = std::move(m_pendingQueue.front());
m_pendingQueue.pop_front();
}
if (ShouldTraceAsyncPath(request.path)) {
TraceAsyncLoad(
Containers::String("[AsyncLoader] worker-begin id=") +
Containers::String(std::to_string(request.requestId).c_str()) +
" type=" +
GetResourceTypeName(request.type) +
" path=" +
request.path);
}
LoadResult result;
try {
result = ResourceManager::Get().LoadResource(request.path, request.type, request.settings);
} catch (const std::exception& exception) {
Debug::Logger::Get().Error(
Debug::LogCategory::FileSystem,
Containers::String("Async load threw exception for resource: ") +
request.path +
" - " +
exception.what());
result = LoadResult(
Containers::String("Async load threw exception: ") + exception.what());
} catch (...) {
Debug::Logger::Get().Error(
Debug::LogCategory::FileSystem,
Containers::String("Async load threw unknown exception for resource: ") +
request.path);
result = LoadResult("Async load threw unknown exception");
}
if (ShouldTraceAsyncPath(request.path)) {
TraceAsyncLoad(
Containers::String("[AsyncLoader] worker-end id=") +
Containers::String(std::to_string(request.requestId).c_str()) +
" type=" +
GetResourceTypeName(request.type) +
" path=" +
request.path +
" success=" +
Containers::String(result && result.resource != nullptr ? "1" : "0") +
(result.errorMessage.Empty()
? Containers::String()
: Containers::String(" error=") + result.errorMessage));
}
QueueCompleted(std::move(request), std::move(result));
}
}
void AsyncLoader::QueueCompleted(LoadRequest request, LoadResult result) {
std::lock_guard lock(m_completedMutex);
(void)request;
(void)result;
std::lock_guard<std::mutex> lock(m_completedMutex);
m_completedQueue.emplace_back(std::move(request), std::move(result));
}
} // namespace Resources

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;
}

View File

@@ -1,5 +1,6 @@
#include "Scene/Scene.h"
#include "Components/ComponentFactoryRegistry.h"
#include "Debug/Logger.h"
#include "Components/GameObject.h"
#include "Components/TransformComponent.h"
#include <sstream>
@@ -11,6 +12,16 @@ namespace Components {
namespace {
bool ShouldTraceSceneComponentPayload(const std::string& payload) {
return payload.find("builtin://") != std::string::npos ||
payload.find("backpack") != std::string::npos ||
payload.find("New Material.mat") != std::string::npos;
}
} // namespace
namespace {
struct PendingComponentData {
std::string type;
std::string payload;
@@ -338,6 +349,19 @@ void Scene::DeserializeFromString(const std::string& data) {
}
for (const PendingComponentData& componentData : pending.components) {
if (ShouldTraceSceneComponentPayload(componentData.payload)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[Scene] Deserialize component objectId=") +
Containers::String(std::to_string(pending.id).c_str()) +
" name=" +
Containers::String(pending.name.c_str()) +
" type=" +
Containers::String(componentData.type.c_str()) +
" payload=" +
Containers::String(componentData.payload.c_str()));
}
if (Component* component = ComponentFactoryRegistry::Get().CreateComponent(go.get(), componentData.type)) {
if (!componentData.payload.empty()) {
std::istringstream componentStream(componentData.payload);
@@ -396,11 +420,24 @@ std::string Scene::SerializeToString() const {
void Scene::Load(const std::string& filePath) {
std::ifstream file(filePath);
if (!file.is_open()) {
Debug::Logger::Get().Warning(
Debug::LogCategory::FileSystem,
Containers::String("[Scene] Load failed to open file=") + Containers::String(filePath.c_str()));
return;
}
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[Scene] Load file=") + Containers::String(filePath.c_str()));
std::stringstream buffer;
buffer << file.rdbuf();
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[Scene] Load bytes=") +
Containers::String(std::to_string(buffer.str().size()).c_str()) +
" file=" +
Containers::String(filePath.c_str()));
DeserializeFromString(buffer.str());
}