"Functonal" asset manager

This commit is contained in:
OusmBlueNinja 2025-05-21 11:46:17 -05:00
parent 6225c07dbd
commit 4e2f6b8cfd
3 changed files with 212 additions and 126 deletions

View File

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

View File

@ -1,110 +1,196 @@
#include "AssetManager.h"
#include <yaml-cpp/yaml.h>
#include "assets/Texture2D.h"
#include "Logger.h"
#include "Profiler.h"
#include <string>
#include <fstream>
#include <yaml-cpp/yaml.h>
namespace fs = std::filesystem;
namespace OX {
namespace OX
{
std::unordered_map<std::string, std::shared_ptr<Asset> > AssetManager::s_LoadedAssets;
std::unordered_map<std::string, AssetManager::AssetMetadata> AssetManager::s_MetadataMap;
std::shared_ptr<ResourceTreeNode> AssetManager::s_FileTree = std::make_shared<ResourceTreeNode>();
std::unordered_map<std::string, std::shared_ptr<Asset>> AssetManager::s_AssetMap;
std::unordered_map<std::string, std::type_index> AssetManager::s_AssetTypes;
std::unordered_map<std::string, AssetManager::AssetMetadata> AssetManager::s_AssetDatabase;
std::mutex AssetManager::s_QueueMutex;
std::queue<std::string> AssetManager::s_DeferredLoadQueue;
std::mutex AssetManager::s_QueueMutex;
std::queue<std::pair<std::string, AssetManager::AssetMetadata>> AssetManager::s_LoadQueue;
fs::path AssetManager::s_ProjectRoot;
std::atomic<bool> AssetManager::s_Scanning = false;
std::thread AssetManager::s_ScanThread;
std::thread AssetManager::s_BackgroundThread;
std::atomic<bool> AssetManager::s_Running = false;
void AssetManager::Init(const std::string &projectRoot)
{
s_ProjectRoot = fs::absolute(projectRoot);
s_Scanning = true;
s_FileTree = std::make_shared<ResourceTreeNode>();
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<std::mutex> lock(s_QueueMutex);
s_LoadQueue.push({ virtualPath, meta });
}
}
void AssetManager::Tick() {
OX_PROFILE_FUNCTION();
std::lock_guard<std::mutex> 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> asset;
if (meta.type == "texture2D")
asset = std::make_shared<Texture2D>();
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<ResourceTreeNode>();
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<std::mutex> lock(s_QueueMutex);
s_MetadataMap[resPath] = {type, path.string()};
s_DeferredLoadQueue.push(resPath);
}
}
}
}
void AssetManager::AddToTree(const std::string &resPath, bool isDir)
{
std::vector<std::string> 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<ResourceTreeNode>();
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<Asset> 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> asset;
if (meta.type == "texture2D")
asset = std::make_shared<Texture2D>();
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<ResourceTreeNode> AssetManager::GetFileTree()
{
return s_FileTree;
}
} // namespace OX

View File

@ -1,60 +1,61 @@
#pragma once
#include <unordered_map>
#include <string>
#include <memory>
#include <typeindex>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <memory>
#include <filesystem>
#include <mutex>
#include <thread>
#include <atomic>
#include "Asset.h"
namespace OX {
class Asset;
struct ResourceTreeNode {
std::string name;
std::string path; // res://...
bool isDirectory = false;
std::vector<std::shared_ptr<ResourceTreeNode>> 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<typename T>
static std::shared_ptr<T> 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<T>(it->second);
}
static std::shared_ptr<Asset> Get(const std::string& resPath);
static std::shared_ptr<ResourceTreeNode> 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<std::string, std::shared_ptr<Asset>> s_AssetMap;
static std::unordered_map<std::string, std::type_index> s_AssetTypes;
static std::unordered_map<std::string, AssetMetadata> s_AssetDatabase;
static std::unordered_map<std::string, std::shared_ptr<Asset>> s_LoadedAssets;
static std::unordered_map<std::string, AssetMetadata> s_MetadataMap;
static std::shared_ptr<ResourceTreeNode> s_FileTree;
static std::mutex s_QueueMutex;
static std::queue<std::pair<std::string, AssetMetadata>> s_LoadQueue;
static std::queue<std::string> s_DeferredLoadQueue;
static std::thread s_BackgroundThread;
static std::atomic<bool> s_Running;
static std::filesystem::path s_ProjectDirectory;
static std::filesystem::path s_ProjectRoot;
static std::atomic<bool> s_Scanning;
static std::thread s_ScanThread;
};
} // namespace OX