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:
2026-10-03 14:47:02 +08:00
parent ffe33e9967
commit 007d175314
14 changed files with 867 additions and 1 deletions
+94 -1
View File
@@ -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)