serialization: scene save/load (Unity-style YAML + UE-style GUID registry)
Engine-layer SceneSerializer (yaml-cpp submodule): entities/components
to versioned YAML, atomic clear-and-rebuild on load with per-entity
fault tolerance, missing-field defaults, unknown-component skip and
UUID dedup. Asset references are {Guid, Path} pairs resolved through
the new AssetManager GUID registry (assets/asset_registry.yaml) with
path fallback; procedural:// pseudo-paths rebuild from parameters.
Editor File menu gets Save/Load Scene modals; app re-resolves stale
entity handles via onSceneLoaded callback.
This commit is contained in:
@@ -4,13 +4,106 @@
|
||||
|
||||
#include <Engine/Assets/AssetManager.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
#include <Engine/Core/UUID.h>
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace Engine::Assets {
|
||||
|
||||
namespace {
|
||||
// GUID 注册表落盘位置(相对 CWD,与 assets/ 约定一致)
|
||||
constexpr const char* kGuidRegistryPath = "assets/asset_registry.yaml";
|
||||
// 伪路径前缀:参数确定性生成,无稳定身份需求,不进 GUID 注册表
|
||||
constexpr std::string_view kProceduralPrefix = "procedural://";
|
||||
bool IsProceduralPath(const std::string& path)
|
||||
{
|
||||
return path.rfind(kProceduralPrefix, 0) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
AssetManager::AssetManager(IAssetUploader& uploader)
|
||||
: m_Uploader(uploader)
|
||||
{
|
||||
ENGINE_INFO("AssetManager created");
|
||||
LoadGUIDRegistry();
|
||||
ENGINE_INFO("AssetManager created ({} guids registered)", m_GuidToPath.size());
|
||||
}
|
||||
|
||||
// ---- 资产 GUID 注册表 ----
|
||||
|
||||
u64 AssetManager::GetOrCreateGUID(const std::string& path)
|
||||
{
|
||||
if (path.empty() || IsProceduralPath(path)) return 0;
|
||||
|
||||
if (auto it = m_PathToGuid.find(path); it != m_PathToGuid.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// 64 位随机 + 双 map 双向查重(UUID::New 撞已有 GUID 概率可忽略,
|
||||
// 但注册表是持久身份,防御性重试)
|
||||
u64 guid = 0;
|
||||
do { guid = UUID::New().Value(); } while (m_GuidToPath.contains(guid));
|
||||
|
||||
m_PathToGuid.emplace(path, guid);
|
||||
m_GuidToPath.emplace(guid, path);
|
||||
SaveGUIDRegistry();
|
||||
return guid;
|
||||
}
|
||||
|
||||
std::string AssetManager::ResolveGUID(const u64 guid) const
|
||||
{
|
||||
if (auto it = m_GuidToPath.find(guid); it != m_GuidToPath.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void AssetManager::LoadGUIDRegistry()
|
||||
{
|
||||
try {
|
||||
YAML::Node root = YAML::LoadFile(kGuidRegistryPath);
|
||||
if (const YAML::Node assets = root["Assets"]; assets.IsSequence()) {
|
||||
for (const auto& a : assets) {
|
||||
const u64 guid = a["Guid"].as<u64>(0);
|
||||
auto path = a["Path"].as<std::string>();
|
||||
if (guid != 0 && !path.empty() &&
|
||||
!m_GuidToPath.contains(guid)) {
|
||||
m_GuidToPath.emplace(guid, path);
|
||||
m_PathToGuid.emplace(std::move(path), guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const YAML::Exception& e) {
|
||||
// 注册表缺失/损坏不致命:场景里同时存了 Path 兜底
|
||||
ENGINE_WARN("AssetManager: guid registry unavailable ({})", e.what());
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
ENGINE_WARN("AssetManager: guid registry unreadable ({})", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AssetManager::SaveGUIDRegistry() const
|
||||
{
|
||||
try {
|
||||
YAML::Node root;
|
||||
for (const auto& [guid, path] : m_GuidToPath) {
|
||||
YAML::Node a;
|
||||
a["Guid"] = guid;
|
||||
a["Path"] = path;
|
||||
root["Assets"].push_back(a);
|
||||
}
|
||||
std::ofstream out(kGuidRegistryPath, std::ios::trunc);
|
||||
if (!out) {
|
||||
ENGINE_WARN("AssetManager: cannot write {}", kGuidRegistryPath);
|
||||
return;
|
||||
}
|
||||
out << root;
|
||||
}
|
||||
catch (const YAML::Exception& e) {
|
||||
ENGINE_ERROR("AssetManager: guid registry save failed ({})", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
ModelRef AssetManager::LoadModel(const std::string& path)
|
||||
|
||||
Reference in New Issue
Block a user