Fix play mode runtime scene replacement

This commit is contained in:
2026-04-08 00:33:50 +08:00
parent 69cb80ccd4
commit 23ff4004f4
9 changed files with 149 additions and 0 deletions

View File

@@ -72,6 +72,7 @@ void PlaySessionController::Attach(IEditorContext& context) {
void PlaySessionController::Detach(IEditorContext& context) { void PlaySessionController::Detach(IEditorContext& context) {
StopPlay(context); StopPlay(context);
ClearRuntimeSceneSyncSubscription(context);
if (m_playStartRequestedHandlerId != 0) { if (m_playStartRequestedHandlerId != 0) {
context.GetEventBus().Unsubscribe<PlayModeStartRequestedEvent>(m_playStartRequestedHandlerId); context.GetEventBus().Unsubscribe<PlayModeStartRequestedEvent>(m_playStartRequestedHandlerId);
@@ -141,6 +142,7 @@ bool PlaySessionController::StartPlay(IEditorContext& context) {
XCEngine::Input::InputManager::Get().Shutdown(); XCEngine::Input::InputManager::Get().Shutdown();
XCEngine::Input::InputManager::Get().Initialize(nullptr); XCEngine::Input::InputManager::Get().Initialize(nullptr);
m_runtimeLoop.Start(sceneManager.GetScene()); m_runtimeLoop.Start(sceneManager.GetScene());
EnsureRuntimeSceneSyncSubscription(context);
context.GetUndoManager().ClearHistory(); context.GetUndoManager().ClearHistory();
context.SetRuntimeMode(EditorRuntimeMode::Play); context.SetRuntimeMode(EditorRuntimeMode::Play);
context.GetEventBus().Publish(PlayModeStartedEvent{}); context.GetEventBus().Publish(PlayModeStartedEvent{});
@@ -152,6 +154,7 @@ bool PlaySessionController::StopPlay(IEditorContext& context) {
return false; return false;
} }
ClearRuntimeSceneSyncSubscription(context);
auto& sceneManager = context.GetSceneManager(); auto& sceneManager = context.GetSceneManager();
m_runtimeLoop.Stop(); m_runtimeLoop.Stop();
ResetRuntimeInputBridge(); ResetRuntimeInputBridge();
@@ -206,6 +209,39 @@ void PlaySessionController::ResetRuntimeInputBridge() {
m_hasPendingGameViewInput = false; m_hasPendingGameViewInput = false;
} }
void PlaySessionController::EnsureRuntimeSceneSyncSubscription(IEditorContext& context) {
if (m_runtimeSceneChangedHandlerId != 0) {
return;
}
m_runtimeSceneChangedHandlerId = context.GetEventBus().Subscribe<SceneChangedEvent>(
[this, &context](const SceneChangedEvent&) {
SyncRuntimeSceneIfNeeded(context);
});
}
void PlaySessionController::ClearRuntimeSceneSyncSubscription(IEditorContext& context) {
if (m_runtimeSceneChangedHandlerId == 0) {
return;
}
context.GetEventBus().Unsubscribe<SceneChangedEvent>(m_runtimeSceneChangedHandlerId);
m_runtimeSceneChangedHandlerId = 0;
}
void PlaySessionController::SyncRuntimeSceneIfNeeded(IEditorContext& context) {
if (!IsEditorRuntimeActive(context.GetRuntimeMode())) {
return;
}
auto* currentScene = context.GetSceneManager().GetScene();
if (currentScene == m_runtimeLoop.GetScene()) {
return;
}
m_runtimeLoop.ReplaceScene(currentScene);
}
void PlaySessionController::ApplyGameViewInputFrame(float deltaTime) { void PlaySessionController::ApplyGameViewInputFrame(float deltaTime) {
using XCEngine::Input::InputManager; using XCEngine::Input::InputManager;
using XCEngine::Input::KeyCode; using XCEngine::Input::KeyCode;

View File

@@ -28,9 +28,14 @@ public:
bool ResumePlay(IEditorContext& context); bool ResumePlay(IEditorContext& context);
bool StepPlay(IEditorContext& context); bool StepPlay(IEditorContext& context);
::XCEngine::Components::Scene* GetRuntimeScene() const { return m_runtimeLoop.GetScene(); }
private: private:
void ResetRuntimeInputBridge(); void ResetRuntimeInputBridge();
void ApplyGameViewInputFrame(float deltaTime); void ApplyGameViewInputFrame(float deltaTime);
void EnsureRuntimeSceneSyncSubscription(IEditorContext& context);
void ClearRuntimeSceneSyncSubscription(IEditorContext& context);
void SyncRuntimeSceneIfNeeded(IEditorContext& context);
uint64_t m_playStartRequestedHandlerId = 0; uint64_t m_playStartRequestedHandlerId = 0;
uint64_t m_playStopRequestedHandlerId = 0; uint64_t m_playStopRequestedHandlerId = 0;
@@ -38,6 +43,7 @@ private:
uint64_t m_playResumeRequestedHandlerId = 0; uint64_t m_playResumeRequestedHandlerId = 0;
uint64_t m_playStepRequestedHandlerId = 0; uint64_t m_playStepRequestedHandlerId = 0;
uint64_t m_gameViewInputFrameHandlerId = 0; uint64_t m_gameViewInputFrameHandlerId = 0;
uint64_t m_runtimeSceneChangedHandlerId = 0;
SceneSnapshot m_editorSnapshot = {}; SceneSnapshot m_editorSnapshot = {};
GameViewInputFrameEvent m_pendingGameViewInput = {}; GameViewInputFrameEvent m_pendingGameViewInput = {};
GameViewInputFrameEvent m_appliedGameViewInput = {}; GameViewInputFrameEvent m_appliedGameViewInput = {};

View File

@@ -22,6 +22,7 @@ public:
void Start(Scene* scene); void Start(Scene* scene);
void Stop(); void Stop();
void ReplaceScene(Scene* scene);
void Tick(float deltaTime); void Tick(float deltaTime);
void Pause(); void Pause();

View File

@@ -34,6 +34,7 @@ public:
void Start(Scene* scene); void Start(Scene* scene);
void Stop(); void Stop();
void ReplaceScene(Scene* scene);
void FixedUpdate(float fixedDeltaTime); void FixedUpdate(float fixedDeltaTime);
void Update(float deltaTime); void Update(float deltaTime);

View File

@@ -28,6 +28,7 @@ public:
void OnRuntimeStart(Components::Scene* scene); void OnRuntimeStart(Components::Scene* scene);
void OnRuntimeStop(); void OnRuntimeStop();
void OnRuntimeSceneReplaced(Components::Scene* scene);
void OnFixedUpdate(float fixedDeltaTime); void OnFixedUpdate(float fixedDeltaTime);
void OnUpdate(float deltaTime); void OnUpdate(float deltaTime);

View File

@@ -44,6 +44,12 @@ void RuntimeLoop::Stop() {
m_stepRequested = false; m_stepRequested = false;
} }
void RuntimeLoop::ReplaceScene(Scene* scene) {
m_sceneRuntime.ReplaceScene(scene);
m_fixedAccumulator = 0.0f;
m_stepRequested = false;
}
void RuntimeLoop::Tick(float deltaTime) { void RuntimeLoop::Tick(float deltaTime) {
if (!IsRunning()) { if (!IsRunning()) {
return; return;

View File

@@ -46,6 +46,18 @@ void SceneRuntime::Stop() {
m_scene = nullptr; m_scene = nullptr;
} }
void SceneRuntime::ReplaceScene(Scene* scene) {
if (!m_running) {
m_scene = scene;
m_uiRuntime->Reset();
return;
}
m_scene = scene;
m_uiRuntime->Reset();
Scripting::ScriptEngine::Get().OnRuntimeSceneReplaced(scene);
}
void SceneRuntime::FixedUpdate(float fixedDeltaTime) { void SceneRuntime::FixedUpdate(float fixedDeltaTime) {
if (!m_running || !m_scene || !m_scene->IsActive()) { if (!m_running || !m_scene || !m_scene->IsActive()) {
return; return;

View File

@@ -141,6 +141,54 @@ void ScriptEngine::OnRuntimeStop() {
m_runtime->OnRuntimeStop(stoppedScene); m_runtime->OnRuntimeStop(stoppedScene);
} }
void ScriptEngine::OnRuntimeSceneReplaced(Components::Scene* scene) {
if (!m_runtimeRunning) {
m_runtimeScene = nullptr;
m_runtimeSceneCreatedSubscription = 0;
m_scriptStates.clear();
m_scriptOrder.clear();
return;
}
const float configuredFixedDeltaTime = m_runtimeFixedDeltaTime;
m_runtime->OnRuntimeStop(nullptr);
m_runtimeFixedDeltaTime = configuredFixedDeltaTime;
m_runtimeScene = scene;
m_runtimeSceneCreatedSubscription = 0;
m_scriptStates.clear();
m_scriptOrder.clear();
if (!m_runtimeScene) {
return;
}
m_runtime->OnRuntimeStart(m_runtimeScene);
m_runtimeSceneCreatedSubscription = m_runtimeScene->OnGameObjectCreated().Subscribe(
[this](Components::GameObject* gameObject) {
HandleGameObjectCreated(gameObject);
});
for (Components::GameObject* root : m_runtimeScene->GetRootGameObjects()) {
CollectScriptComponents(root);
}
const std::vector<ScriptInstanceKey> startupKeys = m_scriptOrder;
for (const ScriptInstanceKey& key : startupKeys) {
auto it = m_scriptStates.find(key);
if (it == m_scriptStates.end()) {
continue;
}
ScriptInstanceState& state = it->second;
if (!ShouldScriptRun(state)) {
continue;
}
EnsureScriptReady(state, true);
}
}
void ScriptEngine::OnFixedUpdate(float fixedDeltaTime) { void ScriptEngine::OnFixedUpdate(float fixedDeltaTime) {
if (!m_runtimeRunning) { if (!m_runtimeRunning) {
return; return;

View File

@@ -3,6 +3,7 @@
#include "Core/EditorContext.h" #include "Core/EditorContext.h"
#include "Core/EditorEvents.h" #include "Core/EditorEvents.h"
#include "Core/PlaySessionController.h" #include "Core/PlaySessionController.h"
#include "Commands/EntityCommands.h"
#include <XCEngine/Core/Math/Vector3.h> #include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Input/InputManager.h> #include <XCEngine/Input/InputManager.h>
@@ -236,5 +237,42 @@ TEST_F(PlaySessionControllerTest, GameViewInputFramesDriveAndReleaseRuntimeInput
m_controller.Detach(m_context); m_controller.Detach(m_context);
} }
TEST_F(PlaySessionControllerTest, RuntimeSceneUndoRedoRebindsPlaySessionBeforeStop) {
auto* editorEntity = m_context.GetSceneManager().CreateEntity("Persistent");
ASSERT_NE(editorEntity, nullptr);
const uint64_t editorEntityId = editorEntity->GetID();
ASSERT_TRUE(m_controller.StartPlay(m_context));
ASSERT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Play);
const auto* runtimeSceneBeforeUndo = m_context.GetSceneManager().GetScene();
ASSERT_NE(runtimeSceneBeforeUndo, nullptr);
ASSERT_EQ(m_controller.GetRuntimeScene(), runtimeSceneBeforeUndo);
auto* runtimeEntity = Commands::CreateEmptyEntity(m_context, nullptr, "Create Runtime Entity", "RuntimeOnly");
ASSERT_NE(runtimeEntity, nullptr);
const uint64_t runtimeEntityId = runtimeEntity->GetID();
ASSERT_EQ(m_controller.GetRuntimeScene(), m_context.GetSceneManager().GetScene());
m_context.GetUndoManager().Undo();
const auto* runtimeSceneAfterUndo = m_context.GetSceneManager().GetScene();
ASSERT_NE(runtimeSceneAfterUndo, nullptr);
EXPECT_NE(runtimeSceneAfterUndo, runtimeSceneBeforeUndo);
EXPECT_EQ(m_controller.GetRuntimeScene(), runtimeSceneAfterUndo);
EXPECT_EQ(m_context.GetSceneManager().GetEntity(runtimeEntityId), nullptr);
m_context.GetUndoManager().Redo();
const auto* runtimeSceneAfterRedo = m_context.GetSceneManager().GetScene();
ASSERT_NE(runtimeSceneAfterRedo, nullptr);
EXPECT_NE(runtimeSceneAfterRedo, runtimeSceneAfterUndo);
EXPECT_EQ(m_controller.GetRuntimeScene(), runtimeSceneAfterRedo);
EXPECT_NE(m_context.GetSceneManager().GetEntity(runtimeEntityId), nullptr);
ASSERT_TRUE(m_controller.StopPlay(m_context));
EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Edit);
ASSERT_NE(m_context.GetSceneManager().GetEntity(editorEntityId), nullptr);
EXPECT_EQ(m_context.GetSceneManager().GetEntity(runtimeEntityId), nullptr);
}
} // namespace } // namespace
} // namespace XCEngine::Editor } // namespace XCEngine::Editor