# 资源系统设计与实现 > **目标**:构建游戏引擎资源管理系统,支持多种资源类型的加载、缓存、异步加载 > **阶段**:第四阶段(与RHI并行开发) > **前置依赖**:第一阶段核心基础层(容器、内存管理、线程系统) --- ## 0. 类型声明(文档内统一使用) 本文档中使用的类型说明: - `String` = `XCEngine::Containers::String` - `Array` = `XCEngine::Containers::Array` - `HashMap` = `XCEngine::Containers::HashMap` - `uint8/uint32/uint64` = `XCEngine::Core::uint8/uint32/uint64` - `UniquePtr` = `XCEngine::Core::UniqueRef` 或 `std::unique_ptr` - `SharedPtr` = `XCEngine::Core::Ref` 或 `std::shared_ptr` - `Mutex` = `XCEngine::Threading::Mutex` - `ITask` = `XCEngine::Threading::ITask` --- ## 1. 概述 ### 1.1 设计目标 资源系统是游戏引擎的核心基础设施之一,负责管理游戏运行时所需的所有数据资产。一个优秀的资源系统需要满足以下目标: #### 1.1.1 功能性目标 - **统一资源管理**:提供统一的接口来管理不同类型的资源(纹理、网格、材质、音频等) - **异步加载支持**:支持后台线程异步加载资源,避免阻塞主线程 - **资源缓存机制**:实现智能的资源缓存和淘汰策略,优化内存使用 - **引用计数管理**:通过引用计数自动管理资源生命周期 - **资源依赖管理**:跟踪资源之间的依赖关系,支持资源组加载和批量卸载 - **可扩展架构**:易于添加新的资源类型和加载器 #### 1.1.2 性能目标 - **低内存占用**:通过LRU缓存和按需加载控制内存使用 - **快速加载**:异步加载 + 缓存命中 = 零加载时间 - **线程安全**:多线程环境下安全访问资源 - **零拷贝优化**:尽可能减少不必要的数据拷贝 #### 1.1.3 开发效率目标 - **简单易用的API**:提供清晰的同步/异步加载接口 - **灵活的配置**:支持自定义导入设置和加载选项 - **完善的错误处理**:清晰的错误信息和调试支持 --- ## 2. 系统架构 ### 2.1 整体架构图 ``` ┌─────────────────────────────────────────────────────────────────────────────────┐ │ ResourceSystem │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────────────────────┐ │ │ │ 应用层 (Application) │ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │ │ │ │ │ Game Code │ │ Renderer │ │ Scene │ │ Editor │ │ │ │ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────────┬──────────┘ │ │ │ └────────┼────────────────┼────────────────┼───────────────────┼────────────┘ │ │ │ │ │ │ │ │ ┌────────▼────────────────▼────────────────▼───────────────────▼────────────┐ │ │ │ ResourceManager (资源管理器) │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ • Load(path) - 同步加载 │ │ │ │ │ │ • LoadAsync(path, callback) - 异步加载 │ │ │ │ │ │ • Unload(path/guid) - 卸载资源 │ │ │ │ │ │ • RegisterLoader() - 注册加载器 │ │ │ │ │ │ • AddRef/Release - 引用计数管理 │ │ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └────────┬───────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌────────▼───────────────────────────────────────────────────────────────────┐ │ │ │ ResourceLoader (加载器层) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │ │ │ │ │TextureLoader │ │ MeshLoader │ │ AudioLoader │ │ShaderLoader │ │ │ │ │ │ (纹理加载) │ │ (模型加载) │ │ (音频加载) │ │ (着色器加载) │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ └─────────────┘ │ │ │ └────────┬───────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌────────▼───────────────────────────────────────────────────────────────────┐ │ │ │ ResourceCache (缓存层) │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ • LRU淘汰策略 │ │ │ │ │ │ • 内存压力响应 │ │ │ │ │ │ • 手动刷新接口 │ │ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └────────┬───────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌────────▼───────────────────────────────────────────────────────────────────┐ │ │ │ AsyncLoader (异步加载层) │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ • 后台工作线程 │ │ │ │ │ │ • 任务队列管理 │ │ │ │ │ │ • 完成回调分发 │ │ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └────────┬───────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌────────▼───────────────────────────────────────────────────────────────────┐ │ │ │ FileSystem (文件系统层) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ Stream │ │ Path.h │ │ Archive │ │ Compression │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ └────────────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘ ``` ### 2.2 模块职责 | 模块 | 职责 | 关键类 | |------|------|--------| | **ResourceManager** | 资源总入口,引用计数管理 | ResourceManager | | **ResourceLoader** | 具体资源类型的加载逻辑 | IResourceLoader, TextureLoader, MeshLoader | | **ResourceCache** | 内存缓存,LRU淘汰 | ResourceCache, CacheEntry | | **AsyncLoader** | 异步任务调度 | AsyncLoader, LoadRequest | | **FileSystem** | 文件读写,路径管理 | FileSystem, Path, FileReader | ### 2.3 数据流 ``` 同步加载流程: Load(path) → CheckCache → Found? → Return Handle ↓ Not Found ↓ GetLoader(type) → LoadResource → Create Handle → Add to Cache → Return 异步加载流程: LoadAsync(path, callback) → Create Request → Submit to Queue ↓ Worker Thread: Get Request → GetLoader → LoadResource ↓ Main Thread: Update() → Process Completed → Invoke Callback → Create Handle ``` --- ## 3. 目录结构 ### 3.1 设计目录结构 ``` engine/include/XCEngine/Resources/ ├── Resources.h # 主头文件,导出所有资源系统类 ├── IResource.h # 资源基类接口 ├── ResourceHandle.h # 资源句柄(模板智能指针) ├── ResourceManager.h # 资源管理器 ├── IResourceLoader.h # 加载器基类接口 ├── ResourceCache.h # 资源缓存 ├── AsyncLoader.h # 异步加载器 ├── ImportSettings.h # 导入设置基类 │ ├── Texture.h # 纹理资源 ├── TextureLoader.h # 纹理加载器 ├── TextureImportSettings.h # 纹理导入设置 │ ├── Mesh.h # 网格资源 ├── MeshLoader.h # 模型加载器 ├── MeshImportSettings.h # 模型导入设置 │ ├── Material.h # 材质资源 ├── MaterialLoader.h # 材质加载器 │ ├── Shader.h # 着色器资源 ├── ShaderLoader.h # 着色器加载器 │ ├── AudioClip.h # 音频资源 ├── AudioLoader.h # 音频加载器 │ ├── BinaryResource.h # 二进制资源 └── ResourceTypes.h # 资源类型枚举 engine/src/Resources/ ├── Resources.cpp # 资源系统初始化 ├── ResourceManager.cpp ├── ResourceCache.cpp ├── AsyncLoader.cpp ├── TextureLoader.cpp ├── MeshLoader.cpp ├── MaterialLoader.cpp ├── ShaderLoader.cpp └── AudioLoader.cpp ``` ### 3.2 与现有项目整合 资源系统需要整合到现有项目中,需要创建或更新的文件: ``` engine/ ├── include/XCEngine/ │ └── XCEngine.h # 添加 #include "Resources/Resources.h" │ └── src/ └── Resources/ # 新建目录 └── (上述实现文件) ``` --- ## 4. 核心类型定义 ### 4.1 资源类型枚举 ```cpp // ResourceTypes.h namespace XCEngine { namespace Resources { // 资源类型 enum class ResourceType : Core::uint8 { Unknown = 0, Texture, Mesh, Material, Shader, AudioClip, Binary, AnimationClip, Skeleton, Font, ParticleSystem, Scene, Prefab }; constexpr const char* GetResourceTypeName(ResourceType type) { switch (type) { case ResourceType::Texture: return "Texture"; case ResourceType::Mesh: return "Mesh"; case ResourceType::Material: return "Material"; case ResourceType::Shader: return "Shader"; case ResourceType::AudioClip: return "AudioClip"; case ResourceType::Binary: return "Binary"; case ResourceType::AnimationClip: return "AnimationClip"; case ResourceType::Skeleton: return "Skeleton"; case ResourceType::Font: return "Font"; case ResourceType::ParticleSystem:return "ParticleSystem"; case ResourceType::Scene: return "Scene"; case ResourceType::Prefab: return "Prefab"; default: return "Unknown"; } } // 资源使用64位GUID作为唯一标识符 // GUID生成规则:MD5(path) 的前64位 // 这样可以保证相同路径生成相同的GUID struct ResourceGUID { Core::uint64 value; ResourceGUID() : value(0) {} explicit ResourceGUID(Core::uint64 v) : value(v) {} bool IsValid() const { return value != 0; } bool operator==(const ResourceGUID& other) const { return value == other.value; } bool operator!=(const ResourceGUID& other) const { return value != other.value; } // 从路径生成GUID static ResourceGUID Generate(const char* path); static ResourceGUID Generate(const String& path); String ToString() const; }; // 全局函数 inline ResourceGUID MakeResourceGUID(const char* path) { return ResourceGUID::Generate(path); } // 从模板类型获取ResourceType的辅助函数 template ResourceType GetResourceType(); // 特化模板 template<> inline ResourceType GetResourceType() { return ResourceType::Texture; } template<> inline ResourceType GetResourceType() { return ResourceType::Mesh; } template<> inline ResourceType GetResourceType() { return ResourceType::Material; } template<> inline ResourceType GetResourceType() { return ResourceType::Shader; } template<> inline ResourceType GetResourceType() { return ResourceType::AudioClip; } template<> inline ResourceType GetResourceType() { return ResourceType::Binary; } // 导入设置基类 class ImportSettings { public: virtual ~ImportSettings() = default; // 克隆设置 virtual UniquePtr Clone() const = 0; // 从JSON加载 virtual bool LoadFromJSON(const String& json) { return false; } // 保存为JSON virtual String SaveToJSON() const { return String(); } }; } // namespace Resources } // namespace XCEngine } } // namespace Resources } // namespace XCEngine ``` ### 4.2 资源GUID ```cpp // 资源使用64位GUID作为唯一标识符 // GUID生成规则:MD5(path) 的前64位 // 这样可以保证相同路径生成相同的GUID struct ResourceGUID { uint64 value; ResourceGUID() : value(0) {} explicit ResourceGUID(uint64 v) : value(v) {} bool IsValid() const { return value != 0; } bool operator==(const ResourceGUID& other) const { return value == other.value; } bool operator!=(const ResourceGUID& other) const { return value != other.value; } // 从路径生成GUID static ResourceGUID Generate(const char* path); static ResourceGUID Generate(const String& path); String ToString() const; }; // 全局函数 inline ResourceGUID MakeResourceGUID(const char* path) { return ResourceGUID::Generate(path); } ``` ### 4.3 资源路径 ```cpp // 资源路径使用相对于资源根目录的路径 // 例如:textures/player.png, models/player.fbx // 资源系统会自动添加资源根目录前缀 class ResourcePath { public: ResourcePath() = default; ResourcePath(const char* path); ResourcePath(const String& path); // 获取扩展名 String GetExtension() const; // 获取文件名(不含扩展名) String GetStem() const; // 获取完整路径 String GetFullPath() const; // 获取相对于资源根的路径 String GetRelativePath() const; // 转换为资源GUID ResourceGUID ToGUID() const; // 检查扩展名 bool HasExtension(const char* ext) const; bool HasAnyExtension(const char* const* extensions, Core::uint32 count) const; private: String m_path; }; ``` --- ## 5. 核心接口设计 ### 5.1 资源基类接口 (IResource) ```cpp // IResource.h namespace XCEngine { namespace Resources { class IResource { public: virtual ~IResource() = default; // 获取资源类型 virtual ResourceType GetType() const = 0; // 获取资源名称 virtual const String& GetName() const = 0; // 获取资源路径 virtual const String& GetPath() const = 0; // 获取资源GUID virtual ResourceGUID GetGUID() const = 0; // 资源是否有效 virtual bool IsValid() const = 0; // 获取资源内存大小(用于缓存统计) virtual size_t GetMemorySize() const = 0; // 释放资源(返回到对象池或真正删除) virtual void Release() = 0; protected: // 资源初始化(供子类调用) struct ConstructParams { String name; String path; ResourceGUID guid; size_t memorySize = 0; }; void Initialize(const ConstructParams& params) { m_name = params.name; m_path = params.path; m_guid = params.guid; m_memorySize = params.memorySize; m_isValid = true; } void SetInvalid() { m_isValid = false; } String m_name; String m_path; ResourceGUID m_guid; bool m_isValid = false; size_t m_memorySize = 0; }; } // namespace Resources } // namespace XCEngine ``` ### 5.2 资源句柄 (ResourceHandle) ```cpp // ResourceHandle.h #include #include namespace XCEngine { namespace Resources { // 资源句柄模板类 // 类似智能指针,但专门用于资源管理 template class ResourceHandle { static_assert(std::is_base_of_v, "T must derive from IResource"); public: ResourceHandle() = default; explicit ResourceHandle(T* resource) : m_resource(resource) { if (m_resource) { ResourceManager::Get().AddRef(m_resource->GetGUID()); } } ResourceHandle(const ResourceHandle& other) : m_resource(other.m_resource) { if (m_resource) { ResourceManager::Get().AddRef(m_resource->GetGUID()); } } ResourceHandle(ResourceHandle&& other) noexcept : m_resource(other.m_resource) { // 移动语义不转移引用计数所有权 // 原句柄被掏空,不减少引用计数 other.m_resource = nullptr; } ~ResourceHandle() { Reset(); } ResourceHandle& operator=(const ResourceHandle& other) { if (this != &other) { Reset(); m_resource = other.m_resource; if (m_resource) { ResourceManager::Get().AddRef(m_resource->GetGUID()); } } return *this; } ResourceHandle& operator=(ResourceHandle&& other) noexcept { if (this != &other) { // 先释放当前持有的资源 Reset(); // 移动所有权 m_resource = other.m_resource; // 原句柄被掏空,不减少引用计数 other.m_resource = nullptr; } return *this; } // 访问资源 T* Get() const { return m_resource; } T* operator->() const { return m_resource; } T& operator*() const { return *m_resource; } // 检查有效性 bool IsValid() const { return m_resource != nullptr && m_resource->IsValid(); } explicit operator bool() const { return IsValid(); } // 获取GUID ResourceGUID GetGUID() const { return m_resource ? m_resource->GetGUID() : ResourceGUID(0); } // 获取资源类型 ResourceType GetResourceType() const { return m_resource ? m_resource->GetType() : ResourceType::Unknown; } // 重置句柄 void Reset() { if (m_resource) { ResourceManager::Get().Release(m_resource->GetGUID()); m_resource = nullptr; } } // 交换 void Swap(ResourceHandle& other) { std::swap(m_resource, other.m_resource); } private: T* m_resource = nullptr; }; // 比较操作符 template bool operator==(const ResourceHandle& lhs, const ResourceHandle& rhs) { return lhs.GetGUID() == rhs.GetGUID(); } template bool operator!=(const ResourceHandle& lhs, const ResourceHandle& rhs) { return !(lhs == rhs); } } // namespace Resources } // namespace XCEngine ``` ### 5.3 加载器基类接口 (IResourceLoader) ```cpp // IResourceLoader.h namespace XCEngine { namespace Resources { // 加载结果 struct LoadResult { IResource* resource = nullptr; bool success = false; String errorMessage; LoadResult() = default; explicit LoadResult(IResource* res) : resource(res), success(res != nullptr) {} explicit LoadResult(const String& error) : success(false), errorMessage(error) {} explicit LoadResult(bool success, const String& error = "") : success(success), errorMessage(error) {} operator bool() const { return success && resource != nullptr; } }; // 加载器接口 class IResourceLoader { public: virtual ~IResourceLoader() = default; // 获取支持的资源类型 virtual ResourceType GetResourceType() const = 0; // 获取支持的文件扩展名 virtual Array GetSupportedExtensions() const = 0; // 检查是否可以加载该文件 virtual bool CanLoad(const String& path) const = 0; // 同步加载 virtual LoadResult Load(const String& path, const ImportSettings* settings = nullptr) = 0; // 异步加载(默认实现:同步加载然后在主线程回调) virtual void LoadAsync(const String& path, const ImportSettings* settings, std::function callback) { LoadResult result = Load(path, settings); if (callback) { callback(result); } } // 获取默认导入设置 virtual ImportSettings* GetDefaultSettings() const = 0; protected: // 辅助方法:从文件读取数据 static Array ReadFileData(const String& path); // 辅助方法:解析路径获取扩展名 static String GetExtension(const String& path); }; // 加载器工厂宏(方便注册) #define REGISTER_RESOURCE_LOADER(loaderType) \ namespace { \ struct loaderType##Registrar { \ loaderType##Registrar() { \ ResourceManager::Get().RegisterLoader(new loaderType()); \ } \ } g_##loaderType##Registrar; \ } } // namespace Resources } // namespace XCEngine ``` ### 5.4 资源管理器 (ResourceManager) ```cpp // ResourceManager.h namespace XCEngine { namespace Resources { class ResourceManager { public: static ResourceManager& Get(); // ==================== 初始化 ==================== void Initialize(); void Shutdown(); // 设置资源根目录 void SetResourceRoot(const String& rootPath); const String& GetResourceRoot() const; // ==================== 同步加载 ==================== // 通过路径加载 template ResourceHandle Load(const String& path, ImportSettings* settings = nullptr) { static_assert(std::is_base_of_v, "T must derive from IResource"); ResourcePath resourcePath(path); ResourceGUID guid = resourcePath.ToGUID(); // 1. 检查缓存 IResource* cached = FindInCache(guid); if (cached) { return ResourceHandle(static_cast(cached)); } // 2. 查找加载器 IResourceLoader* loader = FindLoader(GetResourceType()); if (!loader) { Debug::Logger::Get().Warning(Debug::LogCategory::FileSystem, "No loader found for resource type: " + String(GetResourceTypeName(GetResourceType()))); return ResourceHandle(); } // 3. 加载资源 LoadResult result = loader->Load(resourcePath.GetFullPath(), settings); if (!result) { Debug::Logger::Get().Error(Debug::LogCategory::FileSystem, "Failed to load resource: " + path + " - " + result.errorMessage); return ResourceHandle(); } // 4. 添加到缓存 AddToCache(guid, result.resource); return ResourceHandle(static_cast(result.resource)); } // 通过GUID加载 template ResourceHandle Load(ResourceGUID guid) { // 从GUID反向查找路径(需要维护guid到path的映射) // 这里简化处理,实际实现需要从数据库查询 return ResourceHandle(); } // ==================== 异步加载 ==================== // 异步加载资源(需要显式指定类型) void LoadAsync(const String& path, ResourceType type, std::function callback); // 异步加载资源(带自定义设置) void LoadAsync(const String& path, ResourceType type, ImportSettings* settings, std::function callback); // ==================== 卸载 ==================== // 通过路径卸载 void Unload(const String& path); // 通过GUID卸载 void Unload(ResourceGUID guid); // 卸载所有未使用的资源 void UnloadUnused(); // 卸载所有资源 void UnloadAll(); // ==================== 引用计数 ==================== // 增加引用计数 void AddRef(ResourceGUID guid); // 减少引用计数(引用计数为0时可能卸载资源) void Release(ResourceGUID guid); // 获取引用计数 Core::uint32 GetRefCount(ResourceGUID guid) const; // ==================== 加载器管理 ==================== // 注册加载器(ResourceManager拥有加载器) void RegisterLoader(IResourceLoader* loader); // 移除加载器 void UnregisterLoader(ResourceType type); // 获取加载器 IResourceLoader* GetLoader(ResourceType type) const; // ==================== 缓存管理 ==================== // 设置内存预算(字节) void SetMemoryBudget(size_t bytes); // 获取当前内存使用 size_t GetMemoryUsage() const; // 获取内存预算 size_t GetMemoryBudget() const; // 刷新缓存(强制淘汰最少使用的资源) void FlushCache(); // ==================== 查询 ==================== // 查找资源(不增加引用计数) IResource* Find(const String& path); IResource* Find(ResourceGUID guid); // 检查资源是否存在 bool Exists(const String& path) const; bool Exists(ResourceGUID guid) const; // ==================== 资源根目录相关 ==================== // 解析资源路径(添加资源根目录) String ResolvePath(const String& relativePath) const; private: ResourceManager() = default; ~ResourceManager() = default; // 内部方法 IResource* FindInCache(ResourceGUID guid); void AddToCache(ResourceGUID guid, IResource* resource); IResourceLoader* FindLoader(ResourceType type); void ReloadResource(ResourceGUID guid); // 数据成员 String m_resourceRoot; // 资源缓存 HashMap m_resourceCache; // 引用计数 HashMap m_refCounts; // GUID到路径的映射(用于从guid加载) HashMap m_guidToPath; // 加载器 HashMap> m_loaders; // 缓存统计 size_t m_memoryUsage = 0; size_t m_memoryBudget = 512 * 1024 * 1024; // 默认512MB // 异步加载器 UniquePtr m_asyncLoader; // 互斥锁 Threading::Mutex m_mutex; friend class ResourceHandleBase; }; } // namespace Resources } // namespace XCEngine ``` ### 5.5 资源缓存 (ResourceCache) ```cpp // ResourceCache.h #include namespace XCEngine { namespace Resources { // 缓存条目 struct CacheEntry { IResource* resource; ResourceGUID guid; size_t memorySize; Core::uint64 lastAccessTime; // 使用Profiler::Get().GetTickCount() Core::uint32 accessCount; CacheEntry() : resource(nullptr), memorySize(0), lastAccessTime(0), accessCount(0) {} CacheEntry(IResource* res, size_t size) : resource(res), guid(res->GetGUID()), memorySize(size), lastAccessTime(GetCurrentTick()), accessCount(1) {} private: static Core::uint64 GetCurrentTick(); }; // LRU缓存实现 class ResourceCache { public: ResourceCache(); ~ResourceCache(); // 添加资源到缓存 void Add(ResourceGUID guid, IResource* resource); // 从缓存移除 void Remove(ResourceGUID guid); // 查找资源(不移动到头部) IResource* Find(ResourceGUID guid) const; // 访问资源(更新LRU信息) void Touch(ResourceGUID guid); // 获取缓存大小 size_t GetSize() const { return m_cache.Size(); } // 获取内存使用 size_t GetMemoryUsage() const { return m_memoryUsage; } // 设置内存预算 void SetMemoryBudget(size_t bytes); size_t GetMemoryBudget() const { return m_memoryBudget; } // 内存压力响应(淘汰资源直到满足要求) void OnMemoryPressure(size_t requiredBytes); // 引用计数为0时的通知(供ResourceManager调用) void OnZeroRefCount(ResourceGUID guid); // 刷新缓存(淘汰所有未使用的资源) void Flush(); // 清空缓存 void Clear(); // 获取最少使用的资源(用于调试) Array GetLRUList(size_t count) const; private: // 淘汰最少使用的资源 void Evict(size_t requiredBytes); // 更新内存统计 void UpdateMemoryStats(); // 数据成员 HashMap m_cache; Array m_lruOrder; // 按访问时间排序,最少使用的在前面 size_t m_memoryUsage = 0; size_t m_memoryBudget = 512 * 1024 * 1024; // 默认512MB Mutex m_mutex; }; } // namespace Resources } // namespace XCEngine ``` ### 5.6 异步加载器 (AsyncLoader) ```cpp // AsyncLoader.h #include #include namespace XCEngine { namespace Resources { // 加载请求 struct LoadRequest { String path; ResourceType type; std::function callback; UniquePtr settings; Core::uint64 requestId; LoadRequest() : requestId(0) {} LoadRequest(const String& p, ResourceType t, std::function cb, ImportSettings* s = nullptr) : path(p), type(t), callback(std::move(cb)), settings(s), requestId(GenerateRequestId()) {} private: static Core::uint64 GenerateRequestId(); }; // 加载任务(工作线程执行) class LoadTask : public Threading::ITask { public: LoadTask(LoadRequest request, IResourceLoader* loader); void Execute() override; LoadResult& GetResult() { return m_result; } private: LoadRequest m_request; IResourceLoader* m_loader; LoadResult m_result; }; // 异步加载器 class AsyncLoader { public: static AsyncLoader& Get(); // 初始化 void Initialize(Core::uint32 workerThreadCount = 2); void Shutdown(); // 提交加载请求(需要显式指定类型) void Submit(const String& path, ResourceType type, std::function callback); // 提交加载请求(带自定义设置) void Submit(const String& path, ResourceType type, ImportSettings* settings, std::function callback); // 更新(每帧调用,处理完成的加载) void Update(); // 检查是否正在加载 bool IsLoading() const { return m_pendingCount > 0; } // 获取待处理数量 Core::uint32 GetPendingCount() const { return m_pendingCount; } // 获取加载进度(0.0 - 1.0) float GetProgress() const; // 取消所有待处理的请求 void CancelAll(); // 取消特定请求 void Cancel(Core::uint64 requestId); private: AsyncLoader() = default; ~AsyncLoader() = default; void SubmitInternal(LoadRequest& request); // 待处理的请求队列 Threading::Mutex m_queueMutex; Array m_pendingQueue; // 完成的请求队列(主线程处理) Threading::Mutex m_completedMutex; Array m_completedQueue; // 统计 std::atomic m_pendingCount{0}; std::atomic m_completedCount{0}; Core::uint32 m_totalRequested = 0; // 加载器查找 IResourceLoader* FindLoader(ResourceType type) const; }; } // namespace Resources } // namespace XCEngine ``` --- ## 6. 具体资源类型 ### 6.1 纹理资源 (Texture) ```cpp // Texture.h namespace XCEngine { namespace Resources { // 纹理类型 enum class TextureType { Texture2D, Texture3D, TextureCube, Texture2DArray, TextureCubeArray }; // 纹理格式 enum class TextureFormat { Unknown, R8_UNORM, RG8_UNORM, RGBA8_UNORM, RGBA8_SRGB, R16_FLOAT, RG16_FLOAT, RGBA16_FLOAT, R32_FLOAT, RG32_FLOAT, RGBA32_FLOAT, D16_UNORM, D24_UNORM_S8_UINT, D32_FLOAT, D32_FLOAT_S8_X24_UINT, BC1_UNORM, BC1_UNORM_SRGB, BC2_UNORM, BC2_UNORM_SRGB, BC3_UNORM, BC3_UNORM_SRGB, BC4_UNORM, BC5_UNORM, BC6H_UF16, BC7_UNORM, BC7_UNORM_SRGB }; // 纹理使用flags enum class TextureUsage : Core::uint8 { None = 0, ShaderResource = 1 << 0, RenderTarget = 1 << 1, DepthStencil = 1 << 2, UnorderedAccess = 1 << 3, TransferSrc = 1 << 4, TransferDst = 1 << 5 }; // 纹理资源 class Texture : public IResource { public: Texture(); virtual ~Texture() override; // IResource 接口 ResourceType GetType() const override { return ResourceType::Texture; } const String& GetName() const override { return m_name; } const 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; // 纹理属性 Core::uint32 GetWidth() const { return m_width; } Core::uint32 GetHeight() const { return m_height; } Core::uint32 GetDepth() const { return m_depth; } Core::uint32 GetMipLevels() const { return m_mipLevels; } Core::uint32 GetArraySize() const { return m_arraySize; } TextureType GetTextureType() const { return m_textureType; } TextureFormat GetFormat() const { return m_format; } TextureUsage GetUsage() const { return m_usage; } // RHI资源 class IRHIResource* GetRHIResource() const { return m_rhiResource; } void SetRHIResource(class IRHIResource* resource); // 创建纹理(从原始数据) bool Create(Core::uint32 width, Core::uint32 height, Core::uint32 depth, Core::uint32 mipLevels, TextureType type, TextureFormat format, const void* data, size_t dataSize); // 生成Mipmap bool GenerateMipmaps(); private: // 成员变量 String m_name; String m_path; ResourceGUID m_guid; bool m_isValid = false; size_t m_memorySize = 0; Core::uint32 m_width = 0; Core::uint32 m_height = 0; Core::uint32 m_depth = 1; Core::uint32 m_mipLevels = 1; Core::uint32 m_arraySize = 1; TextureType m_textureType = TextureType::Texture2D; TextureFormat m_format = TextureFormat::RGBA8_UNORM; TextureUsage m_usage = TextureUsage::ShaderResource; class IRHIResource* m_rhiResource = nullptr; }; } // namespace Resources } // namespace XCEngine ``` ### 6.2 纹理导入设置 ```cpp // TextureImportSettings.h namespace XCEngine { namespace Resources { class TextureImportSettings : public ImportSettings { public: TextureImportSettings(); virtual ~TextureImportSettings() override; // ==================== 纹理设置 ==================== // 纹理类型 void SetTextureType(TextureType type) { m_textureType = type; } TextureType GetTextureType() const { return m_textureType; } // 目标格式(用于压缩) void SetTargetFormat(TextureFormat format) { m_targetFormat = format; } TextureFormat GetTargetFormat() const { return m_targetFormat; } // 是否生成Mipmap void SetGenerateMipmaps(bool generate) { m_generateMipmaps = generate; } bool GetGenerateMipmaps() const { return m_generateMipmaps; } // Mipmap过滤方式 void SetMipmapFilter(MipmapFilter filter) { m_mipmapFilter = filter; } MipmapFilter GetMipmapFilter() const { return m_mipmapFilter; } // 各向异性过滤 void SetMaxAnisotropy(Core::uint32 anisotropy) { m_maxAnisotropy = anisotropy; } Core::uint32 GetMaxAnisotropy() const { return m_maxAnisotropy; } // 是否sRGB(影响着色器中的采样) void SetSRGB(bool srgb) { m_sRGB = srgb; } bool GetSRGB() const { return m_sRGB; } // 是否启用纹理数组 void SetTextureArray(bool array) { m_textureArray = array; } bool GetTextureArray() const { return m_textureArray; } // 垂直翻转 void SetFlipVertical(bool flip) { m_flipVertical = flip; } bool GetFlipVertical() const { return m_flipVertical; } // 水平翻转 void SetFlipHorizontal(bool flip) { m_flipHorizontal = flip; } bool GetFlipHorizontal() const { return m_flipHorizontal; } // 边框颜色(对于透明纹理) void SetBorderColor(const Math::Color& color) { m_borderColor = color; } const Math::Color& GetBorderColor() const { return m_borderColor; } // ==================== 压缩设置 ==================== // 压缩质量 void SetCompressionQuality(CompressionQuality quality) { m_compressionQuality = quality; } CompressionQuality GetCompressionQuality() const { return m_compressionQuality; } // 是否使用硬件压缩 void SetUseHardwareCompression(bool use) { m_useHardwareCompression = use; } bool GetUseHardwareCompression() const { return m_useHardwareCompression; } // ==================== 实用方法 ==================== // 克隆设置 UniquePtr Clone() const override; // 从JSON加载 bool LoadFromJSON(const String& json); // 保存为JSON String SaveToJSON() const; private: // 成员变量 TextureType m_textureType = TextureType::Texture2D; TextureFormat m_targetFormat = TextureFormat::Unknown; // 自动选择 bool m_generateMipmaps = true; MipmapFilter m_mipmapFilter = MipmapFilter::Box; Core::uint32 m_maxAnisotropy = 16; bool m_sRGB = false; bool m_textureArray = false; bool m_flipVertical = false; bool m_flipHorizontal = false; Math::Color m_borderColor = Math::Color::Black; CompressionQuality m_compressionQuality = CompressionQuality::High; bool m_useHardwareCompression = true; }; // Mipmap过滤方式 enum class MipmapFilter { Box, Kaiser }; // 压缩质量 enum class CompressionQuality { Low, Medium, High, Ultra }; } // namespace Resources } // namespace XCEngine ``` ### 6.3 纹理加载器 ```cpp // TextureLoader.h namespace XCEngine { namespace Resources { class TextureLoader : public IResourceLoader { public: TextureLoader(); virtual ~TextureLoader() override; // IResourceLoader 接口 ResourceType GetResourceType() const override { return ResourceType::Texture; } Array GetSupportedExtensions() const override { return Array{".png", ".jpg", ".jpeg", ".tga", ".bmp", ".dds", ".hdr", ".exr", ".psd"}; } bool CanLoad(const String& path) const override { String ext = GetExtension(path); return ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".tga" || ext == ".bmp" || ext == ".dds" || ext == ".hdr" || ext == ".exr" || ext == ".psd"; } LoadResult Load(const String& path, const ImportSettings* settings = nullptr) override; ImportSettings* GetDefaultSettings() const override { return new TextureImportSettings(); } private: // 内部加载方法 LoadResult LoadPNG(const String& path, const TextureImportSettings* settings); LoadResult LoadJPG(const String& path, const TextureImportSettings* settings); LoadResult LoadTGA(const String& path, const TextureImportSettings* settings); LoadResult LoadDDS(const String& path, const TextureImportSettings* settings); LoadResult LoadHDR(const String& path, const TextureImportSettings* settings); // 辅助方法 TextureFormat DetectFormat(const String& path) const; bool ConvertFormat(const Core::uint8* srcData, Core::uint32 width, Core::uint32 height, TextureFormat srcFormat, TextureFormat dstFormat, Core::uint8*& outData, size_t& outDataSize); }; // 注册加载器 REGISTER_RESOURCE_LOADER(TextureLoader); } // namespace Resources } // namespace XCEngine ``` ### 6.4 网格资源 (Mesh) ```cpp // Mesh.h namespace XCEngine { namespace Resources { // 顶点属性 enum class VertexAttribute : Core::uint32 { Position = 1 << 0, Normal = 1 << 1, Tangent = 1 << 2, Color = 1 << 3, UV0 = 1 << 4, UV1 = 1 << 5, UV2 = 1 << 6, UV3 = 1 << 7, BoneWeights = 1 << 8, BoneIndices = 1 << 9 }; // 网格子资源(用于多材质) struct MeshSection { Core::uint32 baseVertex; Core::uint32 vertexCount; Core::uint32 startIndex; Core::uint32 indexCount; Core::uint32 materialID; Math::AABB boundingBox; }; // 网格资源 class Mesh : public IResource { public: Mesh(); virtual ~Mesh() override; // IResource 接口 ResourceType GetType() const override { return ResourceType::Mesh; } const String& GetName() const override { return m_name; } const 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; // ==================== 顶点数据 ==================== // 设置顶点数据 void SetVertexData(const void* data, size_t size, Core::uint32 vertexCount, Core::uint32 vertexStride, VertexAttribute attributes); // 获取顶点数据 const void* GetVertexData() const { return m_vertexData.Data(); } size_t GetVertexDataSize() const { return m_vertexData.Size(); } Core::uint32 GetVertexCount() const { return m_vertexCount; } Core::uint32 GetVertexStride() const { return m_vertexStride; } VertexAttribute GetVertexAttributes() const { return m_attributes; } // ==================== 索引数据 ==================== // 设置索引数据 void SetIndexData(const void* data, size_t size, Core::uint32 indexCount, bool use32Bit); // 获取索引数据 const void* GetIndexData() const { return m_indexData.Data(); } size_t GetIndexDataSize() const { return m_indexData.Size(); } Core::uint32 GetIndexCount() const { return m_indexCount; } bool IsUse32BitIndex() const { return m_use32BitIndex; } // ==================== 子网格 ==================== void AddSection(const MeshSection& section); const Array& GetSections() const { return m_sections; } // ==================== 包围盒 ==================== void SetBoundingBox(const Math::AABB& box); const Math::AABB& GetBoundingBox() const { return m_boundingBox; } // ==================== 骨骼动画(可选)==================== void SetSkeleton(class Skeleton* skeleton); class Skeleton* GetSkeleton() const { return m_skeleton; } // ==================== RHI资源 ==================== class IRHIBuffer* GetVertexBuffer() const { return m_vertexBuffer; } class IRHIBuffer* GetIndexBuffer() const { return m_indexBuffer; } void SetRHIBuffers(class IRHIBuffer* vb, class IRHIBuffer* ib); private: // 成员变量 String m_name; String m_path; ResourceGUID m_guid; bool m_isValid = false; size_t m_memorySize = 0; // 顶点数据 Array m_vertexData; Core::uint32 m_vertexCount = 0; Core::uint32 m_vertexStride = 0; VertexAttribute m_attributes = VertexAttribute::Position; // 索引数据 Array m_indexData; Core::uint32 m_indexCount = 0; bool m_use32BitIndex = false; // 子网格 Array m_sections; // 包围盒 Math::AABB m_boundingBox; // 骨骼 class Skeleton* m_skeleton = nullptr; // RHI资源 class IRHIBuffer* m_vertexBuffer = nullptr; class IRHIBuffer* m_indexBuffer = nullptr; }; } // namespace Resources } // namespace XCEngine ``` ### 6.5 材质资源 (Material) ```cpp // Material.h namespace XCEngine { namespace Resources { // 材质属性类型 enum class MaterialPropertyType { Float, Float2, Float3, Float4, Int, Int2, Int3, Int4, Bool, Texture, Cubemap }; // 材质属性 struct MaterialProperty { String name; MaterialPropertyType type; // 值联合体 union Value { float floatValue[4]; Core::int32 intValue[4]; bool boolValue; ResourceHandle textureValue; Value() { memset(this, 0, sizeof(Value)); } } value; // 引用计数(纹理资源需要特殊处理) Core::uint32 refCount; }; // 材质资源 class Material : public IResource { public: Material(); virtual ~Material() override; // IResource 接口 ResourceType GetType() const override { return ResourceType::Material; } const String& GetName() const override { return m_name; } const 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; // ==================== 着色器 ==================== void SetShader(const ResourceHandle& shader); class Shader* GetShader() const { return m_shader.Get(); } // ==================== 属性设置 ==================== void SetFloat(const String& name, float value); void SetFloat2(const String& name, const Math::Vector2& value); void SetFloat3(const String& name, const Math::Vector3& value); void SetFloat4(const String& name, const Math::Vector4& value); void SetInt(const String& name, int32 value); void SetInt2(const String& name, const Math::IntVector2& value); void SetInt3(const String& name, const Math::IntVector3& value); void SetInt4(const String& name, const Math::IntVector4& value); void SetBool(const String& name, bool value); void SetTexture(const String& name, const ResourceHandle& texture); // ==================== 属性获取 ==================== float GetFloat(const String& name) const; Math::Vector2 GetFloat2(const String& name) const; // ... 其他Get方法 ResourceHandle GetTexture(const String& name) const; // ==================== 属性遍历 ==================== using PropertyIterator = HashMap::Iterator; PropertyIterator BeginProperties() { return m_properties.Begin(); } PropertyIterator EndProperties() { return m_properties.End(); } // ==================== 批处理数据 ==================== // 获取用于GPU的常数缓冲区数据 const Array& GetConstantBufferData() const { return m_constantBufferData; } // 更新常数缓冲区(当属性变化时调用) void UpdateConstantBuffer(); private: // 成员变量 String m_name; String m_path; ResourceGUID m_guid; bool m_isValid = false; size_t m_memorySize = 0; // 着色器 ResourceHandle m_shader; // 属性表 HashMap m_properties; // 常数缓冲区数据 Array m_constantBufferData; // 纹理绑定信息(用于设置到Pipeline) struct TextureBinding { String name; Core::uint32 slot; ResourceHandle texture; }; Array m_textureBindings; }; } // namespace Resources } // namespace XCEngine ``` ### 6.6 音频资源 (AudioClip) ```cpp // AudioClip.h namespace XCEngine { namespace Resources { // 音频格式 enum class AudioFormat { Unknown, WAV, OGG, MP3, FLAC }; // 音频类型 enum class AudioType { SFX, // 音效 Music, // 背景音乐 Voice // 语音 }; // 音频资源 class AudioClip : public IResource { public: AudioClip(); virtual ~AudioClip() override; // IResource 接口 ResourceType GetType() const override { return ResourceType::AudioClip; } const String& GetName() const override { return m_name; } const 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; // ==================== 音频属性 ==================== // 采样率 Core::uint32 GetSampleRate() const { return m_sampleRate; } // 通道数 Core::uint32 GetChannels() const { return m_channels; } // 位深度 Core::uint32 GetBitsPerSample() const { return m_bitsPerSample; } // 持续时间(秒) float GetDuration() const { return m_duration; } // 数据大小 size_t GetDataSize() const { return m_data.Size(); } // 格式 AudioFormat GetFormat() const { return m_format; } // 类型 AudioType GetAudioType() const { return m_audioType; } // ==================== 音频数据 ==================== // 获取PCM数据 const Core::uint8* GetData() const { return m_data.Data(); } // 是否流式(不加载到内存) bool IsStreaming() const { return m_isStreaming; } // 设置流式 void SetStreaming(bool streaming) { m_isStreaming = streaming; } // ==================== 播放控制 ==================== // 创建音频源(用于播放) class IAudioSource* CreateSource() const; private: // 成员变量 String m_name; String m_path; ResourceGUID m_guid; bool m_isValid = false; size_t m_memorySize = 0; // 音频属性 Core::uint32 m_sampleRate = 0; Core::uint32 m_channels = 0; Core::uint32 m_bitsPerSample = 0; float m_duration = 0.0f; AudioFormat m_format = AudioFormat::Unknown; AudioType m_audioType = AudioType::SFX; bool m_isStreaming = false; // 音频数据 Array m_data; }; } // namespace Resources } // namespace XCEngine ``` --- ## 7. 文件系统集成 ### 7.1 文件系统接口 ```cpp // FileSystem.h (扩展现有IO模块) namespace XCEngine { namespace IO { // 文件系统接口 class IFileSystem { public: virtual ~IFileSystem() = default; // 文件操作 virtual bool FileExists(const String& path) const = 0; virtual bool DirectoryExists(const String& path) const = 0; virtual size_t GetFileSize(const String& path) const = 0; virtual bool GetFileTime(const String& path, Core::uint64& outTime) const = 0; // 读取 virtual Array ReadAllBytes(const String& path) const = 0; virtual String ReadAllText(const String& path) const = 0; virtual bool Read(const String& path, void* buffer, size_t size, size_t offset = 0) const = 0; // 写入 virtual bool WriteAllBytes(const String& path, const void* data, size_t size) = 0; virtual bool WriteAllText(const String& path, const String& text) = 0; virtual bool Write(const String& path, const void* data, size_t size, size_t offset = 0) = 0; // 目录操作 virtual bool CreateDirectory(const String& path) = 0; virtual bool DeleteDirectory(const String& path, bool recursive = false) = 0; virtual bool DeleteFile(const String& path) = 0; // 遍历 virtual void EnumerateFiles(const String& path, const String& pattern, Array& outFiles) const = 0; virtual void EnumerateDirectories(const String& path, const String& pattern, Array& outDirectories) const = 0; // 获取路径信息 virtual String GetExtension(const String& path) const = 0; virtual String GetFileName(const String& path) const = 0; virtual String GetFileNameWithoutExtension(const String& path) const = 0; virtual String GetDirectoryName(const String& path) const = 0; virtual String GetFullPath(const String& path) const = 0; virtual String Combine(const String& path1, const String& path2) const = 0; }; // 获取文件系统单例 IFileSystem* GetFileSystem(); void SetFileSystem(IFileSystem* fs); // Windows文件系统实现(默认) class WindowsFileSystem : public IFileSystem { public: // 实现Windows API调用 }; } // namespace IO } // namespace XCEngine ``` ### 7.2 资源文件系统 (ResourceFileSystem) ```cpp // ResourceFileSystem.h namespace XCEngine { namespace Resources { // 资源文件系统 - 管理资源包/归档 class ResourceFileSystem { public: ResourceFileSystem(); ~ResourceFileSystem(); // 初始化 void Initialize(const String& rootPath); void Shutdown(); // 添加资源包 bool AddArchive(const String& archivePath); bool AddDirectory(const String& directoryPath); // 移除资源包 void RemoveArchive(const String& archivePath); // 查找资源 bool FindResource(const String& relativePath, String& outAbsolutePath) const; // 读取资源数据 Array ReadResource(const String& relativePath) const; // 检查资源是否存在 bool Exists(const String& relativePath) const; // 获取资源信息 struct ResourceInfo { String path; size_t size; Core::uint64 modifiedTime; bool inArchive; String archivePath; }; bool GetResourceInfo(const String& relativePath, ResourceInfo& outInfo) const; // 枚举资源 void EnumerateResources(const String& pattern, Array& outResources) const; private: // 资源包接口 class IArchive { public: virtual ~IArchive() = default; virtual bool Open(const String& path) = 0; virtual void Close() = 0; virtual bool Read(const String& fileName, void* buffer, size_t size, size_t offset) const = 0; virtual size_t GetSize(const String& fileName) const = 0; virtual bool Exists(const String& fileName) const = 0; virtual void Enumerate(const String& pattern, Array& outFiles) const = 0; }; // 数据成员 String m_rootPath; Array> m_archives; Array m_directories; // 缓存 mutable HashMap m_infoCache; }; } // namespace Resources } // namespace XCEngine ``` --- ## 8. 资源打包系统 ### 8.1 资源包格式 ``` ┌─────────────────────────────────────────────────────┐ │ ResourcePackage │ ├─────────────────────────────────────────────────────┤ │ Header (固定大小) │ │ ├── Magic: "XCRP" (4 bytes) │ │ ├── Version: uint16 │ │ ├── Manifest Size: uint32 │ │ ├── Asset Count: uint32 │ │ └── Data Offset: uint64 │ ├─────────────────────────────────────────────────────┤ │ Manifest (JSON/二进制) │ │ ├── File 1: { path, offset, size, checksum } │ │ ├── File 2: { path, offset, size, checksum } │ │ └── ... │ ├─────────────────────────────────────────────────────┤ │ Data Section │ │ ├── File 1 Data (对齐到16字节) │ │ ├── File 2 Data │ │ └── ... │ └─────────────────────────────────────────────────────┘ ``` ### 8.2 资源包构建器 ```cpp // ResourcePackageBuilder.h namespace XCEngine { namespace Resources { class ResourcePackageBuilder { public: ResourcePackageBuilder(); ~ResourcePackageBuilder(); // 添加文件 bool AddFile(const String& sourcePath, const String& relativePath); // 添加目录 bool AddDirectory(const String& sourceDir, const String& relativeBase = ""); // 设置输出路径 void SetOutputPath(const String& path) { m_outputPath = path; } // 构建包 bool Build(); // 获取进度 float GetProgress() const { return m_progress; } // 获取错误信息 const String& GetError() const { return m_error; } private: // 文件条目 struct FileEntry { String sourcePath; String relativePath; size_t size; uint64 checksum; }; // 成员变量 String m_outputPath; Array m_files; float m_progress = 0.0f; String m_error; // 辅助方法 Core::uint64 CalculateChecksum(const void* data, size_t size); bool WriteHeader(FileWriter& writer, size_t dataOffset); bool WriteManifest(FileWriter& writer); bool WriteData(FileWriter& writer); }; } // namespace Resources } // namespace XCEngine ``` --- ## 9. 资源依赖管理 ### 9.1 依赖图 ```cpp // ResourceDependencyGraph.h namespace XCEngine { namespace Resources { // 资源依赖节点 struct DependencyNode { ResourceGUID guid; ResourceType type; Array dependencies; // 当前资源依赖的资源 Array dependents; // 依赖当前资源的资源 Core::uint32 refCount = 0; }; // 依赖图 class ResourceDependencyGraph { public: ResourceDependencyGraph(); ~ResourceDependencyGraph(); // 添加节点 void AddNode(ResourceGUID guid, ResourceType type); // 添加依赖关系 void AddDependency(ResourceGUID owner, ResourceGUID dependency); // 移除节点 void RemoveNode(ResourceGUID guid); // 移除依赖关系 void RemoveDependency(ResourceGUID owner, ResourceGUID dependency); // 获取依赖(当前资源依赖的资源) Array GetDependencies(ResourceGUID guid) const; // 获取依赖者(依赖当前资源的资源) Array GetDependents(ResourceGUID guid) const; // 获取传递依赖(所有层级的依赖) Array GetAllDependencies(ResourceGUID guid) const; // 引用计数管理 void IncrementRefCount(ResourceGUID guid); void DecrementRefCount(ResourceGUID guid); Core::uint32 GetRefCount(ResourceGUID guid) const; // 查找循环依赖 bool HasCircularDependency(ResourceGUID guid, Array& outCycle) const; // 拓扑排序(用于加载顺序) Array TopologicalSort() const; // 加载资源组及其所有依赖 Array LoadWithDependencies(const String& entryPath); // 卸载资源组(仅当没有依赖者时) bool Unload(ResourceGUID guid); private: HashMap m_nodes; }; } // namespace Resources } // namespace XCEngine ``` --- ## 10. 实现顺序 ### 10.1 阶段划分 ``` 阶段 4.1: 基础框架 (3天) ├── 4.1.1 ResourceTypes.h - 资源类型枚举 ├── 4.1.2 IResource.h - 资源基类接口 ├── 4.1.3 ResourceHandle.h - 资源句柄 ├── 4.1.4 IResourceLoader.h - 加载器接口 └── 4.1.5 ResourceManager.h/cpp - 资源管理器基础 阶段 4.2: 缓存系统 (2天) ├── 4.2.1 ResourceCache.h/cpp - LRU缓存实现 └── 4.2.2 与ResourceManager集成 阶段 4.3: 异步加载 (2天) ├── 4.3.1 AsyncLoader.h/cpp - 异步加载器 ├── 4.3.2 与TaskSystem集成 └── 4.3.3 完成回调机制 阶段 4.4: 具体资源类型 (4天) ├── 4.4.1 Texture + TextureLoader - 纹理资源 ├── 4.4.2 Mesh + MeshLoader - 网格资源 ├── 4.4.3 Material + MaterialLoader - 材质资源 └── 4.4.4 AudioClip + AudioLoader - 音频资源 阶段 4.5: 文件系统集成 (2天) ├── 4.5.1 ResourceFileSystem - 资源文件系统 └── 4.5.2 资源包支持 阶段 4.6: 依赖管理 (2天) ├── 4.6.1 ResourceDependencyGraph - 依赖图 └── 4.6.2 资源组加载/卸载 阶段 4.7: 测试与集成 (2天) ├── 4.7.1 单元测试 └── 4.7.2 与现有mvs集成 ``` ### 10.2 详细任务列表 ``` 任务 4.1.1: ResourceTypes.h 实现: - ResourceType 枚举(Texture, Mesh, Material, Shader, AudioClip等) - GetResourceTypeName() 函数 - ResourceGUID 结构体 任务 4.1.2: IResource.h 实现: - IResource 抽象基类 - 纯虚方法:GetType(), GetName(), GetPath(), GetGUID(), IsValid(), GetMemorySize(), Release() - 虚析构函数 任务 4.1.3: ResourceHandle.h 实现: - ResourceHandle 模板类 - 构造/析构(自动引用计数) - operator->(), Get(), IsValid() - 移动语义支持 任务 4.1.4: IResourceLoader.h 实现: - IResourceLoader 抽象基类 - LoadResult 结构体 - GetResourceType(), GetSupportedExtensions(), CanLoad(), Load() - REGISTER_RESOURCE_LOADER 宏 任务 4.1.5: ResourceManager.h/cpp 实现: - ResourceManager 单例 - Initialize()/Shutdown() - Load(path) 模板方法 - Unload(path/guid) - AddRef()/Release() - RegisterLoader() - SetMemoryBudget() 任务 4.2.1: ResourceCache.h/cpp 实现: - CacheEntry 结构体 - ResourceCache 类 - Add(), Remove(), Find(), Touch() - SetMemoryBudget(), OnMemoryPressure() - LRU淘汰逻辑 任务 4.3.1: AsyncLoader.h/cpp 实现: - LoadRequest 结构体 - AsyncLoader 单例 - Submit(path, callback) - Update() - 处理完成队列 - IsLoading(), GetProgress() 任务 4.4.1: Texture + TextureLoader 实现: - Texture 类(继承IResource) - TextureImportSettings 类 - TextureLoader 类(继承IResourceLoader) - 使用stb_image加载PNG/JPG/TGA - REGISTER_RESOURCE_LOADER(TextureLoader) 任务 4.4.2: Mesh + MeshLoader 实现: - Mesh 类(继承IResource) - MeshSection 结构体 - MeshImportSettings 类 - MeshLoader 类(使用Assimp) - 支持OBJ/glTF格式 - REGISTER_RESOURCE_LOADER(MeshLoader) 任务 4.4.3: Material + MaterialLoader 实现: - Material 类 - MaterialProperty 结构体 - MaterialLoader 类(解析JSON格式) - REGISTER_RESOURCE_LOADER(MaterialLoader) 任务 4.4.4: AudioClip + AudioLoader 实现: - AudioClip 类 - AudioLoader 类 - 支持WAV格式 - REGISTER_RESOURCE_LOADER(AudioLoader) 任务 4.5.1: ResourceFileSystem 实现: - ResourceFileSystem 类 - IArchive 接口 - AddArchive(), AddDirectory() - ReadResource(), Exists() 任务 4.6.1: ResourceDependencyGraph 实现: - DependencyNode 结构体 - ResourceDependencyGraph 类 - AddDependency(), RemoveDependency() - GetDependencies(), GetDependents() - TopologicalSort() ``` --- ## 11. 详细执行计划 > 本节详细说明如何逐步实现资源系统,包括每个文件的具体内容、实现步骤、测试方法和集成要点。 ### 11.1 文件创建清单 首先,需要创建以下目录结构和文件: ``` engine/include/XCEngine/Resources/ ├── Resources.h # 主头文件 ├── ResourceTypes.h # 资源类型枚举 ├── ImportSettings.h # 导入设置基类 ├── IResource.h # 资源基类接口 ├── ResourceHandle.h # 资源句柄 ├── ResourceManager.h # 资源管理器 ├── IResourceLoader.h # 加载器基类 ├── ResourceCache.h # 资源缓存 ├── AsyncLoader.h # 异步加载器 ├── Texture.h # 纹理资源 ├── TextureLoader.h # 纹理加载器 ├── TextureImportSettings.h # 纹理导入设置 ├── Mesh.h # 网格资源 ├── MeshLoader.h # 网格加载器 ├── MeshImportSettings.h # 网格导入设置 ├── Material.h # 材质资源 ├── MaterialLoader.h # 材质加载器 ├── Shader.h # 着色器资源 ├── ShaderLoader.h # 着色器加载器 ├── AudioClip.h # 音频资源 ├── AudioLoader.h # 音频加载器 └── BinaryResource.h # 二进制资源 engine/src/Resources/ ├── Resources.cpp # 资源系统初始化 ├── ResourceManager.cpp # 资源管理器实现 ├── ResourceCache.cpp # 缓存实现 ├── AsyncLoader.cpp # 异步加载实现 ├── TextureLoader.cpp # 纹理加载实现 ├── MeshLoader.cpp # 网格加载实现 ├── MaterialLoader.cpp # 材质加载实现 ├── ShaderLoader.cpp # 着色器加载实现 └── AudioLoader.cpp # 音频加载实现 ``` ### 11.2 第一阶段:基础框架(天1-3) #### 步骤 11.2.1 创建主头文件 Resources.h ```cpp // Resources.h #pragma once #include "ResourceTypes.h" #include "ImportSettings.h" #include "IResource.h" #include "ResourceHandle.h" #include "ResourceManager.h" #include "IResourceLoader.h" #include "ResourceCache.h" #include "AsyncLoader.h" // 具体资源类型 #include "Texture.h" #include "TextureLoader.h" #include "TextureImportSettings.h" #include "Mesh.h" #include "MeshLoader.h" #include "MeshImportSettings.h" #include "Material.h" #include "MaterialLoader.h" #include "Shader.h" #include "ShaderLoader.h" #include "AudioClip.h" #include "AudioLoader.h" #include "BinaryResource.h" ``` #### 步骤 11.2.2 实现 ResourceTypes.h **文件**: `engine/include/XCEngine/Resources/ResourceTypes.h` ```cpp #pragma once #include "../Core/Types.h" #include "../Containers/String.h" #include "../Containers/Array.h" #include namespace XCEngine { namespace Resources { // 资源类型枚举 enum class ResourceType : Core::uint8 { Unknown = 0, Texture, Mesh, Material, Shader, AudioClip, Binary, AnimationClip, Skeleton, Font, ParticleSystem, Scene, Prefab }; constexpr const char* GetResourceTypeName(ResourceType type) { switch (type) { case ResourceType::Texture: return "Texture"; case ResourceType::Mesh: return "Mesh"; case ResourceType::Material: return "Material"; case ResourceType::Shader: return "Shader"; case ResourceType::AudioClip: return "AudioClip"; case ResourceType::Binary: return "Binary"; case ResourceType::AnimationClip: return "AnimationClip"; case ResourceType::Skeleton: return "Skeleton"; case ResourceType::Font: return "Font"; case ResourceType::ParticleSystem:return "ParticleSystem"; case ResourceType::Scene: return "Scene"; case ResourceType::Prefab: return "Prefab"; default: return "Unknown"; } } // ResourceGUID - 64位唯一标识符 struct ResourceGUID { Core::uint64 value; ResourceGUID() : value(0) {} explicit ResourceGUID(Core::uint64 v) : value(v) {} bool IsValid() const { return value != 0; } bool operator==(const ResourceGUID& other) const { return value == other.value; } bool operator!=(const ResourceGUID& other) const { return value != other.value; } static ResourceGUID Generate(const char* path); static ResourceGUID Generate(const Containers::String& path); Containers::String ToString() const; }; inline ResourceGUID MakeResourceGUID(const char* path) { return ResourceGUID::Generate(path); } // 模板特化获取ResourceType template ResourceType GetResourceType(); template<> inline ResourceType GetResourceType() { return ResourceType::Texture; } template<> inline ResourceType GetResourceType() { return ResourceType::Mesh; } template<> inline ResourceType GetResourceType() { return ResourceType::Material; } template<> inline ResourceType GetResourceType() { return ResourceType::Shader; } template<> inline ResourceType GetResourceType() { return ResourceType::AudioClip; } template<> inline ResourceType GetResourceType() { return ResourceType::Binary; } // 导入设置基类 class ImportSettings { public: virtual ~ImportSettings() = default; virtual UniquePtr Clone() const = 0; virtual bool LoadFromJSON(const Containers::String& json) { return false; } virtual Containers::String SaveToJSON() const { return Containers::String(); } }; } // namespace Resources } // namespace XCEngine ``` **实现文件** `engine/src/Resources/ResourceTypes.cpp`: ```cpp #include "Resources/ResourceTypes.h" #include namespace XCEngine { namespace Resources { // 简单哈希实现(实际项目中可使用MD5) ResourceGUID ResourceGUID::Generate(const char* path) { Core::uint64 hash = 14695981039346656037ULL; // FNV offset basis while (*path) { hash ^= static_cast(*path); hash *= 1099511628211ULL; // FNV prime path++; } return ResourceGUID(hash); } ResourceGUID ResourceGUID::Generate(const Containers::String& path) { return Generate(path.CStr()); } Containers::String ResourceGUID::ToString() const { char buffer[17]; snprintf(buffer, sizeof(buffer), "%016llx", value); return Containers::String(buffer); } } // namespace Resources } // namespace XCEngine ``` #### 步骤 11.2.3 实现 IResource.h **文件**: `engine/include/XCEngine/Resources/IResource.h` ```cpp #pragma once #include "ResourceTypes.h" #include "../Containers/String.h" namespace XCEngine { namespace Resources { class IResource { public: virtual ~IResource() = default; virtual ResourceType GetType() const = 0; virtual const Containers::String& GetName() const = 0; virtual const Containers::String& GetPath() const = 0; virtual ResourceGUID GetGUID() const = 0; virtual bool IsValid() const = 0; virtual size_t GetMemorySize() const = 0; virtual void Release() = 0; protected: struct ConstructParams { Containers::String name; Containers::String path; ResourceGUID guid; size_t memorySize = 0; }; void Initialize(const ConstructParams& params) { m_name = params.name; m_path = params.path; m_guid = params.guid; m_memorySize = params.memorySize; m_isValid = true; } void SetInvalid() { m_isValid = false; } Containers::String m_name; Containers::String m_path; ResourceGUID m_guid; bool m_isValid = false; size_t m_memorySize = 0; }; } // namespace Resources } // namespace XCEngine ``` #### 步骤 11.2.4 实现 ResourceHandle.h **文件**: `engine/include/XCEngine/Resources/ResourceHandle.h` ```cpp #pragma once #include "IResource.h" #include #include namespace XCEngine { namespace Resources { template class ResourceHandle { static_assert(std::is_base_of_v, "T must derive from IResource"); public: ResourceHandle() = default; explicit ResourceHandle(T* resource) : m_resource(resource) { if (m_resource) { ResourceManager::Get().AddRef(m_resource->GetGUID()); } } ResourceHandle(const ResourceHandle& other) : m_resource(other.m_resource) { if (m_resource) { ResourceManager::Get().AddRef(m_resource->GetGUID()); } } ResourceHandle(ResourceHandle&& other) noexcept : m_resource(other.m_resource) { other.m_resource = nullptr; } ~ResourceHandle() { Reset(); } ResourceHandle& operator=(const ResourceHandle& other) { if (this != &other) { Reset(); m_resource = other.m_resource; if (m_resource) { ResourceManager::Get().AddRef(m_resource->GetGUID()); } } return *this; } ResourceHandle& operator=(ResourceHandle&& other) noexcept { if (this != &other) { Reset(); m_resource = other.m_resource; other.m_resource = nullptr; } return *this; } T* Get() const { return m_resource; } T* operator->() const { return m_resource; } T& operator*() const { return *m_resource; } bool IsValid() const { return m_resource != nullptr && m_resource->IsValid(); } explicit operator bool() const { return IsValid(); } ResourceGUID GetGUID() const { return m_resource ? m_resource->GetGUID() : ResourceGUID(0); } ResourceType GetResourceType() const { return m_resource ? m_resource->GetType() : ResourceType::Unknown; } void Reset() { if (m_resource) { ResourceManager::Get().Release(m_resource->GetGUID()); m_resource = nullptr; } } void Swap(ResourceHandle& other) { std::swap(m_resource, other.m_resource); } private: T* m_resource = nullptr; }; template bool operator==(const ResourceHandle& lhs, const ResourceHandle& rhs) { return lhs.GetGUID() == rhs.GetGUID(); } template bool operator!=(const ResourceHandle& lhs, const ResourceHandle& rhs) { return !(lhs == rhs); } } // namespace Resources } // namespace XCEngine ``` #### 步骤 11.2.5 实现 IResourceLoader.h **文件**: `engine/include/XCEngine/Resources/IResourceLoader.h` ```cpp #pragma once #include "IResource.h" #include "ResourceTypes.h" #include "../Containers/String.h" #include "../Containers/Array.h" #include namespace XCEngine { namespace Resources { struct LoadResult { IResource* resource = nullptr; bool success = false; Containers::String errorMessage; LoadResult() = default; explicit LoadResult(IResource* res) : resource(res), success(res != nullptr) {} explicit LoadResult(const Containers::String& error) : success(false), errorMessage(error) {} explicit LoadResult(bool success, const Containers::String& error = "") : success(success), errorMessage(error) {} operator bool() const { return success && resource != nullptr; } }; class IResourceLoader { public: virtual ~IResourceLoader() = default; virtual ResourceType GetResourceType() const = 0; virtual Containers::Array GetSupportedExtensions() const = 0; virtual bool CanLoad(const Containers::String& path) const = 0; virtual LoadResult Load(const Containers::String& path, const ImportSettings* settings = nullptr) = 0; virtual void LoadAsync(const Containers::String& path, const ImportSettings* settings, std::function callback) { LoadResult result = Load(path, settings); if (callback) { callback(result); } } virtual ImportSettings* GetDefaultSettings() const = 0; protected: static Containers::Array ReadFileData(const Containers::String& path); static Containers::String GetExtension(const Containers::String& path); }; #define REGISTER_RESOURCE_LOADER(loaderType) \ namespace { \ struct loaderType##Registrar { \ loaderType##Registrar() { \ ResourceManager::Get().RegisterLoader(new loaderType()); \ } \ } g_##loaderType##Registrar; \ } } // namespace Resources } // namespace XCEngine ``` #### 步骤 11.2.6 实现 ResourceManager.h **文件**: `engine/include/XCEngine/Resources/ResourceManager.h` ```cpp #pragma once #include "IResourceLoader.h" #include "ResourceCache.h" #include "AsyncLoader.h" #include "../Containers/String.h" #include "../Containers/Array.h" #include "../Containers/HashMap.h" #include "../Threading/Mutex.h" #include "../Debug/Logger.h" namespace XCEngine { namespace Resources { class ResourceManager { public: static ResourceManager& Get(); void Initialize(); void Shutdown(); void SetResourceRoot(const Containers::String& rootPath); const Containers::String& GetResourceRoot() const; template ResourceHandle Load(const Containers::String& path, ImportSettings* settings = nullptr) { static_assert(std::is_base_of_v, "T must derive from IResource"); // 实现见文档Section 5.4 } void LoadAsync(const Containers::String& path, ResourceType type, std::function callback); void LoadAsync(const Containers::String& path, ResourceType type, ImportSettings* settings, std::function callback); void Unload(const Containers::String& path); void Unload(ResourceGUID guid); void UnloadUnused(); void UnloadAll(); void AddRef(ResourceGUID guid); void Release(ResourceGUID guid); Core::uint32 GetRefCount(ResourceGUID guid) const; void RegisterLoader(IResourceLoader* loader); void UnregisterLoader(ResourceType type); IResourceLoader* GetLoader(ResourceType type) const; void SetMemoryBudget(size_t bytes); size_t GetMemoryUsage() const; size_t GetMemoryBudget() const; void FlushCache(); IResource* Find(const Containers::String& path); IResource* Find(ResourceGUID guid); bool Exists(const Containers::String& path) const; bool Exists(ResourceGUID guid) const; Containers::String ResolvePath(const Containers::String& relativePath) const; private: ResourceManager() = default; ~ResourceManager() = default; IResource* FindInCache(ResourceGUID guid); void AddToCache(ResourceGUID guid, IResource* resource); IResourceLoader* FindLoader(ResourceType type); void ReloadResource(ResourceGUID guid); Containers::String m_resourceRoot; Containers::HashMap m_resourceCache; Containers::HashMap m_refCounts; Containers::HashMap m_guidToPath; Containers::HashMap> m_loaders; size_t m_memoryUsage = 0; size_t m_memoryBudget = 512 * 1024 * 1024; UniquePtr m_asyncLoader; Threading::Mutex m_mutex; friend class ResourceHandleBase; }; } // namespace Resources } // namespace XCEngine ``` #### 步骤 11.2.7 实现 ResourceManager.cpp **文件**: `engine/src/Resources/ResourceManager.cpp` ```cpp #include "Resources/ResourceManager.h" #include "Resources/ResourceHandle.h" namespace XCEngine { namespace Resources { ResourceManager& ResourceManager::Get() { static ResourceManager instance; return instance; } void ResourceManager::Initialize() { m_asyncLoader = UniquePtr(new AsyncLoader()); m_asyncLoader->Initialize(2); // 默认2个工作线程 } void ResourceManager::Shutdown() { UnloadAll(); m_asyncLoader->Shutdown(); m_asyncLoader.Reset(); } void ResourceManager::SetResourceRoot(const Containers::String& rootPath) { m_resourceRoot = rootPath; } const Containers::String& ResourceManager::GetResourceRoot() const { return m_resourceRoot; } void ResourceManager::AddRef(ResourceGUID guid) { std::lock_guard lock(m_mutex); auto it = m_refCounts.Find(guid); if (it == m_refCounts.End()) { m_refCounts.Insert(guid, 1); } else { it->second++; } if (!m_resourceCache.Contains(guid)) { ReloadResource(guid); } } void ResourceManager::Release(ResourceGUID guid) { std::lock_guard lock(m_mutex); auto it = m_refCounts.Find(guid); if (it != m_refCounts.End()) { it->second--; if (it->second == 0) { m_refCounts.Erase(guid); m_cache.OnZeroRefCount(guid); } } } Core::uint32 ResourceManager::GetRefCount(ResourceGUID guid) const { std::lock_guard lock(m_mutex); auto it = m_refCounts.Find(guid); return it != m_refCounts.End() ? it->second : 0; } void ResourceManager::RegisterLoader(IResourceLoader* loader) { std::lock_guard lock(m_mutex); m_loaders.Insert(loader->GetResourceType(), UniquePtr(loader)); } IResourceLoader* ResourceManager::GetLoader(ResourceType type) const { std::lock_guard lock(m_mutex); auto it = m_loaders.Find(type); return it != m_loaders.End() ? it->second.Get() : nullptr; } IResourceLoader* ResourceManager::FindLoader(ResourceType type) { return GetLoader(type); } IResource* ResourceManager::FindInCache(ResourceGUID guid) { std::lock_guard lock(m_mutex); auto it = m_resourceCache.Find(guid); return it != m_resourceCache.End() ? it->second : nullptr; } void ResourceManager::AddToCache(ResourceGUID guid, IResource* resource) { std::lock_guard lock(m_mutex); m_resourceCache.Insert(guid, resource); m_memoryUsage += resource->GetMemorySize(); m_cache.Add(guid, resource); if (m_memoryUsage > m_memoryBudget) { m_cache.OnMemoryPressure(m_memoryUsage - m_memoryBudget); } } void ResourceManager::Unload(ResourceGUID guid) { std::lock_guard lock(m_mutex); auto it = m_resourceCache.Find(guid); if (it != m_resourceCache.End()) { IResource* resource = it->second; m_resourceCache.Erase(guid); m_memoryUsage -= resource->GetMemorySize(); resource->Release(); } } void ResourceManager::UnloadAll() { std::lock_guard lock(m_mutex); for (auto& pair : m_resourceCache) { pair.second->Release(); } m_resourceCache.Clear(); m_refCounts.Clear(); m_memoryUsage = 0; } void ResourceManager::SetMemoryBudget(size_t bytes) { m_memoryBudget = bytes; } size_t ResourceManager::GetMemoryUsage() const { return m_memoryUsage; } size_t ResourceManager::GetMemoryBudget() const { return m_memoryBudget; } void ResourceManager::FlushCache() { m_cache.Flush(); } IResource* ResourceManager::Find(const Containers::String& path) { ResourceGUID guid = MakeResourceGUID(path.CStr()); return Find(guid); } IResource* ResourceManager::Find(ResourceGUID guid) { return FindInCache(guid); } bool ResourceManager::Exists(const Containers::String& path) const { return Find(MakeResourceGUID(path.CStr())) != nullptr; } bool ResourceManager::Exists(ResourceGUID guid) const { return false; // 需要实现 } Containers::String ResourceManager::ResolvePath(const Containers::String& relativePath) const { return m_resourceRoot + "/" + relativePath; } void ResourceManager::LoadAsync(const Containers::String& path, ResourceType type, std::function callback) { LoadAsync(path, type, nullptr, callback); } void ResourceManager::LoadAsync(const Containers::String& path, ResourceType type, ImportSettings* settings, std::function)> callback) { m_asyncLoader->Submit(path, type, settings, callback); } void ResourceManager::Unload(const Containers::String& path) { Unload(MakeResourceGUID(path.CStr())); } void ResourceManager::UnloadUnused() { // 实现清理未使用资源 } void ResourceManager::UnregisterLoader(ResourceType type) { m_loaders.Erase(type); } void ResourceManager::ReloadResource(ResourceGUID guid) { // 实现重新加载逻辑 } } // namespace Resources } // namespace XCEngine ``` ### 11.3 第二阶段:缓存系统(天4-5) #### 步骤 11.3.1 实现 ResourceCache.h **文件**: `engine/include/XCEngine/Resources/ResourceCache.h` ```cpp #pragma once #include "ResourceTypes.h" #include "../Containers/HashMap.h" #include "../Containers/Array.h" #include "../Threading/Mutex.h" #include namespace XCEngine { namespace Resources { struct CacheEntry { IResource* resource; ResourceGUID guid; size_t memorySize; Core::uint64 lastAccessTime; Core::uint32 accessCount; CacheEntry() : resource(nullptr), memorySize(0), lastAccessTime(0), accessCount(0) {} CacheEntry(IResource* res, size_t size) : resource(res), guid(res->GetGUID()), memorySize(size), lastAccessTime(GetCurrentTick()), accessCount(1) {} private: static Core::uint64 GetCurrentTick(); }; class ResourceCache { public: ResourceCache(); ~ResourceCache(); void Add(ResourceGUID guid, IResource* resource); void Remove(ResourceGUID guid); IResource* Find(ResourceGUID guid) const; void Touch(ResourceGUID guid); size_t GetSize() const { return m_cache.Size(); } size_t GetMemoryUsage() const { return m_memoryUsage; } void SetMemoryBudget(size_t bytes); size_t GetMemoryBudget() const { return m_memoryBudget; } void OnMemoryPressure(size_t requiredBytes); void OnZeroRefCount(ResourceGUID guid); void Flush(); void Clear(); Containers::Array GetLRUList(size_t count) const; private: void Evict(size_t requiredBytes); void UpdateMemoryStats(); Containers::HashMap m_cache; Containers::Array m_lruOrder; size_t m_memoryUsage = 0; size_t m_memoryBudget = 512 * 1024 * 1024; Threading::Mutex m_mutex; }; } // namespace Resources } // namespace XCEngine ``` #### 步骤 11.3.2 实现 ResourceCache.cpp **文件**: `engine/src/Resources/ResourceCache.cpp` ```cpp #include "Resources/ResourceCache.h" namespace XCEngine { namespace Resources { // 简单的时间戳实现,实际可使用Profiler::Get().GetTickCount() static Core::uint64 g_currentTick = 0; Core::uint64 CacheEntry::GetCurrentTick() { return ++g_currentTick; } ResourceCache::ResourceCache() = default; ResourceCache::~ResourceCache() = default; void ResourceCache::Add(ResourceGUID guid, IResource* resource) { std::lock_guard lock(m_mutex); if (m_cache.Contains(guid)) { return; // 已存在 } CacheEntry entry(resource, resource->GetMemorySize()); m_cache.Insert(guid, entry); m_lruOrder.Add(guid); m_memoryUsage += entry.memorySize; } void ResourceCache::Remove(ResourceGUID guid) { std::lock_guard lock(m_mutex); auto it = m_cache.Find(guid); if (it != m_cache.End()) { m_memoryUsage -= it->second.memorySize; m_cache.Erase(guid); for (auto lit = m_lruOrder.Begin(); lit != m_lruOrder.End(); ++lit) { if (*lit == guid) { m_lruOrder.Erase(lit); break; } } } } IResource* ResourceCache::Find(ResourceGUID guid) const { std::lock_guard lock(m_mutex); auto it = m_cache.Find(guid); return it != m_cache.End() ? it->second.resource : nullptr; } void ResourceCache::Touch(ResourceGUID guid) { std::lock_guard lock(m_mutex); auto it = m_cache.Find(guid); if (it != m_cache.End()) { it->second.lastAccessTime = CacheEntry::GetCurrentTick(); it->second.accessCount++; } } void ResourceCache::SetMemoryBudget(size_t bytes) { std::lock_guard lock(m_mutex); m_memoryBudget = bytes; } void ResourceCache::OnMemoryPressure(size_t requiredBytes) { std::lock_guard lock(m_mutex); if (m_memoryUsage + requiredBytes <= m_memoryBudget) { return; } size_t targetRelease = (m_memoryUsage + requiredBytes) - m_memoryBudget; Evict(targetRelease); } void ResourceCache::OnZeroRefCount(ResourceGUID guid) { // 引用计数为0时,标记资源可以被淘汰 // 实际淘汰由LRU策略决定 } void ResourceCache::Evict(size_t requiredBytes) { size_t released = 0; for (auto it = m_lruOrder.Begin(); it != m_lruOrder.End() && released < requiredBytes; ) { ResourceGUID guid = *it; auto cacheIt = m_cache.Find(guid); if (cacheIt != m_cache.End()) { CacheEntry& entry = cacheIt->second; if (ResourceManager::Get().GetRefCount(guid) == 0) { size_t entrySize = entry.memorySize; m_cache.Erase(guid); m_memoryUsage -= entrySize; released += entrySize; entry.resource->Release(); it = m_lruOrder.Erase(it); } else { ++it; } } else { ++it; } } } void ResourceCache::Flush() { std::lock_guard lock(m_mutex); for (auto& pair : m_cache) { if (ResourceManager::Get().GetRefCount(pair.first) == 0) { pair.second.resource->Release(); } } m_cache.Clear(); m_lruOrder.Clear(); m_memoryUsage = 0; } void ResourceCache::Clear() { std::lock_guard lock(m_mutex); m_cache.Clear(); m_lruOrder.Clear(); m_memoryUsage = 0; } Containers::Array ResourceCache::GetLRUList(size_t count) const { std::lock_guard lock(m_mutex); Containers::Array result; size_t n = std::min(count, static_cast(m_lruOrder.Size())); for (size_t i = 0; i < n; i++) { result.Add(m_lruOrder[i]); } return result; } } // namespace Resources } // namespace XCEngine ``` ### 11.4 第三阶段:异步加载(天6-7) #### 步骤 11.4.1 实现 AsyncLoader.h **文件**: `engine/include/XCEngine/Resources/AsyncLoader.h` ```cpp #pragma once #include "IResourceLoader.h" #include "../Containers/Array.h" #include "../Threading/Mutex.h" #include #include namespace XCEngine { namespace Resources { struct LoadRequest { Containers::String path; ResourceType type; std::function callback; UniquePtr settings; Core::uint64 requestId; LoadRequest() : requestId(0) {} LoadRequest(const Containers::String& p, ResourceType t, std::function cb, ImportSettings* s = nullptr) : path(p), type(t), callback(std::move(cb)), settings(s), requestId(GenerateRequestId()) {} private: static Core::uint64 GenerateRequestId(); }; class LoadTask : public Threading::ITask { public: LoadTask(LoadRequest request, IResourceLoader* loader); void Execute() override; LoadResult& GetResult() { return m_result; } private: LoadRequest m_request; IResourceLoader* m_loader; LoadResult m_result; }; class AsyncLoader { public: static AsyncLoader& Get(); void Initialize(Core::uint32 workerThreadCount = 2); void Shutdown(); void Submit(const Containers::String& path, ResourceType type, std::function callback); void Submit(const Containers::String& path, ResourceType type, ImportSettings* settings, std::function callback); void Update(); bool IsLoading() const { return m_pendingCount > 0; } Core::uint32 GetPendingCount() const { return m_pendingCount; } float GetProgress() const; void CancelAll(); void Cancel(Core::uint64 requestId); private: AsyncLoader() = default; ~AsyncLoader() = default; void SubmitInternal(LoadRequest& request); IResourceLoader* FindLoader(ResourceType type) const; Threading::Mutex m_queueMutex; Containers::Array m_pendingQueue; Threading::Mutex m_completedMutex; Containers::Array m_completedQueue; std::atomic m_pendingCount{0}; std::atomic m_completedCount{0}; Core::uint32 m_totalRequested = 0; }; } // namespace Resources } // namespace XCEngine ``` #### 步骤 11.4.2 实现 AsyncLoader.cpp **文件**: `engine/src/Resources/AsyncLoader.cpp` ```cpp #include "Resources/AsyncLoader.h" #include "Resources/ResourceManager.h" #include "../Threading/TaskSystem.h" namespace XCEngine { namespace Resources { Core::uint64 LoadRequest::GenerateRequestId() { static std::atomic s_requestId{0}; return ++s_requestId; } LoadTask::LoadTask(LoadRequest request, IResourceLoader* loader) : m_request(std::move(request)), m_loader(loader) {} void LoadTask::Execute() { m_result = m_loader->Load(m_request.path, m_request.settings.Get()); // 放入完成队列 AsyncLoader::Get().QueueCompleted(std::move(m_request), std::move(m_result)); } AsyncLoader& AsyncLoader::Get() { static AsyncLoader instance; return instance; } void AsyncLoader::Initialize(Core::uint32 workerThreadCount) { // TaskSystem已初始化,此处不需要额外操作 } void AsyncLoader::Shutdown() { CancelAll(); } void AsyncLoader::Submit(const Containers::String& path, ResourceType type, std::function callback) { Submit(path, type, nullptr, callback); } void AsyncLoader::Submit(const Containers::String& path, ResourceType type, ImportSettings* settings, std::function callback) { LoadRequest request(path, type, callback, settings); SubmitInternal(request); } void AsyncLoader::SubmitInternal(LoadRequest& request) { IResourceLoader* loader = FindLoader(request.type); if (!loader) { if (request.callback) { LoadResult result(Containers::String("No loader for type: ") + GetResourceTypeName(request.type)); request.callback(result); } return; } { std::lock_guard lock(m_queueMutex); m_pendingQueue.Add(request); m_pendingCount++; m_totalRequested++; } // 提交到任务系统 auto task = new LoadTask(request, loader); Threading::TaskSystem::Get().Submit(UniquePtr(task)); } void AsyncLoader::Update() { Containers::Array completed; { std::lock_guard lock(m_completedMutex); completed = std::move(m_completedQueue); m_completedQueue.Clear(); } for (auto& request : completed) { m_pendingCount--; if (request.callback) { request.callback(m_completedResults[request.requestId]); } } m_completedResults.Clear(); } float AsyncLoader::GetProgress() const { if (m_totalRequested == 0) return 1.0f; return static_cast(m_totalRequested - m_pendingCount) / m_totalRequested; } void AsyncLoader::CancelAll() { std::lock_guard lock(m_queueMutex); m_pendingQueue.Clear(); m_pendingCount = 0; } void AsyncLoader::Cancel(Core::uint64 requestId) { std::lock_guard lock(m_queueMutex); for (auto it = m_pendingQueue.Begin(); it != m_pendingQueue.End(); ++it) { if (it->requestId == requestId) { m_pendingQueue.Erase(it); m_pendingCount--; return; } } } IResourceLoader* AsyncLoader::FindLoader(ResourceType type) const { return ResourceManager::Get().GetLoader(type); } } // namespace Resources } // namespace XCEngine ``` ### 11.5 第四阶段:具体资源类型(天8-11) #### 步骤 11.5.1 实现 Texture 及其加载器 **文件**: `engine/include/XCEngine/Resources/Texture.h` ```cpp #pragma once #include "IResource.h" #include "../Core/Types.h" namespace XCEngine { namespace Resources { enum class TextureType { Texture2D, Texture3D, TextureCube, Texture2DArray, TextureCubeArray }; enum class TextureFormat { Unknown, R8_UNORM, RG8_UNORM, RGBA8_UNORM, RGBA8_SRGB, R16_FLOAT, RG16_FLOAT, RGBA16_FLOAT, R32_FLOAT, RG32_FLOAT, RGBA32_FLOAT, D16_UNORM, D24_UNORM_S8_UINT, D32_FLOAT, D32_FLOAT_S8_X24_UINT, BC1_UNORM, BC1_UNORM_SRGB, BC2_UNORM, BC2_UNORM_SRGB, BC3_UNORM, BC3_UNORM_SRGB, BC4_UNORM, BC5_UNORM, BC6H_UF16, BC7_UNORM, BC7_UNORM_SRGB }; enum class TextureUsage : Core::uint8 { None = 0, ShaderResource = 1 << 0, RenderTarget = 1 << 1, DepthStencil = 1 << 2, UnorderedAccess = 1 << 3, TransferSrc = 1 << 4, TransferDst = 1 << 5 }; class Texture : public IResource { public: Texture(); virtual ~Texture() override; ResourceType GetType() const override { return ResourceType::Texture; } 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; Core::uint32 GetWidth() const { return m_width; } Core::uint32 GetHeight() const { return m_height; } Core::uint32 GetDepth() const { return m_depth; } Core::uint32 GetMipLevels() const { return m_mipLevels; } Core::uint32 GetArraySize() const { return m_arraySize; } TextureType GetTextureType() const { return m_textureType; } TextureFormat GetFormat() const { return m_format; } TextureUsage GetUsage() const { return m_usage; } class IRHIResource* GetRHIResource() const { return m_rhiResource; } void SetRHIResource(class IRHIResource* resource); bool Create(Core::uint32 width, Core::uint32 height, Core::uint32 depth, Core::uint32 mipLevels, TextureType type, TextureFormat format, const void* data, size_t dataSize); bool GenerateMipmaps(); private: Core::uint32 m_width = 0; Core::uint32 m_height = 0; Core::uint32 m_depth = 1; Core::uint32 m_mipLevels = 1; Core::uint32 m_arraySize = 1; TextureType m_textureType = TextureType::Texture2D; TextureFormat m_format = TextureFormat::RGBA8_UNORM; TextureUsage m_usage = TextureUsage::ShaderResource; class IRHIResource* m_rhiResource = nullptr; }; } // namespace Resources } // namespace XCEngine ``` **实现要点**: 1. 使用stb_image库加载PNG/JPG/TGA 2. 支持生成Mipmap 3. 完成后创建RHI资源 #### 步骤 11.5.2 实现 Mesh 及其加载器 **文件**: `engine/include/XCEngine/Resources/Mesh.h` ```cpp #pragma once #include "IResource.h" #include "../Containers/Array.h" #include "../Math/AABB.h" namespace XCEngine { namespace Resources { enum class VertexAttribute : Core::uint32 { Position = 1 << 0, Normal = 1 << 1, Tangent = 1 << 2, Color = 1 << 3, UV0 = 1 << 4, UV1 = 1 << 5, UV2 = 1 << 6, UV3 = 1 << 7, BoneWeights = 1 << 8, BoneIndices = 1 << 9 }; struct MeshSection { Core::uint32 baseVertex; Core::uint32 vertexCount; Core::uint32 startIndex; Core::uint32 indexCount; Core::uint32 materialID; Math::AABB boundingBox; }; class Mesh : public IResource { public: Mesh(); virtual ~Mesh() override; ResourceType GetType() const override { return ResourceType::Mesh; } 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; void SetVertexData(const void* data, size_t size, Core::uint32 vertexCount, Core::uint32 vertexStride, VertexAttribute attributes); const void* GetVertexData() const { return m_vertexData.Data(); } size_t GetVertexDataSize() const { return m_vertexData.Size(); } Core::uint32 GetVertexCount() const { return m_vertexCount; } Core::uint32 GetVertexStride() const { return m_vertexStride; } VertexAttribute GetVertexAttributes() const { return m_attributes; } void SetIndexData(const void* data, size_t size, Core::uint32 indexCount, bool use32Bit); const void* GetIndexData() const { return m_indexData.Data(); } size_t GetIndexDataSize() const { return m_indexData.Size(); } Core::uint32 GetIndexCount() const { return m_indexCount; } bool IsUse32BitIndex() const { return m_use32BitIndex; } void AddSection(const MeshSection& section); const Containers::Array& GetSections() const { return m_sections; } void SetBoundingBox(const Math::AABB& box); const Math::AABB& GetBoundingBox() const { return m_boundingBox; } class IRHIBuffer* GetVertexBuffer() const { return m_vertexBuffer; } class IRHIBuffer* GetIndexBuffer() const { return m_indexBuffer; } void SetRHIBuffers(class IRHIBuffer* vb, class IRHIBuffer* ib); private: Containers::Array m_vertexData; Core::uint32 m_vertexCount = 0; Core::uint32 m_vertexStride = 0; VertexAttribute m_attributes = VertexAttribute::Position; Containers::Array m_indexData; Core::uint32 m_indexCount = 0; bool m_use32BitIndex = false; Containers::Array m_sections; Math::AABB m_boundingBox; class IRHIBuffer* m_vertexBuffer = nullptr; class IRHIBuffer* m_indexBuffer = nullptr; }; } // namespace Resources } // namespace XCEngine ``` #### 步骤 11.5.3 实现 Material 及其加载器 **文件**: `engine/include/XCEngine/Resources/Material.h` ```cpp #pragma once #include "IResource.h" #include "ResourceHandle.h" #include "Texture.h" #include "../Containers/HashMap.h" #include "../Math/Vector2.h" #include "../Math/Vector3.h" #include "../Math/Vector4.h" namespace XCEngine { namespace Resources { enum class MaterialPropertyType { Float, Float2, Float3, Float4, Int, Int2, Int3, Int4, Bool, Texture, Cubemap }; struct MaterialProperty { Containers::String name; MaterialPropertyType type; union Value { float floatValue[4]; Core::int32 intValue[4]; bool boolValue; ResourceHandle textureValue; Value() { memset(this, 0, sizeof(Value)); } } value; Core::uint32 refCount; }; class Material : public IResource { public: Material(); virtual ~Material() override; ResourceType GetType() const override { return ResourceType::Material; } 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; void SetShader(const ResourceHandle& shader); class Shader* GetShader() const { return m_shader.Get(); } void SetFloat(const Containers::String& name, float value); void SetFloat2(const Containers::String& name, const Math::Vector2& value); void SetFloat3(const Containers::String& name, const Math::Vector3& value); void SetFloat4(const Containers::String& name, const Math::Vector4& value); void SetInt(const Containers::String& name, int32 value); void SetBool(const Containers::String& name, bool value); void SetTexture(const Containers::String& name, const ResourceHandle& texture); float GetFloat(const Containers::String& name) const; ResourceHandle GetTexture(const Containers::String& name) const; const Containers::Array& GetConstantBufferData() const { return m_constantBufferData; } void UpdateConstantBuffer(); private: ResourceHandle m_shader; Containers::HashMap m_properties; Containers::Array m_constantBufferData; struct TextureBinding { Containers::String name; Core::uint32 slot; ResourceHandle texture; }; Containers::Array m_textureBindings; }; } // namespace Resources } // namespace XCEngine ``` #### 步骤 11.5.4 实现 AudioClip 及其加载器 类似实现,支持WAV格式加载。 ### 11.6 第五阶段:文件系统集成(天12-13) #### 步骤 11.6.1 实现 ResourceFileSystem **文件**: `engine/include/XCEngine/Resources/ResourceFileSystem.h` ```cpp #pragma once #include "ResourceTypes.h" #include "../Containers/String.h" #include "../Containers/Array.h" #include "../Containers/HashMap.h" namespace XCEngine { namespace Resources { class ResourceFileSystem { public: ResourceFileSystem(); ~ResourceFileSystem(); void Initialize(const Containers::String& rootPath); void Shutdown(); bool AddArchive(const Containers::String& archivePath); bool AddDirectory(const Containers::String& directoryPath); void RemoveArchive(const Containers::String& archivePath); bool FindResource(const Containers::String& relativePath, Containers::String& outAbsolutePath) const; Containers::Array ReadResource(const Containers::String& relativePath) const; bool Exists(const Containers::String& relativePath) const; struct ResourceInfo { Containers::String path; size_t size; Core::uint64 modifiedTime; bool inArchive; Containers::String archivePath; }; bool GetResourceInfo(const Containers::String& relativePath, ResourceInfo& outInfo) const; void EnumerateResources(const Containers::String& pattern, Containers::Array& outResources) const; private: class IArchive { public: virtual ~IArchive() = default; virtual bool Open(const Containers::String& path) = 0; virtual void Close() = 0; virtual bool Read(const Containers::String& fileName, void* buffer, size_t size, size_t offset) const = 0; virtual size_t GetSize(const Containers::String& fileName) const = 0; virtual bool Exists(const Containers::String& fileName) const = 0; virtual void Enumerate(const Containers::String& pattern, Containers::Array& outFiles) const = 0; }; Containers::String m_rootPath; Containers::Array> m_archives; Containers::Array m_directories; mutable Containers::HashMap m_infoCache; }; } // namespace Resources } // namespace XCEngine ``` ### 11.7 第六阶段:依赖管理(天14-15) #### 步骤 11.7.1 实现 ResourceDependencyGraph ```cpp // 见文档Section 9 ``` ### 11.8 第七阶段:测试与集成(天16-17) #### 步骤 11.8.1 单元测试 创建测试用例: 1. 资源加载/卸载 2. 引用计数 3. 缓存淘汰 4. 异步加载 5. 多线程安全 #### 步骤 11.8.2 集成到现有mvs项目 在示例应用中使用资源系统加载纹理和模型。 ### 11.9 每日任务清单 | 天数 | 任务 | 完成标准 | |------|------|----------| | 1 | 创建目录结构 + Resources.h + ResourceTypes.h | 编译通过 | | 2 | IResource.h + ResourceHandle.h | 智能指针正常工作 | | 3 | IResourceLoader.h + ResourceManager.h框架 | 加载器接口完成 | | 4 | ResourceCache.h/cpp | LRU淘汰正常 | | 5 | ResourceManager与Cache集成 | 引用计数正常 | | 6 | AsyncLoader.h/cpp框架 | 任务提交正常 | | 7 | 异步回调机制 | 主线程回调正常 | | 8 | Texture.h + 基础加载 | PNG加载成功 | | 9 | Texture RHI资源创建 | 渲染器能使用 | | 10 | Mesh.h + 加载器 | OBJ加载成功 | | 11 | Material.h + 加载器 | JSON材质加载 | | 12 | ResourceFileSystem | 目录/归档支持 | | 13 | 资源打包Builder | 打包成功 | | 14 | DependencyGraph | 依赖关系正确 | | 15 | 资源组加载 | 批量加载正常 | | 16 | 单元测试 | 测试通过 | | 17 | 集成测试 | 示例运行正常 | --- ## 12. 测试设计 > 本节详细说明资源系统的测试设计,确保每个阶段完成后都能立即进行测试验收。测试采用Google Test框架,与现有项目保持一致。 ### 12.1 测试目录结构 ``` tests/ ├── Resources/ # 新增资源系统测试 │ ├── CMakeLists.txt # 构建配置 │ ├── fixtures/ │ │ └── ResourcesTestFixture.h # 测试夹具基类 │ ├── test_resource_types.cpp # 资源类型测试 │ ├── test_resource_guid.cpp # GUID生成测试 │ ├── test_iresource.cpp # 资源接口测试 │ ├── test_resource_handle.cpp # 资源句柄测试 │ ├── test_resource_cache.cpp # 缓存测试 │ ├── test_async_loader.cpp # 异步加载测试 │ ├── test_texture.cpp # 纹理资源测试 │ ├── test_mesh.cpp # 网格资源测试 │ ├── test_material.cpp # 材质资源测试 │ ├── test_audio_clip.cpp # 音频资源测试 │ ├── test_resource_manager.cpp # 资源管理器测试 │ ├── test_resource_filesystem.cpp # 文件系统测试 │ ├── test_resource_dependency.cpp # 依赖管理测试 │ └── test_data/ │ ├── test_texture.png # 测试用纹理 │ ├── test_mesh.obj # 测试用模型 │ └── test_material.json # 测试用材质 ``` ### 12.2 测试夹具基类 **文件**: `tests/Resources/fixtures/ResourcesTestFixture.h` ```cpp #pragma once #include #include #include #include namespace XCEngine { namespace Resources { namespace Test { class ResourcesTestFixture : public ::testing::Test { protected: static void SetUpTestSuite() { // 初始化资源系统 ResourceManager::Get().Initialize(); ResourceManager::Get().SetResourceRoot("tests/Resources/test_data/"); AsyncLoader::Get().Initialize(2); } static void TearDownTestSuite() { ResourceManager::Get().UnloadAll(); AsyncLoader::Get().Shutdown(); ResourceManager::Get().Shutdown(); } void SetUp() override { // 每个测试前清空缓存 ResourceManager::Get().UnloadAll(); ResourceManager::Get().FlushCache(); } void TearDown() override { // 每个测试后确保清空资源 ResourceManager::Get().UnloadAll(); } // 辅助方法:获取测试数据目录 Containers::String GetTestDataPath(const Containers::String& filename) { return Containers::String("tests/Resources/test_data/") + filename; } }; } // namespace Test } // namespace Resources } // namespace XCEngine ``` ### 12.3 第一阶段测试:基础框架(天1-3完成后) #### 测试 12.3.1 资源类型枚举测试 **文件**: `tests/Resources/test_resource_types.cpp` ```cpp #include #include using namespace XCEngine::Resources; namespace { TEST(Resources_Types, ResourceType_EnumValues) { EXPECT_EQ(static_cast(ResourceType::Unknown), 0); EXPECT_EQ(static_cast(ResourceType::Texture), 1); EXPECT_EQ(static_cast(ResourceType::Mesh), 2); EXPECT_EQ(static_cast(ResourceType::Material), 3); EXPECT_EQ(static_cast(ResourceType::Shader), 4); EXPECT_EQ(static_cast(ResourceType::AudioClip), 5); EXPECT_EQ(static_cast(ResourceType::Binary), 6); } TEST(Resources_Types, GetResourceTypeName) { EXPECT_STREQ(GetResourceTypeName(ResourceType::Texture), "Texture"); EXPECT_STREQ(GetResourceTypeName(ResourceType::Mesh), "Mesh"); EXPECT_STREQ(GetResourceTypeName(ResourceType::Material), "Material"); EXPECT_STREQ(GetResourceTypeName(ResourceType::Unknown), "Unknown"); } TEST(Resources_Types, GetResourceType_TemplateSpecializations) { EXPECT_EQ(GetResourceType(), ResourceType::Texture); EXPECT_EQ(GetResourceType(), ResourceType::Mesh); EXPECT_EQ(GetResourceType(), ResourceType::Material); EXPECT_EQ(GetResourceType(), ResourceType::Shader); EXPECT_EQ(GetResourceType(), ResourceType::AudioClip); EXPECT_EQ(GetResourceType(), ResourceType::Binary); } } // namespace ``` #### 测试 12.3.2 ResourceGUID测试 **文件**: `tests/Resources/test_resource_guid.cpp` ```cpp #include #include using namespace XCEngine::Resources; namespace { TEST(Resources_GUID, DefaultConstructor) { ResourceGUID guid; EXPECT_FALSE(guid.IsValid()); EXPECT_EQ(guid.value, 0); } TEST(Resources_GUID, ValueConstructor) { ResourceGUID guid(12345); EXPECT_TRUE(guid.IsValid()); EXPECT_EQ(guid.value, 12345); } TEST(Resources_GUID, Generate_FromCString) { ResourceGUID guid1 = ResourceGUID::Generate("textures/player.png"); ResourceGUID guid2 = ResourceGUID::Generate("textures/player.png"); // 相同路径应生成相同GUID EXPECT_EQ(guid1, guid2); EXPECT_TRUE(guid1.IsValid()); } TEST(Resources_GUID, Generate_FromString) { Containers::String path = "models/player.fbx"; ResourceGUID guid = ResourceGUID::Generate(path); EXPECT_TRUE(guid.IsValid()); } TEST(Resources_GUID, Generate_DifferentPaths) { ResourceGUID guid1 = ResourceGUID::Generate("textures/a.png"); ResourceGUID guid2 = ResourceGUID::Generate("textures/b.png"); // 不同路径应生成不同GUID EXPECT_NE(guid1, guid2); } TEST(Resources_GUID, ToString) { ResourceGUID guid(0x1234567890ABCDEF); Containers::String str = guid.ToString(); EXPECT_EQ(str.Size(), 16u); EXPECT_STREQ(str.CStr(), "1234567890abcdef"); } TEST(Resources_GUID, MakeResourceGUID_Helper) { ResourceGUID guid = MakeResourceGUID("test/path"); EXPECT_TRUE(guid.IsValid()); } TEST(Resources_GUID, EqualityOperators) { ResourceGUID guid1(100); ResourceGUID guid2(100); ResourceGUID guid3(200); EXPECT_TRUE(guid1 == guid2); EXPECT_TRUE(guid1 != guid3); } } // namespace ``` #### 测试 12.3.3 资源句柄测试 **文件**: `tests/Resources/test_resource_handle.cpp` ```cpp #include #include #include using namespace XCEngine::Resources; // 测试用伪资源 class TestResource : public IResource { public: TestResource(const Containers::String& name) { ConstructParams params; params.name = name; params.path = name; params.guid = ResourceGUID::Generate(name.CStr()); params.memorySize = 1024; Initialize(params); } ResourceType GetType() const override { return ResourceType::Binary; } 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 { delete this; } }; namespace { TEST(Resources_Handle, DefaultConstructor) { ResourceHandle handle; EXPECT_FALSE(handle.IsValid()); EXPECT_EQ(handle.Get(), nullptr); } TEST(Resources_Handle, ConstructorWithValidResource) { TestResource* resource = new TestResource("test_resource"); ResourceHandle handle(resource); EXPECT_TRUE(handle.IsValid()); EXPECT_EQ(handle.Get(), resource); EXPECT_EQ(handle->GetName().CStr(), Containers::String("test_resource")); } TEST(Resources_Handle, CopyConstructor_AddsRef) { TestResource* resource = new TestResource("test_resource"); ResourceHandle handle1(resource); Core::uint32 refCountBefore = ResourceManager::Get().GetRefCount(resource->GetGUID()); ResourceHandle handle2(handle1); Core::uint32 refCountAfter = ResourceManager::Get().GetRefCount(resource->GetGUID()); EXPECT_EQ(refCountAfter, refCountBefore + 1); } TEST(Resources_Handle, MoveConstructor_DoesNotAddRef) { TestResource* resource = new TestResource("test_resource"); ResourceHandle handle1(resource); Core::uint32 refCountBefore = ResourceManager::Get().GetRefCount(resource->GetGUID()); ResourceHandle handle2(std::move(handle1)); // 移动语义不应增加引用计数 EXPECT_EQ(handle1.Get(), nullptr); EXPECT_EQ(handle2.Get(), resource); } TEST(Resources_Handle, Destructor_ReleasesRef) { TestResource* resource = new TestResource("test_resource"); { ResourceHandle handle(resource); EXPECT_TRUE(handle.IsValid()); } // handle销毁后应释放引用 // 注意:此时资源应该被卸载(因为引用计数为0) } TEST(Resources_Handle, Reset) { TestResource* resource = new TestResource("test_resource"); ResourceHandle handle(resource); EXPECT_TRUE(handle.IsValid()); handle.Reset(); EXPECT_FALSE(handle.IsValid()); EXPECT_EQ(handle.Get(), nullptr); } TEST(Resources_Handle, ArrowOperator) { TestResource* resource = new TestResource("test_resource"); ResourceHandle handle(resource); EXPECT_STREQ(handle->GetName().CStr(), "test_resource"); EXPECT_EQ(handle->GetGUID(), resource->GetGUID()); } TEST(Resources_Handle, BoolOperator) { ResourceHandle emptyHandle; TestResource* resource = new TestResource("test"); ResourceHandle validHandle(resource); EXPECT_FALSE(emptyHandle); EXPECT_TRUE(validHandle); } TEST(Resources_Handle, GetGUID) { TestResource* resource = new TestResource("test_resource"); ResourceHandle handle(resource); EXPECT_EQ(handle.GetGUID(), resource->GetGUID()); EXPECT_TRUE(handle.GetGUID().IsValid()); } TEST(Resources_Handle, GetResourceType) { TestResource* resource = new TestResource("test_resource"); ResourceHandle handle(resource); EXPECT_EQ(handle.GetResourceType(), ResourceType::Binary); } TEST(Resources_Handle, EqualityOperators) { TestResource* resource = new TestResource("test"); ResourceHandle handle1(resource); ResourceHandle handle2(resource); EXPECT_TRUE(handle1 == handle2); TestResource* resource2 = new TestResource("test2"); ResourceHandle handle3(resource2); EXPECT_TRUE(handle1 != handle3); } } // namespace ``` ### 12.4 第二阶段测试:缓存系统(天4-5完成后) #### 测试 12.4.1 资源缓存测试 **文件**: `tests/Resources/test_resource_cache.cpp` ```cpp #include #include #include "fixtures/ResourcesTestFixture.h" using namespace XCEngine::Resources; namespace { class ResourceCache_Test : public ResourcesTestFixture { protected: void SetUp() override { ResourcesTestFixture::SetUp(); m_cache = new ResourceCache(); } void TearDown() override { delete m_cache; ResourcesTestFixture::TearDown(); } ResourceCache* m_cache; }; TEST_F(ResourceCache_Test, AddResource) { TestResource* resource = new TestResource("test1"); ResourceGUID guid = resource->GetGUID(); m_cache->Add(guid, resource); EXPECT_EQ(m_cache->GetSize(), 1u); EXPECT_EQ(m_cache->GetMemoryUsage(), resource->GetMemorySize()); } TEST_F(ResourceCache_Test, FindResource) { TestResource* resource = new TestResource("test1"); ResourceGUID guid = resource->GetGUID(); m_cache->Add(guid, resource); IResource* found = m_cache->Find(guid); EXPECT_EQ(found, resource); } TEST_F(ResourceCache_Test, FindNonExistent) { ResourceGUID guid(9999); IResource* found = m_cache->Find(guid); EXPECT_EQ(found, nullptr); } TEST_F(ResourceCache_Test, RemoveResource) { TestResource* resource = new TestResource("test1"); ResourceGUID guid = resource->GetGUID(); m_cache->Add(guid, resource); m_cache->Remove(guid); EXPECT_EQ(m_cache->GetSize(), 0u); EXPECT_EQ(m_cache->Find(guid), nullptr); } TEST_F(ResourceCache_Test, Touch_UpdatesAccessTime) { TestResource* resource = new TestResource("test1"); ResourceGUID guid = resource->GetGUID(); m_cache->Add(guid, resource); m_cache->Touch(guid); // Touch后应能找到资源 EXPECT_EQ(m_cache->Find(guid), resource); } TEST_F(ResourceCache_Test, MemoryBudget) { m_cache->SetMemoryBudget(1024); // 1KB EXPECT_EQ(m_cache->GetMemoryBudget(), 1024u); } TEST_F(ResourceCache_Test, OnMemoryPressure_EvictUnreferenced) { m_cache->SetMemoryBudget(512); // 添加多个资源 for (int i = 0; i < 5; i++) { TestResource* resource = new TestResource(Containers::String("test") + Containers::String::FromInt(i)); m_cache->Add(resource->GetGUID(), resource); } // 触发内存压力 m_cache->OnMemoryPressure(1024); // 应该有资源被淘汰 // 具体数量取决于LRU策略 } TEST_F(ResourceCache_Test, Clear) { for (int i = 0; i < 3; i++) { TestResource* resource = new TestResource(Containers::String("test") + Containers::String::FromInt(i)); m_cache->Add(resource->GetGUID(), resource); } m_cache->Clear(); EXPECT_EQ(m_cache->GetSize(), 0u); EXPECT_EQ(m_cache->GetMemoryUsage(), 0u); } TEST_F(ResourceCache_Test, GetLRUList) { // 添加资源并访问 TestResource* r1 = new TestResource("test1"); TestResource* r2 = new TestResource("test2"); TestResource* r3 = new TestResource("test3"); m_cache->Add(r1->GetGUID(), r1); m_cache->Add(r2->GetGUID(), r2); m_cache->Add(r3->GetGUID(), r3); // 访问r1使其最新 m_cache->Touch(r1->GetGUID()); auto lru = m_cache->GetLRUList(2); // r1应该是最新的,不应在LRU列表前几位 EXPECT_GE(lru.Size(), 0u); } } // namespace ``` ### 12.5 第三阶段测试:异步加载(天6-7完成后) #### 测试 12.5.1 异步加载器测试 **文件**: `tests/Resources/test_async_loader.cpp` ```cpp #include #include #include "fixtures/ResourcesTestFixture.h" #include using namespace XCEngine::Resources; namespace { class AsyncLoader_Test : public ResourcesTestFixture { protected: void SetUp() override { ResourcesTestFixture::SetUp(); } }; TEST_F(AsyncLoader_Test, Submit_IncrementsPendingCount) { AsyncLoader& loader = AsyncLoader::Get(); Core::uint32 pendingBefore = loader.GetPendingCount(); // 注意:需要先注册加载器,否则会立即失败 // 这里测试API本身 EXPECT_GE(pendingBefore, 0u); } TEST_F(AsyncLoader_Test, Update_ProcessesCompletedRequests) { AsyncLoader& loader = AsyncLoader::Get(); // 调用Update处理完成队列 loader.Update(); // 不应崩溃 EXPECT_TRUE(true); } TEST_F(AsyncLoader_Test, IsLoading) { AsyncLoader& loader = AsyncLoader::Get(); bool isLoading = loader.IsLoading(); EXPECT_FALSE(isLoading); // 初始状态 } TEST_F(AsyncLoader_Test, GetProgress) { AsyncLoader& loader = AsyncLoader::Get(); float progress = loader.GetProgress(); // 0.0 - 1.0之间 EXPECT_GE(progress, 0.0f); EXPECT_LE(progress, 1.0f); } TEST_F(AsyncLoader_Test, CancelAll) { AsyncLoader& loader = AsyncLoader::Get(); loader.CancelAll(); EXPECT_EQ(loader.GetPendingCount(), 0u); } TEST_F(AsyncLoader_Test, CallbackInvocation) { std::atomic callbackCount{0}; // 模拟回调 auto callback = [&callbackCount](LoadResult result) { callbackCount++; }; // 调用Update确保没有pending请求 AsyncLoader::Get().Update(); EXPECT_EQ(callbackCount, 0); } } // namespace ``` ### 12.6 第四阶段测试:具体资源类型(天8-11完成后) #### 测试 12.6.1 纹理资源测试 **文件**: `tests/Resources/test_texture.cpp` ```cpp #include #include #include #include "fixtures/ResourcesTestFixture.h" using namespace XCEngine::Resources; namespace { class Texture_Test : public ResourcesTestFixture { protected: void SetUp() override { ResourcesTestFixture::SetUp(); } }; TEST_F(Texture_Test, DefaultConstructor) { Texture texture; EXPECT_FALSE(texture.IsValid()); EXPECT_EQ(texture.GetType(), ResourceType::Texture); } TEST_F(Texture_Test, GetDimensions_Default) { Texture texture; EXPECT_EQ(texture.GetWidth(), 0u); EXPECT_EQ(texture.GetHeight(), 0u); EXPECT_EQ(texture.GetDepth(), 1u); EXPECT_EQ(texture.GetMipLevels(), 1u); } TEST_F(Texture_Test, GetTextureType_Default) { Texture texture; EXPECT_EQ(texture.GetTextureType(), TextureType::Texture2D); } TEST_F(Texture_Test, GetFormat_Default) { Texture texture; EXPECT_EQ(texture.GetFormat(), TextureFormat::RGBA8_UNORM); } TEST_F(Texture_Test, Create) { Texture texture; bool result = texture.Create( 256, 256, 1, 1, TextureType::Texture2D, TextureFormat::RGBA8_UNORM, nullptr, 0 ); EXPECT_TRUE(result); EXPECT_EQ(texture.GetWidth(), 256u); EXPECT_EQ(texture.GetHeight(), 256u); EXPECT_TRUE(texture.IsValid()); } TEST_F(Texture_Test, CreateWithMipmaps) { Texture texture; bool result = texture.Create( 256, 256, 1, 4, TextureType::Texture2D, TextureFormat::RGBA8_UNORM, nullptr, 0 ); EXPECT_TRUE(result); EXPECT_EQ(texture.GetMipLevels(), 4u); } TEST_F(Texture_Test, GetMemorySize) { Texture texture; // 256x256 RGBA8 = 256*256*4 = 262144 bytes texture.Create(256, 256, 1, 1, TextureType::Texture2D, TextureFormat::RGBA8_UNORM, nullptr, 0); size_t memorySize = texture.GetMemorySize(); EXPECT_GT(memorySize, 0u); } TEST_F(Texture_Test, GetName) { Texture texture; ConstructParams params; params.name = "TestTexture"; params.path = "textures/test.png"; params.guid = ResourceGUID::Generate("test"); params.memorySize = 1024; texture.Initialize(params); EXPECT_STREQ(texture.GetName().CStr(), "TestTexture"); } TEST_F(Texture_Test, GetPath) { Texture texture; ConstructParams params; params.name = "TestTexture"; params.path = "textures/test.png"; params.guid = ResourceGUID::Generate("test"); params.memorySize = 1024; texture.Initialize(params); EXPECT_STREQ(texture.GetPath().CStr(), "textures/test.png"); } TEST_F(Texture_Test, TextureLoader_CanLoad) { TextureLoader loader; EXPECT_TRUE(loader.CanLoad("textures/test.png")); EXPECT_TRUE(loader.CanLoad("textures/test.jpg")); EXPECT_TRUE(loader.CanLoad("textures/test.tga")); EXPECT_FALSE(loader.CanLoad("models/test.obj")); } TEST_F(Texture_Test, TextureLoader_GetSupportedExtensions) { TextureLoader loader; auto extensions = loader.GetSupportedExtensions(); EXPECT_GE(extensions.Size(), 5u); } TEST_F(Texture_Test, TextureLoader_GetResourceType) { TextureLoader loader; EXPECT_EQ(loader.GetResourceType(), ResourceType::Texture); } TEST_F(Texture_Test, TextureLoader_GetDefaultSettings) { TextureLoader loader; ImportSettings* settings = loader.GetDefaultSettings(); EXPECT_NE(settings, nullptr); EXPECT_TRUE(dynamic_cast(settings) != nullptr); delete settings; } TEST_F(Texture_Test, TextureImportSettings_Defaults) { TextureImportSettings settings; EXPECT_EQ(settings.GetTextureType(), TextureType::Texture2D); EXPECT_TRUE(settings.GetGenerateMipmaps()); EXPECT_EQ(settings.GetMaxAnisotropy(), 16u); } TEST_F(Texture_Test, TextureImportSettings_Modify) { TextureImportSettings settings; settings.SetTextureType(TextureType::TextureCube); settings.SetGenerateMipmaps(false); settings.SetMaxAnisotropy(8); settings.SetSRGB(true); EXPECT_EQ(settings.GetTextureType(), TextureType::TextureCube); EXPECT_FALSE(settings.GetGenerateMipmaps()); EXPECT_EQ(settings.GetMaxAnisotropy(), 8u); EXPECT_TRUE(settings.GetSRGB()); } } // namespace ``` #### 测试 12.6.2 网格资源测试 **文件**: `tests/Resources/test_mesh.cpp` ```cpp #include #include #include "fixtures/ResourcesTestFixture.h" using namespace XCEngine::Resources; namespace { class Mesh_Test : public ResourcesTestFixture {}; TEST_F(Mesh_Test, DefaultConstructor) { Mesh mesh; EXPECT_FALSE(mesh.IsValid()); EXPECT_EQ(mesh.GetType(), ResourceType::Mesh); } TEST_F(Mesh_Test, GetVertexCount_Default) { Mesh mesh; EXPECT_EQ(mesh.GetVertexCount(), 0u); } TEST_F(Mesh_Test, GetIndexCount_Default) { Mesh mesh; EXPECT_EQ(mesh.GetIndexCount(), 0u); } TEST_F(Mesh_Test, SetVertexData) { Mesh mesh; // 简单顶点数据:位置 float vertices[] = { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }; mesh.SetVertexData(vertices, sizeof(vertices), 3, 12, VertexAttribute::Position); EXPECT_EQ(mesh.GetVertexCount(), 3u); EXPECT_EQ(mesh.GetVertexStride(), 12u); EXPECT_TRUE(mesh.GetVertexAttributes() & VertexAttribute::Position); } TEST_F(Mesh_Test, SetIndexData) { Mesh mesh; // 三角形索引 uint16_t indices[] = {0, 1, 2}; mesh.SetIndexData(indices, sizeof(indices), 3, false); EXPECT_EQ(mesh.GetIndexCount(), 3u); EXPECT_FALSE(mesh.IsUse32BitIndex()); } TEST_F(Mesh_Test, SetIndexData_32Bit) { Mesh mesh; uint32_t indices[] = {0, 1, 2, 0, 2, 3}; mesh.SetIndexData(indices, sizeof(indices), 6, true); EXPECT_EQ(mesh.GetIndexCount(), 6u); EXPECT_TRUE(mesh.IsUse32BitIndex()); } TEST_F(Mesh_Test, AddSection) { Mesh mesh; MeshSection section; section.baseVertex = 0; section.vertexCount = 3; section.startIndex = 0; section.indexCount = 3; section.materialID = 0; mesh.AddSection(section); EXPECT_EQ(mesh.GetSections().Size(), 1u); } TEST_F(Mesh_Test, BoundingBox) { Mesh mesh; Math::AABB box; box.Min = Math::Vector3(0, 0, 0); box.Max = Math::Vector3(1, 1, 1); mesh.SetBoundingBox(box); EXPECT_EQ(mesh.GetBoundingBox().Min, Math::Vector3(0, 0, 0)); EXPECT_EQ(mesh.GetBoundingBox().Max, Math::Vector3(1, 1, 1)); } TEST_F(Mesh_Test, VertexAttribute_Enum) { // 测试顶点属性标志 VertexAttribute attrs = VertexAttribute::Position | VertexAttribute::Normal | VertexAttribute::UV0; EXPECT_TRUE(attrs & VertexAttribute::Position); EXPECT_TRUE(attrs & VertexAttribute::Normal); EXPECT_TRUE(attrs & VertexAttribute::UV0); EXPECT_FALSE(attrs & VertexAttribute::Tangent); } } // namespace ``` #### 测试 12.6.3 材质资源测试 **文件**: `tests/Resources/test_material.cpp` ```cpp #include #include #include "fixtures/ResourcesTestFixture.h" using namespace XCEngine::Resources; namespace { class Material_Test : public ResourcesTestFixture {}; TEST_F(Material_Test, DefaultConstructor) { Material material; EXPECT_FALSE(material.IsValid()); EXPECT_EQ(material.GetType(), ResourceType::Material); } TEST_F(Material_Test, SetFloat) { Material material; material.SetFloat("Metallic", 0.5f); EXPECT_FLOAT_EQ(material.GetFloat("Metallic"), 0.5f); } TEST_F(Material_Test, SetFloat2) { Material material; material.SetFloat2("UVScale", Math::Vector2(2.0f, 3.0f)); Math::Vector2 value = material.GetFloat2("UVScale"); EXPECT_FLOAT_EQ(value.x, 2.0f); EXPECT_FLOAT_EQ(value.y, 3.0f); } TEST_F(Material_Test, SetFloat3) { Material material; material.SetFloat3("Albedo", Math::Vector3(1.0f, 0.0f, 0.0f)); Math::Vector3 value = material.GetFloat3("Albedo"); EXPECT_FLOAT_EQ(value.x, 1.0f); EXPECT_FLOAT_EQ(value.y, 0.0f); EXPECT_FLOAT_EQ(value.z, 0.0f); } TEST_F(Material_Test, SetFloat4) { Material material; material.SetFloat4("Color", Math::Vector4(1.0f, 1.0f, 1.0f, 0.5f)); Math::Vector4 value = material.GetFloat4("Color"); EXPECT_FLOAT_EQ(value.w, 0.5f); } TEST_F(Material_Test, SetInt) { Material material; material.SetInt("RenderQueue", 2000); EXPECT_EQ(material.GetInt("RenderQueue"), 2000); } TEST_F(Material_Test, SetBool) { Material material; material.SetBool("EnableInstancing", true); EXPECT_TRUE(material.GetBool("EnableInstancing")); } TEST_F(Material_Test, SetTexture) { Material material; // 创建测试纹理 Texture* tex = new Texture(); tex->Initialize({"TestTex", "test.png", ResourceGUID::Generate("test"), 1024}); ResourceHandle handle(tex); material.SetTexture("MainTex", handle); // 验证纹理设置成功 // 注意:需要实现GetTexture方法 } TEST_F(Material_Test, PropertyNotFound) { Material material; // 查询不存在的属性应有默认值 EXPECT_FLOAT_EQ(material.GetFloat("NonExistent"), 0.0f); } } // namespace ``` ### 12.7 第五阶段测试:资源管理器(天3-5完成后) #### 测试 12.7.1 资源管理器测试 **文件**: `tests/Resources/test_resource_manager.cpp` ```cpp #include #include #include "fixtures/ResourcesTestFixture.h" using namespace XCEngine::Resources; namespace { class ResourceManager_Test : public ResourcesTestFixture {}; TEST_F(ResourceManager_Test, GetSingleton) { ResourceManager& manager = ResourceManager::Get(); EXPECT_EQ(&manager, &ResourceManager::Get()); } TEST_F(ResourceManager_Test, SetResourceRoot) { ResourceManager& manager = ResourceManager::Get(); manager.SetResourceRoot("resources/"); EXPECT_STREQ(manager.GetResourceRoot().CStr(), "resources/"); } TEST_F(ResourceManager_Test, SetMemoryBudget) { ResourceManager& manager = ResourceManager::Get(); manager.SetMemoryBudget(1024 * 1024); // 1MB EXPECT_EQ(manager.GetMemoryBudget(), 1024u * 1024u); } TEST_F(ResourceManager_Test, GetMemoryUsage_Initial) { ResourceManager& manager = ResourceManager::Get(); EXPECT_EQ(manager.GetMemoryUsage(), 0u); } TEST_F(ResourceManager_Test, RegisterLoader) { ResourceManager& manager = ResourceManager::Get(); // 纹理加载器应该已经注册 IResourceLoader* loader = manager.GetLoader(ResourceType::Texture); EXPECT_NE(loader, nullptr); } TEST_F(ResourceManager_Test, GetLoader_NotFound) { ResourceManager& manager = ResourceManager::Get(); IResourceLoader* loader = manager.GetLoader(ResourceType::Unknown); EXPECT_EQ(loader, nullptr); } TEST_F(ResourceManager_Test, AddRef_Release) { ResourceGUID guid(12345); ResourceManager::Get().AddRef(guid); EXPECT_EQ(ResourceManager::Get().GetRefCount(guid), 1u); ResourceManager::Get().AddRef(guid); EXPECT_EQ(ResourceManager::Get().GetRefCount(guid), 2u); ResourceManager::Get().Release(guid); EXPECT_EQ(ResourceManager::Get().GetRefCount(guid), 1u); ResourceManager::Get().Release(guid); // 引用计数为0时应返回0 } TEST_F(ResourceManager_Test, UnloadAll) { ResourceManager& manager = ResourceManager::Get(); // 添加一些引用 ResourceGUID guid1(100); ResourceGUID guid2(200); manager.AddRef(guid1); manager.AddRef(guid2); manager.UnloadAll(); // 所有引用计数应被清除 EXPECT_EQ(manager.GetRefCount(guid1), 0u); EXPECT_EQ(manager.GetRefCount(guid2), 0u); } TEST_F(ResourceManager_Test, Exists) { ResourceManager& manager = ResourceManager::Get(); // 未加载的资源应返回false EXPECT_FALSE(manager.Exists("nonexistent.png")); } TEST_F(ResourceManager_Test, ResolvePath) { ResourceManager& manager = ResourceManager::Get(); manager.SetResourceRoot("myresources/"); Containers::String resolved = manager.ResolvePath("textures/test.png"); EXPECT_STREQ(resolved.CStr(), "myresources//textures/test.png"); } } // namespace ``` ### 12.8 测试运行脚本 **文件**: `tests/Resources/run_tests.bat` ```bat @echo off echo ======================================== echo XCEngine Resources System Tests echo ======================================== cd /d %~dp0\..\..\build if exist "tests\Resources\xcengine_resources_tests.exe" ( echo Running Resources tests... tests\Resources\xcengine_resources_tests.exe if %ERRORLEVEL% EQU 0 ( echo. echo [SUCCESS] All resource tests passed! ) else ( echo. echo [FAILED] Some tests failed! exit /b 1 ) ) else ( echo Error: Test executable not found! echo Please build the project first. exit /b 1 ) ``` ### 12.9 测试CMakeLists.txt **文件**: `tests/Resources/CMakeLists.txt` ```cmake # ============================================================ # Resources Library Tests # ============================================================ set(RESOURCES_TEST_SOURCES test_resource_types.cpp test_resource_guid.cpp test_iresource.cpp test_resource_handle.cpp test_resource_cache.cpp test_async_loader.cpp test_texture.cpp test_mesh.cpp test_material.cpp test_audio_clip.cpp test_resource_manager.cpp test_resource_filesystem.cpp test_resource_dependency.cpp ) add_executable(xcengine_resources_tests ${RESOURCES_TEST_SOURCES}) if(MSVC) set_target_properties(xcengine_resources_tests PROPERTIES LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib" ) endif() target_link_libraries(xcengine_resources_tests PRIVATE XCEngine GTest::gtest GTest::gtest_main ) target_include_directories(xcengine_resources_tests PRIVATE ${CMAKE_SOURCE_DIR}/engine/include ${CMAKE_SOURCE_DIR}/tests/fixtures ${CMAKE_SOURCE_DIR}/tests/Resources/fixtures ) # 复制测试数据到输出目录 file(COPY ${CMAKE_SOURCE_DIR}/tests/Resources/test_data DESTINATION ${CMAKE_BINARY_DIR}/tests/Resources/) add_test(NAME ResourcesTests COMMAND xcengine_resources_tests) ``` ### 12.10 阶段测试验收清单 每个阶段完成后,运行对应测试验证功能正确性: | 阶段 | 完成后运行测试 | 验收标准 | |------|---------------|----------| | **天1-3: 基础框架** | `test_resource_types.cpp`
`test_resource_guid.cpp`
`test_resource_handle.cpp` | ✅ ResourceType枚举正确
✅ GUID生成稳定
✅ 句柄引用计数正确 | | **天4-5: 缓存系统** | `test_resource_cache.cpp` | ✅ LRU淘汰正常
✅ 内存预算生效
✅ 内存压力响应 | | **天6-7: 异步加载** | `test_async_loader.cpp` | ✅ 任务提交正常
✅ 回调正常
✅ 进度计算正确 | | **天8-9: 纹理** | `test_texture.cpp` | ✅ 创建/设置属性正常
✅ 加载器识别格式
✅ 导入设置生效 | | **天10: 网格** | `test_mesh.cpp` | ✅ 顶点/索引数据正常
✅ 子网格正常
✅ 包围盒正常 | | **天11: 材质** | `test_material.cpp` | ✅ 属性设置/获取正常
✅ 类型转换正确 | | **天12-13: 文件系统** | `test_resource_filesystem.cpp` | ✅ 目录遍历正常
✅ 归档加载正常 | | **天14-15: 依赖管理** | `test_resource_dependency.cpp` | ✅ 依赖关系正确
✅ 循环检测正常 | | **天16-17: 集成** | 全部测试 | ✅ 所有测试通过
✅ 内存无泄漏 | ### 12.11 每日测试执行流程 ```bash # 每天完成代码后,在build目录执行: cd build # 编译测试 cmake --build . --target xcengine_resources_tests # 运行当天相关测试 ctest -R "Resources_类型名" -V # 例如: # 第3天完成后运行: ctest -R "Resources_Types|Resources_GUID|Resources_Handle" -V # 第5天完成后运行: ctest -R "Resources_Cache" -V # 全部测试通过后进行下一阶段 ``` ### 12.12 测试数据准备 需要在 `tests/Resources/test_data/` 目录下准备以下测试文件: ``` test_data/ ├── textures/ │ ├── test_256x256.png # 256x256 PNG测试纹理 │ ├── test_512x512.jpg # 512x512 JPG测试纹理 │ └── test_red.tga # 纯红色TGA ├── models/ │ ├── cube.obj # 简单立方体 │ ├── sphere.obj # 球体 │ └── plane.obj # 平面 ├── materials/ │ ├── test_material.json # 测试材质 │ └── test_pbr.json # PBR材质 ├── audio/ │ ├── test_mono.wav # 单声道WAV │ └── test_stereo.wav # 立体声WAV └── archives/ └── test.xcrap # 测试资源包 ``` --- ## 13. 关键技术点 ### 13.1 引用计数与缓存淘汰的交互 ```cpp // 引用计数管理 void ResourceManager::AddRef(ResourceGUID guid) { std::lock_guard lock(m_mutex); // 增加引用计数 auto it = m_refCounts.Find(guid); if (it == m_refCounts.End()) { m_refCounts.Insert(guid, 1); } else { it->second++; } // 确保资源在缓存中 if (!m_resourceCache.Contains(guid)) { // 重新加载资源 ReloadResource(guid); } } void ResourceManager::Release(ResourceGUID guid) { std::lock_guard lock(m_mutex); auto it = m_refCounts.Find(guid); if (it != m_refCounts.End()) { it->second--; // 引用计数为0时,不立即卸载 // 由缓存系统根据LRU策略决定何时卸载 if (it->second == 0) { m_refCounts.Erase(guid); // 通知缓存系统资源可以淘汰 m_cache.OnZeroRefCount(guid); } } } ``` ### 13.2 异步加载线程安全 ```cpp // AsyncLoader::Update() 在主线程调用 void AsyncLoader::Update() { // 1. 从完成队列取出结果(线程安全) Array completed; { std::lock_guard lock(m_completedMutex); completed = std::move(m_completedQueue); m_completedQueue.Clear(); } // 2. 在主线程处理完成回调 for (auto& request : completed) { if (request.callback) { // 回调中可能创建ResourceHandle,增加引用计数 request.callback(m_result); } } } // 工作线程函数 void LoadTask::Execute() { // 在工作线程执行实际加载 m_result = m_loader->Load(m_request.path, m_request.settings.get()); // 放入完成队列 AsyncLoader::Get().QueueCompleted(std::move(m_request), std::move(m_result)); } ``` ### 13.3 内存压力响应 ```cpp void ResourceCache::OnMemoryPressure(size_t requiredBytes) { std::lock_guard lock(m_mutex); if (m_memoryUsage + requiredBytes <= m_memoryBudget) { return; // 内存充足 } // 需要释放内存 size_t targetRelease = (m_memoryUsage + requiredBytes) - m_memoryBudget; // 按LRU顺序淘汰 for (auto it = m_lruOrder.Begin(); it != m_lruOrder.End() && targetRelease > 0; ) { ResourceGUID guid = *it; auto cacheIt = m_cache.Find(guid); if (cacheIt != m_cache.End()) { CacheEntry& entry = cacheIt->second; // 检查是否有外部引用 if (ResourceManager::Get().GetRefCount(guid) == 0) { size_t releasedSize = entry.memorySize; // 从缓存移除 m_cache.Erase(guid); m_memoryUsage -= releasedSize; targetRelease -= releasedSize; // 销毁资源 entry.resource->Release(); // 从LRU列表移除 it = m_lruOrder.Erase(it); } else { ++it; // 有外部引用,跳过 } } else { ++it; } } } ``` --- ## 14. 使用示例 ### 14.1 基本使用 ```cpp // 初始化资源系统 void InitResourceSystem() { // 设置资源根目录 ResourceManager::Get().SetResourceRoot("resources/"); // 初始化异步加载器(2个工作线程) AsyncLoader::Get().Initialize(2); // 设置内存预算(1GB) ResourceManager::Get().SetMemoryBudget(1024 * 1024 * 1024); } // 同步加载纹理 void LoadTextureExample() { // 加载纹理 ResourceHandle tex = ResourceManager::Get().Load("textures/player.png"); if (tex.IsValid()) { // 使用纹理 Texture* texture = tex.Get(); printf("Loaded texture: %s, %dx%d\n", tex->GetName().CStr(), texture->GetWidth(), texture->GetHeight()); // 传递给渲染器 renderer->SetTexture(0, texture->GetRHIResource()); } // 离开作用域时自动释放(引用计数-1) } // 异步加载网格 void LoadMeshAsyncExample() { ResourceManager::Get().LoadAsync("models/player.fbx", ResourceType::Mesh, [](LoadResult result) { if (result && result.resource) { // 在主线程回调中处理 // 注意:需要手动转换为正确的类型 Mesh* mesh = static_cast(result.resource); printf("Mesh loaded: %s\n", mesh->GetName().CStr()); // 添加到场景 // 注意:这里需要创建ResourceHandle来管理生命周期 ResourceHandle meshHandle(mesh); scene->AddMesh(meshHandle); } else { printf("Failed to load mesh: %s\n", result.errorMessage.CStr()); } }); // 异步加载进行中... // 主线程继续执行其他逻辑 } ``` ### 14.2 资源组加载 ```cpp // 加载场景所需的所有资源 void LoadSceneResources(const String& scenePath) { // 1. 加载场景配置文件 String sceneConfigPath = "scenes/" + scenePath + ".scene"; ResourceHandle sceneConfig = ResourceManager::Get().Load(sceneConfigPath); // 2. 解析配置文件获取依赖列表 Array dependencies = ParseSceneDependencies(sceneConfig.Get()); // 3. 异步加载所有依赖 Core::uint32 totalResources = dependencies.Size(); Core::uint32 loadedCount = 0; for (const auto& dep : dependencies) { // 根据扩展名判断资源类型 String ext = Path::GetExtension(dep); ResourceType type = ResourceType::Binary; // 默认 if (ext == ".png" || ext == ".jpg") { type = ResourceType::Texture; } else if (ext == ".fbx" || ext == ".obj") { type = ResourceType::Mesh; } // ... 其他类型 ResourceManager::Get().LoadAsync(dep, type, [&loadedCount, totalResources](LoadResult result) { loadedCount++; printf("Loading progress: %d/%d\n", loadedCount, totalResources); }); } } ``` ### 14.3 自定义导入设置 ```cpp void LoadTextureWithSettings() { // 创建导入设置 TextureImportSettings* settings = new TextureImportSettings(); settings->SetGenerateMipmaps(true); settings->SetSRGB(true); settings->SetTargetFormat(TextureFormat::BC7_UNORM); settings->SetMaxAnisotropy(16); // 加载时传入设置 ResourceHandle tex = ResourceManager::Get().Load( "textures/diffuse.png", settings); // settings由ResourceManager管理,不需要手动delete } ``` --- ## 15. 验收标准 ### 13.1 功能验收 - [ ] 能够通过路径加载Texture资源(PNG, JPG, TGA) - [ ] 能够通过路径加载Mesh资源(OBJ, glTF) - [ ] 能够通过路径加载Material资源(JSON格式) - [ ] 能够通过路径加载AudioClip资源(WAV格式) - [ ] 异步加载功能正常工作,回调在主线程执行 - [ ] 资源缓存机制正常工作,LRU淘汰策略生效 - [ ] 引用计数正确管理,零引用时资源可被淘汰 - [ ] 内存预算控制正常工作,超出预算时自动淘汰资源 ### 13.2 性能验收 - [ ] 缓存命中时资源加载时间为0 - [ ] 异步加载不阻塞主线程 - [ ] 多线程加载时无数据竞争 - [ ] 内存使用稳定,无内存泄漏 ### 13.3 代码质量 - [ ] 编译通过,无警告 - [ ] 遵循项目编码规范 - [ ] 必要的注释和文档 - [ ] 错误处理完善 --- ## 16. 后续扩展 ### 14.1 可选的扩展功能 | 功能 | 说明 | 工作量 | |------|------|--------| | **资源热加载** | 编辑器中修改资源后实时更新 | 2天 | | **资源版本控制** | 检测资源变化,自动重新加载 | 2天 | | **资源加密** | 支持加密的资源包 | 1天 | | **资源流式加载** | 大资源分块加载 | 3天 | | **资源分析器** | 可视化资源使用情况 | 2天 | ### 14.2 资源格式扩展 | 格式 | 说明 | 优先级 | |------|------|--------| | glTF 2.0 | 现代3D模型格式,高优先级 | P0 | | DDS | 压缩纹理格式 | P0 | | OGG/Vorbis | 压缩音频格式 | P1 | | PSD | Photoshop源文件 | P2 | | EXR | HDR图像 | P2 | --- ## 17. 依赖关系 ``` 资源系统依赖关系图: 第一阶段基础层 │ ├── Math Library ─────┐ │ │ ├── Containers ──────┼──► 资源系统 │ │ ├── Memory ────────┤ │ │ └── Threading ─────────┘ │ ▼ ┌─────────────────┐ │ AsyncLoader │ (依赖TaskSystem) └────────┬────────┘ │ ┌────────▼────────┐ │ ResourceManager │ └────────┬────────┘ │ ┌────────▼────────┐ ┌─────────────┐ │ ResourceCache │────►│ 引用计数 │ └────────┬────────┘ │ 管理 │ │ └─────────────┘ ┌────────▼────────┐ │ ResourceLoader │ └────────┬────────┘ │ ┌────────▼────────┐ │ 具体资源类型 │ │ (Texture等) │ └─────────────────┘ ``` --- *文档版本: 1.0* *更新日期: 2026-03-17*