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

View File

@@ -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<Core::uint64> 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<void(LoadResult)> callback) {
Submit(path, type, nullptr, std::move(callback));
}
void AsyncLoader::Submit(const Containers::String& path, ResourceType type, ImportSettings* settings,
std::function<void(LoadResult)> 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<LoadRequest> 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<float>(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

View File

@@ -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<ResourceGUID> 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<ResourceGUID> ResourceCache::GetLRUList(size_t count) const {
std::lock_guard lock(m_mutex);
Containers::Array<ResourceGUID> result;
size_t n = std::min(count, static_cast<size_t>(m_lruOrder.Size()));
for (size_t i = 0; i < n; i++) {
result.PushBack(m_lruOrder[i]);
}
return result;
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -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<AsyncLoader>();
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<IResourceLoader>(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<void(LoadResult)> callback) {
LoadAsync(path, type, nullptr, callback);
}
void ResourceManager::LoadAsync(const Containers::String& path, ResourceType type,
ImportSettings* settings,
std::function<void(LoadResult)> 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

View File

@@ -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<Core::uint64>(*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

View File

@@ -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)

View File

@@ -0,0 +1,62 @@
#include <gtest/gtest.h>
#include <XCEngine/Resources/ResourceTypes.h>
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

View File

@@ -0,0 +1,34 @@
#include <gtest/gtest.h>
#include <XCEngine/Resources/ResourceTypes.h>
using namespace XCEngine::Resources;
namespace {
TEST(Resources_Types, ResourceType_EnumValues) {
EXPECT_EQ(static_cast<Core::uint8>(ResourceType::Unknown), 0);
EXPECT_EQ(static_cast<Core::uint8>(ResourceType::Texture), 1);
EXPECT_EQ(static_cast<Core::uint8>(ResourceType::Mesh), 2);
EXPECT_EQ(static_cast<Core::uint8>(ResourceType::Material), 3);
EXPECT_EQ(static_cast<Core::uint8>(ResourceType::Shader), 4);
EXPECT_EQ(static_cast<Core::uint8>(ResourceType::AudioClip), 5);
EXPECT_EQ(static_cast<Core::uint8>(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<Texture>(), ResourceType::Texture);
EXPECT_EQ(GetResourceType<Mesh>(), ResourceType::Mesh);
EXPECT_EQ(GetResourceType<Material>(), ResourceType::Material);
EXPECT_EQ(GetResourceType<Shader>(), ResourceType::Shader);
EXPECT_EQ(GetResourceType<AudioClip>(), ResourceType::AudioClip);
EXPECT_EQ(GetResourceType<BinaryResource>(), ResourceType::Binary);
}
} // namespace