feat: Play-In-Editor (PIE) with in-memory scene snapshot
Editor toolbar gains Play/Stop (Ctrl+P) with UE semantics: - Play: serialize current scene to an in-memory YAML snapshot via new SceneSerializer::SerializeToString, switch display to kDisplayDirect (primary camera entity renders), unfreeze time (timeScale 0 -> 1) - Stop: DeserializeFromString atomically rebuilds the pre-play scene, back to kDisplayViewport (editor camera), refreeze time; selection cleared and gizmo drag reset since all entity handles dangle - SceneSerializer refactored: SerializeDoc/ParseRoot shared by file and memory variants; file Save/Load behavior unchanged - Running state disables gizmo + editor hotkeys in the viewport (input goes to game scripts), draws yellow PLAYING hint and a no-camera warning; File->Save/Load disabled while playing (UE behavior) Physics bodies/constraints/characters and script instances are reconciled automatically by the existing per-frame sync on restore.
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
#include "StatsPanel.h"
|
#include "StatsPanel.h"
|
||||||
|
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
|
#include <Engine/Core/Time.h>
|
||||||
#include <Engine/Serialization/SceneSerializer.h>
|
#include <Engine/Serialization/SceneSerializer.h>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -36,6 +37,8 @@ Editor::~Editor() = default;
|
|||||||
void Editor::Init(const EditorContext& ctx)
|
void Editor::Init(const EditorContext& ctx)
|
||||||
{
|
{
|
||||||
m_Ctx = ctx;
|
m_Ctx = ctx;
|
||||||
|
// PIE 运行态指针:指向本对象成员(ctx 拷贝后补接,面板只读)
|
||||||
|
m_Ctx.playState = &m_Playing;
|
||||||
|
|
||||||
m_Hierarchy = std::make_unique<HierarchyPanel>();
|
m_Hierarchy = std::make_unique<HierarchyPanel>();
|
||||||
m_Inspector = std::make_unique<InspectorPanel>();
|
m_Inspector = std::make_unique<InspectorPanel>();
|
||||||
@@ -44,6 +47,60 @@ void Editor::Init(const EditorContext& ctx)
|
|||||||
m_AssetBrowser = std::make_unique<AssetBrowserPanel>();
|
m_AssetBrowser = std::make_unique<AssetBrowserPanel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- PIE(Play-In-Editor)----
|
||||||
|
|
||||||
|
// Play:当前场景 → 内存 YAML 快照 → 切游戏渲染(primary 相机实体)
|
||||||
|
// → 解冻时间(物理/脚本/动画开始跑)。快照失败不进 Play(现场不动)
|
||||||
|
void Editor::DoPlay()
|
||||||
|
{
|
||||||
|
if (!m_Ctx.scene || !m_Ctx.assets || !m_Ctx.renderer) return;
|
||||||
|
|
||||||
|
m_PlaySnapshot = Serialization::SceneSerializer::SerializeToString(
|
||||||
|
*m_Ctx.scene, *m_Ctx.assets, "PlaySnapshot");
|
||||||
|
if (m_PlaySnapshot.empty())
|
||||||
|
{
|
||||||
|
ENGINE_ERROR("PIE: snapshot failed, play aborted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Playing = true;
|
||||||
|
m_Ctx.renderer->SetDisplayMode(kDisplayDirect);
|
||||||
|
Time::SetTimeScale(1.0f);
|
||||||
|
ENGINE_INFO("PIE: playing (scene snapshot {} bytes)",
|
||||||
|
m_PlaySnapshot.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop:快照原子换血恢复 → 回编辑器渲染 → 冻结时间。
|
||||||
|
// 换血后所有 Entity 句柄悬空:清选中(描边同步清)、Gizmo 拖拽态
|
||||||
|
// 复位。物理 body/约束/角色由对账系统随实体重建/清理,无需干预。
|
||||||
|
// 恢复失败(快照损坏,理论罕见)也必须回编辑态,现场留给用户
|
||||||
|
// File→Load 或退出前 Save
|
||||||
|
void Editor::DoStop()
|
||||||
|
{
|
||||||
|
if (!m_Ctx.scene || !m_Ctx.assets || !m_Ctx.renderer) return;
|
||||||
|
|
||||||
|
std::string envPath;
|
||||||
|
if (!Serialization::SceneSerializer::DeserializeFromString(
|
||||||
|
*m_Ctx.scene, *m_Ctx.assets, m_PlaySnapshot, envPath))
|
||||||
|
{
|
||||||
|
ENGINE_ERROR("PIE: snapshot restore failed (scene is now the "
|
||||||
|
"played-out state)");
|
||||||
|
}
|
||||||
|
m_PlaySnapshot.clear();
|
||||||
|
|
||||||
|
m_Playing = false;
|
||||||
|
m_Ctx.renderer->SetDisplayMode(kDisplayViewport);
|
||||||
|
Time::SetTimeScale(0.0f);
|
||||||
|
SelectEntity(entt::null);
|
||||||
|
m_Gizmo.dragging = false;
|
||||||
|
ENGINE_INFO("PIE: stopped, editor scene restored");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Editor::TogglePlay()
|
||||||
|
{
|
||||||
|
m_Playing ? DoStop() : DoPlay();
|
||||||
|
}
|
||||||
|
|
||||||
void Editor::Draw()
|
void Editor::Draw()
|
||||||
{
|
{
|
||||||
// ImGuizmo 每帧首调(官方约定):内部会清 mbOverGizmoHotspot 等
|
// ImGuizmo 每帧首调(官方约定):内部会清 mbOverGizmoHotspot 等
|
||||||
@@ -133,8 +190,47 @@ void Editor::DrawMenuBar()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- PIE 工具栏(UE 主工具栏语义):Play/Stop 切换按钮 +
|
||||||
|
// Ctrl+P 全局热键。运行态按钮变 Stop(黄底提示在视口内)----
|
||||||
|
{
|
||||||
|
const ImGuiIO& io = ImGui::GetIO();
|
||||||
|
// Ctrl+P:文本输入中(改名/路径框)不触发,防打字误切 PIE
|
||||||
|
if (!m_Playing && io.KeyCtrl && !io.WantTextInput &&
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_P, false))
|
||||||
|
{
|
||||||
|
TogglePlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Playing)
|
||||||
|
{
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button,
|
||||||
|
ImVec4(0.75f, 0.45f, 0.10f, 1.0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||||
|
ImVec4(0.85f, 0.55f, 0.15f, 1.0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||||
|
ImVec4(0.90f, 0.62f, 0.20f, 1.0f));
|
||||||
|
if (ImGui::SmallButton("Stop (Ctrl+P)"))
|
||||||
|
{
|
||||||
|
TogglePlay();
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.72f, 0.25f, 1.0f),
|
||||||
|
"PLAYING - editor input disabled");
|
||||||
|
}
|
||||||
|
else if (ImGui::SmallButton("Play (Ctrl+P)"))
|
||||||
|
{
|
||||||
|
TogglePlay();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Separator();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui::BeginMenu("File"))
|
if (ImGui::BeginMenu("File"))
|
||||||
{
|
{
|
||||||
|
// 运行态禁存取:Save 会把"玩出来的状态"当编辑结果落盘,
|
||||||
|
// Load 会撕掉快照恢复链(UE 运行态同样禁)
|
||||||
|
ImGui::BeginDisabled(m_Playing);
|
||||||
if (ImGui::MenuItem("Save Scene...", nullptr))
|
if (ImGui::MenuItem("Save Scene...", nullptr))
|
||||||
{
|
{
|
||||||
m_ShowSaveModal = true;
|
m_ShowSaveModal = true;
|
||||||
@@ -143,6 +239,7 @@ void Editor::DrawMenuBar()
|
|||||||
{
|
{
|
||||||
m_ShowLoadModal = true;
|
m_ShowLoadModal = true;
|
||||||
}
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::MenuItem("Quit", "Alt+F4"))
|
if (ImGui::MenuItem("Quit", "Alt+F4"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,6 +55,17 @@ public:
|
|||||||
// 请求退出(弹确认框;窗口关闭事件走这里)
|
// 请求退出(弹确认框;窗口关闭事件走这里)
|
||||||
void RequestQuit() { m_ShowQuitConfirm = true; }
|
void RequestQuit() { m_ShowQuitConfirm = true; }
|
||||||
|
|
||||||
|
// ---- PIE(Play-In-Editor,UE 工具栏语义)----
|
||||||
|
bool IsPlaying() const { return m_Playing; }
|
||||||
|
// Play:内存快照当前场景 → 切游戏渲染(primary 相机)→ 解冻时间;
|
||||||
|
// Stop:快照原子换血恢复 → 回编辑器渲染 → 冻结时间。
|
||||||
|
// 编辑器态默认 timeScale=0(物理冻结 = UE 语义),Play 解冻
|
||||||
|
void TogglePlay();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DoPlay(); // TogglePlay 的 Play 半程(快照 + 切渲染 + 解冻)
|
||||||
|
void DoStop(); // TogglePlay 的 Stop 半程(恢复 + 回编辑器 + 冻结)
|
||||||
|
|
||||||
// ---- 选中状态 ----
|
// ---- 选中状态 ----
|
||||||
// 同步描边:Renderer 落帧后 SelectionMask/Outline 只画选中实体
|
// 同步描边:Renderer 落帧后 SelectionMask/Outline 只画选中实体
|
||||||
//(bits = 实体位模式,0 = 清选中)
|
//(bits = 实体位模式,0 = 清选中)
|
||||||
@@ -109,6 +120,10 @@ private:
|
|||||||
bool m_ShowSaveModal = false;
|
bool m_ShowSaveModal = false;
|
||||||
bool m_ShowLoadModal = false;
|
bool m_ShowLoadModal = false;
|
||||||
char m_ScenePathBuf[256] = "scenes/sandbox.scene";
|
char m_ScenePathBuf[256] = "scenes/sandbox.scene";
|
||||||
|
|
||||||
|
// PIE 状态:Play 时场景 YAML 内存快照,Stop 时原子换血恢复
|
||||||
|
bool m_Playing = false;
|
||||||
|
std::string m_PlaySnapshot;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //HELLOVULKAN_EDITOR_H
|
#endif //HELLOVULKAN_EDITOR_H
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ struct EditorContext {
|
|||||||
// 无关;面板经此做 F 焦点/Gizmo 视图/对齐跳转)
|
// 无关;面板经此做 F 焦点/Gizmo 视图/对齐跳转)
|
||||||
Engine::EditorCamera *editorCamera = nullptr;
|
Engine::EditorCamera *editorCamera = nullptr;
|
||||||
|
|
||||||
|
// PIE 运行态(Play-In-Editor):Editor 持有,只读指针。
|
||||||
|
// 面板门控用——运行态禁 Gizmo/编辑器热键,渲染源已是 primary 相机
|
||||||
|
const bool *playState = nullptr;
|
||||||
|
|
||||||
std::function<int()> getEnvIndex; // 当前环境表下标
|
std::function<int()> getEnvIndex; // 当前环境表下标
|
||||||
std::function<void(int)> setEnvIndex;
|
std::function<void(int)> setEnvIndex;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -152,7 +152,10 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
|||||||
selected != entt::null
|
selected != entt::null
|
||||||
? reg.try_get<ECS::TransformComponent>(selected)
|
? reg.try_get<ECS::TransformComponent>(selected)
|
||||||
: nullptr;
|
: nullptr;
|
||||||
if (out.focused && !ImGui::GetIO().WantTextInput &&
|
// PIE 运行态:输入归游戏(脚本读 GLFW 轮询),Gizmo/编辑器
|
||||||
|
// 热键全部让位(UE Play 语义:Scene view 编辑操作冻结)
|
||||||
|
const bool playing = ctx.playState && *ctx.playState;
|
||||||
|
if (!playing && out.focused && !ImGui::GetIO().WantTextInput &&
|
||||||
!ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
!ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_W)) gizmo.op = ImGuizmo::TRANSLATE;
|
if (ImGui::IsKeyPressed(ImGuiKey_W)) gizmo.op = ImGuizmo::TRANSLATE;
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_E)) gizmo.op = ImGuizmo::ROTATE;
|
if (ImGui::IsKeyPressed(ImGuiKey_E)) gizmo.op = ImGuizmo::ROTATE;
|
||||||
@@ -169,7 +172,8 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
|||||||
// ---- Gizmo 操纵杆(ImGui drawlist 叠加,无 GPU pass)----
|
// ---- Gizmo 操纵杆(ImGui drawlist 叠加,无 GPU pass)----
|
||||||
// 选中实体 + 有 Transform 才出杆;矩阵分解直接写回
|
// 选中实体 + 有 Transform 才出杆;矩阵分解直接写回
|
||||||
// TransformComponent(与 Inspector 同源,下一帧数值自动跟随)
|
// TransformComponent(与 Inspector 同源,下一帧数值自动跟随)
|
||||||
if (tr != nullptr) {
|
// PIE 运行态不出杆(编辑操作冻结)
|
||||||
|
if (tr != nullptr && !playing) {
|
||||||
// 相机矩阵:跟随渲染源(编辑器形态 = EditorCamera,游戏
|
// 相机矩阵:跟随渲染源(编辑器形态 = EditorCamera,游戏
|
||||||
// 形态 = primary 相机实体;与 SceneRenderer::BuildFrame
|
// 形态 = primary 相机实体;与 SceneRenderer::BuildFrame
|
||||||
// 同一分派),参数与渲染侧一致(aspect = 渲染分辨率、
|
// 同一分派),参数与渲染侧一致(aspect = 渲染分辨率、
|
||||||
@@ -277,14 +281,23 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gizmo 状态提示(左上角 drawlist 叠加,原因同下:不占布局行)
|
// Gizmo 状态提示(左上角 drawlist 叠加,原因同下:不占布局行)
|
||||||
|
// PIE 运行态 = 黄色 PLAYING 提示替代编辑状态
|
||||||
{
|
{
|
||||||
char tip[64];
|
char tip[64];
|
||||||
std::snprintf(tip, sizeof(tip), "Space: %s [T] Snap: Ctrl",
|
if (playing) {
|
||||||
|
std::snprintf(tip, sizeof(tip),
|
||||||
|
"PLAYING - Ctrl+P to stop");
|
||||||
|
} else {
|
||||||
|
std::snprintf(tip, sizeof(tip),
|
||||||
|
"Space: %s [T] Snap: Ctrl",
|
||||||
gizmo.localSpace ? "Local" : "World");
|
gizmo.localSpace ? "Local" : "World");
|
||||||
|
}
|
||||||
const ImVec2 imgMin = ImGui::GetItemRectMin();
|
const ImVec2 imgMin = ImGui::GetItemRectMin();
|
||||||
ImGui::GetWindowDrawList()->AddText(
|
ImGui::GetWindowDrawList()->AddText(
|
||||||
ImVec2(imgMin.x + 8.0f, imgMin.y + 8.0f),
|
ImVec2(imgMin.x + 8.0f, imgMin.y + 8.0f),
|
||||||
IM_COL32(255, 255, 255, 150), tip);
|
playing ? IM_COL32(255, 184, 46, 220)
|
||||||
|
: IM_COL32(255, 255, 255, 150),
|
||||||
|
tip);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic 刚体提示:物理每帧把姿态写回 Transform,拖拽即刻
|
// Dynamic 刚体提示:物理每帧把姿态写回 Transform,拖拽即刻
|
||||||
@@ -292,6 +305,22 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
|||||||
// 用 drawlist 叠加而非 ImGui::TextDisabled:后者在 Image 下方
|
// 用 drawlist 叠加而非 ImGui::TextDisabled:后者在 Image 下方
|
||||||
// 占一行布局 → 滚动条出现/消失循环 → avail 抖动 → 渲染分辨率
|
// 占一行布局 → 滚动条出现/消失循环 → avail 抖动 → 渲染分辨率
|
||||||
// 每帧重报(视口疯狂顶动);且默认字体无中文字形(显示 ?)
|
// 每帧重报(视口疯狂顶动);且默认字体无中文字形(显示 ?)
|
||||||
|
if (playing) {
|
||||||
|
// 运行态无 primary 相机实体 = 游戏画面黑屏(BuildFrame
|
||||||
|
// 跳帧),提示补相机(Hierarchy 右键或 Add Component)
|
||||||
|
ECS::TransformComponent *camT = nullptr;
|
||||||
|
if (ViewCamera(ctx, camT) == nullptr) {
|
||||||
|
const ImVec2 imgMin = ImGui::GetItemRectMin();
|
||||||
|
ImGui::GetWindowDrawList()->AddText(
|
||||||
|
ImVec2(imgMin.x + 8.0f,
|
||||||
|
imgMin.y + size.y * 0.5f),
|
||||||
|
IM_COL32(255, 120, 80, 230),
|
||||||
|
"No primary camera entity - add a Camera "
|
||||||
|
"component and check 'Primary'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic 刚体提示(drawlist 叠加,理由同上)
|
||||||
if (selected != entt::null) {
|
if (selected != entt::null) {
|
||||||
if (const auto *rb = reg.try_get<ECS::RigidBodyComponent>(selected);
|
if (const auto *rb = reg.try_get<ECS::RigidBodyComponent>(selected);
|
||||||
rb != nullptr &&
|
rb != nullptr &&
|
||||||
|
|||||||
@@ -55,6 +55,23 @@ namespace Engine::Serialization {
|
|||||||
Assets::AssetManager& assets,
|
Assets::AssetManager& assets,
|
||||||
const std::string& inPath,
|
const std::string& inPath,
|
||||||
std::string& outEnvironmentPath);
|
std::string& outEnvironmentPath);
|
||||||
|
|
||||||
|
// ---- 内存快照(同一套实体读写,不同载体)----
|
||||||
|
// 消费者:编辑器 Play-In-Editor 快照恢复、Prefab、运行时动态
|
||||||
|
// 拼装场景。documentName 仅写入文档头 "Scene" 字段(注释性),
|
||||||
|
// 快照可传实体名或留空;environmentPath 语义同文件版。
|
||||||
|
static std::string SerializeToString(
|
||||||
|
const ECS::Scene& scene,
|
||||||
|
Assets::AssetManager& assets,
|
||||||
|
const std::string& documentName = {},
|
||||||
|
const std::string& environmentPath = {});
|
||||||
|
|
||||||
|
// 失败(YAML 异常/版本过高)返回 false 且不动现场。
|
||||||
|
// 实体悬空语义同文件版 Deserialize。
|
||||||
|
static bool DeserializeFromString(ECS::Scene& scene,
|
||||||
|
Assets::AssetManager& assets,
|
||||||
|
const std::string& yamlText,
|
||||||
|
std::string& outEnvironmentPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine::Serialization
|
} // namespace Engine::Serialization
|
||||||
|
|||||||
@@ -384,20 +384,22 @@ namespace Engine::Serialization {
|
|||||||
|
|
||||||
// ---- 保存 ----
|
// ---- 保存 ----
|
||||||
|
|
||||||
bool SceneSerializer::Serialize(const ECS::Scene& scene,
|
// Emitter 构建段(文件版与内存版共用):documentName 空则不写
|
||||||
|
// 文档头 "Scene" 字段(快照无文件名,该字段是注释性的)
|
||||||
|
static std::string SerializeDoc(const ECS::Scene& scene,
|
||||||
Assets::AssetManager& assets,
|
Assets::AssetManager& assets,
|
||||||
const std::string& outPath,
|
const std::string& documentName,
|
||||||
const std::string& environmentPath)
|
const std::string& environmentPath)
|
||||||
{
|
{
|
||||||
try {
|
|
||||||
// float32 无损往返需 9 位有效数字(std::numeric_limits
|
// float32 无损往返需 9 位有效数字(std::numeric_limits
|
||||||
// <float>::max_digits10)
|
// <float>::max_digits10)
|
||||||
YAML::Emitter out;
|
YAML::Emitter out;
|
||||||
out.SetFloatPrecision(9);
|
out.SetFloatPrecision(9);
|
||||||
|
|
||||||
out << YAML::BeginMap;
|
out << YAML::BeginMap;
|
||||||
out << YAML::Key << "Scene" << YAML::Value
|
if (!documentName.empty()) {
|
||||||
<< std::filesystem::path(outPath).stem().string();
|
out << YAML::Key << "Scene" << YAML::Value << documentName;
|
||||||
|
}
|
||||||
out << YAML::Key << "Version" << YAML::Value << kSceneVersion;
|
out << YAML::Key << "Version" << YAML::Value << kSceneVersion;
|
||||||
if (!environmentPath.empty()) {
|
if (!environmentPath.empty()) {
|
||||||
out << YAML::Key << "Environment" << YAML::Value
|
out << YAML::Key << "Environment" << YAML::Value
|
||||||
@@ -424,7 +426,23 @@ namespace Engine::Serialization {
|
|||||||
|
|
||||||
if (!out.good()) {
|
if (!out.good()) {
|
||||||
ENGINE_ERROR("SceneSerializer: emit failed: {}", out.GetLastError());
|
ENGINE_ERROR("SceneSerializer: emit failed: {}", out.GetLastError());
|
||||||
return false;
|
return {};
|
||||||
|
}
|
||||||
|
return {out.c_str(), out.size()};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneSerializer::Serialize(const ECS::Scene& scene,
|
||||||
|
Assets::AssetManager& assets,
|
||||||
|
const std::string& outPath,
|
||||||
|
const std::string& environmentPath)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const std::string doc = SerializeDoc(
|
||||||
|
scene, assets,
|
||||||
|
std::filesystem::path(outPath).stem().string(),
|
||||||
|
environmentPath);
|
||||||
|
if (doc.empty()) {
|
||||||
|
return false; // emit 失败,日志已在 SerializeDoc 打过
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream file(outPath, std::ios::trunc);
|
std::ofstream file(outPath, std::ios::trunc);
|
||||||
@@ -433,7 +451,7 @@ namespace Engine::Serialization {
|
|||||||
outPath);
|
outPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
file << out.c_str();
|
file << doc;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (const YAML::Exception& e) {
|
catch (const YAML::Exception& e) {
|
||||||
@@ -446,35 +464,42 @@ namespace Engine::Serialization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string SceneSerializer::SerializeToString(
|
||||||
|
const ECS::Scene& scene,
|
||||||
|
Assets::AssetManager& assets,
|
||||||
|
const std::string& documentName,
|
||||||
|
const std::string& environmentPath)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return SerializeDoc(scene, assets, documentName, environmentPath);
|
||||||
|
}
|
||||||
|
catch (const YAML::Exception& e) {
|
||||||
|
ENGINE_ERROR("SceneSerializer: snapshot failed ({})", e.what());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
ENGINE_ERROR("SceneSerializer: snapshot failed ({})", e.what());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 加载 ----
|
// ---- 加载 ----
|
||||||
|
|
||||||
bool SceneSerializer::Deserialize(ECS::Scene& scene,
|
// 解析段(文件版与内存快照共用):root → 原子换血重建。
|
||||||
|
// sourceName 仅用于日志(文件路径或 "snapshot")
|
||||||
|
static bool ParseRoot(ECS::Scene& scene,
|
||||||
Assets::AssetManager& assets,
|
Assets::AssetManager& assets,
|
||||||
const std::string& inPath,
|
const YAML::Node& root,
|
||||||
|
const std::string& sourceName,
|
||||||
std::string& outEnvironmentPath)
|
std::string& outEnvironmentPath)
|
||||||
{
|
{
|
||||||
outEnvironmentPath.clear();
|
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 宽容旧档
|
// Version 闸:高版本拒载(结构未知),缺失按 0 宽容旧档
|
||||||
if (const int version = root["Version"].as<int>(0);
|
if (const int version = root["Version"].as<int>(0);
|
||||||
version > kSceneVersion) {
|
version > kSceneVersion) {
|
||||||
ENGINE_ERROR("SceneSerializer: '{}' version {} > supported {}",
|
ENGINE_ERROR("SceneSerializer: '{}' version {} > supported {}",
|
||||||
inPath, version, kSceneVersion);
|
sourceName, version, kSceneVersion);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,8 +739,53 @@ namespace Engine::Serialization {
|
|||||||
scene.UpdateTransforms();
|
scene.UpdateTransforms();
|
||||||
|
|
||||||
ENGINE_INFO("SceneSerializer: loaded {} entities from '{}'",
|
ENGINE_INFO("SceneSerializer: loaded {} entities from '{}'",
|
||||||
parsed.size(), inPath);
|
parsed.size(), sourceName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SceneSerializer::Deserialize(ECS::Scene& scene,
|
||||||
|
Assets::AssetManager& assets,
|
||||||
|
const std::string& inPath,
|
||||||
|
std::string& outEnvironmentPath)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return ParseRoot(scene, assets, root, inPath, outEnvironmentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneSerializer::DeserializeFromString(
|
||||||
|
ECS::Scene& scene,
|
||||||
|
Assets::AssetManager& assets,
|
||||||
|
const std::string& yamlText,
|
||||||
|
std::string& outEnvironmentPath)
|
||||||
|
{
|
||||||
|
YAML::Node root;
|
||||||
|
try {
|
||||||
|
root = YAML::Load(yamlText);
|
||||||
|
}
|
||||||
|
catch (const YAML::Exception& e) {
|
||||||
|
ENGINE_ERROR("SceneSerializer: snapshot parse failed ({})",
|
||||||
|
e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
ENGINE_ERROR("SceneSerializer: snapshot parse failed ({})",
|
||||||
|
e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ParseRoot(scene, assets, root, "snapshot", outEnvironmentPath);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Engine::Serialization
|
} // namespace Engine::Serialization
|
||||||
|
|||||||
Reference in New Issue
Block a user