diff --git a/Engine/src/Serialization/SceneSerializer.cpp b/Engine/src/Serialization/SceneSerializer.cpp index 9e06772..527da35 100644 --- a/Engine/src/Serialization/SceneSerializer.cpp +++ b/Engine/src/Serialization/SceneSerializer.cpp @@ -1,10 +1,18 @@ // // Created by atdunbg on 2026/10/3. // -// SceneSerializer:场景 ↔ YAML。结构见头文件注释与计划 v3。 +// SceneSerializer:场景 ↔ YAML。结构见头文件注释。 // -// 原子性:Deserialize 先把整个文件解析进中间结构(逐实体容错), -// 全部成功才 scene.Clear() 换血重建——解析失败不动现场。 +// 写端:手动 Emitter 逐键输出(短集合用 Flow 单行数组/内联 map, +// 浮点 9 位精度保证 float32 无损往返)。 +// 读端:yaml-cpp 新版对"僵尸节点"(缺失键的返回值)硬化——operator=、 +// size()、Type()、链式 operator[] 一律抛 InvalidNode,只有 IsDefined/ +// as(fallback)/begin() 安全。因此读侧铁律:每层节点先 +// `const Node n = parent["Key"]` 拷贝初始化 + `if (n)` 判空再访问, +// 绝不对可能缺失的键做赋值或链式下标。 +// +// 原子性:Deserialize 先解析到中间结构(逐实体容错),全部成功才 +// scene.Clear() 换血重建——解析失败不动现场。 // #include @@ -32,18 +40,33 @@ namespace Engine::Serialization { namespace { - // ---- 写出辅助:glm → 数组 ---- + // ---- 写端辅助:Flow 单行数组(Position: [0, 1.2, -6])---- - std::vector Vec3Arr(const glm::vec3& v) { return {v.x, v.y, v.z}; } - std::vector Vec4Arr(const glm::vec4& v) { return {v.x, v.y, v.z, v.w}; } - std::vector QuatArr(const glm::quat& q) { return {q.w, q.x, q.y, q.z}; } + void EmitVec3(YAML::Emitter& e, const glm::vec3& v) + { + e << YAML::Flow << YAML::BeginSeq << v.x << v.y << v.z + << YAML::EndSeq; + } - // ---- 读入辅助:缺字段/类型错 → 默认值(as(fallback) 语义)---- + void EmitVec4(YAML::Emitter& e, const glm::vec4& v) + { + e << YAML::Flow << YAML::BeginSeq << v.x << v.y << v.z << v.w + << YAML::EndSeq; + } + + void EmitQuat(YAML::Emitter& e, const glm::quat& q) + { + e << YAML::Flow << YAML::BeginSeq << q.w << q.x << q.y << q.z + << YAML::EndSeq; + } + + // ---- 读端辅助:缺字段/类型错/僵尸节点 → 默认值 ---- glm::vec3 ReadVec3(const YAML::Node& n, const glm::vec3& def) { if (!n || !n.IsSequence() || n.size() < 3) return def; - return {n[0].as(def.x), n[1].as(def.y), n[2].as(def.z)}; + return {n[0].as(def.x), n[1].as(def.y), + n[2].as(def.z)}; } glm::vec4 ReadVec4(const YAML::Node& n, const glm::vec4& def) @@ -144,103 +167,120 @@ namespace Engine::Serialization { return ref; } - // 资产引用双写:{Guid, Path}(procedural 只有 Path,Guid=0 不写) - void WriteModelRef(YAML::Node& node, Assets::AssetManager& assets, - ModelRef ref) - { - const std::string& path = assets.Models().Path(ref); - if (path.empty()) { - ENGINE_WARN("SceneSerializer: entity has invalid model ref, " - "MeshRenderer skipped"); - return; - } - YAML::Node m; - if (const u64 guid = assets.GetOrCreateGUID(path); guid != 0) { - m["Guid"] = guid; - } - m["Path"] = path; - node["Model"] = m; - } - // ---- 中间结构:解析全部成功才碰 registry ---- + // comps 持 Components 节点(仅在键存在且为 map 时赋值—— + // yaml-cpp 新版对僵尸节点做 operator= 会抛异常) struct ParsedEntity { u64 uuid = 0; std::string name = "entity"; - YAML::Node transform, meshRenderer, animation, camera, light, - rigidBody; // 未出现 = undefined + YAML::Node comps; // 缺省 = Null(无组件) }; - void SerializeEntity(YAML::Node& out, Assets::AssetManager& assets, - const ECS::Registry& reg, entt::entity e) + // 写端:单实体 → Emitter(调用方已进入 Entities 序列) + void EmitEntity(YAML::Emitter& out, Assets::AssetManager& assets, + const ECS::Registry& reg, entt::entity e) { const auto& id = reg.get(e); - YAML::Node ent; - ent["Entity"] = id.uuid.Value(); + out << YAML::BeginMap; + out << YAML::Key << "Entity" << YAML::Value << id.uuid.Value(); if (const auto* name = reg.try_get(e)) { - ent["Name"] = name->name; + out << YAML::Key << "Name" << YAML::Value << name->name; } - YAML::Node comps; + out << YAML::Key << "Components" << YAML::Value << YAML::BeginMap; if (const auto* t = reg.try_get(e)) { - YAML::Node n; - n["Position"] = Vec3Arr(t->GetPosition()); - n["Rotation"] = QuatArr(t->GetRotationQuat()); // (w,x,y,z) 无损 - n["Scale"] = Vec3Arr(t->GetScale()); - comps["Transform"] = n; + out << YAML::Key << "Transform" << YAML::Value + << YAML::BeginMap; + out << YAML::Key << "Position" << YAML::Value; + EmitVec3(out, t->GetPosition()); + out << YAML::Key << "Rotation" << YAML::Value; + EmitQuat(out, t->GetRotationQuat()); // (w,x,y,z) 无损 + out << YAML::Key << "Scale" << YAML::Value; + EmitVec3(out, t->GetScale()); + out << YAML::EndMap; } if (const auto* mr = reg.try_get(e)) { - YAML::Node n; - WriteModelRef(n, assets, mr->model); - if (const YAML::Node m = n["Model"]) comps["MeshRenderer"] = n; + // ModelRef → {Guid, Path}:Path 反查为空 = 无效引用,跳过 + const std::string& path = assets.Models().Path(mr->model); + if (!path.empty()) { + out << YAML::Key << "MeshRenderer" << YAML::Value + << YAML::BeginMap; + out << YAML::Key << "Model" << YAML::Value << YAML::Flow + << YAML::BeginMap; + if (const u64 guid = assets.GetOrCreateGUID(path); + guid != 0) { + out << YAML::Key << "Guid" << YAML::Value << guid; + } + out << YAML::Key << "Path" << YAML::Value << path; + out << YAML::EndMap; // Model(flow) + out << YAML::EndMap; // MeshRenderer + } + else { + ENGINE_WARN("SceneSerializer: entity has invalid model " + "ref, MeshRenderer skipped"); + } } if (const auto* a = reg.try_get(e)) { - YAML::Node n; - n["ClipIndex"] = a->clipIndex; - n["Playing"] = a->playing; - n["Loop"] = a->loop; - n["Speed"] = a->speed; // fade*/time 是运行态,不存 - comps["Animation"] = n; + out << YAML::Key << "Animation" << YAML::Value << YAML::Flow + << YAML::BeginMap; + out << YAML::Key << "ClipIndex" << YAML::Value << a->clipIndex; + out << YAML::Key << "Playing" << YAML::Value << a->playing; + out << YAML::Key << "Loop" << YAML::Value << a->loop; + out << YAML::Key << "Speed" << YAML::Value << a->speed; + out << YAML::EndMap; } if (const auto* c = reg.try_get(e)) { - YAML::Node n; - n["Primary"] = c->primary; // 相机不存投影参数(调用方传入) - comps["Camera"] = n; + out << YAML::Key << "Camera" << YAML::Value << YAML::Flow + << YAML::BeginMap; + out << YAML::Key << "Primary" << YAML::Value << c->primary; + out << YAML::EndMap; } if (const auto* l = reg.try_get(e)) { - YAML::Node n; - n["Type"] = static_cast(l->type); - n["Direction"] = Vec3Arr(l->direction); - n["Color"] = Vec4Arr(l->color); - n["Radius"] = l->radius; - n["CastShadow"] = l->castShadow; - comps["Light"] = n; + out << YAML::Key << "Light" << YAML::Value << YAML::BeginMap; + out << YAML::Key << "Type" << YAML::Value + << static_cast(l->type); + out << YAML::Key << "Direction" << YAML::Value; + EmitVec3(out, l->direction); + out << YAML::Key << "Color" << YAML::Value; + EmitVec4(out, l->color); + out << YAML::Key << "Radius" << YAML::Value << l->radius; + out << YAML::Key << "CastShadow" << YAML::Value + << l->castShadow; + out << YAML::EndMap; } if (const auto* rb = reg.try_get(e)) { - YAML::Node n; - n["Type"] = static_cast(rb->type); - n["Collider"] = static_cast(rb->collider); - n["BoxHalfExtents"] = Vec3Arr(rb->boxHalfExtents); - n["SphereRadius"] = rb->sphereRadius; - n["Mass"] = rb->mass; - n["GravityScale"] = rb->gravityScale; - n["Friction"] = rb->friction; - n["Restitution"] = rb->restitution; - n["IsSensor"] = rb->isSensor; + out << YAML::Key << "RigidBody" << YAML::Value + << YAML::BeginMap; + out << YAML::Key << "Type" << YAML::Value + << static_cast(rb->type); + out << YAML::Key << "Collider" << YAML::Value + << static_cast(rb->collider); + out << YAML::Key << "BoxHalfExtents" << YAML::Value; + EmitVec3(out, rb->boxHalfExtents); + out << YAML::Key << "SphereRadius" << YAML::Value + << rb->sphereRadius; + out << YAML::Key << "Mass" << YAML::Value << rb->mass; + out << YAML::Key << "GravityScale" << YAML::Value + << rb->gravityScale; + out << YAML::Key << "Friction" << YAML::Value << rb->friction; + out << YAML::Key << "Restitution" << YAML::Value + << rb->restitution; + out << YAML::Key << "IsSensor" << YAML::Value << rb->isSensor; // bodyId/bodyValid/velocity 由 PhysicsWorld 对账重建,不存 - comps["RigidBody"] = n; + out << YAML::EndMap; } - if (comps.size() > 0) ent["Components"] = comps; - out.push_back(ent); + out << YAML::EndMap; // Components + out << YAML::EndMap; // Entity } } // namespace @@ -253,35 +293,50 @@ namespace Engine::Serialization { const std::string& environmentPath) { try { - YAML::Node root; - root["Scene"] = std::filesystem::path(outPath).stem().string(); - root["Version"] = kSceneVersion; + // float32 无损往返需 9 位有效数字(std::numeric_limits + // ::max_digits10) + YAML::Emitter out; + out.SetFloatPrecision(9); + + out << YAML::BeginMap; + out << YAML::Key << "Scene" << YAML::Value + << std::filesystem::path(outPath).stem().string(); + out << YAML::Key << "Version" << YAML::Value << kSceneVersion; if (!environmentPath.empty()) { - root["Environment"] = environmentPath; + out << YAML::Key << "Environment" << YAML::Value + << environmentPath; } const auto& s = scene.GetSettings(); - YAML::Node st; - st["ClearColor"] = Vec4Arr(s.ClearColor); - st["AmbientColor"] = Vec4Arr(s.AmbientColor); - st["DrawSkybox"] = s.DrawSkybox; - root["Settings"] = st; + out << YAML::Key << "Settings" << YAML::Value << YAML::BeginMap; + out << YAML::Key << "ClearColor" << YAML::Value; + EmitVec4(out, s.ClearColor); + out << YAML::Key << "AmbientColor" << YAML::Value; + EmitVec4(out, s.AmbientColor); + out << YAML::Key << "DrawSkybox" << YAML::Value << s.DrawSkybox; + out << YAML::EndMap; - YAML::Node entities(YAML::NodeType::Sequence); + out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq; const ECS::Registry& reg = scene.GetRegistry(); reg.view().each( [&](entt::entity e, const ECS::IdComponent&) { - SerializeEntity(entities, assets, reg, e); + EmitEntity(out, assets, reg, e); }); - root["Entities"] = entities; + out << YAML::EndSeq; + out << YAML::EndMap; - std::ofstream out(outPath, std::ios::trunc); - if (!out) { + if (!out.good()) { + ENGINE_ERROR("SceneSerializer: emit failed: {}", out.GetLastError()); + return false; + } + + std::ofstream file(outPath, std::ios::trunc); + if (!file) { ENGINE_ERROR("SceneSerializer: cannot open '{}' for write", outPath); return false; } - out << root; + file << out.c_str(); return true; } catch (const YAML::Exception& e) { @@ -326,6 +381,7 @@ namespace Engine::Serialization { return false; } + // 僵尸节点安全:as(fallback) 对缺失键返回 fallback outEnvironmentPath = root["Environment"].as(""); if (const YAML::Node st = root["Settings"]; st && st.IsMap()) { @@ -339,7 +395,7 @@ namespace Engine::Serialization { std::vector parsed; const YAML::Node entities = root["Entities"]; - if (entities.IsSequence()) { + if (entities && entities.IsSequence()) { parsed.reserve(entities.size()); for (const auto& e : entities) { try { @@ -348,13 +404,8 @@ namespace Engine::Serialization { pe.name = e["Name"].as(pe.name); if (const YAML::Node comps = e["Components"]; comps && comps.IsMap()) { - pe.transform = comps["Transform"]; - pe.meshRenderer = comps["MeshRenderer"]; - pe.animation = comps["Animation"]; - pe.camera = comps["Camera"]; - pe.light = comps["Light"]; - pe.rigidBody = comps["RigidBody"]; - // 未识别的组件键自然跳过(前向兼容) + // 仅在 rhs 有效时赋值(僵尸节点 operator= 会抛) + pe.comps = comps; } parsed.push_back(std::move(pe)); } @@ -380,32 +431,34 @@ namespace Engine::Serialization { } handle.GetComponent().uuid = UUID{uuid}; - if (pe.transform) { - const YAML::Node n = pe.transform; + const YAML::Node comps = pe.comps; // 拷贝初始化,缺省 Null + + if (const YAML::Node n = comps["Transform"]) { auto& t = handle.GetComponent(); t.SetPosition(ReadVec3(n["Position"], t.GetPosition())); t.SetRotationQuat(ReadQuat(n["Rotation"])); t.SetScale(ReadVec3(n["Scale"], t.GetScale())); } - if (pe.meshRenderer) { - const YAML::Node n = pe.meshRenderer; - const u64 guid = n["Model"]["Guid"].as(0); - const auto path = n["Model"]["Path"].as(""); - if (ModelRef ref = ResolveModel(assets, guid, path); - ref.Valid()) { - handle.AddComponent( - ECS::MeshRendererComponent{ref}); - } - else { - ENGINE_WARN("SceneSerializer: model unresolved " - "(guid={}, path='{}'), MeshRenderer skipped", - guid, path); + if (const YAML::Node n = comps["MeshRenderer"]) { + if (const YAML::Node m = n["Model"]) { + const u64 guid = m["Guid"].as(0); + const auto path = m["Path"].as(""); + if (ModelRef ref = ResolveModel(assets, guid, path); + ref.Valid()) { + handle.AddComponent( + ECS::MeshRendererComponent{ref}); + } + else { + ENGINE_WARN("SceneSerializer: model unresolved " + "(guid={}, path='{}'), MeshRenderer " + "skipped", + guid, path); + } } } - if (pe.animation) { - const YAML::Node n = pe.animation; + if (const YAML::Node n = comps["Animation"]) { auto& a = handle.AddComponent(); a.clipIndex = n["ClipIndex"].as(a.clipIndex); a.playing = n["Playing"].as(a.playing); @@ -413,16 +466,15 @@ namespace Engine::Serialization { a.speed = n["Speed"].as(a.speed); } - if (pe.camera) { - const YAML::Node n = pe.camera; + if (const YAML::Node n = comps["Camera"]) { auto& c = handle.AddComponent(); c.primary = n["Primary"].as(false); } - if (pe.light) { - const YAML::Node n = pe.light; + if (const YAML::Node n = comps["Light"]) { auto& l = handle.AddComponent(); - if (const u8 type = n["Type"].as(0); type <= 1) { + if (const int type = n["Type"].as(0); type >= 0 && + type <= 1) { l.type = static_cast(type); } l.direction = ReadVec3(n["Direction"], l.direction); @@ -431,14 +483,15 @@ namespace Engine::Serialization { l.castShadow = n["CastShadow"].as(l.castShadow); } - if (pe.rigidBody) { - const YAML::Node n = pe.rigidBody; + if (const YAML::Node n = comps["RigidBody"]) { auto& rb = handle.AddComponent(); - if (const u8 type = n["Type"].as(0); type <= 2) { - rb.type = static_cast( - type); + if (const int type = n["Type"].as(0); + type >= 0 && type <= 2) { + rb.type = + static_cast(type); } - if (const u8 col = n["Collider"].as(0); col <= 2) { + if (const int col = n["Collider"].as(0); + col >= 0 && col <= 2) { rb.collider = static_cast( col);