Fix play mode runtime scene replacement
This commit is contained in:
@@ -72,6 +72,7 @@ void PlaySessionController::Attach(IEditorContext& context) {
|
||||
|
||||
void PlaySessionController::Detach(IEditorContext& context) {
|
||||
StopPlay(context);
|
||||
ClearRuntimeSceneSyncSubscription(context);
|
||||
|
||||
if (m_playStartRequestedHandlerId != 0) {
|
||||
context.GetEventBus().Unsubscribe<PlayModeStartRequestedEvent>(m_playStartRequestedHandlerId);
|
||||
@@ -141,6 +142,7 @@ bool PlaySessionController::StartPlay(IEditorContext& context) {
|
||||
XCEngine::Input::InputManager::Get().Shutdown();
|
||||
XCEngine::Input::InputManager::Get().Initialize(nullptr);
|
||||
m_runtimeLoop.Start(sceneManager.GetScene());
|
||||
EnsureRuntimeSceneSyncSubscription(context);
|
||||
context.GetUndoManager().ClearHistory();
|
||||
context.SetRuntimeMode(EditorRuntimeMode::Play);
|
||||
context.GetEventBus().Publish(PlayModeStartedEvent{});
|
||||
@@ -152,6 +154,7 @@ bool PlaySessionController::StopPlay(IEditorContext& context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ClearRuntimeSceneSyncSubscription(context);
|
||||
auto& sceneManager = context.GetSceneManager();
|
||||
m_runtimeLoop.Stop();
|
||||
ResetRuntimeInputBridge();
|
||||
@@ -206,6 +209,39 @@ void PlaySessionController::ResetRuntimeInputBridge() {
|
||||
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) {
|
||||
using XCEngine::Input::InputManager;
|
||||
using XCEngine::Input::KeyCode;
|
||||
|
||||
@@ -28,9 +28,14 @@ public:
|
||||
bool ResumePlay(IEditorContext& context);
|
||||
bool StepPlay(IEditorContext& context);
|
||||
|
||||
::XCEngine::Components::Scene* GetRuntimeScene() const { return m_runtimeLoop.GetScene(); }
|
||||
|
||||
private:
|
||||
void ResetRuntimeInputBridge();
|
||||
void ApplyGameViewInputFrame(float deltaTime);
|
||||
void EnsureRuntimeSceneSyncSubscription(IEditorContext& context);
|
||||
void ClearRuntimeSceneSyncSubscription(IEditorContext& context);
|
||||
void SyncRuntimeSceneIfNeeded(IEditorContext& context);
|
||||
|
||||
uint64_t m_playStartRequestedHandlerId = 0;
|
||||
uint64_t m_playStopRequestedHandlerId = 0;
|
||||
@@ -38,6 +43,7 @@ private:
|
||||
uint64_t m_playResumeRequestedHandlerId = 0;
|
||||
uint64_t m_playStepRequestedHandlerId = 0;
|
||||
uint64_t m_gameViewInputFrameHandlerId = 0;
|
||||
uint64_t m_runtimeSceneChangedHandlerId = 0;
|
||||
SceneSnapshot m_editorSnapshot = {};
|
||||
GameViewInputFrameEvent m_pendingGameViewInput = {};
|
||||
GameViewInputFrameEvent m_appliedGameViewInput = {};
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
|
||||
void Start(Scene* scene);
|
||||
void Stop();
|
||||
void ReplaceScene(Scene* scene);
|
||||
|
||||
void Tick(float deltaTime);
|
||||
void Pause();
|
||||
|
||||
@@ -34,6 +34,7 @@ public:
|
||||
|
||||
void Start(Scene* scene);
|
||||
void Stop();
|
||||
void ReplaceScene(Scene* scene);
|
||||
|
||||
void FixedUpdate(float fixedDeltaTime);
|
||||
void Update(float deltaTime);
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
|
||||
void OnRuntimeStart(Components::Scene* scene);
|
||||
void OnRuntimeStop();
|
||||
void OnRuntimeSceneReplaced(Components::Scene* scene);
|
||||
|
||||
void OnFixedUpdate(float fixedDeltaTime);
|
||||
void OnUpdate(float deltaTime);
|
||||
|
||||
@@ -44,6 +44,12 @@ void RuntimeLoop::Stop() {
|
||||
m_stepRequested = false;
|
||||
}
|
||||
|
||||
void RuntimeLoop::ReplaceScene(Scene* scene) {
|
||||
m_sceneRuntime.ReplaceScene(scene);
|
||||
m_fixedAccumulator = 0.0f;
|
||||
m_stepRequested = false;
|
||||
}
|
||||
|
||||
void RuntimeLoop::Tick(float deltaTime) {
|
||||
if (!IsRunning()) {
|
||||
return;
|
||||
|
||||
@@ -46,6 +46,18 @@ void SceneRuntime::Stop() {
|
||||
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) {
|
||||
if (!m_running || !m_scene || !m_scene->IsActive()) {
|
||||
return;
|
||||
|
||||
@@ -141,6 +141,54 @@ void ScriptEngine::OnRuntimeStop() {
|
||||
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) {
|
||||
if (!m_runtimeRunning) {
|
||||
return;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Core/EditorContext.h"
|
||||
#include "Core/EditorEvents.h"
|
||||
#include "Core/PlaySessionController.h"
|
||||
#include "Commands/EntityCommands.h"
|
||||
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Input/InputManager.h>
|
||||
@@ -236,5 +237,42 @@ TEST_F(PlaySessionControllerTest, GameViewInputFramesDriveAndReleaseRuntimeInput
|
||||
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 XCEngine::Editor
|
||||
|
||||
Reference in New Issue
Block a user