feat: expand editor scripting asset and viewport flow
This commit is contained in:
@@ -592,6 +592,28 @@ bool AssetDatabase::TryGetPrimaryAssetPath(const AssetGUID& guid, Containers::St
|
||||
return true;
|
||||
}
|
||||
|
||||
void AssetDatabase::BuildLookupSnapshot(std::unordered_map<std::string, AssetGUID>& outPathToGuid,
|
||||
std::unordered_map<AssetGUID, Containers::String>& outGuidToPath) const {
|
||||
outPathToGuid.clear();
|
||||
outGuidToPath.clear();
|
||||
outPathToGuid.reserve(m_sourcesByPathKey.size());
|
||||
outGuidToPath.reserve(m_sourcesByGuid.size());
|
||||
|
||||
for (const auto& [pathKey, record] : m_sourcesByPathKey) {
|
||||
if (!record.guid.IsValid() || record.relativePath.Empty()) {
|
||||
continue;
|
||||
}
|
||||
outPathToGuid[pathKey] = record.guid;
|
||||
}
|
||||
|
||||
for (const auto& [guid, record] : m_sourcesByGuid) {
|
||||
if (!guid.IsValid() || record.relativePath.Empty()) {
|
||||
continue;
|
||||
}
|
||||
outGuidToPath[guid] = record.relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
void AssetDatabase::EnsureProjectLayout() {
|
||||
std::error_code ec;
|
||||
fs::create_directories(fs::path(m_assetsRoot.CStr()), ec);
|
||||
|
||||
88
engine/src/Core/Asset/AssetImportService.cpp
Normal file
88
engine/src/Core/Asset/AssetImportService.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include <XCEngine/Core/Asset/AssetImportService.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
void AssetImportService::Initialize() {
|
||||
}
|
||||
|
||||
void AssetImportService::Shutdown() {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
m_assetDatabase.Shutdown();
|
||||
m_projectRoot.Clear();
|
||||
}
|
||||
|
||||
void AssetImportService::SetProjectRoot(const Containers::String& projectRoot) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
|
||||
if (m_projectRoot == projectRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_projectRoot.Empty()) {
|
||||
m_assetDatabase.Shutdown();
|
||||
}
|
||||
|
||||
m_projectRoot = projectRoot;
|
||||
if (!m_projectRoot.Empty()) {
|
||||
m_assetDatabase.Initialize(m_projectRoot);
|
||||
}
|
||||
}
|
||||
|
||||
Containers::String AssetImportService::GetProjectRoot() const {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
return m_projectRoot;
|
||||
}
|
||||
|
||||
void AssetImportService::Refresh() {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
if (!m_projectRoot.Empty()) {
|
||||
m_assetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetImportService::EnsureArtifact(const Containers::String& requestPath,
|
||||
ResourceType requestedType,
|
||||
AssetDatabase::ResolvedAsset& outAsset) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
if (m_projectRoot.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_assetDatabase.EnsureArtifact(requestPath, requestedType, outAsset);
|
||||
}
|
||||
|
||||
bool AssetImportService::TryGetAssetRef(const Containers::String& requestPath,
|
||||
ResourceType resourceType,
|
||||
AssetRef& outRef) const {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
if (m_projectRoot.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_assetDatabase.TryGetAssetRef(requestPath, resourceType, outRef);
|
||||
}
|
||||
|
||||
bool AssetImportService::TryGetPrimaryAssetPath(const AssetGUID& guid, Containers::String& outRelativePath) const {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
if (m_projectRoot.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_assetDatabase.TryGetPrimaryAssetPath(guid, outRelativePath);
|
||||
}
|
||||
|
||||
void AssetImportService::BuildLookupSnapshot(std::unordered_map<std::string, AssetGUID>& outPathToGuid,
|
||||
std::unordered_map<AssetGUID, Containers::String>& outGuidToPath) const {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
outPathToGuid.clear();
|
||||
outGuidToPath.clear();
|
||||
if (m_projectRoot.Empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_assetDatabase.BuildLookupSnapshot(outPathToGuid, outGuidToPath);
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
173
engine/src/Core/Asset/ProjectAssetIndex.cpp
Normal file
173
engine/src/Core/Asset/ProjectAssetIndex.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#include <XCEngine/Core/Asset/ProjectAssetIndex.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/AssetImportService.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string ToStdString(const Containers::String& value) {
|
||||
return std::string(value.CStr());
|
||||
}
|
||||
|
||||
Containers::String NormalizePathString(const std::filesystem::path& path) {
|
||||
return Containers::String(path.lexically_normal().generic_string().c_str());
|
||||
}
|
||||
|
||||
bool HasVirtualPathScheme(const Containers::String& value) {
|
||||
return ToStdString(value).find("://") != std::string::npos;
|
||||
}
|
||||
|
||||
Containers::String MakeAssetLookupRelativePath(const Containers::String& projectRoot,
|
||||
const Containers::String& requestPath) {
|
||||
if (requestPath.Empty() || HasVirtualPathScheme(requestPath)) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
const std::filesystem::path inputPath(requestPath.CStr());
|
||||
if (inputPath.is_absolute()) {
|
||||
if (projectRoot.Empty()) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
const std::filesystem::path relativePath =
|
||||
std::filesystem::relative(inputPath, std::filesystem::path(projectRoot.CStr()), ec);
|
||||
if (ec) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
const Containers::String normalizedRelative = NormalizePathString(relativePath);
|
||||
if (normalizedRelative.StartsWith("Assets/") || normalizedRelative == "Assets") {
|
||||
return normalizedRelative;
|
||||
}
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
const Containers::String normalizedRequest = NormalizePathString(inputPath);
|
||||
if (normalizedRequest.StartsWith("Assets/") || normalizedRequest == "Assets") {
|
||||
return normalizedRequest;
|
||||
}
|
||||
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
std::string MakeAssetLookupPathKey(const Containers::String& relativePath) {
|
||||
std::string key = ToStdString(relativePath);
|
||||
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return key;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ProjectAssetIndex::ResetProjectRoot(const Containers::String& projectRoot) {
|
||||
std::unique_lock<std::shared_mutex> lock(m_mutex);
|
||||
m_projectRoot = projectRoot;
|
||||
m_assetGuidByPathKey.clear();
|
||||
m_assetPathByGuid.clear();
|
||||
}
|
||||
|
||||
void ProjectAssetIndex::RefreshFrom(const AssetImportService& importService) {
|
||||
std::unordered_map<std::string, AssetGUID> pathToGuid;
|
||||
std::unordered_map<AssetGUID, Containers::String> guidToPath;
|
||||
const Containers::String projectRoot = importService.GetProjectRoot();
|
||||
if (!projectRoot.Empty()) {
|
||||
importService.BuildLookupSnapshot(pathToGuid, guidToPath);
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(m_mutex);
|
||||
m_projectRoot = projectRoot;
|
||||
m_assetGuidByPathKey = std::move(pathToGuid);
|
||||
m_assetPathByGuid = std::move(guidToPath);
|
||||
}
|
||||
|
||||
bool ProjectAssetIndex::TryGetAssetRef(AssetImportService& importService,
|
||||
const Containers::String& path,
|
||||
ResourceType resourceType,
|
||||
AssetRef& outRef) const {
|
||||
bool resolved = false;
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(m_mutex);
|
||||
const Containers::String relativePath = MakeAssetLookupRelativePath(m_projectRoot, path);
|
||||
if (!relativePath.Empty()) {
|
||||
const auto lookupIt = m_assetGuidByPathKey.find(MakeAssetLookupPathKey(relativePath));
|
||||
if (lookupIt != m_assetGuidByPathKey.end()) {
|
||||
outRef.assetGuid = lookupIt->second;
|
||||
outRef.localID = kMainAssetLocalID;
|
||||
outRef.resourceType = resourceType;
|
||||
resolved = outRef.IsValid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
resolved = importService.TryGetAssetRef(path, resourceType, outRef);
|
||||
if (!resolved) {
|
||||
const Containers::String projectRoot = importService.GetProjectRoot();
|
||||
const Containers::String relativePath = MakeAssetLookupRelativePath(projectRoot, path);
|
||||
if (!relativePath.Empty() && !projectRoot.Empty()) {
|
||||
auto* index = const_cast<ProjectAssetIndex*>(this);
|
||||
importService.Refresh();
|
||||
index->RefreshFrom(importService);
|
||||
resolved = importService.TryGetAssetRef(path, resourceType, outRef);
|
||||
}
|
||||
}
|
||||
|
||||
if (resolved) {
|
||||
Containers::String relativePath;
|
||||
if (importService.TryGetPrimaryAssetPath(outRef.assetGuid, relativePath)) {
|
||||
const_cast<ProjectAssetIndex*>(this)->RememberResolvedPath(outRef.assetGuid, relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
bool ProjectAssetIndex::TryResolveAssetPath(const AssetImportService& importService,
|
||||
const AssetRef& assetRef,
|
||||
Containers::String& outPath) const {
|
||||
if (!assetRef.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool resolved = false;
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(m_mutex);
|
||||
const auto lookupIt = m_assetPathByGuid.find(assetRef.assetGuid);
|
||||
if (lookupIt != m_assetPathByGuid.end()) {
|
||||
outPath = lookupIt->second;
|
||||
resolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
resolved = importService.TryGetPrimaryAssetPath(assetRef.assetGuid, outPath);
|
||||
if (resolved) {
|
||||
const_cast<ProjectAssetIndex*>(this)->RememberResolvedPath(assetRef.assetGuid, outPath);
|
||||
}
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
void ProjectAssetIndex::RememberResolvedPath(const AssetGUID& assetGuid, const Containers::String& relativePath) {
|
||||
if (!assetGuid.IsValid() || relativePath.Empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(m_mutex);
|
||||
m_assetGuidByPathKey[MakeAssetLookupPathKey(relativePath)] = assetGuid;
|
||||
m_assetPathByGuid[assetGuid] = relativePath;
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
@@ -85,6 +85,7 @@ void ResourceManager::EnsureInitialized() {
|
||||
RegisterBuiltinLoader(*this, g_meshLoader);
|
||||
RegisterBuiltinLoader(*this, g_shaderLoader);
|
||||
RegisterBuiltinLoader(*this, g_textureLoader);
|
||||
m_assetImportService.Initialize();
|
||||
|
||||
m_asyncLoader = std::move(asyncLoader);
|
||||
}
|
||||
@@ -96,23 +97,24 @@ void ResourceManager::Shutdown() {
|
||||
m_asyncLoader.reset();
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
|
||||
m_assetDatabase.Shutdown();
|
||||
m_assetImportService.Shutdown();
|
||||
ResourceFileSystem::Get().Shutdown();
|
||||
m_projectAssetIndex.ResetProjectRoot();
|
||||
|
||||
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);
|
||||
m_assetDatabase.Initialize(rootPath);
|
||||
m_assetImportService.SetProjectRoot(rootPath);
|
||||
m_projectAssetIndex.RefreshFrom(m_assetImportService);
|
||||
} else {
|
||||
m_assetImportService.SetProjectRoot(Containers::String());
|
||||
ResourceFileSystem::Get().Shutdown();
|
||||
m_assetDatabase.Shutdown();
|
||||
m_projectAssetIndex.ResetProjectRoot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,14 +362,14 @@ 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();
|
||||
m_assetImportService.Refresh();
|
||||
m_projectAssetIndex.RefreshFrom(m_assetImportService);
|
||||
}
|
||||
}
|
||||
|
||||
bool ResourceManager::TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
|
||||
const bool resolved = m_assetDatabase.TryGetAssetRef(path, resourceType, outRef);
|
||||
const bool resolved = m_projectAssetIndex.TryGetAssetRef(m_assetImportService, path, resourceType, outRef);
|
||||
|
||||
if (ShouldTraceResourcePath(path)) {
|
||||
Debug::Logger::Get().Info(
|
||||
Debug::LogCategory::FileSystem,
|
||||
@@ -384,12 +386,8 @@ bool ResourceManager::TryGetAssetRef(const Containers::String& path, ResourceTyp
|
||||
}
|
||||
|
||||
bool ResourceManager::TryResolveAssetPath(const AssetRef& assetRef, Containers::String& outPath) const {
|
||||
if (!assetRef.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
const bool resolved = m_projectAssetIndex.TryResolveAssetPath(m_assetImportService, assetRef, outPath);
|
||||
|
||||
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,
|
||||
@@ -512,30 +510,27 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
|
||||
}
|
||||
|
||||
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)) {
|
||||
AssetDatabase::ResolvedAsset resolvedAsset;
|
||||
if (!m_resourceRoot.Empty() &&
|
||||
m_assetImportService.EnsureArtifact(path, type, resolvedAsset) &&
|
||||
resolvedAsset.artifactReady) {
|
||||
m_projectAssetIndex.RememberResolvedPath(resolvedAsset.assetGuid, resolvedAsset.relativePath);
|
||||
loadPath = resolvedAsset.artifactMainPath;
|
||||
if (ShouldTraceResourcePath(path)) {
|
||||
Debug::Logger::Get().Info(
|
||||
Debug::LogCategory::FileSystem,
|
||||
Containers::String("[ResourceManager] LoadResource direct path=") +
|
||||
Containers::String("[ResourceManager] LoadResource artifact path=") +
|
||||
path +
|
||||
" loadPath=" +
|
||||
" artifact=" +
|
||||
loadPath);
|
||||
}
|
||||
} else if (ShouldTraceResourcePath(path)) {
|
||||
Debug::Logger::Get().Info(
|
||||
Debug::LogCategory::FileSystem,
|
||||
Containers::String("[ResourceManager] LoadResource direct path=") +
|
||||
path +
|
||||
" loadPath=" +
|
||||
loadPath);
|
||||
}
|
||||
|
||||
LoadResult result;
|
||||
|
||||
Reference in New Issue
Block a user