Finalize library bootstrap status and stabilize async asset regressions
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user