#include "Components/VolumeRendererComponent.h" #include "Core/Asset/ResourceManager.h" #include "Debug/Logger.h" #include namespace XCEngine { namespace Components { namespace { std::string ToStdString(const Containers::String& value) { return std::string(value.CStr()); } bool HasVirtualPathScheme(const std::string& path) { return path.find("://") != std::string::npos; } std::string EncodeAssetRef(const Resources::AssetRef& assetRef) { if (!assetRef.IsValid()) { return std::string(); } return ToStdString(assetRef.assetGuid.ToString()) + "," + std::to_string(assetRef.localID) + "," + std::to_string(static_cast(assetRef.resourceType)); } bool TryDecodeAssetRef(const std::string& value, Resources::AssetRef& outRef) { const size_t firstComma = value.find(','); const size_t secondComma = firstComma == std::string::npos ? std::string::npos : value.find(',', firstComma + 1); if (firstComma == std::string::npos || secondComma == std::string::npos) { return false; } const Containers::String guidText(value.substr(0, firstComma).c_str()); outRef.assetGuid = Resources::AssetGUID::ParseOrDefault(guidText); outRef.localID = static_cast(std::stoull(value.substr(firstComma + 1, secondComma - firstComma - 1))); outRef.resourceType = static_cast(std::stoi(value.substr(secondComma + 1))); return outRef.IsValid(); } std::string VolumeFieldPathFromHandle(const Resources::ResourceHandle& volumeField) { return volumeField.Get() != nullptr ? ToStdString(volumeField->GetPath()) : std::string(); } std::string MaterialPathFromHandle(const Resources::ResourceHandle& material) { return material.Get() != nullptr ? ToStdString(material->GetPath()) : std::string(); } uint64_t GetVolumeTraceSteadyMs() { using Clock = std::chrono::steady_clock; static const Clock::time_point s_start = Clock::now(); return static_cast(std::chrono::duration_cast( Clock::now() - s_start).count()); } void LogVolumeTraceFileSystem(const std::string& message) { Containers::String entry("[VolumeTrace] "); entry += message.c_str(); Debug::Logger::Get().Info(Debug::LogCategory::FileSystem, entry); } } // namespace struct VolumeRendererComponent::PendingVolumeLoadState { Resources::LoadResult result; uint64_t requestedAtMs = 0u; bool completed = false; }; struct VolumeRendererComponent::PendingMaterialLoadState { Resources::LoadResult result; bool completed = false; }; Resources::VolumeField* VolumeRendererComponent::GetVolumeField() const { const_cast(this)->EnsureDeferredAsyncVolumeLoadStarted(); const_cast(this)->ResolvePendingVolumeField(); return m_volumeField.Get(); } const Resources::ResourceHandle& VolumeRendererComponent::GetVolumeFieldHandle() const { const_cast(this)->EnsureDeferredAsyncVolumeLoadStarted(); const_cast(this)->ResolvePendingVolumeField(); return m_volumeField; } void VolumeRendererComponent::SetVolumeFieldPath(const std::string& volumeFieldPath) { m_pendingVolumeLoad.reset(); m_asyncVolumeLoadRequested = false; m_volumeFieldPath = volumeFieldPath; if (m_volumeFieldPath.empty()) { m_volumeField.Reset(); m_volumeFieldRef.Reset(); return; } m_volumeField = Resources::ResourceManager::Get().Load(m_volumeFieldPath.c_str()); if (!Resources::ResourceManager::Get().TryGetAssetRef( m_volumeFieldPath.c_str(), Resources::ResourceType::VolumeField, m_volumeFieldRef)) { m_volumeFieldRef.Reset(); } } void VolumeRendererComponent::SetVolumeField( const Resources::ResourceHandle& volumeField) { m_pendingVolumeLoad.reset(); m_asyncVolumeLoadRequested = false; m_volumeField = volumeField; m_volumeFieldPath = VolumeFieldPathFromHandle(volumeField); if (m_volumeFieldPath.empty() || !Resources::ResourceManager::Get().TryGetAssetRef( m_volumeFieldPath.c_str(), Resources::ResourceType::VolumeField, m_volumeFieldRef)) { m_volumeFieldRef.Reset(); } } void VolumeRendererComponent::SetVolumeField(Resources::VolumeField* volumeField) { SetVolumeField(Resources::ResourceHandle(volumeField)); } void VolumeRendererComponent::ClearVolumeField() { m_pendingVolumeLoad.reset(); m_asyncVolumeLoadRequested = false; m_volumeField.Reset(); m_volumeFieldPath.clear(); m_volumeFieldRef.Reset(); } Resources::Material* VolumeRendererComponent::GetMaterial() const { const_cast(this)->EnsureDeferredAsyncMaterialLoadStarted(); const_cast(this)->ResolvePendingMaterial(); return m_material.Get(); } const Resources::ResourceHandle& VolumeRendererComponent::GetMaterialHandle() const { const_cast(this)->EnsureDeferredAsyncMaterialLoadStarted(); const_cast(this)->ResolvePendingMaterial(); return m_material; } void VolumeRendererComponent::SetMaterialPath(const std::string& materialPath) { m_pendingMaterialLoad.reset(); m_asyncMaterialLoadRequested = false; m_materialPath = materialPath; if (m_materialPath.empty()) { m_material.Reset(); m_materialRef.Reset(); return; } m_material = Resources::ResourceManager::Get().Load(m_materialPath.c_str()); if (!Resources::ResourceManager::Get().TryGetAssetRef( m_materialPath.c_str(), Resources::ResourceType::Material, m_materialRef)) { m_materialRef.Reset(); } } void VolumeRendererComponent::SetMaterial( const Resources::ResourceHandle& material) { m_pendingMaterialLoad.reset(); m_asyncMaterialLoadRequested = false; m_material = material; m_materialPath = MaterialPathFromHandle(material); if (m_materialPath.empty() || !Resources::ResourceManager::Get().TryGetAssetRef( m_materialPath.c_str(), Resources::ResourceType::Material, m_materialRef)) { m_materialRef.Reset(); } } void VolumeRendererComponent::SetMaterial(Resources::Material* material) { SetMaterial(Resources::ResourceHandle(material)); } void VolumeRendererComponent::ClearMaterial() { m_pendingMaterialLoad.reset(); m_asyncMaterialLoadRequested = false; m_material.Reset(); m_materialPath.clear(); m_materialRef.Reset(); } void VolumeRendererComponent::Serialize(std::ostream& os) const { Resources::AssetRef volumeFieldRef = m_volumeFieldRef; if (!volumeFieldRef.IsValid() && !m_volumeFieldPath.empty() && !HasVirtualPathScheme(m_volumeFieldPath) && Resources::ResourceManager::Get().TryGetAssetRef( m_volumeFieldPath.c_str(), Resources::ResourceType::VolumeField, volumeFieldRef)) { } Resources::AssetRef materialRef = m_materialRef; if (!materialRef.IsValid() && !m_materialPath.empty() && !HasVirtualPathScheme(m_materialPath) && Resources::ResourceManager::Get().TryGetAssetRef( m_materialPath.c_str(), Resources::ResourceType::Material, materialRef)) { } os << "volumeRef=" << EncodeAssetRef(volumeFieldRef) << ";"; if (!volumeFieldRef.IsValid() && !m_volumeFieldPath.empty() && HasVirtualPathScheme(m_volumeFieldPath)) { os << "volumePath=" << m_volumeFieldPath << ";"; } os << "materialRef=" << EncodeAssetRef(materialRef) << ";"; if (!materialRef.IsValid() && !m_materialPath.empty() && HasVirtualPathScheme(m_materialPath)) { os << "materialPath=" << m_materialPath << ";"; } os << "castShadows=" << (m_castShadows ? 1 : 0) << ";"; os << "receiveShadows=" << (m_receiveShadows ? 1 : 0) << ";"; } void VolumeRendererComponent::Deserialize(std::istream& is) { ClearVolumeField(); ClearMaterial(); m_castShadows = true; m_receiveShadows = true; std::string token; std::string pendingVolumePath; Resources::AssetRef pendingVolumeRef; std::string pendingMaterialPath; Resources::AssetRef pendingMaterialRef; while (std::getline(is, token, ';')) { if (token.empty()) { continue; } const size_t eqPos = token.find('='); if (eqPos == std::string::npos) { continue; } const std::string key = token.substr(0, eqPos); const std::string value = token.substr(eqPos + 1); if (key == "volumePath") { pendingVolumePath = value; } else if (key == "volumeRef") { TryDecodeAssetRef(value, pendingVolumeRef); } else if (key == "materialPath") { pendingMaterialPath = value; } else if (key == "materialRef") { TryDecodeAssetRef(value, pendingMaterialRef); } else if (key == "castShadows") { m_castShadows = (std::stoi(value) != 0); } else if (key == "receiveShadows") { m_receiveShadows = (std::stoi(value) != 0); } } if (pendingVolumeRef.IsValid()) { bool restoredOrQueued = false; m_volumeFieldRef = pendingVolumeRef; if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) { Containers::String resolvedPath; if (Resources::ResourceManager::Get().TryResolveAssetPath(pendingVolumeRef, resolvedPath)) { m_volumeFieldPath = ToStdString(resolvedPath); restoredOrQueued = true; } } if (!restoredOrQueued) { m_volumeField = Resources::ResourceManager::Get().Load(pendingVolumeRef); m_volumeFieldPath = m_volumeField.Get() != nullptr ? VolumeFieldPathFromHandle(m_volumeField) : pendingVolumePath; } } else if (!pendingVolumePath.empty() && HasVirtualPathScheme(pendingVolumePath)) { if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) { m_volumeFieldPath = pendingVolumePath; } else { SetVolumeFieldPath(pendingVolumePath); } } if (pendingMaterialRef.IsValid()) { bool restoredOrQueued = false; m_materialRef = pendingMaterialRef; if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) { Containers::String resolvedPath; if (Resources::ResourceManager::Get().TryResolveAssetPath(pendingMaterialRef, resolvedPath)) { m_materialPath = ToStdString(resolvedPath); restoredOrQueued = true; } } if (!restoredOrQueued) { m_material = Resources::ResourceManager::Get().Load(pendingMaterialRef); m_materialPath = m_material.Get() != nullptr ? MaterialPathFromHandle(m_material) : pendingMaterialPath; } } else if (!pendingMaterialPath.empty() && HasVirtualPathScheme(pendingMaterialPath)) { if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) { m_materialPath = pendingMaterialPath; } else { SetMaterialPath(pendingMaterialPath); } } } void VolumeRendererComponent::BeginAsyncVolumeLoad(const std::string& volumeFieldPath) { if (volumeFieldPath.empty()) { m_pendingVolumeLoad.reset(); m_asyncVolumeLoadRequested = false; m_volumeField.Reset(); return; } m_asyncVolumeLoadRequested = true; m_volumeField.Reset(); m_pendingVolumeLoad = std::make_shared(); m_pendingVolumeLoad->requestedAtMs = GetVolumeTraceSteadyMs(); LogVolumeTraceFileSystem( "BeginAsyncVolumeLoad path=" + volumeFieldPath + " steady_ms=" + std::to_string(m_pendingVolumeLoad->requestedAtMs)); std::weak_ptr weakState = m_pendingVolumeLoad; Resources::ResourceManager::Get().LoadAsync( volumeFieldPath.c_str(), Resources::ResourceType::VolumeField, [weakState, volumeFieldPath](Resources::LoadResult result) { if (std::shared_ptr state = weakState.lock()) { const uint64_t completedAtMs = GetVolumeTraceSteadyMs(); const bool succeeded = static_cast(result) && result.resource != nullptr; size_t payloadBytes = 0u; if (succeeded) { if (auto* volumeField = static_cast(result.resource)) { payloadBytes = volumeField->GetPayloadSize(); } } LogVolumeTraceFileSystem( "AsyncVolumeLoadCompleted path=" + volumeFieldPath + " steady_ms=" + std::to_string(completedAtMs) + " elapsed_ms=" + std::to_string(completedAtMs - state->requestedAtMs) + " success=" + std::to_string(succeeded ? 1 : 0) + " payload_bytes=" + std::to_string(payloadBytes)); state->result = std::move(result); state->completed = true; } }); } void VolumeRendererComponent::EnsureDeferredAsyncVolumeLoadStarted() { if (m_asyncVolumeLoadRequested || m_volumeField.Get() != nullptr || m_volumeFieldPath.empty()) { return; } BeginAsyncVolumeLoad(m_volumeFieldPath); } void VolumeRendererComponent::ResolvePendingVolumeField() { if (!m_pendingVolumeLoad || !m_pendingVolumeLoad->completed) { return; } std::shared_ptr completedLoad = std::move(m_pendingVolumeLoad); m_pendingVolumeLoad.reset(); if (!completedLoad->result || completedLoad->result.resource == nullptr) { return; } m_volumeField = Resources::ResourceHandle( static_cast(completedLoad->result.resource)); if (m_volumeField.Get() == nullptr) { return; } const uint64_t resolvedAtMs = GetVolumeTraceSteadyMs(); LogVolumeTraceFileSystem( "ResolvePendingVolumeField path=" + VolumeFieldPathFromHandle(m_volumeField) + " steady_ms=" + std::to_string(resolvedAtMs) + " elapsed_ms=" + std::to_string(resolvedAtMs - completedLoad->requestedAtMs) + " payload_bytes=" + std::to_string(m_volumeField->GetPayloadSize())); m_volumeFieldPath = VolumeFieldPathFromHandle(m_volumeField); if (!Resources::ResourceManager::Get().TryGetAssetRef( m_volumeFieldPath.c_str(), Resources::ResourceType::VolumeField, m_volumeFieldRef)) { m_volumeFieldRef.Reset(); } } void VolumeRendererComponent::BeginAsyncMaterialLoad(const std::string& materialPath) { if (materialPath.empty()) { m_pendingMaterialLoad.reset(); m_asyncMaterialLoadRequested = false; m_material.Reset(); return; } m_asyncMaterialLoadRequested = true; m_material.Reset(); m_pendingMaterialLoad = std::make_shared(); std::weak_ptr weakState = m_pendingMaterialLoad; Resources::ResourceManager::Get().LoadAsync( materialPath.c_str(), Resources::ResourceType::Material, [weakState](Resources::LoadResult result) { if (std::shared_ptr state = weakState.lock()) { state->result = std::move(result); state->completed = true; } }); } void VolumeRendererComponent::EnsureDeferredAsyncMaterialLoadStarted() { if (m_asyncMaterialLoadRequested || m_material.Get() != nullptr || m_materialPath.empty()) { return; } BeginAsyncMaterialLoad(m_materialPath); } void VolumeRendererComponent::ResolvePendingMaterial() { if (!m_pendingMaterialLoad || !m_pendingMaterialLoad->completed) { return; } std::shared_ptr completedLoad = std::move(m_pendingMaterialLoad); m_pendingMaterialLoad.reset(); if (!completedLoad->result || completedLoad->result.resource == nullptr) { return; } m_material = Resources::ResourceHandle( static_cast(completedLoad->result.resource)); if (m_material.Get() == nullptr) { return; } m_materialPath = MaterialPathFromHandle(m_material); if (!Resources::ResourceManager::Get().TryGetAssetRef( m_materialPath.c_str(), Resources::ResourceType::Material, m_materialRef)) { m_materialRef.Reset(); } } } // namespace Components } // namespace XCEngine