"Functonal" asset manager
This commit is contained in:
parent
6225c07dbd
commit
4e2f6b8cfd
@ -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}
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user