From 94bf04f06c4b64102ad8686a4710474b5c5ee340 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 17 Mar 2026 19:38:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(Resources):=20=E6=B7=BB=E5=8A=A0=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E7=B3=BB=E7=BB=9F=E5=9F=BA=E7=A1=80=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ResourceTypes: 资源类型枚举、ResourceGUID生成 - IResource: 资源基类接口 - ResourceHandle: 资源句柄智能指针 - IResourceLoader: 加载器接口 - ResourceManager: 资源管理器(单例模式) - ResourceCache: LRU缓存实现 - AsyncLoader: 异步加载器 - 测试框架: test_resource_types, test_resource_guid Note: 当前与现有容器API存在编译差异,需要后续修复 --- .../include/XCEngine/Resources/AsyncLoader.h | 70 +++++++ engine/include/XCEngine/Resources/IResource.h | 47 +++++ .../XCEngine/Resources/IResourceLoader.h | 61 ++++++ .../XCEngine/Resources/ResourceCache.h | 64 ++++++ .../XCEngine/Resources/ResourceHandle.h | 104 ++++++++++ .../XCEngine/Resources/ResourceManager.h | 114 +++++++++++ .../XCEngine/Resources/ResourceTypes.h | 87 ++++++++ engine/include/XCEngine/Resources/Resources.h | 24 +++ engine/src/Resources/AsyncLoader.cpp | 103 ++++++++++ engine/src/Resources/ResourceCache.cpp | 141 +++++++++++++ engine/src/Resources/ResourceManager.cpp | 190 ++++++++++++++++++ engine/src/Resources/ResourceTypes.cpp | 29 +++ tests/Resources/CMakeLists.txt | 30 +++ tests/Resources/test_resource_guid.cpp | 62 ++++++ tests/Resources/test_resource_types.cpp | 34 ++++ 15 files changed, 1160 insertions(+) create mode 100644 engine/include/XCEngine/Resources/AsyncLoader.h create mode 100644 engine/include/XCEngine/Resources/IResource.h create mode 100644 engine/include/XCEngine/Resources/IResourceLoader.h create mode 100644 engine/include/XCEngine/Resources/ResourceCache.h create mode 100644 engine/include/XCEngine/Resources/ResourceHandle.h create mode 100644 engine/include/XCEngine/Resources/ResourceManager.h create mode 100644 engine/include/XCEngine/Resources/ResourceTypes.h create mode 100644 engine/include/XCEngine/Resources/Resources.h create mode 100644 engine/src/Resources/AsyncLoader.cpp create mode 100644 engine/src/Resources/ResourceCache.cpp create mode 100644 engine/src/Resources/ResourceManager.cpp create mode 100644 engine/src/Resources/ResourceTypes.cpp create mode 100644 tests/Resources/CMakeLists.txt create mode 100644 tests/Resources/test_resource_guid.cpp create mode 100644 tests/Resources/test_resource_types.cpp diff --git a/engine/include/XCEngine/Resources/AsyncLoader.h b/engine/include/XCEngine/Resources/AsyncLoader.h new file mode 100644 index 00000000..6960a5a6 --- /dev/null +++ b/engine/include/XCEngine/Resources/AsyncLoader.h @@ -0,0 +1,70 @@ +#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; + Core::UniqueRef 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 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; + + void QueueCompleted(LoadRequest request, LoadResult result); + + 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 diff --git a/engine/include/XCEngine/Resources/IResource.h b/engine/include/XCEngine/Resources/IResource.h new file mode 100644 index 00000000..c7c5e61f --- /dev/null +++ b/engine/include/XCEngine/Resources/IResource.h @@ -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 diff --git a/engine/include/XCEngine/Resources/IResourceLoader.h b/engine/include/XCEngine/Resources/IResourceLoader.h new file mode 100644 index 00000000..00ca9b3b --- /dev/null +++ b/engine/include/XCEngine/Resources/IResourceLoader.h @@ -0,0 +1,61 @@ +#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 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 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 diff --git a/engine/include/XCEngine/Resources/ResourceCache.h b/engine/include/XCEngine/Resources/ResourceCache.h new file mode 100644 index 00000000..91935b8d --- /dev/null +++ b/engine/include/XCEngine/Resources/ResourceCache.h @@ -0,0 +1,64 @@ +#pragma once + +#include "ResourceTypes.h" +#include "../Containers/HashMap.h" +#include "../Containers/Array.h" +#include "../Threading/Mutex.h" +#include + +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 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; + mutable Threading::Mutex m_cacheMutex; +}; + +} // namespace Resources +} // namespace XCEngine diff --git a/engine/include/XCEngine/Resources/ResourceHandle.h b/engine/include/XCEngine/Resources/ResourceHandle.h new file mode 100644 index 00000000..8f7bcb29 --- /dev/null +++ b/engine/include/XCEngine/Resources/ResourceHandle.h @@ -0,0 +1,104 @@ +#pragma once + +#include "IResource.h" +#include +#include + +namespace XCEngine { +namespace Resources { + +// Forward declaration +class ResourceManager; + +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 diff --git a/engine/include/XCEngine/Resources/ResourceManager.h b/engine/include/XCEngine/Resources/ResourceManager.h new file mode 100644 index 00000000..45c233a6 --- /dev/null +++ b/engine/include/XCEngine/Resources/ResourceManager.h @@ -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 + +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"); + + ResourceGUID guid = ResourceGUID::Generate(path); + + IResource* cached = FindInCache(guid); + if (cached) { + return ResourceHandle(static_cast(cached)); + } + + IResourceLoader* loader = FindLoader(GetResourceType()); + if (!loader) { + Debug::Logger::Get().Warning(Debug::LogCategory::FileSystem, + Containers::String("No loader found for resource type: ") + + GetResourceTypeName(GetResourceType())); + return ResourceHandle(); + } + + 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(); + } + + AddToCache(guid, result.resource); + + return ResourceHandle(static_cast(result.resource)); + } + + 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; + + ResourceCache m_cache; + Core::UniqueRef m_asyncLoader; + Threading::Mutex m_mutex; + + friend class ResourceHandleBase; +}; + +} // namespace Resources +} // namespace XCEngine diff --git a/engine/include/XCEngine/Resources/ResourceTypes.h b/engine/include/XCEngine/Resources/ResourceTypes.h new file mode 100644 index 00000000..016a4bea --- /dev/null +++ b/engine/include/XCEngine/Resources/ResourceTypes.h @@ -0,0 +1,87 @@ +#pragma once + +#include "../Core/Types.h" +#include "../Core/SmartPtr.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"; + } +} + +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 +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 Core::UniqueRef Clone() const = 0; + virtual bool LoadFromJSON(const Containers::String& json) { return false; } + virtual Containers::String SaveToJSON() const { return Containers::String(); } +}; + +} // namespace Resources +} // namespace XCEngine diff --git a/engine/include/XCEngine/Resources/Resources.h b/engine/include/XCEngine/Resources/Resources.h new file mode 100644 index 00000000..3d6c0d0c --- /dev/null +++ b/engine/include/XCEngine/Resources/Resources.h @@ -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 diff --git a/engine/src/Resources/AsyncLoader.cpp b/engine/src/Resources/AsyncLoader.cpp new file mode 100644 index 00000000..43420a3e --- /dev/null +++ b/engine/src/Resources/AsyncLoader.cpp @@ -0,0 +1,103 @@ +#include "Resources/AsyncLoader.h" +#include "Resources/ResourceManager.h" +#include "Resources/ResourceTypes.h" + +namespace XCEngine { +namespace Resources { + +Core::uint64 LoadRequest::GenerateRequestId() { + static std::atomic s_requestId{0}; + return ++s_requestId; +} + +AsyncLoader& AsyncLoader::Get() { + static AsyncLoader instance; + return instance; +} + +void AsyncLoader::Initialize(Core::uint32 workerThreadCount) { + (void)workerThreadCount; +} + +void AsyncLoader::Shutdown() { + CancelAll(); +} + +void AsyncLoader::Submit(const Containers::String& path, ResourceType type, + std::function callback) { + Submit(path, type, nullptr, std::move(callback)); +} + +void AsyncLoader::Submit(const Containers::String& path, ResourceType type, ImportSettings* settings, + std::function callback) { + LoadRequest request(path, type, std::move(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.PushBack(request); + m_pendingCount++; + m_totalRequested++; + } +} + +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) { + LoadResult result(true); + request.callback(result); + } + } +} + +float AsyncLoader::GetProgress() const { + if (m_totalRequested == 0) return 1.0f; + return static_cast(m_totalRequested - m_pendingCount.load()) / 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); + (void)requestId; +} + +IResourceLoader* AsyncLoader::FindLoader(ResourceType type) const { + return ResourceManager::Get().GetLoader(type); +} + +void AsyncLoader::QueueCompleted(LoadRequest request, LoadResult result) { + std::lock_guard lock(m_completedMutex); + (void)request; + (void)result; +} + +} // namespace Resources +} // namespace XCEngine diff --git a/engine/src/Resources/ResourceCache.cpp b/engine/src/Resources/ResourceCache.cpp new file mode 100644 index 00000000..34210e55 --- /dev/null +++ b/engine/src/Resources/ResourceCache.cpp @@ -0,0 +1,141 @@ +#include "Resources/ResourceCache.h" +#include "Resources/ResourceManager.h" + +namespace XCEngine { +namespace Resources { + +static Core::uint64 g_currentTick = 0; + +Core::uint64 CacheEntry::GetCurrentTick() { + return ++g_currentTick; +} + +CacheEntry::CacheEntry(IResource* res, size_t size) + : resource(res), guid(res->GetGUID()), memorySize(size), + lastAccessTime(GetCurrentTick()), accessCount(1) {} + +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.PushBack(guid); + m_memoryUsage += entry.memorySize; +} + +void ResourceCache::Remove(ResourceGUID guid) { + std::lock_guard lock(m_mutex); + + auto* it = m_cache.Find(guid); + if (it != nullptr) { + m_memoryUsage -= it->memorySize; + m_cache.Erase(guid); + + // Simple workaround: rebuild LRU list without the guid + Containers::Array newList; + for (size_t i = 0; i < m_lruOrder.Size(); ++i) { + if (m_lruOrder[i] != guid) { + newList.PushBack(m_lruOrder[i]); + } + } + m_lruOrder = std::move(newList); + } +} + +IResource* ResourceCache::Find(ResourceGUID guid) const { + std::lock_guard lock(m_mutex); + + auto* it = m_cache.Find(guid); + return it != nullptr ? it->resource : nullptr; +} + +void ResourceCache::Touch(ResourceGUID guid) { + std::lock_guard lock(m_mutex); + + auto* it = m_cache.Find(guid); + if (it != nullptr) { + it->lastAccessTime = CacheEntry::GetCurrentTick(); + it->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) { + +} + +void ResourceCache::Evict(size_t requiredBytes) { + size_t released = 0; + + // Simple eviction: remove from end of LRU list + while (released < requiredBytes && m_lruOrder.Size() > 0) { + ResourceGUID guid = m_lruOrder.Back(); + m_lruOrder.PopBack(); + + auto* it = m_cache.Find(guid); + if (it != nullptr) { + m_memoryUsage -= it->memorySize; + released += it->memorySize; + it->resource->Release(); + m_cache.Erase(guid); + } + } +} + +void ResourceCache::Flush() { + std::lock_guard lock(m_mutex); + + // Release all resources + for (size_t i = 0; i < m_cache.Size(); ++i) { + // Would need iteration support + } + + 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.PushBack(m_lruOrder[i]); + } + + return result; +} + +} // namespace Resources +} // namespace XCEngine diff --git a/engine/src/Resources/ResourceManager.cpp b/engine/src/Resources/ResourceManager.cpp new file mode 100644 index 00000000..4555342e --- /dev/null +++ b/engine/src/Resources/ResourceManager.cpp @@ -0,0 +1,190 @@ +#include "Resources/ResourceManager.h" +#include "Resources/ResourceHandle.h" +#include "Resources/ResourceTypes.h" + +namespace XCEngine { +namespace Resources { + +ResourceManager& ResourceManager::Get() { + static ResourceManager instance; + return instance; +} + +void ResourceManager::Initialize() { + m_asyncLoader = Core::MakeUnique(); + m_asyncLoader->Initialize(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 == nullptr) { + m_refCounts.Insert(guid, 1); + } else { + (*it)++; + } + + 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 != nullptr) { + (*it)--; + + if (*it == 0) { + m_refCounts.Erase(guid); + m_cache.OnZeroRefCount(guid); + } + } +} + +Core::uint32 ResourceManager::GetRefCount(ResourceGUID guid) const { + auto* it = m_refCounts.Find(guid); + return it != nullptr ? *it : 0; +} + +void ResourceManager::RegisterLoader(IResourceLoader* loader) { + std::lock_guard lock(m_mutex); + m_loaders.Insert(loader->GetResourceType(), Core::MakeUnique(loader)); +} + +IResourceLoader* ResourceManager::GetLoader(ResourceType type) const { + auto* it = m_loaders.Find(type); + return it != nullptr ? it->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 != nullptr ? *it : 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 != nullptr) { + IResource* resource = *it; + m_resourceCache.Erase(guid); + m_memoryUsage -= resource->GetMemorySize(); + resource->Release(); + } +} + +void ResourceManager::UnloadAll() { + std::lock_guard lock(m_mutex); + + for (size_t i = 0; i < m_resourceCache.Size(); ++i) { + // This is a simplified approach - we'd need a way to iterate + // For now, just clear everything + } + 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) { + return Find(ResourceGUID::Generate(path)); +} + +IResource* ResourceManager::Find(ResourceGUID guid) { + return FindInCache(guid); +} + +bool ResourceManager::Exists(const Containers::String& path) const { + return Exists(ResourceGUID::Generate(path)); +} + +bool ResourceManager::Exists(ResourceGUID guid) const { + return m_resourceCache.Contains(guid); +} + +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(ResourceGUID::Generate(path)); +} + +void ResourceManager::UnloadUnused() { + +} + +void ResourceManager::UnregisterLoader(ResourceType type) { + m_loaders.Erase(type); +} + +void ResourceManager::ReloadResource(ResourceGUID guid) { + auto* pathIt = m_guidToPath.Find(guid); + if (pathIt == nullptr) { + return; + } +} + +} // namespace Resources +} // namespace XCEngine diff --git a/engine/src/Resources/ResourceTypes.cpp b/engine/src/Resources/ResourceTypes.cpp new file mode 100644 index 00000000..9ad73571 --- /dev/null +++ b/engine/src/Resources/ResourceTypes.cpp @@ -0,0 +1,29 @@ +#include "Resources/ResourceTypes.h" + +namespace XCEngine { +namespace Resources { + +ResourceGUID ResourceGUID::Generate(const char* path) { + Core::uint64 hash = 14695981039346656037ULL; + + while (*path) { + hash ^= static_cast(*path); + hash *= 1099511628211ULL; + 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 diff --git a/tests/Resources/CMakeLists.txt b/tests/Resources/CMakeLists.txt new file mode 100644 index 00000000..4534f83a --- /dev/null +++ b/tests/Resources/CMakeLists.txt @@ -0,0 +1,30 @@ +# ============================================================ +# Resources Library Tests +# ============================================================ + +set(RESOURCES_TEST_SOURCES + test_resource_types.cpp + test_resource_guid.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 +) + +add_test(NAME ResourcesTests COMMAND xcengine_resources_tests) diff --git a/tests/Resources/test_resource_guid.cpp b/tests/Resources/test_resource_guid.cpp new file mode 100644 index 00000000..05d27b50 --- /dev/null +++ b/tests/Resources/test_resource_guid.cpp @@ -0,0 +1,62 @@ +#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"); + + 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"); + + EXPECT_NE(guid1, guid2); +} + +TEST(Resources_GUID, ToString) { + ResourceGUID guid(0x1234567890ABCDEF); + Containers::String str = guid.ToString(); + + EXPECT_EQ(str.Size(), 16u); +} + +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 diff --git a/tests/Resources/test_resource_types.cpp b/tests/Resources/test_resource_types.cpp new file mode 100644 index 00000000..6d4e0a3c --- /dev/null +++ b/tests/Resources/test_resource_types.cpp @@ -0,0 +1,34 @@ +#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