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:
@@ -55,6 +55,23 @@ namespace Engine::Serialization {
|
||||
Assets::AssetManager& assets,
|
||||
const std::string& inPath,
|
||||
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
|
||||
|
||||
@@ -384,47 +384,65 @@ namespace Engine::Serialization {
|
||||
|
||||
// ---- 保存 ----
|
||||
|
||||
// Emitter 构建段(文件版与内存版共用):documentName 空则不写
|
||||
// 文档头 "Scene" 字段(快照无文件名,该字段是注释性的)
|
||||
static std::string SerializeDoc(const ECS::Scene& scene,
|
||||
Assets::AssetManager& assets,
|
||||
const std::string& documentName,
|
||||
const std::string& environmentPath)
|
||||
{
|
||||
// float32 无损往返需 9 位有效数字(std::numeric_limits
|
||||
// <float>::max_digits10)
|
||||
YAML::Emitter out;
|
||||
out.SetFloatPrecision(9);
|
||||
|
||||
out << YAML::BeginMap;
|
||||
if (!documentName.empty()) {
|
||||
out << YAML::Key << "Scene" << YAML::Value << documentName;
|
||||
}
|
||||
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<ECS::IdComponent>().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 {};
|
||||
}
|
||||
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 {
|
||||
// float32 无损往返需 9 位有效数字(std::numeric_limits
|
||||
// <float>::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<ECS::IdComponent>().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;
|
||||
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);
|
||||
@@ -433,7 +451,7 @@ namespace Engine::Serialization {
|
||||
outPath);
|
||||
return false;
|
||||
}
|
||||
file << out.c_str();
|
||||
file << doc;
|
||||
return true;
|
||||
}
|
||||
catch (const YAML::Exception& e) {
|
||||
@@ -446,35 +464,42 @@ namespace Engine::Serialization {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 加载 ----
|
||||
|
||||
bool SceneSerializer::Deserialize(ECS::Scene& scene,
|
||||
Assets::AssetManager& assets,
|
||||
const std::string& inPath,
|
||||
std::string& outEnvironmentPath)
|
||||
std::string SceneSerializer::SerializeToString(
|
||||
const ECS::Scene& scene,
|
||||
Assets::AssetManager& assets,
|
||||
const std::string& documentName,
|
||||
const std::string& environmentPath)
|
||||
{
|
||||
outEnvironmentPath.clear();
|
||||
|
||||
YAML::Node root;
|
||||
try {
|
||||
root = YAML::LoadFile(inPath);
|
||||
return SerializeDoc(scene, assets, documentName, environmentPath);
|
||||
}
|
||||
catch (const YAML::Exception& e) {
|
||||
ENGINE_ERROR("SceneSerializer: cannot parse '{}' ({})", inPath,
|
||||
e.what());
|
||||
return false;
|
||||
ENGINE_ERROR("SceneSerializer: snapshot failed ({})", e.what());
|
||||
return {};
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
ENGINE_ERROR("SceneSerializer: cannot read '{}' ({})", inPath,
|
||||
e.what());
|
||||
return false;
|
||||
ENGINE_ERROR("SceneSerializer: snapshot failed ({})", e.what());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 加载 ----
|
||||
|
||||
// 解析段(文件版与内存快照共用):root → 原子换血重建。
|
||||
// sourceName 仅用于日志(文件路径或 "snapshot")
|
||||
static bool ParseRoot(ECS::Scene& scene,
|
||||
Assets::AssetManager& assets,
|
||||
const YAML::Node& root,
|
||||
const std::string& sourceName,
|
||||
std::string& outEnvironmentPath)
|
||||
{
|
||||
outEnvironmentPath.clear();
|
||||
|
||||
// Version 闸:高版本拒载(结构未知),缺失按 0 宽容旧档
|
||||
if (const int version = root["Version"].as<int>(0);
|
||||
version > kSceneVersion) {
|
||||
ENGINE_ERROR("SceneSerializer: '{}' version {} > supported {}",
|
||||
inPath, version, kSceneVersion);
|
||||
sourceName, version, kSceneVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -714,8 +739,53 @@ namespace Engine::Serialization {
|
||||
scene.UpdateTransforms();
|
||||
|
||||
ENGINE_INFO("SceneSerializer: loaded {} entities from '{}'",
|
||||
parsed.size(), inPath);
|
||||
parsed.size(), sourceName);
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user