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