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
@@ -172,14 +172,31 @@ namespace Engine
AssetCache<Model>& Models() { return m_Models; }
// ---- 资产 GUID 注册表(UE AssetRegistry 模式:中央注册表)----
// 磁盘路径资产持久映射 GUID→路径(存 assets/asset_registry.yaml),
// 场景文件以 GUID 为主键引用资产——重命名/移动资产不断引用,
// Path 仅作人工可读提示与解析兜底。procedural:// 伪路径(参数
// 确定性生成,无需稳定身份)不参与
// 查询/分配(新路径分配 GUID 并立即持久化,崩溃安全)
u64 GetOrCreateGUID(const std::string& path);
// GUID → 路径;未登记返回空串
std::string ResolveGUID(u64 guid) const;
// 后续扩展点:TextureHandle / MaterialHandle / ShaderHandle
// 各自一个 AssetCache<T> + 便捷方法,模式与 Model 完全一致。
private:
void LoadGUIDRegistry();
void SaveGUIDRegistry() const;
IAssetUploader& m_Uploader;
AssetCache<Model> m_Models;
AssetCache<Cubemap> m_Environments;
// GUID 注册表(双向);dirty 即时保存,无析构落盘依赖
std::unordered_map<u64, std::string> m_GuidToPath;
std::unordered_map<std::string, u64> m_PathToGuid;
};
} // namespace Assets
} // namespace Engine
+11
View File
@@ -46,6 +46,7 @@ namespace Engine::ECS {
bool DrawSkybox = true;
};
Settings& GetSettings() { return m_Settings; }
const Settings& GetSettings() const { return m_Settings; } // 序列化只读
// ---- 实体 ----
@@ -54,6 +55,15 @@ namespace Engine::ECS {
void DestroyEntity(Entity e); // 延迟:帧尾统一销毁
void FlushDestroyQueue(); // 每帧末尾由 Application 调用
// 整场景换血(序列化加载):立即销毁全部实体并清空延迟队列。
// 仅用于"校验通过后的原子替换"——调用方持有的 Entity/EntityHandle
// 全部悬空,必须自行重解析(GetEntityHandle 可从裸 entity 重建)
void Clear();
// 裸 entity → 句柄表皮(EntityHandle 构造私有 + friend Scene;
// 序列化加载后应用层重解析悬空句柄用)。无效 entity 返回空句柄
EntityHandle GetEntityHandle(Entity e);
// 帧循环统一重算脏 transform 的 world 矩阵(UE transform-update
// 阶段模式)——由 Application 在 OnUpdate 之后、渲染之前调用,
// 用户代码永远不需要手动 Compose
@@ -64,6 +74,7 @@ namespace Engine::ECS {
Entity FindEntityByUUID(UUID uuid);
Registry& GetRegistry() { return m_Registry; }
const Registry& GetRegistry() const { return m_Registry; } // 序列化等只读遍历
// ---- 组件便捷接口(转发 entt)----
@@ -0,0 +1,62 @@
//
// Created by atdunbg on 2026/10/3.
//
// SceneSerializer:场景 → YAML 文件的双向转换(Unity 场景 YAML 同构简化)。
//
// 参照系(定案):Unity 的场景 YAML 结构 + UE AssetRegistry 式中央
// GUID 注册表(AssetManager 维护,存 assets/asset_registry.yaml)。
// 场景内资产引用双写 {Guid, Path}:GUID 是主键(重命名/移动资产不断
// 引用),Path 是人工可读提示 + 注册表缺失时的解析兜底。
//
// 引擎层身份:本类不感知编辑器(kEnvPaths 表)与渲染器——
// environmentPath 以普通字符串经参数进出,消费方(编辑器/游戏运行时)
// 自行解释。一套 API 服务两个消费者:编辑器 File 菜单 与
// 游戏运行时动态加载,不存在"另一套序列化"。
//
// 前向兼容(Prefab 路线预留):
// - 场景 = 实体节点列表,Prefab 复用同一套实体读写函数
// - Version 闸:高版本文件拒载并 WARN,低版本宽容
// - 未知组件名跳过(Unity 语义:新版场景在旧版引擎打开可玩)
// - UUID 重复 → 重新生成(即 Prefab 实例化的 ID 重映射)
//
#ifndef HELLOVULKAN_SCENESERIALIZER_H
#define HELLOVULKAN_SCENESERIALIZER_H
#include <string>
namespace Engine::ECS { class Scene; }
namespace Engine::Assets { class AssetManager; }
namespace Engine::Serialization {
// 当前场景文件格式版本(字段/结构变更时递增)
inline constexpr int kSceneVersion = 1;
class SceneSerializer {
public:
// ---- 保存 ----
// scene 序列化到 outPath(YAML)。environmentPath 可空(不写
// Environment 字段)。失败(无法写文件)返回 false。
static bool Serialize(const ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& outPath,
const std::string& environmentPath = {});
// ---- 加载 ----
// 原子换血:先解析校验到中间结构,全部成功才 scene.Clear() 重建
//(解析失败不动现场)。成功时 outEnvironmentPath 带回场景记录的
// 环境路径(空 = 场景未带环境,消费方保持当前环境即可)。
// 健壮性:单实体损坏跳过继续、缺字段取默认值、未知组件跳过、
// UUID 重复重新生成、YAML 异常优雅报错。
// 注意:调用方持有的 Entity/EntityHandle 在成功后全部悬空,
// 必须重解析(Scene::GetEntityHandle / 按 UUID 查询)。
static bool Deserialize(ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& inPath,
std::string& outEnvironmentPath);
};
} // namespace Engine::Serialization
#endif //HELLOVULKAN_SCENESERIALIZER_H