feat: expand editor scripting asset and viewport flow

This commit is contained in:
2026-04-03 13:22:30 +08:00
parent ed8c27fde2
commit a05d0b80a2
124 changed files with 10397 additions and 1737 deletions

View File

@@ -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);

View 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

View 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

View File

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