Implement initial Unity-style asset library cache

This commit is contained in:
2026-04-02 03:03:36 +08:00
parent 619856ab22
commit 4c167bec0e
29 changed files with 4818 additions and 73 deletions

View File

@@ -1,6 +1,7 @@
#pragma once
#include <XCEngine/Components/Component.h>
#include <XCEngine/Core/Asset/AssetRef.h>
#include <XCEngine/Core/Asset/ResourceHandle.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
@@ -16,6 +17,7 @@ public:
Resources::Mesh* GetMesh() const { return m_mesh.Get(); }
const Resources::ResourceHandle<Resources::Mesh>& GetMeshHandle() const { return m_mesh; }
const std::string& GetMeshPath() const { return m_meshPath; }
const Resources::AssetRef& GetMeshAssetRef() const { return m_meshRef; }
void SetMeshPath(const std::string& meshPath);
void SetMesh(const Resources::ResourceHandle<Resources::Mesh>& mesh);
@@ -28,6 +30,7 @@ public:
private:
Resources::ResourceHandle<Resources::Mesh> m_mesh;
std::string m_meshPath;
Resources::AssetRef m_meshRef;
};
} // namespace Components

View File

@@ -1,6 +1,7 @@
#pragma once
#include <XCEngine/Components/Component.h>
#include <XCEngine/Core/Asset/AssetRef.h>
#include <XCEngine/Core/Asset/ResourceHandle.h>
#include <XCEngine/Resources/Material/Material.h>
@@ -20,6 +21,7 @@ public:
const Resources::ResourceHandle<Resources::Material>& GetMaterialHandle(size_t index) const;
const std::string& GetMaterialPath(size_t index) const;
const std::vector<std::string>& GetMaterialPaths() const { return m_materialPaths; }
const std::vector<Resources::AssetRef>& GetMaterialAssetRefs() const { return m_materialRefs; }
void SetMaterialPath(size_t index, const std::string& materialPath);
void SetMaterial(size_t index, const Resources::ResourceHandle<Resources::Material>& material);
@@ -45,6 +47,7 @@ private:
std::vector<Resources::ResourceHandle<Resources::Material>> m_materials;
std::vector<std::string> m_materialPaths;
std::vector<Resources::AssetRef> m_materialRefs;
bool m_castShadows = true;
bool m_receiveShadows = true;
uint32_t m_renderLayer = 0;

View File

@@ -0,0 +1,59 @@
#pragma once
#include <XCEngine/Core/Math/Bounds.h>
#include <XCEngine/Core/Types.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Texture/Texture.h>
namespace XCEngine {
namespace Resources {
constexpr Core::uint32 kTextureArtifactSchemaVersion = 1;
constexpr Core::uint32 kMeshArtifactSchemaVersion = 1;
struct TextureArtifactHeader {
char magic[8] = { 'X', 'C', 'T', 'E', 'X', '0', '1', '\0' };
Core::uint32 schemaVersion = kTextureArtifactSchemaVersion;
Core::uint32 textureType = 0;
Core::uint32 textureFormat = 0;
Core::uint32 width = 0;
Core::uint32 height = 0;
Core::uint32 depth = 0;
Core::uint32 mipLevels = 0;
Core::uint32 arraySize = 0;
Core::uint64 pixelDataSize = 0;
};
struct MeshArtifactHeader {
char magic[8] = { 'X', 'C', 'M', 'E', 'S', 'H', '1', '\0' };
Core::uint32 schemaVersion = kMeshArtifactSchemaVersion;
Core::uint32 vertexCount = 0;
Core::uint32 vertexStride = 0;
Core::uint32 vertexAttributes = 0;
Core::uint32 indexCount = 0;
Core::uint32 use32BitIndex = 0;
Core::uint32 sectionCount = 0;
Core::uint32 materialCount = 0;
Core::uint32 textureCount = 0;
Math::Vector3 boundsMin = Math::Vector3::Zero();
Math::Vector3 boundsMax = Math::Vector3::Zero();
Core::uint64 vertexDataSize = 0;
Core::uint64 indexDataSize = 0;
};
struct MaterialArtifactHeader {
Core::int32 renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
MaterialRenderState renderState = {};
Core::uint32 tagCount = 0;
Core::uint32 propertyCount = 0;
Core::uint32 textureBindingCount = 0;
};
struct MaterialPropertyArtifact {
Core::uint32 propertyType = 0;
MaterialProperty::Value value = {};
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,130 @@
#pragma once
#include <XCEngine/Core/Asset/AssetRef.h>
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/Types.h>
#include <filesystem>
#include <unordered_map>
namespace XCEngine {
namespace Resources {
class AssetDatabase {
public:
struct SourceAssetRecord {
AssetGUID guid;
Containers::String relativePath;
Containers::String metaPath;
bool isFolder = false;
Containers::String importerName;
Core::uint32 importerVersion = 0;
Containers::String metaHash;
Containers::String sourceHash;
Core::uint64 sourceFileSize = 0;
Core::uint64 sourceWriteTime = 0;
Containers::String lastKnownArtifactKey;
};
struct ArtifactRecord {
Containers::String artifactKey;
AssetGUID assetGuid;
Containers::String importerName;
Core::uint32 importerVersion = 0;
ResourceType resourceType = ResourceType::Unknown;
Containers::String artifactDirectory;
Containers::String mainArtifactPath;
Containers::String sourceHash;
Containers::String metaHash;
Core::uint64 sourceFileSize = 0;
Core::uint64 sourceWriteTime = 0;
LocalID mainLocalID = kMainAssetLocalID;
};
struct ResolvedAsset {
bool exists = false;
bool artifactReady = false;
Containers::String absolutePath;
Containers::String relativePath;
AssetGUID assetGuid;
ResourceType resourceType = ResourceType::Unknown;
Containers::String artifactMainPath;
Containers::String artifactDirectory;
LocalID mainLocalID = kMainAssetLocalID;
};
void Initialize(const Containers::String& projectRoot);
void Shutdown();
void Refresh();
bool ResolvePath(const Containers::String& requestPath,
Containers::String& outAbsolutePath,
Containers::String& outRelativePath) const;
bool TryGetAssetGuid(const Containers::String& requestPath, AssetGUID& outGuid) const;
bool TryGetAssetRef(const Containers::String& requestPath, ResourceType resourceType, AssetRef& outRef) const;
bool EnsureArtifact(const Containers::String& requestPath,
ResourceType requestedType,
ResolvedAsset& outAsset);
bool TryGetPrimaryAssetPath(const AssetGUID& guid, Containers::String& outRelativePath) const;
const Containers::String& GetProjectRoot() const { return m_projectRoot; }
const Containers::String& GetAssetsRoot() const { return m_assetsRoot; }
const Containers::String& GetLibraryRoot() const { return m_libraryRoot; }
private:
static constexpr Core::uint32 kCurrentImporterVersion = 1;
void EnsureProjectLayout();
void LoadSourceAssetDB();
void SaveSourceAssetDB() const;
void LoadArtifactDB();
void SaveArtifactDB() const;
void ScanAssets();
void ScanAssetPath(const std::filesystem::path& path,
std::unordered_map<std::string, bool>& seenPaths);
void RemoveMissingRecords(const std::unordered_map<std::string, bool>& seenPaths);
bool EnsureMetaForPath(const std::filesystem::path& sourcePath,
bool isFolder,
SourceAssetRecord& outRecord);
bool ReadMetaFile(const std::filesystem::path& metaPath,
SourceAssetRecord& inOutRecord) const;
void WriteMetaFile(const std::filesystem::path& metaPath,
const SourceAssetRecord& record) const;
Containers::String NormalizeRelativePath(const std::filesystem::path& sourcePath) const;
static Containers::String NormalizePathString(const std::filesystem::path& path);
static Containers::String NormalizePathString(const Containers::String& path);
static Containers::String MakeKey(const Containers::String& path);
static Containers::String GetImporterNameForPath(const Containers::String& relativePath, bool isFolder);
static ResourceType GetPrimaryResourceTypeForImporter(const Containers::String& importerName);
bool ShouldReimport(const SourceAssetRecord& sourceRecord,
const ArtifactRecord* artifactRecord) const;
bool ImportAsset(const SourceAssetRecord& sourceRecord,
ArtifactRecord& outRecord);
bool ImportTextureAsset(const SourceAssetRecord& sourceRecord,
ArtifactRecord& outRecord);
bool ImportModelAsset(const SourceAssetRecord& sourceRecord,
ArtifactRecord& outRecord);
Containers::String BuildArtifactKey(const SourceAssetRecord& sourceRecord) const;
Containers::String BuildArtifactDirectory(const Containers::String& artifactKey) const;
static Containers::String ReadWholeFileText(const std::filesystem::path& path);
static Containers::String ComputeFileHash(const std::filesystem::path& path);
static Core::uint64 GetFileSizeValue(const std::filesystem::path& path);
static Core::uint64 GetFileWriteTimeValue(const std::filesystem::path& path);
Containers::String m_projectRoot;
Containers::String m_assetsRoot;
Containers::String m_libraryRoot;
Containers::String m_sourceDbPath;
Containers::String m_artifactDbPath;
std::unordered_map<std::string, SourceAssetRecord> m_sourcesByPathKey;
std::unordered_map<AssetGUID, SourceAssetRecord> m_sourcesByGuid;
std::unordered_map<AssetGUID, ArtifactRecord> m_artifactsByGuid;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,54 @@
#pragma once
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/Types.h>
namespace XCEngine {
namespace Resources {
struct AssetGUID {
Core::uint64 high = 0;
Core::uint64 low = 0;
AssetGUID() = default;
AssetGUID(Core::uint64 inHigh, Core::uint64 inLow)
: high(inHigh), low(inLow) {}
bool IsValid() const {
return high != 0 || low != 0;
}
bool operator==(const AssetGUID& other) const {
return high == other.high && low == other.low;
}
bool operator!=(const AssetGUID& other) const {
return !(*this == other);
}
static AssetGUID Generate();
static bool TryParse(const Containers::String& text, AssetGUID& outGuid);
static AssetGUID ParseOrDefault(const Containers::String& text);
Containers::String ToString() const;
};
using LocalID = Core::uint64;
constexpr LocalID kInvalidLocalID = 0;
constexpr LocalID kMainAssetLocalID = 1;
AssetGUID HashBytesToAssetGUID(const void* data, size_t size);
AssetGUID HashStringToAssetGUID(const Containers::String& text);
} // namespace Resources
} // namespace XCEngine
namespace std {
template<>
struct hash<XCEngine::Resources::AssetGUID> {
size_t operator()(const XCEngine::Resources::AssetGUID& guid) const noexcept {
return static_cast<size_t>(guid.high ^ (guid.low * 0x9e3779b97f4a7c15ULL));
}
};
}

View File

@@ -0,0 +1,46 @@
#pragma once
#include <XCEngine/Core/Asset/AssetGUID.h>
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <string>
namespace XCEngine {
namespace Resources {
struct AssetRef {
AssetGUID assetGuid;
LocalID localID = kInvalidLocalID;
ResourceType resourceType = ResourceType::Unknown;
bool IsValid() const {
return assetGuid.IsValid() && localID != kInvalidLocalID && resourceType != ResourceType::Unknown;
}
void Reset() {
assetGuid = AssetGUID();
localID = kInvalidLocalID;
resourceType = ResourceType::Unknown;
}
Containers::String ToString() const {
if (!IsValid()) {
return Containers::String();
}
return assetGuid.ToString() + ":" + Containers::String(std::to_string(localID).c_str()) +
":" + Containers::String(GetResourceTypeName(resourceType));
}
};
template<typename T>
AssetRef MakeMainAssetRef(const AssetGUID& assetGuid) {
AssetRef ref;
ref.assetGuid = assetGuid;
ref.localID = kMainAssetLocalID;
ref.resourceType = GetResourceType<T>();
return ref;
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -1,6 +1,7 @@
#pragma once
#include <XCEngine/Core/IO/IResourceLoader.h>
#include "AssetDatabase.h"
#include "ResourceCache.h"
#include "AsyncLoader.h"
#include "ResourceHandle.h"
@@ -28,33 +29,26 @@ public:
template<typename T>
ResourceHandle<T> Load(const Containers::String& path, ImportSettings* settings = nullptr) {
static_assert(std::is_base_of_v<IResource, T>, "T must derive from IResource");
ResourceGUID guid = ResourceGUID::Generate(path);
IResource* cached = FindInCache(guid);
if (cached) {
return ResourceHandle<T>(static_cast<T*>(cached));
}
IResourceLoader* loader = FindLoader(GetResourceType<T>());
if (!loader) {
Debug::Logger::Get().Warning(Debug::LogCategory::FileSystem,
Containers::String("No loader found for resource type: ") +
GetResourceTypeName(GetResourceType<T>()));
LoadResult result = LoadResource(path, GetResourceType<T>(), settings);
if (!result || result.resource == nullptr) {
return ResourceHandle<T>();
}
LoadResult result = loader->Load(path, settings);
if (!result) {
Debug::Logger::Get().Error(Debug::LogCategory::FileSystem,
Containers::String("Failed to load resource: ") + path + " - " + result.errorMessage);
return ResourceHandle<T>();
}
AddToCache(guid, result.resource);
return ResourceHandle<T>(static_cast<T*>(result.resource));
}
template<typename T>
ResourceHandle<T> Load(const AssetRef& assetRef, ImportSettings* settings = nullptr) {
static_assert(std::is_base_of_v<IResource, T>, "T must derive from IResource");
Containers::String path;
if (!assetRef.IsValid() || !m_assetDatabase.TryGetPrimaryAssetPath(assetRef.assetGuid, path)) {
return ResourceHandle<T>();
}
return Load<T>(path, settings);
}
void LoadAsync(const Containers::String& path, ResourceType type,
std::function<void(LoadResult)> callback);
@@ -102,6 +96,8 @@ public:
Containers::Array<Containers::String> GetResourcePaths() const;
void UnloadGroup(const Containers::Array<ResourceGUID>& guids);
void RefreshAssetDatabase();
bool TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const;
private:
ResourceManager() = default;
@@ -111,6 +107,7 @@ private:
void AddToCache(ResourceGUID guid, IResource* resource);
IResourceLoader* FindLoader(ResourceType type);
void ReloadResource(ResourceGUID guid);
LoadResult LoadResource(const Containers::String& path, ResourceType type, ImportSettings* settings);
Containers::String m_resourceRoot;
Containers::HashMap<ResourceGUID, IResource*> m_resourceCache;
@@ -121,6 +118,7 @@ private:
size_t m_memoryUsage = 0;
size_t m_memoryBudget = 512 * 1024 * 1024;
AssetDatabase m_assetDatabase;
ResourceCache m_cache;
Core::UniqueRef<AsyncLoader> m_asyncLoader;
Threading::Mutex m_mutex;

View File

@@ -38,6 +38,7 @@ public:
bool Insert(Pair&& pair);
bool Erase(const Key& key);
void Clear();
Array<Pair> GetPairs() const;
size_t Size() const { return m_size; }
bool Empty() const { return m_size == 0; }
@@ -274,6 +275,19 @@ void HashMap<Key, Value>::Clear() {
m_size = 0;
}
template<typename Key, typename Value>
Array<typename HashMap<Key, Value>::Pair> HashMap<Key, Value>::GetPairs() const {
Array<Pair> pairs;
pairs.Reserve(m_size);
for (size_t bucketIndex = 0; bucketIndex < m_buckets.Size(); ++bucketIndex) {
const Bucket& bucket = m_buckets[bucketIndex];
for (size_t pairIndex = 0; pairIndex < bucket.pairs.Size(); ++pairIndex) {
pairs.PushBack(bucket.pairs[pairIndex]);
}
}
return pairs;
}
template<typename Key, typename Value>
size_t HashMap<Key, Value>::GetBucketIndex(const Key& key) const {
if (m_bucketCount == 0) {

View File

@@ -0,0 +1,34 @@
#pragma once
#include <XCEngine/Core/IO/IResourceLoader.h>
namespace XCEngine {
namespace Resources {
enum class BuiltinPrimitiveType {
Cube,
Sphere,
Capsule,
Cylinder,
Plane,
Quad
};
bool IsBuiltinResourcePath(const Containers::String& path);
bool IsBuiltinMeshPath(const Containers::String& path);
bool IsBuiltinMaterialPath(const Containers::String& path);
bool IsBuiltinTexturePath(const Containers::String& path);
const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType);
Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType);
Containers::String GetBuiltinDefaultPrimitiveMaterialPath();
Containers::String GetBuiltinDefaultPrimitiveTexturePath();
bool TryParseBuiltinPrimitiveType(const Containers::String& path, BuiltinPrimitiveType& outPrimitiveType);
LoadResult CreateBuiltinMeshResource(const Containers::String& path);
LoadResult CreateBuiltinMaterialResource(const Containers::String& path);
LoadResult CreateBuiltinTextureResource(const Containers::String& path);
} // namespace Resources
} // namespace XCEngine

View File

@@ -10,6 +10,7 @@
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Vector4.h>
#include <cstring>
#include <vector>
namespace XCEngine {
namespace Resources {
@@ -126,6 +127,17 @@ struct MaterialProperty {
MaterialProperty() : type(MaterialPropertyType::Float), refCount(0) {}
};
struct MaterialTagEntry {
Containers::String name;
Containers::String value;
};
struct MaterialTextureBinding {
Containers::String name;
Core::uint32 slot = 0;
ResourceHandle<Texture> texture;
};
class Material : public IResource {
public:
Material();
@@ -158,6 +170,9 @@ public:
void RemoveTag(const Containers::String& name);
void ClearTags();
Core::uint32 GetTagCount() const { return static_cast<Core::uint32>(m_tags.Size()); }
Containers::String GetTagName(Core::uint32 index) const;
Containers::String GetTagValue(Core::uint32 index) const;
const Containers::Array<MaterialTagEntry>& GetTags() const { return m_tags; }
void SetFloat(const Containers::String& name, float value);
void SetFloat2(const Containers::String& name, const Math::Vector2& value);
@@ -175,7 +190,11 @@ public:
bool GetBool(const Containers::String& name) const;
ResourceHandle<Texture> GetTexture(const Containers::String& name) const;
Core::uint32 GetTextureBindingCount() const { return static_cast<Core::uint32>(m_textureBindings.Size()); }
Containers::String GetTextureBindingName(Core::uint32 index) const;
ResourceHandle<Texture> GetTextureBindingTexture(Core::uint32 index) const;
const Containers::Array<MaterialTextureBinding>& GetTextureBindings() const { return m_textureBindings; }
std::vector<MaterialProperty> GetProperties() const;
const Containers::Array<Core::uint8>& GetConstantBufferData() const { return m_constantBufferData; }
void UpdateConstantBuffer();
void RecalculateMemorySize();
@@ -191,20 +210,10 @@ private:
Core::int32 m_renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
MaterialRenderState m_renderState;
Containers::String m_shaderPass;
struct TagEntry {
Containers::String name;
Containers::String value;
};
Containers::Array<TagEntry> m_tags;
Containers::Array<MaterialTagEntry> m_tags;
Containers::HashMap<Containers::String, MaterialProperty> m_properties;
Containers::Array<Core::uint8> m_constantBufferData;
struct TextureBinding {
Containers::String name;
Core::uint32 slot;
ResourceHandle<Texture> texture;
};
Containers::Array<TextureBinding> m_textureBindings;
Containers::Array<MaterialTextureBinding> m_textureBindings;
};
} // namespace Resources

View File

@@ -13,6 +13,7 @@
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>