2026-04-02 19:37:35 +08:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
|
|
|
|
#include "Core/EditorContext.h"
|
|
|
|
|
#include "Core/EditorEvents.h"
|
|
|
|
|
#include "Core/PlaySessionController.h"
|
2026-04-08 00:33:50 +08:00
|
|
|
#include "Commands/EntityCommands.h"
|
2026-04-02 19:37:35 +08:00
|
|
|
|
|
|
|
|
#include <XCEngine/Core/Math/Vector3.h>
|
2026-04-03 13:22:30 +08:00
|
|
|
#include <XCEngine/Input/InputManager.h>
|
2026-04-02 19:37:35 +08:00
|
|
|
|
|
|
|
|
namespace XCEngine::Editor {
|
|
|
|
|
namespace {
|
|
|
|
|
|
2026-04-03 13:22:30 +08:00
|
|
|
GameViewInputFrameEvent CreateGameViewInputFrame(
|
|
|
|
|
bool focused,
|
|
|
|
|
bool hovered,
|
|
|
|
|
std::initializer_list<XCEngine::Input::KeyCode> keys = {},
|
|
|
|
|
std::initializer_list<XCEngine::Input::MouseButton> 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<size_t>(key);
|
|
|
|
|
if (index < event.keyDown.size()) {
|
|
|
|
|
event.keyDown[index] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const XCEngine::Input::MouseButton button : mouseButtons) {
|
|
|
|
|
const size_t index = static_cast<size_t>(button);
|
|
|
|
|
if (index < event.mouseButtonDown.size()) {
|
|
|
|
|
event.mouseButtonDown[index] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return event;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:37:35 +08:00
|
|
|
class PlaySessionControllerTest : public ::testing::Test {
|
|
|
|
|
protected:
|
|
|
|
|
void SetUp() override {
|
2026-04-03 13:22:30 +08:00
|
|
|
XCEngine::Input::InputManager::Get().Shutdown();
|
2026-04-02 19:37:35 +08:00
|
|
|
m_context.GetSceneManager().NewScene("Play Session Scene");
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 13:22:30 +08:00
|
|
|
void TearDown() override {
|
|
|
|
|
m_controller.Detach(m_context);
|
|
|
|
|
XCEngine::Input::InputManager::Get().Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:37:35 +08:00
|
|
|
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<PlayModeStartedEvent>(
|
|
|
|
|
[&](const PlayModeStartedEvent&) {
|
|
|
|
|
++startedCount;
|
|
|
|
|
});
|
|
|
|
|
const uint64_t stoppedSubscription = m_context.GetEventBus().Subscribe<PlayModeStoppedEvent>(
|
|
|
|
|
[&](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<PlayModeStartedEvent>(startedSubscription);
|
|
|
|
|
m_context.GetEventBus().Unsubscribe<PlayModeStoppedEvent>(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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:56:07 +08:00
|
|
|
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<PlayModePausedEvent>(
|
|
|
|
|
[&](const PlayModePausedEvent&) {
|
|
|
|
|
++pausedCount;
|
|
|
|
|
});
|
|
|
|
|
const uint64_t resumedSubscription = m_context.GetEventBus().Subscribe<PlayModeResumedEvent>(
|
|
|
|
|
[&](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<PlayModePausedEvent>(pausedSubscription);
|
|
|
|
|
m_context.GetEventBus().Unsubscribe<PlayModeResumedEvent>(resumedSubscription);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 13:22:30 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 00:33:50 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:37:35 +08:00
|
|
|
} // namespace
|
|
|
|
|
} // namespace XCEngine::Editor
|