Finalize library bootstrap status and stabilize async asset regressions

This commit is contained in:
2026-04-04 19:44:59 +08:00
parent 013e5a73b9
commit bcef1f145b
25 changed files with 3415 additions and 81 deletions

View File

@@ -413,14 +413,14 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Core/UIElementTree.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Core/UIContext.h
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Core/UIBuildContext.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Core/UIElementTree.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/StyleTypes.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/Theme.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/StyleSet.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/StyleResolver.h
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Style/StyleTypes.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Style/Theme.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Style/StyleResolver.cpp
/src/UI/Core/UIElementTree.cpp
/include/XCEngine/UI/Style/StyleTypes.h
/include/XCEngine/UI/Style/Theme.h
/include/XCEngine/UI/Style/StyleSet.h
/include/XCEngine/UI/Style/StyleResolver.h
/src/UI/Style/StyleTypes.cpp
/src/UI/Style/Theme.cpp
/src/UI/Style/StyleResolver.cpp
# Input
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Input/InputTypes.h

View File

@@ -10,28 +10,91 @@ namespace Resources {
class AssetImportService {
public:
struct ImportStatusSnapshot {
Core::uint64 revision = 0;
bool inProgress = false;
bool success = false;
Containers::String operation;
Containers::String targetPath;
Containers::String message;
Core::uint64 startedAtMs = 0;
Core::uint64 completedAtMs = 0;
Core::uint64 durationMs = 0;
Core::uint32 importedAssetCount = 0;
Core::uint32 removedArtifactCount = 0;
bool HasValue() const {
return revision != 0;
}
};
struct LookupSnapshot {
std::unordered_map<std::string, AssetGUID> assetGuidByPathKey;
std::unordered_map<AssetGUID, Containers::String> assetPathByGuid;
void Clear() {
assetGuidByPathKey.clear();
assetPathByGuid.clear();
}
};
struct ImportedAsset {
bool exists = false;
bool artifactReady = false;
bool imported = false;
Containers::String absolutePath;
Containers::String relativePath;
AssetGUID assetGuid;
ResourceType resourceType = ResourceType::Unknown;
Containers::String runtimeLoadPath;
Containers::String artifactDirectory;
LocalID mainLocalID = kMainAssetLocalID;
};
void Initialize();
void Shutdown();
void SetProjectRoot(const Containers::String& projectRoot);
Containers::String GetProjectRoot() const;
Containers::String GetLibraryRoot() const;
bool BootstrapProject();
void Refresh();
bool ClearLibraryCache();
bool RebuildLibraryCache();
bool ReimportAllAssets();
bool ReimportAsset(const Containers::String& requestPath,
ImportedAsset& outAsset);
ImportStatusSnapshot GetLastImportStatus() const;
bool TryGetImportableResourceType(const Containers::String& requestPath,
ResourceType& outType) const;
bool EnsureArtifact(const Containers::String& requestPath,
ResourceType requestedType,
AssetDatabase::ResolvedAsset& outAsset);
ImportedAsset& outAsset);
bool TryGetAssetRef(const Containers::String& requestPath,
ResourceType resourceType,
AssetRef& outRef) const;
bool TryGetPrimaryAssetPath(const AssetGUID& guid, Containers::String& outRelativePath) const;
void BuildLookupSnapshot(std::unordered_map<std::string, AssetGUID>& outPathToGuid,
std::unordered_map<AssetGUID, Containers::String>& outGuidToPath) const;
void BuildLookupSnapshot(LookupSnapshot& outSnapshot) const;
private:
static ImportedAsset ConvertResolvedAsset(const AssetDatabase::ResolvedAsset& resolvedAsset);
void ResetImportStatusLocked();
void BeginImportStatusLocked(const Containers::String& operation,
const Containers::String& targetPath,
const Containers::String& message);
void FinishImportStatusLocked(const Containers::String& operation,
const Containers::String& targetPath,
bool success,
const Containers::String& message,
const AssetDatabase::MaintenanceStats& stats);
mutable std::recursive_mutex m_mutex;
Containers::String m_projectRoot;
AssetDatabase m_assetDatabase;
ImportStatusSnapshot m_lastImportStatus;
Core::uint64 m_nextImportStatusRevision = 1;
};
} // namespace Resources

View File

@@ -18,22 +18,26 @@ public:
ResourceHandle() = default;
explicit ResourceHandle(T* resource)
: m_resource(resource) {
if (m_resource) {
ResourceManager::Get().AddRef(m_resource->GetGUID());
: m_resource(resource),
m_guid(resource != nullptr ? resource->GetGUID() : ResourceGUID()) {
if (m_guid.IsValid()) {
ResourceManager::Get().AddRef(m_guid);
}
}
ResourceHandle(const ResourceHandle& other)
: m_resource(other.m_resource) {
if (m_resource) {
ResourceManager::Get().AddRef(m_resource->GetGUID());
: m_resource(other.m_resource),
m_guid(other.m_guid) {
if (m_guid.IsValid()) {
ResourceManager::Get().AddRef(m_guid);
}
}
ResourceHandle(ResourceHandle&& other) noexcept
: m_resource(other.m_resource) {
: m_resource(other.m_resource),
m_guid(other.m_guid) {
other.m_resource = nullptr;
other.m_guid = ResourceGUID();
}
~ResourceHandle() {
@@ -44,8 +48,9 @@ public:
if (this != &other) {
Reset();
m_resource = other.m_resource;
if (m_resource) {
ResourceManager::Get().AddRef(m_resource->GetGUID());
m_guid = other.m_guid;
if (m_guid.IsValid()) {
ResourceManager::Get().AddRef(m_guid);
}
}
return *this;
@@ -55,7 +60,9 @@ public:
if (this != &other) {
Reset();
m_resource = other.m_resource;
m_guid = other.m_guid;
other.m_resource = nullptr;
other.m_guid = ResourceGUID();
}
return *this;
}
@@ -64,11 +71,11 @@ public:
T* operator->() const { return m_resource; }
T& operator*() const { return *m_resource; }
bool IsValid() const { return m_resource != nullptr && m_resource->IsValid(); }
bool IsValid() const { return m_resource != nullptr && m_guid.IsValid() && m_resource->IsValid(); }
explicit operator bool() const { return IsValid(); }
ResourceGUID GetGUID() const {
return m_resource ? m_resource->GetGUID() : ResourceGUID(0);
return m_guid;
}
ResourceType GetResourceType() const {
@@ -76,18 +83,21 @@ public:
}
void Reset() {
if (m_resource) {
ResourceManager::Get().Release(m_resource->GetGUID());
if (m_guid.IsValid()) {
ResourceManager::Get().Release(m_guid);
m_resource = nullptr;
m_guid = ResourceGUID();
}
}
void Swap(ResourceHandle& other) {
std::swap(m_resource, other.m_resource);
std::swap(m_guid, other.m_guid);
}
private:
T* m_resource = nullptr;
ResourceGUID m_guid;
};
template<typename T>

View File

@@ -42,6 +42,7 @@ public:
void SetResourceRoot(const Containers::String& rootPath);
const Containers::String& GetResourceRoot() const;
bool BootstrapProjectAssets();
template<typename T>
ResourceHandle<T> Load(const Containers::String& path, ImportSettings* settings = nullptr) {
@@ -116,7 +117,13 @@ public:
Containers::Array<Containers::String> GetResourcePaths() const;
void UnloadGroup(const Containers::Array<ResourceGUID>& guids);
void RefreshAssetDatabase();
void RefreshProjectAssets();
bool CanReimportProjectAsset(const Containers::String& path) const;
bool ReimportProjectAsset(const Containers::String& path);
bool ClearProjectLibraryCache();
bool RebuildProjectAssetCache();
Containers::String GetProjectLibraryRoot() const;
AssetImportService::ImportStatusSnapshot GetProjectAssetImportStatus() const;
bool TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const;
bool TryResolveAssetPath(const AssetRef& assetRef, Containers::String& outPath) const;
void BeginDeferredSceneLoad();

View File

@@ -0,0 +1,38 @@
#pragma once
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Resources/UI/UIDocumentTypes.h>
namespace XCEngine {
namespace Resources {
struct UIDocumentCompileRequest {
UIDocumentKind kind = UIDocumentKind::View;
Containers::String path;
Containers::String expectedRootTag;
};
struct UIDocumentCompileResult {
UIDocumentModel document = {};
Containers::String errorMessage;
bool succeeded = false;
};
bool CompileUIDocument(
const UIDocumentCompileRequest& request,
UIDocumentCompileResult& outResult);
bool WriteUIDocumentArtifact(
const Containers::String& artifactPath,
const UIDocumentCompileResult& compileResult,
Containers::String* outErrorMessage = nullptr);
bool LoadUIDocumentArtifact(
const Containers::String& artifactPath,
UIDocumentKind expectedKind,
UIDocumentCompileResult& outResult);
Containers::String GetUIDocumentDefaultRootTag(UIDocumentKind kind);
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,44 @@
#pragma once
#include <XCEngine/Core/IO/IResourceLoader.h>
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
#include <XCEngine/Resources/UI/UIDocuments.h>
namespace XCEngine {
namespace Resources {
class UIViewLoader : public IResourceLoader {
public:
ResourceType GetResourceType() const override { return ResourceType::UIView; }
Containers::Array<Containers::String> GetSupportedExtensions() const override;
bool CanLoad(const Containers::String& path) const override;
LoadResult Load(const Containers::String& path, const ImportSettings* settings = nullptr) override;
ImportSettings* GetDefaultSettings() const override { return nullptr; }
bool CompileDocument(const Containers::String& path, UIDocumentCompileResult& outResult) const;
};
class UIThemeLoader : public IResourceLoader {
public:
ResourceType GetResourceType() const override { return ResourceType::UITheme; }
Containers::Array<Containers::String> GetSupportedExtensions() const override;
bool CanLoad(const Containers::String& path) const override;
LoadResult Load(const Containers::String& path, const ImportSettings* settings = nullptr) override;
ImportSettings* GetDefaultSettings() const override { return nullptr; }
bool CompileDocument(const Containers::String& path, UIDocumentCompileResult& outResult) const;
};
class UISchemaLoader : public IResourceLoader {
public:
ResourceType GetResourceType() const override { return ResourceType::UISchema; }
Containers::Array<Containers::String> GetSupportedExtensions() const override;
bool CanLoad(const Containers::String& path) const override;
LoadResult Load(const Containers::String& path, const ImportSettings* settings = nullptr) override;
ImportSettings* GetDefaultSettings() const override { return nullptr; }
bool CompileDocument(const Containers::String& path, UIDocumentCompileResult& outResult) const;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,76 @@
#pragma once
#include <XCEngine/Core/Containers/Array.h>
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/Types.h>
namespace XCEngine {
namespace Resources {
enum class UIDocumentKind : Core::uint8 {
View = 0,
Theme,
Schema
};
enum class UIDocumentDiagnosticSeverity : Core::uint8 {
Info = 0,
Warning,
Error
};
struct UIDocumentSourceLocation {
Core::uint32 line = 1;
Core::uint32 column = 1;
};
struct UIDocumentAttribute {
Containers::String name;
Containers::String value;
};
struct UIDocumentDiagnostic {
UIDocumentDiagnosticSeverity severity = UIDocumentDiagnosticSeverity::Error;
UIDocumentSourceLocation location = {};
Containers::String message;
};
struct UIDocumentNode {
Containers::String tagName;
Containers::Array<UIDocumentAttribute> attributes;
Containers::Array<UIDocumentNode> children;
UIDocumentSourceLocation location = {};
bool selfClosing = false;
const UIDocumentAttribute* FindAttribute(const Containers::String& name) const {
for (const UIDocumentAttribute& attribute : attributes) {
if (attribute.name == name) {
return &attribute;
}
}
return nullptr;
}
};
struct UIDocumentModel {
UIDocumentKind kind = UIDocumentKind::View;
Containers::String sourcePath;
Containers::String displayName;
UIDocumentNode rootNode;
Containers::Array<Containers::String> dependencies;
Containers::Array<UIDocumentDiagnostic> diagnostics;
bool valid = false;
void Clear() {
sourcePath.Clear();
displayName.Clear();
rootNode = UIDocumentNode();
dependencies.Clear();
diagnostics.Clear();
valid = false;
}
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,49 @@
#pragma once
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Resources/UI/UIDocumentTypes.h>
namespace XCEngine {
namespace Resources {
class UIDocumentResource : public IResource {
public:
const Containers::String& GetName() const override { return m_name; }
const Containers::String& GetPath() const override { return m_path; }
ResourceGUID GetGUID() const override { return m_guid; }
bool IsValid() const override { return m_isValid; }
size_t GetMemorySize() const override { return m_memorySize; }
void Release() override;
const UIDocumentModel& GetDocument() const { return m_document; }
const UIDocumentNode& GetRootNode() const { return m_document.rootNode; }
const Containers::Array<Containers::String>& GetDependencies() const { return m_document.dependencies; }
const Containers::Array<UIDocumentDiagnostic>& GetDiagnostics() const { return m_document.diagnostics; }
const Containers::String& GetSourcePath() const { return m_document.sourcePath; }
void SetDocumentModel(const UIDocumentModel& document);
void SetDocumentModel(UIDocumentModel&& document);
protected:
void RecalculateMemorySize();
UIDocumentModel m_document = {};
};
class UIView : public UIDocumentResource {
public:
ResourceType GetType() const override { return ResourceType::UIView; }
};
class UITheme : public UIDocumentResource {
public:
ResourceType GetType() const override { return ResourceType::UITheme; }
};
class UISchema : public UIDocumentResource {
public:
ResourceType GetType() const override { return ResourceType::UISchema; }
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
#include <algorithm>
#include <cctype>
@@ -613,34 +614,21 @@ void DestroyImportedMesh(Mesh* mesh) {
return;
}
std::vector<Material*> materials;
materials.reserve(mesh->GetMaterials().Size());
for (Material* material : mesh->GetMaterials()) {
if (material != nullptr) {
materials.push_back(material);
}
}
std::vector<Texture*> textures;
textures.reserve(mesh->GetTextures().Size());
for (Texture* texture : mesh->GetTextures()) {
if (texture != nullptr) {
textures.push_back(texture);
}
}
delete mesh;
for (Material* material : materials) {
delete material;
}
for (Texture* texture : textures) {
delete texture;
}
}
} // namespace
void AssetDatabase::ClearLastErrorMessage() {
m_lastErrorMessage.Clear();
}
void AssetDatabase::SetLastErrorMessage(const Containers::String& message) {
m_lastErrorMessage = message;
}
void AssetDatabase::Initialize(const Containers::String& projectRoot) {
ClearLastErrorMessage();
m_projectRoot = NormalizePathString(projectRoot);
m_assetsRoot = NormalizePathString(fs::path(m_projectRoot.CStr()) / "Assets");
m_libraryRoot = NormalizePathString(fs::path(m_projectRoot.CStr()) / "Library");
@@ -650,12 +638,12 @@ void AssetDatabase::Initialize(const Containers::String& projectRoot) {
EnsureProjectLayout();
LoadSourceAssetDB();
LoadArtifactDB();
ScanAssets();
}
void AssetDatabase::Shutdown() {
SaveSourceAssetDB();
SaveArtifactDB();
ClearLastErrorMessage();
m_projectRoot.Clear();
m_assetsRoot.Clear();
@@ -668,6 +656,7 @@ void AssetDatabase::Shutdown() {
}
AssetDatabase::MaintenanceStats AssetDatabase::Refresh() {
ClearLastErrorMessage();
return ScanAssets();
}
@@ -767,6 +756,7 @@ bool AssetDatabase::ReimportAsset(const Containers::String& requestPath,
ResolvedAsset& outAsset,
MaintenanceStats* outStats) {
outAsset = ResolvedAsset();
ClearLastErrorMessage();
if (outStats != nullptr) {
*outStats = MaintenanceStats();
}
@@ -774,26 +764,33 @@ bool AssetDatabase::ReimportAsset(const Containers::String& requestPath,
Containers::String absolutePath;
Containers::String relativePath;
if (!ResolvePath(requestPath, absolutePath, relativePath) || relativePath.Empty()) {
SetLastErrorMessage(Containers::String("Unable to resolve asset path: ") + requestPath);
return false;
}
const fs::path absoluteFsPath(absolutePath.CStr());
if (!fs::exists(absoluteFsPath) || fs::is_directory(absoluteFsPath)) {
SetLastErrorMessage(Containers::String("Asset source file does not exist: ") + absolutePath);
return false;
}
SourceAssetRecord sourceRecord;
if (!EnsureMetaForPath(absoluteFsPath, false, sourceRecord)) {
SetLastErrorMessage(Containers::String("Failed to prepare asset metadata: ") + absolutePath);
return false;
}
const ResourceType primaryType = GetPrimaryResourceTypeForImporter(sourceRecord.importerName);
if (primaryType == ResourceType::Unknown) {
SetLastErrorMessage(Containers::String("Asset type is not importable: ") + requestPath);
return false;
}
ArtifactRecord rebuiltRecord;
if (!ImportAsset(sourceRecord, rebuiltRecord)) {
if (m_lastErrorMessage.Empty()) {
SetLastErrorMessage(Containers::String("Failed to import asset: ") + requestPath);
}
return false;
}
@@ -811,10 +808,12 @@ bool AssetDatabase::ReimportAsset(const Containers::String& requestPath,
}
PopulateResolvedAssetResult(m_projectRoot, sourceRecord, rebuiltRecord, true, outAsset);
ClearLastErrorMessage();
return true;
}
bool AssetDatabase::ReimportAllAssets(MaintenanceStats* outStats) {
ClearLastErrorMessage();
if (outStats != nullptr) {
*outStats = MaintenanceStats();
}
@@ -1350,6 +1349,15 @@ Containers::String AssetDatabase::GetImporterNameForPath(const Containers::Strin
}
const std::string ext = ToLowerCopy(fs::path(relativePath.CStr()).extension().string());
if (ext == ".xcui") {
return Containers::String("UIViewImporter");
}
if (ext == ".xctheme") {
return Containers::String("UIThemeImporter");
}
if (ext == ".xcschema") {
return Containers::String("UISchemaImporter");
}
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" || ext == ".tga" || ext == ".gif" || ext == ".hdr") {
return Containers::String("TextureImporter");
}
@@ -1367,6 +1375,15 @@ Containers::String AssetDatabase::GetImporterNameForPath(const Containers::Strin
}
ResourceType AssetDatabase::GetPrimaryResourceTypeForImporter(const Containers::String& importerName) {
if (importerName == "UIViewImporter") {
return ResourceType::UIView;
}
if (importerName == "UIThemeImporter") {
return ResourceType::UITheme;
}
if (importerName == "UISchemaImporter") {
return ResourceType::UISchema;
}
if (importerName == "TextureImporter") {
return ResourceType::Texture;
}
@@ -1410,6 +1427,12 @@ bool AssetDatabase::ImportAsset(const SourceAssetRecord& sourceRecord,
ArtifactRecord& outRecord) {
const ResourceType primaryType = GetPrimaryResourceTypeForImporter(sourceRecord.importerName);
switch (primaryType) {
case ResourceType::UIView:
return ImportUIDocumentAsset(sourceRecord, UIDocumentKind::View, "main.xcuiasset", ResourceType::UIView, outRecord);
case ResourceType::UITheme:
return ImportUIDocumentAsset(sourceRecord, UIDocumentKind::Theme, "main.xcthemeasset", ResourceType::UITheme, outRecord);
case ResourceType::UISchema:
return ImportUIDocumentAsset(sourceRecord, UIDocumentKind::Schema, "main.xcschemaasset", ResourceType::UISchema, outRecord);
case ResourceType::Texture:
return ImportTextureAsset(sourceRecord, outRecord);
case ResourceType::Material:
@@ -1419,6 +1442,7 @@ bool AssetDatabase::ImportAsset(const SourceAssetRecord& sourceRecord,
case ResourceType::Shader:
return ImportShaderAsset(sourceRecord, outRecord);
default:
SetLastErrorMessage(Containers::String("No importer available for asset: ") + sourceRecord.relativePath);
return false;
}
}
@@ -1427,10 +1451,12 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
ResourceType requestedType,
ResolvedAsset& outAsset) {
outAsset = ResolvedAsset();
ClearLastErrorMessage();
Containers::String absolutePath;
Containers::String relativePath;
if (!ResolvePath(requestPath, absolutePath, relativePath) || relativePath.Empty()) {
SetLastErrorMessage(Containers::String("Unable to resolve asset path: ") + requestPath);
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
@@ -1441,6 +1467,7 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
const fs::path absoluteFsPath(absolutePath.CStr());
if (!fs::exists(absoluteFsPath)) {
SetLastErrorMessage(Containers::String("Asset source file does not exist: ") + absolutePath);
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
@@ -1454,6 +1481,7 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
SourceAssetRecord sourceRecord;
if (!EnsureMetaForPath(absoluteFsPath, fs::is_directory(absoluteFsPath), sourceRecord)) {
SetLastErrorMessage(Containers::String("Failed to prepare asset metadata: ") + absolutePath);
return false;
}
@@ -1472,6 +1500,13 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
const ResourceType primaryType = GetPrimaryResourceTypeForImporter(sourceRecord.importerName);
if (primaryType == ResourceType::Unknown || primaryType != requestedType) {
SetLastErrorMessage(
Containers::String("Asset type mismatch for ") +
requestPath +
": requested " +
GetResourceTypeName(requestedType) +
", importer produces " +
GetResourceTypeName(primaryType));
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
@@ -1499,6 +1534,9 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
}
ArtifactRecord rebuiltRecord;
if (!ImportAsset(sourceRecord, rebuiltRecord)) {
if (m_lastErrorMessage.Empty()) {
SetLastErrorMessage(Containers::String("Failed to import asset: ") + requestPath);
}
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Error(
Debug::LogCategory::FileSystem,
@@ -1518,11 +1556,13 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
}
if (artifactRecord == nullptr) {
SetLastErrorMessage(Containers::String("Imported asset did not produce an artifact: ") + requestPath);
return false;
}
outAsset.exists = true;
PopulateResolvedAssetResult(m_projectRoot, sourceRecord, *artifactRecord, outAsset.imported, outAsset);
ClearLastErrorMessage();
if (ShouldTraceAssetPath(requestPath)) {
Debug::Logger::Get().Info(
@@ -1789,6 +1829,88 @@ bool AssetDatabase::ImportShaderAsset(const SourceAssetRecord& sourceRecord,
return true;
}
bool AssetDatabase::ImportUIDocumentAsset(const SourceAssetRecord& sourceRecord,
UIDocumentKind kind,
const char* artifactFileName,
ResourceType resourceType,
ArtifactRecord& outRecord) {
const Containers::String absolutePath =
NormalizePathString(fs::path(m_projectRoot.CStr()) / sourceRecord.relativePath.CStr());
UIDocumentCompileResult compileResult = {};
if (!CompileUIDocument(
UIDocumentCompileRequest{kind, absolutePath, GetUIDocumentDefaultRootTag(kind)},
compileResult)) {
SetLastErrorMessage(
!compileResult.errorMessage.Empty()
? compileResult.errorMessage
: Containers::String("Failed to compile UI document: ") + sourceRecord.relativePath);
return false;
}
std::vector<ArtifactDependencyRecord> dependencies;
dependencies.reserve(compileResult.document.dependencies.Size());
std::unordered_set<std::string> seenDependencyPaths;
for (const Containers::String& dependencyPath : compileResult.document.dependencies) {
if (dependencyPath.Empty()) {
continue;
}
ArtifactDependencyRecord dependency;
if (!CaptureDependencyRecord(fs::path(dependencyPath.CStr()), dependency)) {
SetLastErrorMessage(
Containers::String("Failed to capture XCUI dependency metadata: ") + dependencyPath);
return false;
}
const std::string dependencyKey = ToStdString(dependency.path);
if (seenDependencyPaths.insert(dependencyKey).second) {
dependencies.push_back(std::move(dependency));
}
}
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
const Containers::String mainArtifactPath =
NormalizePathString(fs::path(artifactDir.CStr()) / artifactFileName);
std::error_code ec;
fs::remove_all(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
ec.clear();
fs::create_directories(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
if (ec) {
SetLastErrorMessage(
Containers::String("Failed to create UI artifact directory: ") + artifactDir);
return false;
}
Containers::String writeErrorMessage;
const Containers::String absoluteArtifactPath =
NormalizePathString(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr());
if (!WriteUIDocumentArtifact(absoluteArtifactPath, compileResult, &writeErrorMessage)) {
SetLastErrorMessage(
!writeErrorMessage.Empty()
? writeErrorMessage
: Containers::String("Failed to write UI document artifact: ") + mainArtifactPath);
return false;
}
outRecord.artifactKey = artifactKey;
outRecord.assetGuid = sourceRecord.guid;
outRecord.importerName = sourceRecord.importerName;
outRecord.importerVersion = sourceRecord.importerVersion;
outRecord.resourceType = resourceType;
outRecord.artifactDirectory = artifactDir;
outRecord.mainArtifactPath = mainArtifactPath;
outRecord.sourceHash = sourceRecord.sourceHash;
outRecord.metaHash = sourceRecord.metaHash;
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
outRecord.mainLocalID = kMainAssetLocalID;
outRecord.dependencies = std::move(dependencies);
return true;
}
Containers::String AssetDatabase::BuildArtifactKey(
const AssetDatabase::SourceAssetRecord& sourceRecord,
const std::vector<AssetDatabase::ArtifactDependencyRecord>& dependencies) const {
@@ -1891,10 +2013,6 @@ bool AssetDatabase::AreDependenciesCurrent(
currentWriteTime != dependency.writeTime) {
return false;
}
if (ComputeFileHash(resolvedPath) != dependency.hash) {
return false;
}
}
return true;

View File

@@ -1,8 +1,42 @@
#include <XCEngine/Core/Asset/AssetImportService.h>
#include <XCEngine/Debug/Logger.h>
#include <chrono>
#include <filesystem>
namespace XCEngine {
namespace Resources {
namespace fs = std::filesystem;
namespace {
Containers::String ToContainersString(const std::string& value) {
return Containers::String(value.c_str());
}
Containers::String BuildStatsSuffix(const AssetDatabase::MaintenanceStats& stats) {
std::string suffix;
if (stats.importedAssetCount > 0) {
suffix += " imported=";
suffix += std::to_string(stats.importedAssetCount);
}
if (stats.removedArtifactCount > 0) {
suffix += " removedOrphans=";
suffix += std::to_string(stats.removedArtifactCount);
}
return ToContainersString(suffix);
}
Core::uint64 GetCurrentSteadyTimeMs() {
return static_cast<Core::uint64>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count());
}
} // namespace
void AssetImportService::Initialize() {
}
@@ -10,6 +44,7 @@ void AssetImportService::Shutdown() {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
m_assetDatabase.Shutdown();
m_projectRoot.Clear();
ResetImportStatusLocked();
}
void AssetImportService::SetProjectRoot(const Containers::String& projectRoot) {
@@ -24,6 +59,7 @@ void AssetImportService::SetProjectRoot(const Containers::String& projectRoot) {
}
m_projectRoot = projectRoot;
ResetImportStatusLocked();
if (!m_projectRoot.Empty()) {
m_assetDatabase.Initialize(m_projectRoot);
}
@@ -34,22 +70,225 @@ Containers::String AssetImportService::GetProjectRoot() const {
return m_projectRoot;
}
Containers::String AssetImportService::GetLibraryRoot() const {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
return m_assetDatabase.GetLibraryRoot();
}
bool AssetImportService::BootstrapProject() {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (m_projectRoot.Empty()) {
FinishImportStatusLocked(
"Bootstrap Project",
Containers::String(),
false,
"Cannot bootstrap project assets without an active project.",
AssetDatabase::MaintenanceStats());
return false;
}
BeginImportStatusLocked(
"Bootstrap Project",
m_projectRoot,
"Bootstrapping project Library state...");
const AssetDatabase::MaintenanceStats stats = m_assetDatabase.Refresh();
FinishImportStatusLocked(
"Bootstrap Project",
m_projectRoot,
true,
Containers::String("Bootstrapped project Library state.") + BuildStatsSuffix(stats),
stats);
return true;
}
void AssetImportService::Refresh() {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!m_projectRoot.Empty()) {
m_assetDatabase.Refresh();
const AssetDatabase::MaintenanceStats stats = m_assetDatabase.Refresh();
if (stats.removedArtifactCount > 0) {
FinishImportStatusLocked(
"Refresh",
m_projectRoot,
true,
Containers::String("Refresh removed orphan artifact entries.") + BuildStatsSuffix(stats),
stats);
}
}
}
bool AssetImportService::ClearLibraryCache() {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (m_projectRoot.Empty()) {
FinishImportStatusLocked(
"Clear Library",
Containers::String(),
false,
"Cannot clear Library cache without an active project.",
AssetDatabase::MaintenanceStats());
return false;
}
BeginImportStatusLocked(
"Clear Library",
m_assetDatabase.GetLibraryRoot(),
"Clearing Library cache...");
const Containers::String projectRoot = m_projectRoot;
const Containers::String libraryRoot = m_assetDatabase.GetLibraryRoot();
m_assetDatabase.Shutdown();
std::error_code ec;
fs::remove_all(fs::path(libraryRoot.CStr()), ec);
m_assetDatabase.Initialize(projectRoot);
const AssetDatabase::MaintenanceStats stats = m_assetDatabase.Refresh();
const bool succeeded = !ec;
FinishImportStatusLocked(
"Clear Library",
libraryRoot,
succeeded,
succeeded
? Containers::String("Cleared Library cache and rebuilt source asset lookup.") + BuildStatsSuffix(stats)
: Containers::String("Failed to clear Library cache."),
stats);
return succeeded;
}
bool AssetImportService::RebuildLibraryCache() {
if (!ClearLibraryCache()) {
return false;
}
return ReimportAllAssets();
}
bool AssetImportService::ReimportAllAssets() {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (m_projectRoot.Empty()) {
FinishImportStatusLocked(
"Reimport All Assets",
Containers::String(),
false,
"Cannot reimport assets without an active project.",
AssetDatabase::MaintenanceStats());
return false;
}
BeginImportStatusLocked(
"Reimport All Assets",
m_projectRoot,
"Reimporting all project assets...");
m_assetDatabase.Refresh();
AssetDatabase::MaintenanceStats stats;
const bool succeeded = m_assetDatabase.ReimportAllAssets(&stats);
FinishImportStatusLocked(
"Reimport All Assets",
m_projectRoot,
succeeded,
succeeded
? Containers::String("Reimported all project assets.") + BuildStatsSuffix(stats)
: Containers::String("Reimport all assets completed with failures.") + BuildStatsSuffix(stats),
stats);
return succeeded;
}
bool AssetImportService::ReimportAsset(const Containers::String& requestPath,
ImportedAsset& outAsset) {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (m_projectRoot.Empty()) {
FinishImportStatusLocked(
"Reimport Asset",
requestPath,
false,
"Cannot reimport an asset without an active project.",
AssetDatabase::MaintenanceStats());
return false;
}
BeginImportStatusLocked(
"Reimport Asset",
requestPath,
Containers::String("Reimporting asset: ") + requestPath);
m_assetDatabase.Refresh();
AssetDatabase::ResolvedAsset resolvedAsset;
AssetDatabase::MaintenanceStats stats;
if (!m_assetDatabase.ReimportAsset(requestPath, resolvedAsset, &stats)) {
const Containers::String databaseError = m_assetDatabase.GetLastErrorMessage();
FinishImportStatusLocked(
"Reimport Asset",
requestPath,
false,
!databaseError.Empty()
? Containers::String("Failed to reimport asset: ") + requestPath + " - " + databaseError
: Containers::String("Failed to reimport asset: ") + requestPath,
stats);
return false;
}
outAsset = ConvertResolvedAsset(resolvedAsset);
FinishImportStatusLocked(
"Reimport Asset",
requestPath,
true,
Containers::String("Reimported asset: ") + requestPath + BuildStatsSuffix(stats),
stats);
return true;
}
AssetImportService::ImportStatusSnapshot AssetImportService::GetLastImportStatus() const {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
return m_lastImportStatus;
}
bool AssetImportService::TryGetImportableResourceType(const Containers::String& requestPath,
ResourceType& outType) const {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (m_projectRoot.Empty()) {
outType = ResourceType::Unknown;
return false;
}
return m_assetDatabase.TryGetImportableResourceType(requestPath, outType);
}
bool AssetImportService::EnsureArtifact(const Containers::String& requestPath,
ResourceType requestedType,
AssetDatabase::ResolvedAsset& outAsset) {
ImportedAsset& outAsset) {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (m_projectRoot.Empty()) {
return false;
}
return m_assetDatabase.EnsureArtifact(requestPath, requestedType, outAsset);
AssetDatabase::ResolvedAsset resolvedAsset;
if (!m_assetDatabase.EnsureArtifact(requestPath, requestedType, resolvedAsset)) {
const Containers::String databaseError = m_assetDatabase.GetLastErrorMessage();
FinishImportStatusLocked(
"Import Asset",
requestPath,
false,
!databaseError.Empty()
? Containers::String("Failed to build asset artifact: ") + requestPath + " - " + databaseError
: Containers::String("Failed to build asset artifact: ") + requestPath,
AssetDatabase::MaintenanceStats());
return false;
}
outAsset = ConvertResolvedAsset(resolvedAsset);
if (resolvedAsset.imported) {
AssetDatabase::MaintenanceStats stats;
stats.importedAssetCount = 1;
FinishImportStatusLocked(
"Import Asset",
requestPath,
true,
Containers::String("Imported asset artifact: ") + requestPath,
stats);
}
return true;
}
bool AssetImportService::TryGetAssetRef(const Containers::String& requestPath,
@@ -72,16 +311,83 @@ bool AssetImportService::TryGetPrimaryAssetPath(const AssetGUID& guid, Container
return m_assetDatabase.TryGetPrimaryAssetPath(guid, outRelativePath);
}
void AssetImportService::BuildLookupSnapshot(std::unordered_map<std::string, AssetGUID>& outPathToGuid,
std::unordered_map<AssetGUID, Containers::String>& outGuidToPath) const {
void AssetImportService::BuildLookupSnapshot(LookupSnapshot& outSnapshot) const {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
outPathToGuid.clear();
outGuidToPath.clear();
outSnapshot.Clear();
if (m_projectRoot.Empty()) {
return;
}
m_assetDatabase.BuildLookupSnapshot(outPathToGuid, outGuidToPath);
m_assetDatabase.BuildLookupSnapshot(outSnapshot.assetGuidByPathKey, outSnapshot.assetPathByGuid);
}
AssetImportService::ImportedAsset AssetImportService::ConvertResolvedAsset(
const AssetDatabase::ResolvedAsset& resolvedAsset) {
ImportedAsset importedAsset;
importedAsset.exists = resolvedAsset.exists;
importedAsset.artifactReady = resolvedAsset.artifactReady;
importedAsset.imported = resolvedAsset.imported;
importedAsset.absolutePath = resolvedAsset.absolutePath;
importedAsset.relativePath = resolvedAsset.relativePath;
importedAsset.assetGuid = resolvedAsset.assetGuid;
importedAsset.resourceType = resolvedAsset.resourceType;
importedAsset.runtimeLoadPath =
resolvedAsset.artifactReady ? resolvedAsset.artifactMainPath : resolvedAsset.absolutePath;
importedAsset.artifactDirectory = resolvedAsset.artifactDirectory;
importedAsset.mainLocalID = resolvedAsset.mainLocalID;
return importedAsset;
}
void AssetImportService::ResetImportStatusLocked() {
m_lastImportStatus = ImportStatusSnapshot();
m_nextImportStatusRevision = 1;
}
void AssetImportService::BeginImportStatusLocked(const Containers::String& operation,
const Containers::String& targetPath,
const Containers::String& message) {
const Core::uint64 startedAtMs = GetCurrentSteadyTimeMs();
m_lastImportStatus.revision = m_nextImportStatusRevision++;
m_lastImportStatus.inProgress = true;
m_lastImportStatus.success = false;
m_lastImportStatus.operation = operation;
m_lastImportStatus.targetPath = targetPath;
m_lastImportStatus.message = message;
m_lastImportStatus.startedAtMs = startedAtMs;
m_lastImportStatus.completedAtMs = 0;
m_lastImportStatus.durationMs = 0;
m_lastImportStatus.importedAssetCount = 0;
m_lastImportStatus.removedArtifactCount = 0;
}
void AssetImportService::FinishImportStatusLocked(const Containers::String& operation,
const Containers::String& targetPath,
bool success,
const Containers::String& message,
const AssetDatabase::MaintenanceStats& stats) {
const Core::uint64 completedAtMs = GetCurrentSteadyTimeMs();
const Core::uint64 startedAtMs = m_lastImportStatus.startedAtMs != 0
? m_lastImportStatus.startedAtMs
: completedAtMs;
m_lastImportStatus.revision = m_nextImportStatusRevision++;
m_lastImportStatus.inProgress = false;
m_lastImportStatus.success = success;
m_lastImportStatus.operation = operation;
m_lastImportStatus.targetPath = targetPath;
m_lastImportStatus.message = message;
m_lastImportStatus.startedAtMs = startedAtMs;
m_lastImportStatus.completedAtMs = completedAtMs;
m_lastImportStatus.durationMs = completedAtMs >= startedAtMs
? completedAtMs - startedAtMs
: 0;
m_lastImportStatus.importedAssetCount = stats.importedAssetCount;
m_lastImportStatus.removedArtifactCount = stats.removedArtifactCount;
if (success) {
Debug::Logger::Get().Info(Debug::LogCategory::FileSystem, Containers::String("[AssetImport] ") + message);
} else {
Debug::Logger::Get().Error(Debug::LogCategory::FileSystem, Containers::String("[AssetImport] ") + message);
}
}
} // namespace Resources

View File

@@ -76,17 +76,16 @@ void ProjectAssetIndex::ResetProjectRoot(const Containers::String& projectRoot)
}
void ProjectAssetIndex::RefreshFrom(const AssetImportService& importService) {
std::unordered_map<std::string, AssetGUID> pathToGuid;
std::unordered_map<AssetGUID, Containers::String> guidToPath;
AssetImportService::LookupSnapshot snapshot;
const Containers::String projectRoot = importService.GetProjectRoot();
if (!projectRoot.Empty()) {
importService.BuildLookupSnapshot(pathToGuid, guidToPath);
importService.BuildLookupSnapshot(snapshot);
}
std::unique_lock<std::shared_mutex> lock(m_mutex);
m_projectRoot = projectRoot;
m_assetGuidByPathKey = std::move(pathToGuid);
m_assetPathByGuid = std::move(guidToPath);
m_assetGuidByPathKey = std::move(snapshot.assetGuidByPathKey);
m_assetPathByGuid = std::move(snapshot.assetPathByGuid);
}
bool ProjectAssetIndex::TryGetAssetRef(AssetImportService& importService,

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Resources/UI/UIDocumentLoaders.h>
#include <exception>
namespace XCEngine {
@@ -45,6 +46,9 @@ MaterialLoader g_materialLoader;
MeshLoader g_meshLoader;
ShaderLoader g_shaderLoader;
TextureLoader g_textureLoader;
UIViewLoader g_uiViewLoader;
UIThemeLoader g_uiThemeLoader;
UISchemaLoader g_uiSchemaLoader;
} // namespace
@@ -85,6 +89,9 @@ void ResourceManager::EnsureInitialized() {
RegisterBuiltinLoader(*this, g_meshLoader);
RegisterBuiltinLoader(*this, g_shaderLoader);
RegisterBuiltinLoader(*this, g_textureLoader);
RegisterBuiltinLoader(*this, g_uiViewLoader);
RegisterBuiltinLoader(*this, g_uiThemeLoader);
RegisterBuiltinLoader(*this, g_uiSchemaLoader);
m_assetImportService.Initialize();
m_asyncLoader = std::move(asyncLoader);
@@ -106,11 +113,12 @@ void ResourceManager::Shutdown() {
}
void ResourceManager::SetResourceRoot(const Containers::String& rootPath) {
EnsureInitialized();
m_resourceRoot = rootPath;
if (!m_resourceRoot.Empty()) {
ResourceFileSystem::Get().Initialize(rootPath);
m_assetImportService.SetProjectRoot(rootPath);
m_projectAssetIndex.RefreshFrom(m_assetImportService);
BootstrapProjectAssets();
} else {
m_assetImportService.SetProjectRoot(Containers::String());
ResourceFileSystem::Get().Shutdown();
@@ -122,6 +130,16 @@ const Containers::String& ResourceManager::GetResourceRoot() const {
return m_resourceRoot;
}
bool ResourceManager::BootstrapProjectAssets() {
if (m_resourceRoot.Empty()) {
return false;
}
const bool bootstrapped = m_assetImportService.BootstrapProject();
m_projectAssetIndex.RefreshFrom(m_assetImportService);
return bootstrapped;
}
void ResourceManager::AddRef(ResourceGUID guid) {
std::lock_guard lock(m_mutex);
@@ -199,6 +217,7 @@ void ResourceManager::Unload(ResourceGUID guid) {
if (it != nullptr) {
resource = *it;
m_resourceCache.Erase(guid);
m_cache.Remove(guid);
m_guidToPath.Erase(guid);
m_memoryUsage -= resource->GetMemorySize();
}
@@ -223,6 +242,7 @@ void ResourceManager::UnloadAll() {
}
m_resourceCache.Clear();
m_cache.Clear();
m_refCounts.Clear();
m_guidToPath.Clear();
m_memoryUsage = 0;
@@ -346,6 +366,7 @@ void ResourceManager::UnloadGroup(const Containers::Array<ResourceGUID>& guids)
if (it != nullptr) {
IResource* resource = *it;
m_resourceCache.Erase(guid);
m_cache.Remove(guid);
m_guidToPath.Erase(guid);
m_memoryUsage -= resource->GetMemorySize();
resourcesToRelease.PushBack(resource);
@@ -360,13 +381,69 @@ void ResourceManager::UnloadGroup(const Containers::Array<ResourceGUID>& guids)
}
}
void ResourceManager::RefreshAssetDatabase() {
void ResourceManager::RefreshProjectAssets() {
if (!m_resourceRoot.Empty()) {
m_assetImportService.Refresh();
m_projectAssetIndex.RefreshFrom(m_assetImportService);
}
}
bool ResourceManager::CanReimportProjectAsset(const Containers::String& path) const {
if (m_resourceRoot.Empty() || path.Empty()) {
return false;
}
ResourceType importType = ResourceType::Unknown;
return m_assetImportService.TryGetImportableResourceType(path, importType);
}
bool ResourceManager::ReimportProjectAsset(const Containers::String& path) {
if (m_resourceRoot.Empty() || path.Empty()) {
return false;
}
UnloadAll();
AssetImportService::ImportedAsset importedAsset;
const bool reimported = m_assetImportService.ReimportAsset(path, importedAsset);
m_projectAssetIndex.RefreshFrom(m_assetImportService);
if (reimported && importedAsset.assetGuid.IsValid() && !importedAsset.relativePath.Empty()) {
m_projectAssetIndex.RememberResolvedPath(importedAsset.assetGuid, importedAsset.relativePath);
}
return reimported;
}
bool ResourceManager::ClearProjectLibraryCache() {
if (m_resourceRoot.Empty()) {
return false;
}
UnloadAll();
const bool cleared = m_assetImportService.ClearLibraryCache();
m_projectAssetIndex.RefreshFrom(m_assetImportService);
return cleared;
}
bool ResourceManager::RebuildProjectAssetCache() {
if (m_resourceRoot.Empty()) {
return false;
}
UnloadAll();
const bool rebuilt = m_assetImportService.RebuildLibraryCache();
m_projectAssetIndex.RefreshFrom(m_assetImportService);
return rebuilt;
}
Containers::String ResourceManager::GetProjectLibraryRoot() const {
return m_assetImportService.GetLibraryRoot();
}
AssetImportService::ImportStatusSnapshot ResourceManager::GetProjectAssetImportStatus() const {
return m_assetImportService.GetLastImportStatus();
}
bool ResourceManager::TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const {
const bool resolved = m_projectAssetIndex.TryGetAssetRef(m_assetImportService, path, resourceType, outRef);
@@ -510,12 +587,18 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
}
Containers::String loadPath = path;
AssetDatabase::ResolvedAsset resolvedAsset;
if (!m_resourceRoot.Empty() &&
AssetImportService::ImportedAsset resolvedAsset;
ResourceType importableType = ResourceType::Unknown;
const bool shouldUseProjectArtifact =
!m_resourceRoot.Empty() &&
m_assetImportService.TryGetImportableResourceType(path, importableType) &&
importableType == type;
if (shouldUseProjectArtifact &&
m_assetImportService.EnsureArtifact(path, type, resolvedAsset) &&
resolvedAsset.artifactReady) {
m_projectAssetIndex.RememberResolvedPath(resolvedAsset.assetGuid, resolvedAsset.relativePath);
loadPath = resolvedAsset.artifactMainPath;
loadPath = resolvedAsset.runtimeLoadPath;
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,

View File

@@ -307,7 +307,16 @@ void WritePackedMaterialProperty(Core::uint8* destination, const MaterialPropert
Material::Material() = default;
Material::~Material() = default;
Material::~Material() {
// Imported materials can own nested handles and container state; explicitly
// resetting them here avoids teardown-order issues during destruction.
m_shader.Reset();
m_tags = Containers::Array<MaterialTagEntry>();
m_properties = Containers::HashMap<Containers::String, MaterialProperty>();
m_constantLayout = Containers::Array<MaterialConstantFieldDesc>();
m_constantBufferData = Containers::Array<Core::uint8>();
m_textureBindings = Containers::Array<MaterialTextureBinding>();
}
void Material::Release() {
m_shader.Reset();

View File

@@ -2,19 +2,42 @@
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <cstring>
#include <unordered_set>
namespace XCEngine {
namespace Resources {
namespace {
template <typename T>
void DestroyOwnedResources(Containers::Array<T*>& resources) {
std::unordered_set<T*> releasedResources;
for (T* resource : resources) {
if (resource == nullptr) {
continue;
}
if (releasedResources.insert(resource).second) {
delete resource;
}
}
resources.Clear();
}
} // namespace
Mesh::Mesh() = default;
Mesh::~Mesh() = default;
Mesh::~Mesh() {
Release();
}
void Mesh::Release() {
m_vertexData.Clear();
m_indexData.Clear();
m_sections.Clear();
m_materials.Clear();
m_textures.Clear();
DestroyOwnedResources(m_materials);
DestroyOwnedResources(m_textures);
m_vertexCount = 0;
m_vertexStride = 0;
m_attributes = VertexAttribute::Position;

View File

@@ -0,0 +1,878 @@
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
#include <XCEngine/Core/Asset/ArtifactFormats.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_set>
namespace XCEngine {
namespace Resources {
namespace fs = std::filesystem;
namespace {
Containers::String ToContainersString(const std::string& value) {
return Containers::String(value.c_str());
}
std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
std::string ToLowerCopy(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
return value;
}
Containers::String NormalizePathString(const fs::path& path) {
return Containers::String(path.lexically_normal().generic_string().c_str());
}
Containers::String FormatDiagnosticMessage(const Containers::String& path,
const UIDocumentSourceLocation& location,
const Containers::String& message) {
return path +
":" +
Containers::String(std::to_string(location.line).c_str()) +
":" +
Containers::String(std::to_string(location.column).c_str()) +
": " +
message;
}
void WriteString(std::ofstream& stream, const Containers::String& value) {
const Core::uint32 length = static_cast<Core::uint32>(value.Length());
stream.write(reinterpret_cast<const char*>(&length), sizeof(length));
if (length > 0) {
stream.write(value.CStr(), length);
}
}
bool ReadString(std::ifstream& stream, Containers::String& outValue) {
Core::uint32 length = 0;
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
if (!stream) {
return false;
}
if (length == 0) {
outValue.Clear();
return true;
}
std::string buffer(length, '\0');
stream.read(buffer.data(), length);
if (!stream) {
return false;
}
outValue = ToContainersString(buffer);
return true;
}
bool WriteNode(std::ofstream& stream, const UIDocumentNode& node) {
WriteString(stream, node.tagName);
UIDocumentArtifactNodeHeader header;
header.attributeCount = static_cast<Core::uint32>(node.attributes.Size());
header.childCount = static_cast<Core::uint32>(node.children.Size());
header.line = node.location.line;
header.column = node.location.column;
header.selfClosing = node.selfClosing ? 1u : 0u;
stream.write(reinterpret_cast<const char*>(&header), sizeof(header));
if (!stream) {
return false;
}
for (const UIDocumentAttribute& attribute : node.attributes) {
WriteString(stream, attribute.name);
WriteString(stream, attribute.value);
if (!stream) {
return false;
}
}
for (const UIDocumentNode& child : node.children) {
if (!WriteNode(stream, child)) {
return false;
}
}
return static_cast<bool>(stream);
}
bool ReadNode(std::ifstream& stream, UIDocumentNode& outNode) {
if (!ReadString(stream, outNode.tagName)) {
return false;
}
UIDocumentArtifactNodeHeader header;
stream.read(reinterpret_cast<char*>(&header), sizeof(header));
if (!stream) {
return false;
}
outNode.location.line = header.line;
outNode.location.column = header.column;
outNode.selfClosing = header.selfClosing != 0;
outNode.attributes.Clear();
outNode.children.Clear();
outNode.attributes.Reserve(header.attributeCount);
outNode.children.Reserve(header.childCount);
for (Core::uint32 index = 0; index < header.attributeCount; ++index) {
UIDocumentAttribute attribute;
if (!ReadString(stream, attribute.name) ||
!ReadString(stream, attribute.value)) {
return false;
}
outNode.attributes.PushBack(std::move(attribute));
}
for (Core::uint32 index = 0; index < header.childCount; ++index) {
UIDocumentNode child;
if (!ReadNode(stream, child)) {
return false;
}
outNode.children.PushBack(std::move(child));
}
return true;
}
bool IsNameStartChar(char ch) {
return std::isalpha(static_cast<unsigned char>(ch)) != 0 ||
ch == '_' ||
ch == ':';
}
bool IsNameChar(char ch) {
return std::isalnum(static_cast<unsigned char>(ch)) != 0 ||
ch == '_' ||
ch == '-' ||
ch == ':' ||
ch == '.';
}
bool LooksLikeUIDocumentReference(const Containers::String& value) {
const std::string trimmed = ToLowerCopy(ToStdString(value.Trim()));
return trimmed.size() > 5 &&
(trimmed.size() >= 5 && trimmed.rfind(".xcui") == trimmed.size() - 5 ||
trimmed.size() >= 8 && trimmed.rfind(".xctheme") == trimmed.size() - 8 ||
trimmed.size() >= 9 && trimmed.rfind(".xcschema") == trimmed.size() - 9);
}
void AppendUniqueDependency(const Containers::String& dependencyPath,
std::unordered_set<std::string>& seenDependencies,
Containers::Array<Containers::String>& outDependencies) {
const std::string key = ToLowerCopy(ToStdString(dependencyPath));
if (seenDependencies.insert(key).second) {
outDependencies.PushBack(dependencyPath);
}
}
bool CollectUIDocumentDependencies(const fs::path& sourcePath,
const UIDocumentNode& node,
Containers::Array<Containers::String>& outDependencies,
Containers::Array<UIDocumentDiagnostic>& inOutDiagnostics,
Containers::String& outErrorMessage,
std::unordered_set<std::string>& seenDependencies) {
const fs::path sourceDirectory = sourcePath.parent_path();
for (const UIDocumentAttribute& attribute : node.attributes) {
if (!LooksLikeUIDocumentReference(attribute.value)) {
continue;
}
const Containers::String trimmedValue = attribute.value.Trim();
fs::path dependencyPath(trimmedValue.CStr());
if (!dependencyPath.is_absolute()) {
dependencyPath = sourceDirectory / dependencyPath;
}
dependencyPath = dependencyPath.lexically_normal();
if (!fs::exists(dependencyPath) || fs::is_directory(dependencyPath)) {
UIDocumentDiagnostic diagnostic;
diagnostic.severity = UIDocumentDiagnosticSeverity::Error;
diagnostic.location = node.location;
diagnostic.message =
Containers::String("Referenced UI document was not found: ") + trimmedValue;
inOutDiagnostics.PushBack(diagnostic);
outErrorMessage = FormatDiagnosticMessage(
NormalizePathString(sourcePath),
diagnostic.location,
diagnostic.message);
return false;
}
AppendUniqueDependency(
NormalizePathString(dependencyPath),
seenDependencies,
outDependencies);
}
for (const UIDocumentNode& child : node.children) {
if (!CollectUIDocumentDependencies(
sourcePath,
child,
outDependencies,
inOutDiagnostics,
outErrorMessage,
seenDependencies)) {
return false;
}
}
return true;
}
struct SourceFileReadResult {
bool succeeded = false;
fs::path resolvedPath;
std::string content;
Containers::String errorMessage;
};
bool ReadUIDocumentSourceFile(const Containers::String& path, SourceFileReadResult& outResult) {
outResult = SourceFileReadResult();
if (path.Empty()) {
outResult.errorMessage = "UI document path is empty.";
return false;
}
auto tryReadFile = [&](const fs::path& filePath) -> bool {
std::ifstream input(filePath, std::ios::binary);
if (!input.is_open()) {
return false;
}
std::ostringstream buffer;
buffer << input.rdbuf();
outResult.succeeded = static_cast<bool>(input) || input.eof();
outResult.resolvedPath = filePath.lexically_normal();
outResult.content = buffer.str();
return outResult.succeeded;
};
const fs::path requestedPath(path.CStr());
if (tryReadFile(requestedPath)) {
return true;
}
if (!requestedPath.is_absolute()) {
const Containers::String resourceRoot = ResourceManager::Get().GetResourceRoot();
if (!resourceRoot.Empty() &&
tryReadFile(fs::path(resourceRoot.CStr()) / requestedPath)) {
return true;
}
}
outResult.errorMessage = Containers::String("Unable to read UI document source file: ") + path;
return false;
}
class UIDocumentParser {
public:
UIDocumentParser(const std::string& source,
const fs::path& resolvedPath,
const Containers::String& expectedRootTag,
UIDocumentKind kind)
: m_source(source)
, m_resolvedPath(resolvedPath)
, m_expectedRootTag(expectedRootTag)
, m_kind(kind) {
}
bool Parse(UIDocumentCompileResult& outResult) {
outResult = UIDocumentCompileResult();
outResult.document.kind = m_kind;
outResult.document.sourcePath = NormalizePathString(m_resolvedPath);
if (HasUtf8Bom()) {
Advance();
Advance();
Advance();
}
SkipWhitespaceAndTrivia();
if (AtEnd()) {
AddError(CurrentLocation(), "UI document is empty.");
return Finalize(outResult, false);
}
if (Peek() != '<') {
AddError(CurrentLocation(), "Expected '<' at the beginning of the UI document.");
return Finalize(outResult, false);
}
UIDocumentNode rootNode;
if (!ParseElement(rootNode)) {
return Finalize(outResult, false);
}
SkipWhitespaceAndTrivia();
if (!AtEnd()) {
AddError(CurrentLocation(), "Unexpected trailing content after the root element.");
return Finalize(outResult, false);
}
if (!m_expectedRootTag.Empty() &&
rootNode.tagName != m_expectedRootTag) {
AddError(
rootNode.location,
Containers::String("Expected root element <") +
m_expectedRootTag +
">, found <" +
rootNode.tagName +
">.");
return Finalize(outResult, false);
}
outResult.document.rootNode = std::move(rootNode);
if (const UIDocumentAttribute* nameAttribute = outResult.document.rootNode.FindAttribute("name");
nameAttribute != nullptr && !nameAttribute->value.Empty()) {
outResult.document.displayName = nameAttribute->value;
}
std::unordered_set<std::string> seenDependencies;
if (!CollectUIDocumentDependencies(
m_resolvedPath,
outResult.document.rootNode,
outResult.document.dependencies,
outResult.document.diagnostics,
m_errorMessage,
seenDependencies)) {
return Finalize(outResult, false);
}
outResult.document.valid = true;
outResult.succeeded = true;
return Finalize(outResult, true);
}
private:
bool ParseElement(UIDocumentNode& outNode) {
const UIDocumentSourceLocation elementLocation = CurrentLocation();
if (!Consume('<', "Expected '<' to start an element.")) {
return false;
}
if (AtEnd()) {
AddError(elementLocation, "Unexpected end of file after '<'.");
return false;
}
if (Peek() == '/' || Peek() == '!' || Peek() == '?') {
AddError(elementLocation, "Unexpected token while parsing an element.");
return false;
}
Containers::String tagName;
if (!ParseName(tagName, "Expected an element tag name.")) {
return false;
}
outNode.location = elementLocation;
outNode.tagName = tagName;
while (!AtEnd()) {
SkipWhitespace();
if (StartsWith("/>")) {
Advance();
Advance();
outNode.selfClosing = true;
return true;
}
if (Peek() == '>') {
Advance();
break;
}
UIDocumentAttribute attribute;
if (!ParseAttribute(attribute)) {
return false;
}
outNode.attributes.PushBack(std::move(attribute));
}
if (AtEnd()) {
AddError(elementLocation, "Unexpected end of file before element start tag was closed.");
return false;
}
while (true) {
SkipWhitespaceAndTrivia();
if (AtEnd()) {
AddError(
elementLocation,
Containers::String("Unexpected end of file while parsing <") +
outNode.tagName +
">.");
return false;
}
if (StartsWith("</")) {
Advance();
Advance();
Containers::String closeName;
if (!ParseName(closeName, "Expected a closing element tag name.")) {
return false;
}
SkipWhitespace();
if (!Consume('>', "Expected '>' to finish the closing tag.")) {
return false;
}
if (closeName != outNode.tagName) {
AddError(
CurrentLocation(),
Containers::String("Closing tag </") +
closeName +
"> does not match <" +
outNode.tagName +
">.");
return false;
}
return true;
}
if (Peek() != '<') {
AddError(CurrentLocation(), "Text nodes are not supported in XCUI documents.");
return false;
}
UIDocumentNode childNode;
if (!ParseElement(childNode)) {
return false;
}
outNode.children.PushBack(std::move(childNode));
}
}
bool ParseAttribute(UIDocumentAttribute& outAttribute) {
if (!ParseName(outAttribute.name, "Expected an attribute name.")) {
return false;
}
SkipWhitespace();
if (!Consume('=', "Expected '=' after the attribute name.")) {
return false;
}
SkipWhitespace();
return ParseQuotedString(outAttribute.value);
}
bool ParseQuotedString(Containers::String& outValue) {
if (AtEnd()) {
AddError(CurrentLocation(), "Expected a quoted attribute value.");
return false;
}
const char quote = Peek();
if (quote != '"' && quote != '\'') {
AddError(CurrentLocation(), "Attribute values must use single or double quotes.");
return false;
}
Advance();
std::string value;
while (!AtEnd() && Peek() != quote) {
value.push_back(Peek());
Advance();
}
if (AtEnd()) {
AddError(CurrentLocation(), "Unterminated attribute value.");
return false;
}
Advance();
outValue = ToContainersString(value);
return true;
}
bool ParseName(Containers::String& outName, const char* errorMessage) {
if (AtEnd() || !IsNameStartChar(Peek())) {
AddError(CurrentLocation(), errorMessage);
return false;
}
std::string name;
name.push_back(Peek());
Advance();
while (!AtEnd() && IsNameChar(Peek())) {
name.push_back(Peek());
Advance();
}
outName = ToContainersString(name);
return true;
}
void SkipWhitespaceAndTrivia() {
while (!AtEnd()) {
SkipWhitespace();
if (StartsWith("<!--")) {
if (!SkipComment()) {
return;
}
continue;
}
if (StartsWith("<?")) {
if (!SkipProcessingInstruction()) {
return;
}
continue;
}
break;
}
}
void SkipWhitespace() {
while (!AtEnd() &&
std::isspace(static_cast<unsigned char>(Peek())) != 0) {
Advance();
}
}
bool SkipComment() {
const UIDocumentSourceLocation location = CurrentLocation();
Advance();
Advance();
Advance();
Advance();
while (!AtEnd() && !StartsWith("-->")) {
Advance();
}
if (AtEnd()) {
AddError(location, "Unterminated XML comment.");
return false;
}
Advance();
Advance();
Advance();
return true;
}
bool SkipProcessingInstruction() {
const UIDocumentSourceLocation location = CurrentLocation();
Advance();
Advance();
while (!AtEnd() && !StartsWith("?>")) {
Advance();
}
if (AtEnd()) {
AddError(location, "Unterminated processing instruction.");
return false;
}
Advance();
Advance();
return true;
}
bool Consume(char expected, const char* errorMessage) {
if (AtEnd() || Peek() != expected) {
AddError(CurrentLocation(), errorMessage);
return false;
}
Advance();
return true;
}
void AddError(const UIDocumentSourceLocation& location, const Containers::String& message) {
UIDocumentDiagnostic diagnostic;
diagnostic.severity = UIDocumentDiagnosticSeverity::Error;
diagnostic.location = location;
diagnostic.message = message;
m_diagnostics.PushBack(diagnostic);
if (m_errorMessage.Empty()) {
m_errorMessage = FormatDiagnosticMessage(NormalizePathString(m_resolvedPath), location, message);
}
}
bool Finalize(UIDocumentCompileResult& outResult, bool succeeded) {
Containers::Array<UIDocumentDiagnostic> combinedDiagnostics;
combinedDiagnostics.Reserve(
m_diagnostics.Size() +
outResult.document.diagnostics.Size());
for (const UIDocumentDiagnostic& diagnostic : m_diagnostics) {
combinedDiagnostics.PushBack(diagnostic);
}
for (const UIDocumentDiagnostic& diagnostic : outResult.document.diagnostics) {
combinedDiagnostics.PushBack(diagnostic);
}
outResult.document.diagnostics = std::move(combinedDiagnostics);
outResult.succeeded = succeeded;
outResult.errorMessage = succeeded ? Containers::String() : m_errorMessage;
if (!succeeded) {
outResult.document.valid = false;
}
return succeeded;
}
bool HasUtf8Bom() const {
return m_source.size() >= 3 &&
static_cast<unsigned char>(m_source[0]) == 0xEF &&
static_cast<unsigned char>(m_source[1]) == 0xBB &&
static_cast<unsigned char>(m_source[2]) == 0xBF;
}
bool StartsWith(const char* text) const {
const size_t length = std::strlen(text);
return m_offset + length <= m_source.size() &&
m_source.compare(m_offset, length, text) == 0;
}
bool AtEnd() const {
return m_offset >= m_source.size();
}
char Peek() const {
return AtEnd() ? '\0' : m_source[m_offset];
}
void Advance() {
if (AtEnd()) {
return;
}
if (m_source[m_offset] == '\n') {
++m_line;
m_column = 1;
} else {
++m_column;
}
++m_offset;
}
UIDocumentSourceLocation CurrentLocation() const {
UIDocumentSourceLocation location;
location.line = m_line;
location.column = m_column;
return location;
}
const std::string& m_source;
fs::path m_resolvedPath;
Containers::String m_expectedRootTag;
UIDocumentKind m_kind = UIDocumentKind::View;
size_t m_offset = 0;
Core::uint32 m_line = 1;
Core::uint32 m_column = 1;
Containers::Array<UIDocumentDiagnostic> m_diagnostics;
Containers::String m_errorMessage;
};
} // namespace
bool CompileUIDocument(const UIDocumentCompileRequest& request,
UIDocumentCompileResult& outResult) {
outResult = UIDocumentCompileResult();
SourceFileReadResult readResult;
if (!ReadUIDocumentSourceFile(request.path, readResult)) {
outResult.errorMessage = readResult.errorMessage;
outResult.succeeded = false;
outResult.document.kind = request.kind;
return false;
}
const Containers::String expectedRootTag =
request.expectedRootTag.Empty()
? GetUIDocumentDefaultRootTag(request.kind)
: request.expectedRootTag;
UIDocumentParser parser(
readResult.content,
readResult.resolvedPath,
expectedRootTag,
request.kind);
return parser.Parse(outResult);
}
bool WriteUIDocumentArtifact(const Containers::String& artifactPath,
const UIDocumentCompileResult& compileResult,
Containers::String* outErrorMessage) {
if (!compileResult.succeeded || !compileResult.document.valid) {
if (outErrorMessage != nullptr) {
*outErrorMessage = "UI document compile result is invalid.";
}
return false;
}
std::ofstream output(artifactPath.CStr(), std::ios::binary | std::ios::trunc);
if (!output.is_open()) {
if (outErrorMessage != nullptr) {
*outErrorMessage = Containers::String("Unable to write UI document artifact: ") + artifactPath;
}
return false;
}
UIDocumentArtifactFileHeader header;
header.kind = static_cast<Core::uint32>(compileResult.document.kind);
header.dependencyCount = static_cast<Core::uint32>(compileResult.document.dependencies.Size());
header.diagnosticCount = static_cast<Core::uint32>(compileResult.document.diagnostics.Size());
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
if (!output) {
if (outErrorMessage != nullptr) {
*outErrorMessage = Containers::String("Failed to write UI document artifact header: ") + artifactPath;
}
return false;
}
WriteString(output, compileResult.document.sourcePath);
WriteString(output, compileResult.document.displayName);
if (!WriteNode(output, compileResult.document.rootNode)) {
if (outErrorMessage != nullptr) {
*outErrorMessage = Containers::String("Failed to write UI document node tree: ") + artifactPath;
}
return false;
}
for (const Containers::String& dependency : compileResult.document.dependencies) {
WriteString(output, dependency);
if (!output) {
if (outErrorMessage != nullptr) {
*outErrorMessage = Containers::String("Failed to write UI document dependencies: ") + artifactPath;
}
return false;
}
}
for (const UIDocumentDiagnostic& diagnostic : compileResult.document.diagnostics) {
UIDocumentArtifactDiagnosticHeader diagnosticHeader;
diagnosticHeader.severity = static_cast<Core::uint32>(diagnostic.severity);
diagnosticHeader.line = diagnostic.location.line;
diagnosticHeader.column = diagnostic.location.column;
output.write(reinterpret_cast<const char*>(&diagnosticHeader), sizeof(diagnosticHeader));
WriteString(output, diagnostic.message);
if (!output) {
if (outErrorMessage != nullptr) {
*outErrorMessage = Containers::String("Failed to write UI document diagnostics: ") + artifactPath;
}
return false;
}
}
return true;
}
bool LoadUIDocumentArtifact(const Containers::String& artifactPath,
UIDocumentKind expectedKind,
UIDocumentCompileResult& outResult) {
outResult = UIDocumentCompileResult();
std::ifstream input(artifactPath.CStr(), std::ios::binary);
if (!input.is_open()) {
outResult.errorMessage = Containers::String("Unable to open UI document artifact: ") + artifactPath;
return false;
}
UIDocumentArtifactFileHeader header;
input.read(reinterpret_cast<char*>(&header), sizeof(header));
if (!input) {
outResult.errorMessage = Containers::String("Failed to read UI document artifact header: ") + artifactPath;
return false;
}
const UIDocumentArtifactFileHeader expectedHeader;
if (std::memcmp(header.magic, expectedHeader.magic, sizeof(header.magic)) != 0) {
outResult.errorMessage = Containers::String("Invalid UI document artifact magic: ") + artifactPath;
return false;
}
if (header.schemaVersion != kUIDocumentArtifactSchemaVersion) {
outResult.errorMessage = Containers::String("Unsupported UI document artifact schema version: ") + artifactPath;
return false;
}
if (header.kind != static_cast<Core::uint32>(expectedKind)) {
outResult.errorMessage = Containers::String("UI document artifact kind mismatch: ") + artifactPath;
return false;
}
outResult.document.kind = expectedKind;
if (!ReadString(input, outResult.document.sourcePath) ||
!ReadString(input, outResult.document.displayName) ||
!ReadNode(input, outResult.document.rootNode)) {
outResult.errorMessage = Containers::String("Failed to read UI document artifact body: ") + artifactPath;
return false;
}
outResult.document.dependencies.Clear();
outResult.document.dependencies.Reserve(header.dependencyCount);
for (Core::uint32 index = 0; index < header.dependencyCount; ++index) {
Containers::String dependency;
if (!ReadString(input, dependency)) {
outResult.errorMessage = Containers::String("Failed to read UI document artifact dependencies: ") + artifactPath;
return false;
}
outResult.document.dependencies.PushBack(std::move(dependency));
}
outResult.document.diagnostics.Clear();
outResult.document.diagnostics.Reserve(header.diagnosticCount);
for (Core::uint32 index = 0; index < header.diagnosticCount; ++index) {
UIDocumentArtifactDiagnosticHeader diagnosticHeader;
input.read(reinterpret_cast<char*>(&diagnosticHeader), sizeof(diagnosticHeader));
if (!input) {
outResult.errorMessage = Containers::String("Failed to read UI document artifact diagnostic header: ") + artifactPath;
return false;
}
UIDocumentDiagnostic diagnostic;
diagnostic.severity = static_cast<UIDocumentDiagnosticSeverity>(diagnosticHeader.severity);
diagnostic.location.line = diagnosticHeader.line;
diagnostic.location.column = diagnosticHeader.column;
if (!ReadString(input, diagnostic.message)) {
outResult.errorMessage = Containers::String("Failed to read UI document artifact diagnostic message: ") + artifactPath;
return false;
}
outResult.document.diagnostics.PushBack(std::move(diagnostic));
}
outResult.document.valid = true;
outResult.succeeded = true;
return true;
}
Containers::String GetUIDocumentDefaultRootTag(UIDocumentKind kind) {
switch (kind) {
case UIDocumentKind::View:
return "View";
case UIDocumentKind::Theme:
return "Theme";
case UIDocumentKind::Schema:
return "Schema";
default:
return "View";
}
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,61 @@
#include <XCEngine/Resources/UI/UIDocuments.h>
namespace XCEngine {
namespace Resources {
namespace {
size_t MeasureNodeMemorySize(const UIDocumentNode& node) {
size_t size = sizeof(UIDocumentNode) + node.tagName.Length();
for (const UIDocumentAttribute& attribute : node.attributes) {
size += sizeof(UIDocumentAttribute);
size += attribute.name.Length();
size += attribute.value.Length();
}
for (const UIDocumentNode& child : node.children) {
size += MeasureNodeMemorySize(child);
}
return size;
}
size_t MeasureDiagnosticMemorySize(const UIDocumentDiagnostic& diagnostic) {
return sizeof(UIDocumentDiagnostic) + diagnostic.message.Length();
}
} // namespace
void UIDocumentResource::Release() {
m_document.Clear();
SetInvalid();
m_memorySize = 0;
}
void UIDocumentResource::SetDocumentModel(const UIDocumentModel& document) {
m_document = document;
RecalculateMemorySize();
}
void UIDocumentResource::SetDocumentModel(UIDocumentModel&& document) {
m_document = std::move(document);
RecalculateMemorySize();
}
void UIDocumentResource::RecalculateMemorySize() {
size_t size = sizeof(*this);
size += m_document.sourcePath.Length();
size += m_document.displayName.Length();
size += MeasureNodeMemorySize(m_document.rootNode);
for (const Containers::String& dependency : m_document.dependencies) {
size += sizeof(Containers::String) + dependency.Length();
}
for (const UIDocumentDiagnostic& diagnostic : m_document.diagnostics) {
size += MeasureDiagnosticMemorySize(diagnostic);
}
m_memorySize = size;
}
} // namespace Resources
} // namespace XCEngine