#include "Core/PlaySessionController.h" #include "Core/EditorEvents.h" #include "Core/EventBus.h" #include "Core/IEditorContext.h" #include "Core/ISceneManager.h" #include "Core/IUndoManager.h" #include #include namespace XCEngine { namespace Editor { namespace { bool IsModifierKeyDown(const GameViewInputFrameEvent& input, XCEngine::Input::KeyCode key) { const size_t index = static_cast(key); return index < input.keyDown.size() && input.keyDown[index]; } bool IsGameViewInputActive(const GameViewInputFrameEvent& input) { return input.hovered || input.focused; } } // namespace void PlaySessionController::Attach(IEditorContext& context) { if (m_playStartRequestedHandlerId == 0) { m_playStartRequestedHandlerId = context.GetEventBus().Subscribe( [this, &context](const PlayModeStartRequestedEvent&) { StartPlay(context); }); } if (m_playStopRequestedHandlerId == 0) { m_playStopRequestedHandlerId = context.GetEventBus().Subscribe( [this, &context](const PlayModeStopRequestedEvent&) { StopPlay(context); }); } if (m_playPauseRequestedHandlerId == 0) { m_playPauseRequestedHandlerId = context.GetEventBus().Subscribe( [this, &context](const PlayModePauseRequestedEvent&) { PausePlay(context); }); } if (m_playResumeRequestedHandlerId == 0) { m_playResumeRequestedHandlerId = context.GetEventBus().Subscribe( [this, &context](const PlayModeResumeRequestedEvent&) { ResumePlay(context); }); } if (m_playStepRequestedHandlerId == 0) { m_playStepRequestedHandlerId = context.GetEventBus().Subscribe( [this, &context](const PlayModeStepRequestedEvent&) { StepPlay(context); }); } if (m_gameViewInputFrameHandlerId == 0) { m_gameViewInputFrameHandlerId = context.GetEventBus().Subscribe( [this](const GameViewInputFrameEvent& event) { m_pendingGameViewInput = event; m_hasPendingGameViewInput = true; }); } } void PlaySessionController::Detach(IEditorContext& context) { StopPlay(context); ClearRuntimeSceneSyncSubscription(context); if (m_playStartRequestedHandlerId != 0) { context.GetEventBus().Unsubscribe(m_playStartRequestedHandlerId); m_playStartRequestedHandlerId = 0; } if (m_playStopRequestedHandlerId != 0) { context.GetEventBus().Unsubscribe(m_playStopRequestedHandlerId); m_playStopRequestedHandlerId = 0; } if (m_playPauseRequestedHandlerId != 0) { context.GetEventBus().Unsubscribe(m_playPauseRequestedHandlerId); m_playPauseRequestedHandlerId = 0; } if (m_playResumeRequestedHandlerId != 0) { context.GetEventBus().Unsubscribe(m_playResumeRequestedHandlerId); m_playResumeRequestedHandlerId = 0; } if (m_playStepRequestedHandlerId != 0) { context.GetEventBus().Unsubscribe(m_playStepRequestedHandlerId); m_playStepRequestedHandlerId = 0; } if (m_gameViewInputFrameHandlerId != 0) { context.GetEventBus().Unsubscribe(m_gameViewInputFrameHandlerId); m_gameViewInputFrameHandlerId = 0; } ResetRuntimeInputBridge(); } void PlaySessionController::Update(IEditorContext& context, float deltaTime) { (void)context; if (!m_runtimeLoop.IsRunning()) { return; } ApplyGameViewInputFrame(deltaTime); m_runtimeLoop.Tick(deltaTime); } bool PlaySessionController::StartPlay(IEditorContext& context) { if (context.GetRuntimeMode() != EditorRuntimeMode::Edit) { return false; } auto& sceneManager = context.GetSceneManager(); if (!sceneManager.HasActiveScene()) { return false; } m_editorSnapshot = sceneManager.CaptureSceneSnapshot(); if (!m_editorSnapshot.hasScene) { return false; } if (!sceneManager.RestoreSceneSnapshot(m_editorSnapshot)) { return false; } sceneManager.SetSceneDocumentDirtyTrackingEnabled(false); XCEngine::Scripting::ScriptEngine::Get().SetRuntimeFixedDeltaTime(m_runtimeLoop.GetSettings().fixedDeltaTime); ResetRuntimeInputBridge(); 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{}); return true; } bool PlaySessionController::StopPlay(IEditorContext& context) { if (!IsEditorRuntimeActive(context.GetRuntimeMode())) { return false; } ClearRuntimeSceneSyncSubscription(context); auto& sceneManager = context.GetSceneManager(); m_runtimeLoop.Stop(); ResetRuntimeInputBridge(); XCEngine::Input::InputManager::Get().Shutdown(); sceneManager.SetSceneDocumentDirtyTrackingEnabled(true); if (!sceneManager.RestoreSceneSnapshot(m_editorSnapshot)) { return false; } context.GetUndoManager().ClearHistory(); context.SetRuntimeMode(EditorRuntimeMode::Edit); context.GetEventBus().Publish(PlayModeStoppedEvent{}); m_editorSnapshot = {}; return true; } bool PlaySessionController::PausePlay(IEditorContext& context) { if (context.GetRuntimeMode() != EditorRuntimeMode::Play || !m_runtimeLoop.IsRunning()) { return false; } m_runtimeLoop.Pause(); context.SetRuntimeMode(EditorRuntimeMode::Paused); context.GetEventBus().Publish(PlayModePausedEvent{}); return true; } bool PlaySessionController::ResumePlay(IEditorContext& context) { if (context.GetRuntimeMode() != EditorRuntimeMode::Paused || !m_runtimeLoop.IsRunning()) { return false; } m_runtimeLoop.Resume(); context.SetRuntimeMode(EditorRuntimeMode::Play); context.GetEventBus().Publish(PlayModeResumedEvent{}); return true; } bool PlaySessionController::StepPlay(IEditorContext& context) { if (context.GetRuntimeMode() != EditorRuntimeMode::Paused || !m_runtimeLoop.IsRunning()) { return false; } m_runtimeLoop.StepFrame(); return true; } void PlaySessionController::ResetRuntimeInputBridge() { m_pendingGameViewInput = {}; m_appliedGameViewInput = {}; m_hasPendingGameViewInput = false; } void PlaySessionController::EnsureRuntimeSceneSyncSubscription(IEditorContext& context) { if (m_runtimeSceneChangedHandlerId != 0) { return; } m_runtimeSceneChangedHandlerId = context.GetEventBus().Subscribe( [this, &context](const SceneChangedEvent&) { SyncRuntimeSceneIfNeeded(context); }); } void PlaySessionController::ClearRuntimeSceneSyncSubscription(IEditorContext& context) { if (m_runtimeSceneChangedHandlerId == 0) { return; } context.GetEventBus().Unsubscribe(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; using XCEngine::Input::MouseButton; InputManager& inputManager = InputManager::Get(); inputManager.Update(deltaTime); const GameViewInputFrameEvent input = m_hasPendingGameViewInput ? m_pendingGameViewInput : GameViewInputFrameEvent{}; m_hasPendingGameViewInput = false; const bool inputActive = IsGameViewInputActive(input); const bool alt = inputActive && (IsModifierKeyDown(input, KeyCode::LeftAlt) || IsModifierKeyDown(input, KeyCode::RightAlt)); const bool ctrl = inputActive && (IsModifierKeyDown(input, KeyCode::LeftCtrl) || IsModifierKeyDown(input, KeyCode::RightCtrl)); const bool shift = inputActive && (IsModifierKeyDown(input, KeyCode::LeftShift) || IsModifierKeyDown(input, KeyCode::RightShift)); for (size_t index = 0; index < input.keyDown.size(); ++index) { const bool wasDown = m_appliedGameViewInput.keyDown[index]; const bool isDown = inputActive && input.keyDown[index]; if (wasDown == isDown) { continue; } const KeyCode key = static_cast(index); if (isDown) { inputManager.ProcessKeyDown(key, false, alt, ctrl, shift, false); } else { inputManager.ProcessKeyUp(key, alt, ctrl, shift, false); } } for (size_t index = 0; index < input.mouseButtonDown.size(); ++index) { const bool wasDown = m_appliedGameViewInput.mouseButtonDown[index]; const bool isDown = inputActive && input.mouseButtonDown[index]; if (wasDown == isDown) { continue; } inputManager.ProcessMouseButton( static_cast(index), isDown, static_cast(input.mousePosition.x), static_cast(input.mousePosition.y)); } if (inputActive && (input.mousePosition != m_appliedGameViewInput.mousePosition || input.mouseDelta != XCEngine::Math::Vector2::Zero())) { inputManager.ProcessMouseMove( static_cast(input.mousePosition.x), static_cast(input.mousePosition.y), static_cast(input.mouseDelta.x), static_cast(input.mouseDelta.y)); } if (inputActive && input.mouseWheel != 0.0f) { inputManager.ProcessMouseWheel( input.mouseWheel, static_cast(input.mousePosition.x), static_cast(input.mousePosition.y)); } m_appliedGameViewInput = {}; if (inputActive) { m_appliedGameViewInput = input; } } } // namespace Editor } // namespace XCEngine