Implement initial Unity-style asset library cache
This commit is contained in:
1089
engine/src/Core/Asset/AssetDatabase.cpp
Normal file
1089
engine/src/Core/Asset/AssetDatabase.cpp
Normal file
File diff suppressed because it is too large
Load Diff
114
engine/src/Core/Asset/AssetGUID.cpp
Normal file
114
engine/src/Core/Asset/AssetGUID.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include <XCEngine/Core/Asset/AssetGUID.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <random>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
Core::uint64 FNV1a64(const Core::uint8* bytes, size_t size, Core::uint64 seed) {
|
||||
Core::uint64 hash = seed;
|
||||
for (size_t index = 0; index < size; ++index) {
|
||||
hash ^= static_cast<Core::uint64>(bytes[index]);
|
||||
hash *= 1099511628211ULL;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
int HexDigitToInt(char ch) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
return ch - '0';
|
||||
}
|
||||
if (ch >= 'a' && ch <= 'f') {
|
||||
return 10 + (ch - 'a');
|
||||
}
|
||||
if (ch >= 'A' && ch <= 'F') {
|
||||
return 10 + (ch - 'A');
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ParseHex64(const char* text, Core::uint64& outValue) {
|
||||
outValue = 0;
|
||||
for (int index = 0; index < 16; ++index) {
|
||||
const int digit = HexDigitToInt(text[index]);
|
||||
if (digit < 0) {
|
||||
return false;
|
||||
}
|
||||
outValue = (outValue << 4) | static_cast<Core::uint64>(digit);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AssetGUID AssetGUID::Generate() {
|
||||
static std::random_device rd;
|
||||
static std::mt19937_64 generator(rd());
|
||||
|
||||
AssetGUID guid;
|
||||
do {
|
||||
guid.high = generator();
|
||||
guid.low = generator();
|
||||
} while (!guid.IsValid());
|
||||
|
||||
return guid;
|
||||
}
|
||||
|
||||
bool AssetGUID::TryParse(const Containers::String& text, AssetGUID& outGuid) {
|
||||
if (text.Length() != 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::uint64 highValue = 0;
|
||||
Core::uint64 lowValue = 0;
|
||||
if (!ParseHex64(text.CStr(), highValue) ||
|
||||
!ParseHex64(text.CStr() + 16, lowValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outGuid = AssetGUID(highValue, lowValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
AssetGUID AssetGUID::ParseOrDefault(const Containers::String& text) {
|
||||
AssetGUID guid;
|
||||
if (TryParse(text, guid)) {
|
||||
return guid;
|
||||
}
|
||||
return AssetGUID();
|
||||
}
|
||||
|
||||
Containers::String AssetGUID::ToString() const {
|
||||
char buffer[33] = {};
|
||||
#if defined(_MSC_VER)
|
||||
std::snprintf(buffer, sizeof(buffer), "%016llx%016llx",
|
||||
static_cast<unsigned long long>(high),
|
||||
static_cast<unsigned long long>(low));
|
||||
#else
|
||||
std::snprintf(buffer, sizeof(buffer), "%016lx%016lx",
|
||||
static_cast<unsigned long>(high),
|
||||
static_cast<unsigned long>(low));
|
||||
#endif
|
||||
return Containers::String(buffer);
|
||||
}
|
||||
|
||||
AssetGUID HashBytesToAssetGUID(const void* data, size_t size) {
|
||||
if (data == nullptr || size == 0) {
|
||||
return AssetGUID();
|
||||
}
|
||||
|
||||
const auto* bytes = static_cast<const Core::uint8*>(data);
|
||||
const Core::uint64 highHash = FNV1a64(bytes, size, 14695981039346656037ULL);
|
||||
const Core::uint64 lowHash = FNV1a64(bytes, size, 1099511628211ULL ^ 0x9e3779b97f4a7c15ULL);
|
||||
return AssetGUID(highHash, lowHash);
|
||||
}
|
||||
|
||||
AssetGUID HashStringToAssetGUID(const Containers::String& text) {
|
||||
return HashBytesToAssetGUID(text.CStr(), text.Length());
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Core/IO/ResourceFileSystem.h>
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
@@ -31,6 +32,10 @@ ResourceManager& ResourceManager::Get() {
|
||||
}
|
||||
|
||||
void ResourceManager::Initialize() {
|
||||
if (m_asyncLoader) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_asyncLoader = Core::MakeUnique<AsyncLoader>();
|
||||
m_asyncLoader->Initialize(2);
|
||||
|
||||
@@ -42,12 +47,23 @@ void ResourceManager::Initialize() {
|
||||
|
||||
void ResourceManager::Shutdown() {
|
||||
UnloadAll();
|
||||
m_asyncLoader->Shutdown();
|
||||
m_asyncLoader.reset();
|
||||
if (m_asyncLoader) {
|
||||
m_asyncLoader->Shutdown();
|
||||
m_asyncLoader.reset();
|
||||
}
|
||||
m_assetDatabase.Shutdown();
|
||||
ResourceFileSystem::Get().Shutdown();
|
||||
}
|
||||
|
||||
void ResourceManager::SetResourceRoot(const Containers::String& rootPath) {
|
||||
m_resourceRoot = rootPath;
|
||||
if (!m_resourceRoot.Empty()) {
|
||||
ResourceFileSystem::Get().Initialize(rootPath);
|
||||
m_assetDatabase.Initialize(rootPath);
|
||||
} else {
|
||||
ResourceFileSystem::Get().Shutdown();
|
||||
m_assetDatabase.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
const Containers::String& ResourceManager::GetResourceRoot() const {
|
||||
@@ -111,7 +127,8 @@ IResource* ResourceManager::FindInCache(ResourceGUID guid) {
|
||||
|
||||
void ResourceManager::AddToCache(ResourceGUID guid, IResource* resource) {
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
|
||||
resource->m_guid = guid;
|
||||
m_resourceCache.Insert(guid, resource);
|
||||
m_memoryUsage += resource->GetMemorySize();
|
||||
m_cache.Add(guid, resource);
|
||||
@@ -128,6 +145,7 @@ void ResourceManager::Unload(ResourceGUID guid) {
|
||||
if (it != nullptr) {
|
||||
IResource* resource = *it;
|
||||
m_resourceCache.Erase(guid);
|
||||
m_guidToPath.Erase(guid);
|
||||
m_memoryUsage -= resource->GetMemorySize();
|
||||
resource->Release();
|
||||
}
|
||||
@@ -135,13 +153,16 @@ void ResourceManager::Unload(ResourceGUID guid) {
|
||||
|
||||
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
|
||||
|
||||
const auto cachedResources = m_resourceCache.GetPairs();
|
||||
for (const auto& pair : cachedResources) {
|
||||
if (pair.second != nullptr) {
|
||||
pair.second->Release();
|
||||
}
|
||||
}
|
||||
m_resourceCache.Clear();
|
||||
m_refCounts.Clear();
|
||||
m_guidToPath.Clear();
|
||||
m_memoryUsage = 0;
|
||||
}
|
||||
|
||||
@@ -209,6 +230,10 @@ void ResourceManager::ReloadResource(ResourceGUID guid) {
|
||||
if (pathIt == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Containers::String path = *pathIt;
|
||||
auto* typeIt = m_resourceCache.Find(guid);
|
||||
(void)typeIt;
|
||||
}
|
||||
|
||||
Containers::Array<Containers::String> ResourceManager::GetResourcePaths() const {
|
||||
@@ -226,11 +251,60 @@ void ResourceManager::UnloadGroup(const Containers::Array<ResourceGUID>& guids)
|
||||
if (it != nullptr) {
|
||||
IResource* resource = *it;
|
||||
m_resourceCache.Erase(guid);
|
||||
m_guidToPath.Erase(guid);
|
||||
m_memoryUsage -= resource->GetMemorySize();
|
||||
resource->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::RefreshAssetDatabase() {
|
||||
if (!m_resourceRoot.Empty()) {
|
||||
m_assetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
bool ResourceManager::TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const {
|
||||
return m_assetDatabase.TryGetAssetRef(path, resourceType, outRef);
|
||||
}
|
||||
|
||||
LoadResult ResourceManager::LoadResource(const Containers::String& path,
|
||||
ResourceType type,
|
||||
ImportSettings* settings) {
|
||||
const ResourceGUID guid = ResourceGUID::Generate(path);
|
||||
|
||||
if (IResource* cached = FindInCache(guid)) {
|
||||
return LoadResult(cached);
|
||||
}
|
||||
|
||||
IResourceLoader* loader = FindLoader(type);
|
||||
if (loader == nullptr) {
|
||||
Debug::Logger::Get().Warning(Debug::LogCategory::FileSystem,
|
||||
Containers::String("No loader found for resource type: ") +
|
||||
GetResourceTypeName(type));
|
||||
return LoadResult(false, "Loader not found");
|
||||
}
|
||||
|
||||
Containers::String loadPath = path;
|
||||
AssetDatabase::ResolvedAsset resolvedAsset;
|
||||
if (!m_resourceRoot.Empty() &&
|
||||
m_assetDatabase.EnsureArtifact(path, type, resolvedAsset) &&
|
||||
resolvedAsset.artifactReady) {
|
||||
loadPath = resolvedAsset.artifactMainPath;
|
||||
}
|
||||
|
||||
LoadResult result = loader->Load(loadPath, settings);
|
||||
if (!result || result.resource == nullptr) {
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::FileSystem,
|
||||
Containers::String("Failed to load resource: ") + path + " - " + result.errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.resource->m_path = path;
|
||||
AddToCache(guid, result.resource);
|
||||
m_guidToPath.Insert(guid, path);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -1,28 +1,57 @@
|
||||
#include <XCEngine/Core/IO/IResourceLoader.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
Containers::Array<Core::uint8> IResourceLoader::ReadFileData(const Containers::String& path) {
|
||||
namespace {
|
||||
|
||||
Containers::Array<Core::uint8> TryReadFileData(
|
||||
const std::filesystem::path& filePath,
|
||||
bool& opened) {
|
||||
Containers::Array<Core::uint8> data;
|
||||
|
||||
std::ifstream file(path.CStr(), std::ios::binary | std::ios::ate);
|
||||
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
opened = false;
|
||||
return data;
|
||||
}
|
||||
|
||||
std::streamsize size = file.tellg();
|
||||
|
||||
opened = true;
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size <= 0) {
|
||||
return data;
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
data.Resize(static_cast<size_t>(size));
|
||||
if (!file.read(reinterpret_cast<char*>(data.Data()), size)) {
|
||||
data.Clear();
|
||||
}
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Containers::Array<Core::uint8> IResourceLoader::ReadFileData(const Containers::String& path) {
|
||||
bool opened = false;
|
||||
const std::filesystem::path inputPath(path.CStr());
|
||||
Containers::Array<Core::uint8> data = TryReadFileData(inputPath, opened);
|
||||
if (opened || path.Empty() || inputPath.is_absolute()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (resourceRoot.Empty()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
return TryReadFileData(std::filesystem::path(resourceRoot.CStr()) / inputPath, opened);
|
||||
}
|
||||
|
||||
Containers::String IResourceLoader::GetExtension(const Containers::String& path) {
|
||||
Containers::String ext;
|
||||
size_t dotPos = Containers::String::npos;
|
||||
|
||||
Reference in New Issue
Block a user