// // Created by atdunbg on 2026/10/3. // // SceneSerializer:场景 ↔ YAML。结构见头文件注释。 // // 写端:手动 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Engine::Serialization { namespace { // ---- 写端辅助:Flow 单行数组(Position: [0, 1.2, -6])---- void EmitVec3(YAML::Emitter& e, const glm::vec3& v) { e << YAML::Flow << YAML::BeginSeq << v.x << v.y << v.z << YAML::EndSeq; } 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)}; } glm::vec4 ReadVec4(const YAML::Node& n, const glm::vec4& def) { if (!n || !n.IsSequence() || n.size() < 4) return def; return {n[0].as(def.x), n[1].as(def.y), n[2].as(def.z), n[3].as(def.w)}; } // [w,x,y,z] → 归一化 quat;非法/缺失回退单位四元数(防手改档出 NaN) glm::quat ReadQuat(const YAML::Node& n) { if (!n || !n.IsSequence() || n.size() < 4) return {}; const f32 w = n[0].as(1.0f), x = n[1].as(0.0f); const f32 y = n[2].as(0.0f), z = n[3].as(0.0f); const f32 len = std::sqrt(x * x + y * y + z * z + w * w); return len > 1e-8f ? glm::quat(w, x, y, z) / len : glm::quat{}; } // "a,b,c[,d]" → N 个 int(procedural 伪路径参数解析) template bool ParseCsvInts(const std::string& s, int (&out)[N]) { size_t pos = 0; for (size_t i = 0; i < N; ++i) { const size_t next = (i + 1 < N) ? s.find(',', pos) : std::string::npos; if (i + 1 < N && next == std::string::npos) return false; try { out[i] = std::stoi(s.substr(pos, next - pos)); } catch (const std::exception&) { return false; } pos = (next == std::string::npos) ? s.size() : next + 1; } return true; } // procedural:// 伪路径 → 参数反解重建(确定性键必然命中缓存; // 绝不对伪路径调 LoadModel——那会走 assimp 必炸) ModelRef ResolveProcedural(Assets::AssetManager& assets, const std::string& path) { constexpr std::string_view kPlane = "procedural://plane/"; constexpr std::string_view kTPlane = "procedural://transparent-plane/"; if (path.rfind(kTPlane, 0) == 0) { // "{halfSize}/{r},{g},{b},{a}" const std::string rest = path.substr(kTPlane.size()); const auto slash = rest.find('/'); if (slash == std::string::npos) return {}; int c[4]; if (!ParseCsvInts(rest.substr(slash + 1), c)) return {}; try { return assets.CreateTransparentPlane( std::stof(rest.substr(0, slash)), static_cast(c[0]), static_cast(c[1]), static_cast(c[2]), static_cast(c[3])); } catch (const std::exception&) { return {}; } } if (path.rfind(kPlane, 0) == 0) { // "{halfSize}/{r},{g},{b}" const std::string rest = path.substr(kPlane.size()); const auto slash = rest.find('/'); if (slash == std::string::npos) return {}; int c[3]; if (!ParseCsvInts(rest.substr(slash + 1), c)) return {}; try { return assets.CreatePlane( std::stof(rest.substr(0, slash)), static_cast(c[0]), static_cast(c[1]), static_cast(c[2])); } catch (const std::exception&) { return {}; } } return {}; // 未知伪路径 } // 模型引用解析降级链:procedural 反解 → GUID 主键 → Path 兜底。 // 加载成功后 GetOrCreateGUID 回写注册表(注册表丢档自愈) ModelRef ResolveModel(Assets::AssetManager& assets, u64 guid, const std::string& path) { if (ModelRef ref = ResolveProcedural(assets, path); ref.Valid()) { return ref; } std::string resolved; if (guid != 0) resolved = assets.ResolveGUID(guid); if (resolved.empty()) resolved = path; if (resolved.empty()) return {}; ModelRef ref = assets.LoadModel(resolved); if (ref.Valid()) assets.GetOrCreateGUID(resolved); return ref; } // ---- 中间结构:解析全部成功才碰 registry ---- // comps 持 Components 节点(仅在键存在且为 map 时赋值—— // yaml-cpp 新版对僵尸节点做 operator= 会抛异常) struct ParsedEntity { u64 uuid = 0; std::string name = "entity"; YAML::Node comps; // 缺省 = Null(无组件) }; // 写端:单实体 → Emitter(调用方已进入 Entities 序列) void EmitEntity(YAML::Emitter& out, Assets::AssetManager& assets, const ECS::Registry& reg, entt::entity e) { const auto& id = reg.get(e); out << YAML::BeginMap; out << YAML::Key << "Entity" << YAML::Value << id.uuid.Value(); if (const auto* name = reg.try_get(e)) { out << YAML::Key << "Name" << YAML::Value << name->name; } out << YAML::Key << "Components" << YAML::Value << YAML::BeginMap; if (const auto* t = reg.try_get(e)) { 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)) { // 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)) { 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)) { 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)) { 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)) { 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 << "CapsuleRadius" << YAML::Value << rb->capsuleRadius; out << YAML::Key << "CapsuleHalfHeight" << YAML::Value << rb->capsuleHalfHeight; out << YAML::Key << "CylinderRadius" << YAML::Value << rb->cylinderRadius; out << YAML::Key << "CylinderHalfHeight" << YAML::Value << rb->cylinderHalfHeight; if (!rb->compound.empty()) { out << YAML::Key << "Compound" << YAML::Value << YAML::BeginSeq; for (const auto& c : rb->compound) { out << YAML::BeginMap; out << YAML::Key << "Shape" << YAML::Value << static_cast(c.shape); out << YAML::Key << "BoxHalfExtents" << YAML::Value; EmitVec3(out, c.boxHalfExtents); out << YAML::Key << "Radius" << YAML::Value << c.radius; out << YAML::Key << "CapsuleHalfHeight" << YAML::Value << c.capsuleHalfHeight; out << YAML::Key << "Position" << YAML::Value; EmitVec3(out, c.position); out << YAML::Key << "Rotation" << YAML::Value; EmitQuat(out, c.rotation); // (w,x,y,z) out << YAML::EndMap; } out << YAML::EndSeq; } 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 对账重建,不存 out << YAML::EndMap; } if (const auto* jc = reg.try_get(e)) { out << YAML::Key << "Joint" << YAML::Value << YAML::BeginMap; out << YAML::Key << "Type" << YAML::Value << static_cast(jc->type); // 实体引用存 UUID(跨存档稳定;target=0 未设置) out << YAML::Key << "Target" << YAML::Value << jc->target.Value(); out << YAML::Key << "AnchorA" << YAML::Value; EmitVec3(out, jc->anchorA); out << YAML::Key << "AnchorB" << YAML::Value; EmitVec3(out, jc->anchorB); out << YAML::Key << "HingeAxis" << YAML::Value; EmitVec3(out, jc->hingeAxis); out << YAML::Key << "EnableLimit" << YAML::Value << jc->enableLimit; out << YAML::Key << "LimitMin" << YAML::Value << jc->limitMin; out << YAML::Key << "LimitMax" << YAML::Value << jc->limitMax; out << YAML::EndMap; } // 角色只存参数(速度/着地等状态不序列化) if (const auto* cc = reg.try_get(e)) { out << YAML::Key << "CharacterController" << YAML::Value << YAML::BeginMap; out << YAML::Key << "Radius" << YAML::Value << cc->radius; out << YAML::Key << "CapsuleHalfHeight" << YAML::Value << cc->capsuleHalfHeight; out << YAML::Key << "Mass" << YAML::Value << cc->mass; out << YAML::Key << "MaxSlopeAngle" << YAML::Value << cc->maxSlopeAngle; out << YAML::Key << "StepHeight" << YAML::Value << cc->stepHeight; out << YAML::EndMap; } out << YAML::EndMap; // Components out << YAML::EndMap; // Entity } } // namespace // ---- 保存 ---- bool SceneSerializer::Serialize(const ECS::Scene& scene, Assets::AssetManager& assets, const std::string& outPath, const std::string& environmentPath) { try { // 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()) { out << YAML::Key << "Environment" << YAML::Value << environmentPath; } const auto& s = scene.GetSettings(); 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; out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq; const ECS::Registry& reg = scene.GetRegistry(); reg.view().each( [&](entt::entity e, const ECS::IdComponent&) { EmitEntity(out, assets, reg, e); }); out << YAML::EndSeq; out << YAML::EndMap; 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; } file << out.c_str(); return true; } catch (const YAML::Exception& e) { ENGINE_ERROR("SceneSerializer: save failed ({})", e.what()); return false; } catch (const std::exception& e) { ENGINE_ERROR("SceneSerializer: save failed ({})", e.what()); return false; } } // ---- 加载 ---- bool SceneSerializer::Deserialize(ECS::Scene& scene, Assets::AssetManager& assets, const std::string& inPath, std::string& outEnvironmentPath) { outEnvironmentPath.clear(); YAML::Node root; try { root = YAML::LoadFile(inPath); } catch (const YAML::Exception& e) { ENGINE_ERROR("SceneSerializer: cannot parse '{}' ({})", inPath, e.what()); return false; } catch (const std::exception& e) { ENGINE_ERROR("SceneSerializer: cannot read '{}' ({})", inPath, e.what()); return false; } // Version 闸:高版本拒载(结构未知),缺失按 0 宽容旧档 if (const int version = root["Version"].as(0); version > kSceneVersion) { ENGINE_ERROR("SceneSerializer: '{}' version {} > supported {}", inPath, version, kSceneVersion); return false; } // 僵尸节点安全:as(fallback) 对缺失键返回 fallback outEnvironmentPath = root["Environment"].as(""); if (const YAML::Node st = root["Settings"]; st && st.IsMap()) { auto& s = scene.GetSettings(); s.ClearColor = ReadVec4(st["ClearColor"], s.ClearColor); s.AmbientColor = ReadVec4(st["AmbientColor"], s.AmbientColor); s.DrawSkybox = st["DrawSkybox"].as(s.DrawSkybox); } // ---- 阶段 1:解析到中间结构(逐实体容错,不动 registry)---- std::vector parsed; const YAML::Node entities = root["Entities"]; if (entities && entities.IsSequence()) { parsed.reserve(entities.size()); for (const auto& e : entities) { try { ParsedEntity pe; pe.uuid = e["Entity"].as(0); pe.name = e["Name"].as(pe.name); if (const YAML::Node comps = e["Components"]; comps && comps.IsMap()) { // 仅在 rhs 有效时赋值(僵尸节点 operator= 会抛) pe.comps = comps; } parsed.push_back(std::move(pe)); } catch (const std::exception& err) { // 单实体损坏不炸全场:跳过继续 ENGINE_WARN("SceneSerializer: entity skipped ({})", err.what()); } } } // ---- 阶段 2:原子换血重建 ---- scene.Clear(); std::unordered_set seenUuids; for (ParsedEntity& pe : parsed) { auto handle = scene.CreateEntity(pe.name); // UUID 重复/无效 → 重新生成(Prefab 实例化的 ID 重映射语义) u64 uuid = pe.uuid; if (uuid == 0 || !seenUuids.insert(uuid).second) { uuid = UUID::New().Value(); } handle.GetComponent().uuid = UUID{uuid}; 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 (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 (const YAML::Node n = comps["Animation"]) { auto& a = handle.AddComponent(); a.clipIndex = n["ClipIndex"].as(a.clipIndex); a.playing = n["Playing"].as(a.playing); a.loop = n["Loop"].as(a.loop); a.speed = n["Speed"].as(a.speed); } if (const YAML::Node n = comps["Camera"]) { auto& c = handle.AddComponent(); c.primary = n["Primary"].as(false); } if (const YAML::Node n = comps["Light"]) { auto& l = handle.AddComponent(); if (const int type = n["Type"].as(0); type >= 0 && type <= 1) { l.type = static_cast(type); } l.direction = ReadVec3(n["Direction"], l.direction); l.color = ReadVec4(n["Color"], l.color); l.radius = n["Radius"].as(l.radius); l.castShadow = n["CastShadow"].as(l.castShadow); } if (const YAML::Node n = comps["RigidBody"]) { auto& rb = handle.AddComponent(); if (const int type = n["Type"].as(0); type >= 0 && type <= 2) { rb.type = static_cast(type); } if (const int col = n["Collider"].as(0); col >= 0 && col <= 5) { rb.collider = static_cast( col); } rb.boxHalfExtents = ReadVec3(n["BoxHalfExtents"], rb.boxHalfExtents); rb.sphereRadius = n["SphereRadius"].as(rb.sphereRadius); rb.capsuleRadius = n["CapsuleRadius"].as(rb.capsuleRadius); rb.capsuleHalfHeight = n["CapsuleHalfHeight"].as(rb.capsuleHalfHeight); rb.cylinderRadius = n["CylinderRadius"].as(rb.cylinderRadius); rb.cylinderHalfHeight = n["CylinderHalfHeight"].as(rb.cylinderHalfHeight); // 僵尸节点铁律:拷贝初始化 + 判空再遍历(缺 Compound 键安全) if (const YAML::Node cs = n["Compound"]) { for (const auto& item : cs) { ECS::RigidBodyComponent::CompoundChild c; if (const int sh = item["Shape"].as(0); sh >= 0 && sh <= 2) { c.shape = static_cast< ECS::RigidBodyComponent::CompoundChild::Shape>( sh); } c.boxHalfExtents = ReadVec3(item["BoxHalfExtents"], c.boxHalfExtents); c.radius = item["Radius"].as(c.radius); c.capsuleHalfHeight = item["CapsuleHalfHeight"].as( c.capsuleHalfHeight); c.position = ReadVec3(item["Position"], c.position); c.rotation = ReadQuat(item["Rotation"]); rb.compound.push_back(std::move(c)); } } rb.mass = n["Mass"].as(rb.mass); rb.gravityScale = n["GravityScale"].as(rb.gravityScale); rb.friction = n["Friction"].as(rb.friction); rb.restitution = n["Restitution"].as(rb.restitution); rb.isSensor = n["IsSensor"].as(rb.isSensor); // bodyId/bodyValid/velocity 保持默认(对账重建) } // Joint:target 只存 UUID,加载后由 PhysicsWorld 惰性反查 //(两遍解析不需要——UUID 再生/加载顺序均由逐帧解析消化) if (const YAML::Node n = comps["Joint"]) { auto& jc = handle.AddComponent(); if (const int type = n["Type"].as(0); type >= 0 && type <= 2) { jc.type = static_cast(type); } jc.target = UUID{n["Target"].as(0)}; jc.anchorA = ReadVec3(n["AnchorA"], jc.anchorA); jc.anchorB = ReadVec3(n["AnchorB"], jc.anchorB); jc.hingeAxis = ReadVec3(n["HingeAxis"], jc.hingeAxis); jc.enableLimit = n["EnableLimit"].as(jc.enableLimit); jc.limitMin = n["LimitMin"].as(jc.limitMin); jc.limitMax = n["LimitMax"].as(jc.limitMax); } if (const YAML::Node n = comps["CharacterController"]) { auto& cc = handle.AddComponent(); cc.radius = n["Radius"].as(cc.radius); cc.capsuleHalfHeight = n["CapsuleHalfHeight"].as(cc.capsuleHalfHeight); cc.mass = n["Mass"].as(cc.mass); cc.maxSlopeAngle = n["MaxSlopeAngle"].as(cc.maxSlopeAngle); cc.stepHeight = n["StepHeight"].as(cc.stepHeight); // 状态(速度/着地)由 PhysicsWorld 对账重建,不存 } } // 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms: // 显式补算,否则首帧 World = 单位阵 scene.UpdateTransforms(); ENGINE_INFO("SceneSerializer: loaded {} entities from '{}'", parsed.size(), inPath); return true; } } // namespace Engine::Serialization