feat(Resources): 添加资源系统基础框架

- ResourceTypes: 资源类型枚举、ResourceGUID生成
- IResource: 资源基类接口
- ResourceHandle: 资源句柄智能指针
- IResourceLoader: 加载器接口
- ResourceManager: 资源管理器(单例模式)
- ResourceCache: LRU缓存实现
- AsyncLoader: 异步加载器
- 测试框架: test_resource_types, test_resource_guid

Note: 当前与现有容器API存在编译差异,需要后续修复
This commit is contained in:
2026-03-17 19:38:27 +08:00
parent e138fb2075
commit 94bf04f06c
15 changed files with 1160 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
#pragma once
#include "IResourceLoader.h"
#include "../Containers/Array.h"
#include "../Threading/Mutex.h"
#include <atomic>
#include <functional>
namespace XCEngine {
namespace Resources {
struct LoadRequest {
Containers::String path;
ResourceType type;
std::function<void(LoadResult)> callback;
Core::UniqueRef<ImportSettings> settings;
Core::uint64 requestId;
LoadRequest() : requestId(0) {}
LoadRequest(const Containers::String& p, ResourceType t,
std::function<void(LoadResult)> cb, ImportSettings* s = nullptr)
: path(p), type(t), callback(std::move(cb)),
settings(s), requestId(GenerateRequestId()) {}
private:
static Core::uint64 GenerateRequestId();
};
class AsyncLoader {
public:
static AsyncLoader& Get();
void Initialize(Core::uint32 workerThreadCount = 2);
void Shutdown();
void Submit(const Containers::String& path, ResourceType type,
std::function<void(LoadResult)> callback);
void Submit(const Containers::String& path, ResourceType type, ImportSettings* settings,
std::function<void(LoadResult)> 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;
void QueueCompleted(LoadRequest request, LoadResult result);
Threading::Mutex m_queueMutex;
Containers::Array<LoadRequest> m_pendingQueue;
Threading::Mutex m_completedMutex;
Containers::Array<LoadRequest> m_completedQueue;
std::atomic<Core::uint32> m_pendingCount{0};
std::atomic<Core::uint32> m_completedCount{0};
Core::uint32 m_totalRequested = 0;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,47 @@
#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

View File

@@ -0,0 +1,61 @@
#pragma once
#include "IResource.h"
#include "ResourceTypes.h"
#include "../Containers/String.h"
#include "../Containers/Array.h"
#include <functional>
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 inSuccess, const Containers::String& error = "")
: success(inSuccess), errorMessage(error) {}
operator bool() const { return success && resource != nullptr; }
};
class IResourceLoader {
public:
virtual ~IResourceLoader() = default;
virtual ResourceType GetResourceType() const = 0;
virtual Containers::Array<Containers::String> 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<void(LoadResult)> callback) {
LoadResult result = Load(path, settings);
if (callback) {
callback(result);
}
}
virtual ImportSettings* GetDefaultSettings() const = 0;
protected:
static Containers::Array<Core::uint8> 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

View File

@@ -0,0 +1,64 @@
#pragma once
#include "ResourceTypes.h"
#include "../Containers/HashMap.h"
#include "../Containers/Array.h"
#include "../Threading/Mutex.h"
#include <algorithm>
namespace XCEngine {
namespace Resources {
class IResource;
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);
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<ResourceGUID> GetLRUList(size_t count) const;
private:
void Evict(size_t requiredBytes);
void UpdateMemoryStats();
Containers::HashMap<ResourceGUID, CacheEntry> m_cache;
Containers::Array<ResourceGUID> m_lruOrder;
size_t m_memoryUsage = 0;
size_t m_memoryBudget = 512 * 1024 * 1024;
Threading::Mutex m_mutex;
mutable Threading::Mutex m_cacheMutex;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,104 @@
#pragma once
#include "IResource.h"
#include <type_traits>
#include <functional>
namespace XCEngine {
namespace Resources {
// Forward declaration
class ResourceManager;
template<typename T>
class ResourceHandle {
static_assert(std::is_base_of_v<IResource, T>, "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<typename T>
bool operator==(const ResourceHandle<T>& lhs, const ResourceHandle<T>& rhs) {
return lhs.GetGUID() == rhs.GetGUID();
}
template<typename T>
bool operator!=(const ResourceHandle<T>& lhs, const ResourceHandle<T>& rhs) {
return !(lhs == rhs);
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,114 @@
#pragma once
#include "IResourceLoader.h"
#include "ResourceCache.h"
#include "AsyncLoader.h"
#include "ResourceHandle.h"
#include "../Containers/String.h"
#include "../Containers/Array.h"
#include "../Containers/HashMap.h"
#include "../Threading/Mutex.h"
#include "../Debug/Logger.h"
#include <type_traits>
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<typename T>
ResourceHandle<T> Load(const Containers::String& path, ImportSettings* settings = nullptr) {
static_assert(std::is_base_of_v<IResource, T>, "T must derive from IResource");
ResourceGUID guid = ResourceGUID::Generate(path);
IResource* cached = FindInCache(guid);
if (cached) {
return ResourceHandle<T>(static_cast<T*>(cached));
}
IResourceLoader* loader = FindLoader(GetResourceType<T>());
if (!loader) {
Debug::Logger::Get().Warning(Debug::LogCategory::FileSystem,
Containers::String("No loader found for resource type: ") +
GetResourceTypeName(GetResourceType<T>()));
return ResourceHandle<T>();
}
LoadResult result = loader->Load(path, settings);
if (!result) {
Debug::Logger::Get().Error(Debug::LogCategory::FileSystem,
Containers::String("Failed to load resource: ") + path + " - " + result.errorMessage);
return ResourceHandle<T>();
}
AddToCache(guid, result.resource);
return ResourceHandle<T>(static_cast<T*>(result.resource));
}
void LoadAsync(const Containers::String& path, ResourceType type,
std::function<void(LoadResult)> callback);
void LoadAsync(const Containers::String& path, ResourceType type, ImportSettings* settings,
std::function<void(LoadResult)> 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<ResourceGUID, IResource*> m_resourceCache;
Containers::HashMap<ResourceGUID, Core::uint32> m_refCounts;
Containers::HashMap<ResourceGUID, Containers::String> m_guidToPath;
Containers::HashMap<ResourceType, Core::UniqueRef<IResourceLoader>> m_loaders;
size_t m_memoryUsage = 0;
size_t m_memoryBudget = 512 * 1024 * 1024;
ResourceCache m_cache;
Core::UniqueRef<AsyncLoader> m_asyncLoader;
Threading::Mutex m_mutex;
friend class ResourceHandleBase;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,87 @@
#pragma once
#include "../Core/Types.h"
#include "../Core/SmartPtr.h"
#include "../Containers/String.h"
#include "../Containers/Array.h"
#include <cstdint>
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";
}
}
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);
}
template<typename T>
ResourceType GetResourceType();
template<> inline ResourceType GetResourceType<class Texture>() { return ResourceType::Texture; }
template<> inline ResourceType GetResourceType<class Mesh>() { return ResourceType::Mesh; }
template<> inline ResourceType GetResourceType<class Material>() { return ResourceType::Material; }
template<> inline ResourceType GetResourceType<class Shader>() { return ResourceType::Shader; }
template<> inline ResourceType GetResourceType<class AudioClip>() { return ResourceType::AudioClip; }
template<> inline ResourceType GetResourceType<class BinaryResource>() { return ResourceType::Binary; }
class ImportSettings {
public:
virtual ~ImportSettings() = default;
virtual Core::UniqueRef<ImportSettings> Clone() const = 0;
virtual bool LoadFromJSON(const Containers::String& json) { return false; }
virtual Containers::String SaveToJSON() const { return Containers::String(); }
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,24 @@
#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"
// Forward declarations for concrete resource types
namespace XCEngine {
namespace Resources {
class Texture;
class Mesh;
class Material;
class Shader;
class AudioClip;
class BinaryResource;
} // namespace Resources
} // namespace XCEngine