From 4e2f6b8cfd7ae7f2eac56662a06001a370cc42b4 Mon Sep 17 00:00:00 2001 From: OusmBlueNinja <89956790+OusmBlueNinja@users.noreply.github.com> Date: Wed, 21 May 2025 11:46:17 -0500 Subject: [PATCH] "Functonal" asset manager --- CMakeLists.txt | 3 +- src/core/systems/AssetManager.cpp | 272 ++++++++++++++++++++---------- src/core/systems/AssetManager.h | 63 +++---- 3 files changed, 212 insertions(+), 126 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 694ade7..a855c84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,8 +27,6 @@ set(CMAKE_C_EXTENSIONS OFF) include_directories(${CMAKE_BINARY_DIR}/generated) -# Optional message -message(STATUS "Generated Onyx version: ${FULL_VERSION}") # --- Output directories for multi-config builds --- @@ -147,6 +145,7 @@ target_link_libraries(Editor PRIVATE glfw assimp::assimp GLEW::GLEW + yaml-cpp ${CMAKE_DL_LIBS} ) diff --git a/src/core/systems/AssetManager.cpp b/src/core/systems/AssetManager.cpp index 5bb5a7e..b8a4c03 100644 --- a/src/core/systems/AssetManager.cpp +++ b/src/core/systems/AssetManager.cpp @@ -1,110 +1,196 @@ #include "AssetManager.h" -#include #include "assets/Texture2D.h" #include "Logger.h" -#include "Profiler.h" +#include +#include +#include namespace fs = std::filesystem; -namespace OX { +namespace OX +{ + std::unordered_map > AssetManager::s_LoadedAssets; + std::unordered_map AssetManager::s_MetadataMap; + std::shared_ptr AssetManager::s_FileTree = std::make_shared(); -std::unordered_map> AssetManager::s_AssetMap; -std::unordered_map AssetManager::s_AssetTypes; -std::unordered_map AssetManager::s_AssetDatabase; + std::mutex AssetManager::s_QueueMutex; + std::queue AssetManager::s_DeferredLoadQueue; -std::mutex AssetManager::s_QueueMutex; -std::queue> AssetManager::s_LoadQueue; + fs::path AssetManager::s_ProjectRoot; + std::atomic AssetManager::s_Scanning = false; + std::thread AssetManager::s_ScanThread; -std::thread AssetManager::s_BackgroundThread; -std::atomic AssetManager::s_Running = false; + void AssetManager::Init(const std::string &projectRoot) + { + s_ProjectRoot = fs::absolute(projectRoot); + s_Scanning = true; + s_FileTree = std::make_shared(); + s_FileTree->name = "res://"; + s_FileTree->path = "res://"; + s_FileTree->isDirectory = true; -fs::path AssetManager::s_ProjectDirectory; + s_ScanThread = std::thread([=] + { + BackgroundScan(s_ProjectRoot); + s_Scanning = false; + }); + } -void AssetManager::Init(const std::string& projectDir) { - OX_PROFILE_FUNCTION(); - s_ProjectDirectory = fs::absolute(projectDir); - s_Running = true; - s_BackgroundThread = std::thread([] { - ScanAssetsRecursive(s_ProjectDirectory); - }); -} - -void AssetManager::Shutdown() { - OX_PROFILE_FUNCTION(); - s_Running = false; - if (s_BackgroundThread.joinable()) - s_BackgroundThread.join(); -} - -void AssetManager::ScanAssetsRecursive(const fs::path& root) { - OX_PROFILE_FUNCTION(); - for (auto& entry : fs::recursive_directory_iterator(root)) { - if (!entry.is_regular_file()) continue; - - const auto& path = entry.path(); - std::string virtualPath = GetVirtualPath(path); - std::string type = DetectTypeFromExtension(path); - if (type.empty()) continue; - - AssetMetadata meta{ type, path.string() }; + void AssetManager::Shutdown() + { + if (s_ScanThread.joinable()) + s_ScanThread.join(); + } + void AssetManager::Tick() + { std::lock_guard lock(s_QueueMutex); - s_LoadQueue.push({ virtualPath, meta }); - } -} - -void AssetManager::Tick() { - OX_PROFILE_FUNCTION(); - std::lock_guard lock(s_QueueMutex); - if (!s_LoadQueue.empty()) { - auto [id, meta] = s_LoadQueue.front(); - s_LoadQueue.pop(); - RegisterAssetMetadata(id, meta.type, meta.path); - LoadAsset(id); - } -} - -std::string AssetManager::GetVirtualPath(const fs::path& absolute) { - OX_PROFILE_FUNCTION(); - fs::path relative = fs::relative(absolute, s_ProjectDirectory); - return "res://" + relative.generic_string(); -} - -std::string AssetManager::DetectTypeFromExtension(const fs::path& path) { - OX_PROFILE_FUNCTION(); - std::string ext = path.extension().string(); - if (ext == ".png" || ext == ".jpg") return "texture2D"; - if (ext == ".wav" || ext == ".ogg") return "audio"; - if (ext == ".prefab" || ext == ".yaml") return "prefab"; - return ""; -} - -void AssetManager::RegisterAssetMetadata(const std::string& id, const std::string& type, const std::string& path) { - OX_PROFILE_FUNCTION(); - s_AssetDatabase[id] = { type, path }; -} - -bool AssetManager::LoadAsset(const std::string& id) { - OX_PROFILE_FUNCTION(); - auto it = s_AssetDatabase.find(id); - if (it == s_AssetDatabase.end()) return false; - - const auto& meta = it->second; - std::shared_ptr asset; - - if (meta.type == "texture2D") - asset = std::make_shared(); - - - if (asset && asset->LoadFromFile(meta.path)) { - s_AssetMap[id] = asset; - s_AssetTypes.emplace(id, std::type_index(typeid(*asset))); - Logger::LogInfo("Loaded asset '%s' (%s)", id.c_str(), meta.path.c_str()); - return true; + if (!s_DeferredLoadQueue.empty()) { + auto resPath = s_DeferredLoadQueue.front(); + s_DeferredLoadQueue.pop(); + LoadAsset(resPath); + } } - Logger::LogError("Failed to load asset '%s'", id.c_str()); - return false; -} + void AssetManager::SaveAssetPack(const std::string& outputPath) + { + YAML::Emitter out; + out << YAML::BeginMap; + out << YAML::Key << "assets" << YAML::Value << YAML::BeginSeq; + + for (const auto& [resPath, meta] : s_MetadataMap) { + fs::path relative = fs::relative(meta.absolutePath, s_ProjectRoot); + + out << YAML::BeginMap; + out << YAML::Key << "id" << YAML::Value << resPath; + out << YAML::Key << "type" << YAML::Value << meta.type; + out << YAML::Key << "path" << YAML::Value << relative.generic_string(); + out << YAML::EndMap; + } + + out << YAML::EndSeq; + out << YAML::EndMap; + + std::ofstream fout(outputPath); + fout << out.c_str(); + fout.close(); + + Logger::LogInfo("Saved asset pack: %s", outputPath.c_str()); + } + + + void AssetManager::Rescan() + { + if (s_Scanning) return; + s_Scanning = true; + s_FileTree = std::make_shared(); + s_FileTree->name = "res://"; + s_FileTree->path = "res://"; + s_FileTree->isDirectory = true; + + s_ScanThread = std::thread([=] + { + BackgroundScan(s_ProjectRoot); + s_Scanning = false; + }); + } + + void AssetManager::BackgroundScan(const fs::path &root) + { + for (const auto &entry: fs::recursive_directory_iterator(root)) { + const auto &path = entry.path(); + std::string resPath = MakeVirtualPath(path); + + if (entry.is_directory()) { + AddToTree(resPath, true); + } else { + AddToTree(resPath, false); + + std::string type = DetectAssetType(path); + if (!type.empty()) { + std::lock_guard lock(s_QueueMutex); + s_MetadataMap[resPath] = {type, path.string()}; + s_DeferredLoadQueue.push(resPath); + } + } + } + } + + void AssetManager::AddToTree(const std::string &resPath, bool isDir) + { + std::vector parts; + size_t pos = 0, next; + while ((next = resPath.find('/', pos)) != std::string::npos) { + if (next != pos) parts.push_back(resPath.substr(pos, next - pos)); + pos = next + 1; + } + if (pos < resPath.length()) parts.push_back(resPath.substr(pos)); + + auto current = s_FileTree; + for (size_t i = 1; i < parts.size(); ++i) { + auto &name = parts[i]; + auto it = std::find_if(current->children.begin(), current->children.end(), [&](auto &child) + { + return child->name == name; + }); + + if (it == current->children.end()) { + auto newNode = std::make_shared(); + newNode->name = name; + newNode->path = current->path + "/" + name; + newNode->isDirectory = (i < parts.size() - 1) || isDir; + current->children.push_back(newNode); + current = newNode; + } else { + current = *it; + } + } + } + + std::string AssetManager::MakeVirtualPath(const fs::path &full) + { + fs::path rel = fs::relative(full, s_ProjectRoot); + return "res://" + rel.generic_string(); + } + + std::string AssetManager::DetectAssetType(const fs::path &ext) + { + auto e = ext.extension().string(); + if (e == ".png" || e == ".jpg") return "texture2D"; + if (e == ".ogg" || e == ".wav") return "audio"; + return ""; + } + + std::shared_ptr AssetManager::Get(const std::string &resPath) + { + auto it = s_LoadedAssets.find(resPath); + return (it != s_LoadedAssets.end()) ? it->second : nullptr; + } + + bool AssetManager::LoadAsset(const std::string &resPath) + { + auto it = s_MetadataMap.find(resPath); + if (it == s_MetadataMap.end()) return false; + + const auto &meta = it->second; + std::shared_ptr asset; + + if (meta.type == "texture2D") + asset = std::make_shared(); + + if (asset && asset->LoadFromFile(meta.absolutePath)) { + s_LoadedAssets[resPath] = asset; + Logger::LogDebug("Loaded asset: %s", resPath.c_str()); + return true; + } + + Logger::LogError("Failed to load: %s", resPath.c_str()); + return false; + } + + std::shared_ptr AssetManager::GetFileTree() + { + return s_FileTree; + } } // namespace OX diff --git a/src/core/systems/AssetManager.h b/src/core/systems/AssetManager.h index e3cb49a..242ead1 100644 --- a/src/core/systems/AssetManager.h +++ b/src/core/systems/AssetManager.h @@ -1,60 +1,61 @@ #pragma once -#include #include -#include -#include -#include +#include +#include #include +#include #include +#include #include #include -#include "Asset.h" namespace OX { + class Asset; + + struct ResourceTreeNode { + std::string name; + std::string path; // res://... + bool isDirectory = false; + std::vector> children; + }; + class AssetManager { public: - static void Init(const std::string& projectDir); + static void Init(const std::string& projectRoot); static void Shutdown(); - static void Tick(); // Call once per frame + static void Tick(); // Call every frame - static void RegisterAssetMetadata(const std::string& id, const std::string& type, const std::string& path); - static bool LoadAsset(const std::string& id); + static void SaveAssetPack(const std::string& outputPath); - template - static std::shared_ptr Get(const std::string& id) { - if (s_AssetMap.find(id) == s_AssetMap.end()) - LoadAsset(id); - auto it = s_AssetMap.find(id); - if (it == s_AssetMap.end()) return nullptr; - if (s_AssetTypes[id] != std::type_index(typeid(T))) return nullptr; - - return std::static_pointer_cast(it->second); - } + static std::shared_ptr Get(const std::string& resPath); + static std::shared_ptr GetFileTree(); + static bool LoadAsset(const std::string& resPath); + static void Rescan(); // Rebuilds file tree (slow) private: struct AssetMetadata { std::string type; - std::string path; + std::string absolutePath; }; - static void ScanAssetsRecursive(const std::filesystem::path& root); - static std::string DetectTypeFromExtension(const std::filesystem::path& path); - static std::string GetVirtualPath(const std::filesystem::path& absolute); + static void BackgroundScan(const std::filesystem::path& root); + static void AddToTree(const std::string& virtualPath, bool isDir); + static std::string DetectAssetType(const std::filesystem::path& ext); + static std::string MakeVirtualPath(const std::filesystem::path& full); - static std::unordered_map> s_AssetMap; - static std::unordered_map s_AssetTypes; - static std::unordered_map s_AssetDatabase; + static std::unordered_map> s_LoadedAssets; + static std::unordered_map s_MetadataMap; + static std::shared_ptr s_FileTree; static std::mutex s_QueueMutex; - static std::queue> s_LoadQueue; + static std::queue s_DeferredLoadQueue; - static std::thread s_BackgroundThread; - static std::atomic s_Running; - - static std::filesystem::path s_ProjectDirectory; + static std::filesystem::path s_ProjectRoot; + static std::atomic s_Scanning; + static std::thread s_ScanThread; }; } // namespace OX