#include #include "Core/EditorContext.h" #include "Core/EditorEvents.h" #include "Core/PlaySessionController.h" #include "Commands/EntityCommands.h" #include #include namespace XCEngine::Editor { namespace { GameViewInputFrameEvent CreateGameViewInputFrame( bool focused, bool hovered, std::initializer_list keys = {}, std::initializer_list mouseButtons = {}, XCEngine::Math::Vector2 mousePosition = XCEngine::Math::Vector2::Zero(), XCEngine::Math::Vector2 mouseDelta = XCEngine::Math::Vector2::Zero(), float mouseWheel = 0.0f) { GameViewInputFrameEvent event = {}; event.focused = focused; event.hovered = hovered; event.mousePosition = mousePosition; event.mouseDelta = mouseDelta; event.mouseWheel = mouseWheel; for (const XCEngine::Input::KeyCode key : keys) { const size_t index = static_cast(key); if (index < event.keyDown.size()) { event.keyDown[index] = true; } } for (const XCEngine::Input::MouseButton button : mouseButtons) { const size_t index = static_cast(button); if (index < event.mouseButtonDown.size()) { event.mouseButtonDown[index] = true; } } return event; } class PlaySessionControllerTest : public ::testing::Test { protected: void SetUp() override { XCEngine::Input::InputManager::Get().Shutdown(); m_context.GetSceneManager().NewScene("Play Session Scene"); } void TearDown() override { m_controller.Detach(m_context); XCEngine::Input::InputManager::Get().Shutdown(); } EditorContext m_context; PlaySessionController m_controller; }; TEST_F(PlaySessionControllerTest, StartPlayClonesCurrentSceneAndStopRestoresEditorScene) { auto* editorEntity = m_context.GetSceneManager().CreateEntity("Persistent"); ASSERT_NE(editorEntity, nullptr); const uint64_t editorEntityId = editorEntity->GetID(); editorEntity->GetTransform()->SetLocalPosition(Math::Vector3(1.0f, 2.0f, 3.0f)); int startedCount = 0; int stoppedCount = 0; const uint64_t startedSubscription = m_context.GetEventBus().Subscribe( [&](const PlayModeStartedEvent&) { ++startedCount; }); const uint64_t stoppedSubscription = m_context.GetEventBus().Subscribe( [&](const PlayModeStoppedEvent&) { ++stoppedCount; }); ASSERT_TRUE(m_controller.StartPlay(m_context)); EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Play); EXPECT_EQ(startedCount, 1); auto* runtimeEntity = m_context.GetSceneManager().GetEntity(editorEntityId); ASSERT_NE(runtimeEntity, nullptr); runtimeEntity->GetTransform()->SetLocalPosition(Math::Vector3(8.0f, 9.0f, 10.0f)); auto* runtimeOnlyEntity = m_context.GetSceneManager().CreateEntity("RuntimeOnly"); ASSERT_NE(runtimeOnlyEntity, nullptr); const uint64_t runtimeOnlyId = runtimeOnlyEntity->GetID(); ASSERT_TRUE(m_controller.StopPlay(m_context)); EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Edit); EXPECT_EQ(stoppedCount, 1); auto* restoredEntity = m_context.GetSceneManager().GetEntity(editorEntityId); ASSERT_NE(restoredEntity, nullptr); EXPECT_EQ(m_context.GetSceneManager().GetEntity(runtimeOnlyId), nullptr); const Math::Vector3 restoredPosition = restoredEntity->GetTransform()->GetLocalPosition(); EXPECT_NEAR(restoredPosition.x, 1.0f, 1e-4f); EXPECT_NEAR(restoredPosition.y, 2.0f, 1e-4f); EXPECT_NEAR(restoredPosition.z, 3.0f, 1e-4f); m_context.GetEventBus().Unsubscribe(startedSubscription); m_context.GetEventBus().Unsubscribe(stoppedSubscription); } TEST_F(PlaySessionControllerTest, StartAndStopRequestsRouteThroughEventBus) { auto* editorEntity = m_context.GetSceneManager().CreateEntity("Persistent"); ASSERT_NE(editorEntity, nullptr); m_controller.Attach(m_context); m_context.GetEventBus().Publish(PlayModeStartRequestedEvent{}); EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Play); m_context.GetEventBus().Publish(PlayModeStopRequestedEvent{}); EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Edit); m_controller.Detach(m_context); } TEST_F(PlaySessionControllerTest, PauseResumeAndStepRequestsDrivePlayStateMachine) { auto* editorEntity = m_context.GetSceneManager().CreateEntity("Persistent"); ASSERT_NE(editorEntity, nullptr); int pausedCount = 0; int resumedCount = 0; const uint64_t pausedSubscription = m_context.GetEventBus().Subscribe( [&](const PlayModePausedEvent&) { ++pausedCount; }); const uint64_t resumedSubscription = m_context.GetEventBus().Subscribe( [&](const PlayModeResumedEvent&) { ++resumedCount; }); m_controller.Attach(m_context); m_context.GetEventBus().Publish(PlayModeStartRequestedEvent{}); EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Play); m_context.GetEventBus().Publish(PlayModePauseRequestedEvent{}); EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Paused); EXPECT_EQ(pausedCount, 1); m_context.GetEventBus().Publish(PlayModeStepRequestedEvent{}); EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Paused); EXPECT_EQ(pausedCount, 1); EXPECT_EQ(resumedCount, 0); m_context.GetEventBus().Publish(PlayModeResumeRequestedEvent{}); EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Play); EXPECT_EQ(resumedCount, 1); m_context.GetEventBus().Publish(PlayModeStopRequestedEvent{}); EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Edit); m_controller.Detach(m_context); m_context.GetEventBus().Unsubscribe(pausedSubscription); m_context.GetEventBus().Unsubscribe(resumedSubscription); } TEST_F(PlaySessionControllerTest, GameViewInputFramesDoNotAffectInputManagerOutsidePlayMode) { m_controller.Attach(m_context); m_context.GetEventBus().Publish(CreateGameViewInputFrame( true, true, {XCEngine::Input::KeyCode::A}, {XCEngine::Input::MouseButton::Left}, XCEngine::Math::Vector2(120.0f, 48.0f), XCEngine::Math::Vector2(3.0f, -2.0f), 1.0f)); m_controller.Update(m_context, 0.016f); auto& inputManager = XCEngine::Input::InputManager::Get(); EXPECT_FALSE(inputManager.IsKeyDown(XCEngine::Input::KeyCode::A)); EXPECT_FALSE(inputManager.IsMouseButtonDown(XCEngine::Input::MouseButton::Left)); EXPECT_EQ(inputManager.GetMousePosition(), XCEngine::Math::Vector2::Zero()); EXPECT_FLOAT_EQ(inputManager.GetMouseScrollDelta(), 0.0f); m_controller.Detach(m_context); } TEST_F(PlaySessionControllerTest, GameViewInputFramesDriveAndReleaseRuntimeInputDuringPlayMode) { auto* editorEntity = m_context.GetSceneManager().CreateEntity("Persistent"); ASSERT_NE(editorEntity, nullptr); m_controller.Attach(m_context); ASSERT_TRUE(m_controller.StartPlay(m_context)); m_context.GetEventBus().Publish(CreateGameViewInputFrame( true, true, {XCEngine::Input::KeyCode::A, XCEngine::Input::KeyCode::Space}, {XCEngine::Input::MouseButton::Left}, XCEngine::Math::Vector2(120.0f, 48.0f), XCEngine::Math::Vector2(3.0f, -2.0f), 1.0f)); m_controller.Update(m_context, 0.016f); auto& inputManager = XCEngine::Input::InputManager::Get(); EXPECT_TRUE(inputManager.IsKeyDown(XCEngine::Input::KeyCode::A)); EXPECT_TRUE(inputManager.IsKeyPressed(XCEngine::Input::KeyCode::A)); EXPECT_TRUE(inputManager.IsKeyDown(XCEngine::Input::KeyCode::Space)); EXPECT_TRUE(inputManager.IsMouseButtonDown(XCEngine::Input::MouseButton::Left)); EXPECT_TRUE(inputManager.IsMouseButtonClicked(XCEngine::Input::MouseButton::Left)); EXPECT_EQ(inputManager.GetMousePosition(), XCEngine::Math::Vector2(120.0f, 48.0f)); EXPECT_EQ(inputManager.GetMouseDelta(), XCEngine::Math::Vector2(3.0f, -2.0f)); EXPECT_FLOAT_EQ(inputManager.GetMouseScrollDelta(), 1.0f); m_context.GetEventBus().Publish(CreateGameViewInputFrame( true, true, {XCEngine::Input::KeyCode::A, XCEngine::Input::KeyCode::Space}, {XCEngine::Input::MouseButton::Left}, XCEngine::Math::Vector2(120.0f, 48.0f))); m_controller.Update(m_context, 0.016f); EXPECT_TRUE(inputManager.IsKeyDown(XCEngine::Input::KeyCode::A)); EXPECT_FALSE(inputManager.IsKeyPressed(XCEngine::Input::KeyCode::A)); EXPECT_TRUE(inputManager.IsMouseButtonDown(XCEngine::Input::MouseButton::Left)); EXPECT_FALSE(inputManager.IsMouseButtonClicked(XCEngine::Input::MouseButton::Left)); EXPECT_EQ(inputManager.GetMouseDelta(), XCEngine::Math::Vector2::Zero()); EXPECT_FLOAT_EQ(inputManager.GetMouseScrollDelta(), 0.0f); m_context.GetEventBus().Publish(GameViewInputFrameEvent{}); m_controller.Update(m_context, 0.016f); EXPECT_FALSE(inputManager.IsKeyDown(XCEngine::Input::KeyCode::A)); EXPECT_FALSE(inputManager.IsKeyDown(XCEngine::Input::KeyCode::Space)); EXPECT_FALSE(inputManager.IsMouseButtonDown(XCEngine::Input::MouseButton::Left)); 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