chore: snapshot editor work and restore tests
Key points:\n- restore the tests tree removed by bc47e6e\n- capture current editor workspace, scene, and docs reshuffle changes\n- keep local cloud.nvdb resources ignored from this commit
This commit is contained in:
345
tests/editor/test_play_session_controller.cpp
Normal file
345
tests/editor/test_play_session_controller.cpp
Normal file
@@ -0,0 +1,345 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Core/EditorContext.h"
|
||||
#include "Core/EditorEvents.h"
|
||||
#include "Core/PlaySessionController.h"
|
||||
#include "Commands/EntityCommands.h"
|
||||
|
||||
#include <XCEngine/Components/BoxColliderComponent.h>
|
||||
#include <XCEngine/Components/RigidbodyComponent.h>
|
||||
#include <XCEngine/Components/SphereColliderComponent.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Input/InputManager.h>
|
||||
#include <XCEngine/Physics/PhysicsWorld.h>
|
||||
|
||||
namespace XCEngine::Editor {
|
||||
namespace {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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<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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
TEST_F(PlaySessionControllerTest, PlayModeCreatesRuntimePhysicsWorldAndRestoresEditorSnapshotAfterSimulation) {
|
||||
if (!::XCEngine::Physics::PhysicsWorld::IsPhysXAvailable()) {
|
||||
GTEST_SKIP() << "PhysX is not available in this configuration.";
|
||||
}
|
||||
|
||||
auto* ground = m_context.GetSceneManager().CreateEntity("Ground");
|
||||
ASSERT_NE(ground, nullptr);
|
||||
ground->GetTransform()->SetLocalPosition(Math::Vector3(0.0f, -1.0f, 0.0f));
|
||||
auto* groundCollider = ground->AddComponent<::XCEngine::Components::BoxColliderComponent>();
|
||||
ASSERT_NE(groundCollider, nullptr);
|
||||
groundCollider->SetSize(Math::Vector3(10.0f, 1.0f, 10.0f));
|
||||
|
||||
auto* ball = m_context.GetSceneManager().CreateEntity("Ball");
|
||||
ASSERT_NE(ball, nullptr);
|
||||
const uint64_t ballId = ball->GetID();
|
||||
ball->GetTransform()->SetLocalPosition(Math::Vector3(0.0f, 5.0f, 0.0f));
|
||||
auto* rigidbody = ball->AddComponent<::XCEngine::Components::RigidbodyComponent>();
|
||||
ASSERT_NE(rigidbody, nullptr);
|
||||
rigidbody->SetUseGravity(true);
|
||||
rigidbody->SetLinearDamping(0.0f);
|
||||
rigidbody->SetAngularDamping(0.0f);
|
||||
auto* sphereCollider = ball->AddComponent<::XCEngine::Components::SphereColliderComponent>();
|
||||
ASSERT_NE(sphereCollider, nullptr);
|
||||
sphereCollider->SetRadius(0.5f);
|
||||
|
||||
const bool dirtyStateBeforePlay = m_context.GetSceneManager().IsSceneDirty();
|
||||
|
||||
ASSERT_TRUE(m_controller.StartPlay(m_context));
|
||||
ASSERT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Play);
|
||||
|
||||
::XCEngine::Physics::PhysicsWorld* physicsWorld = m_controller.GetRuntimePhysicsWorld();
|
||||
ASSERT_NE(physicsWorld, nullptr);
|
||||
EXPECT_TRUE(physicsWorld->IsInitialized());
|
||||
EXPECT_EQ(physicsWorld->GetTrackedRigidbodyCount(), 1u);
|
||||
EXPECT_EQ(physicsWorld->GetTrackedColliderCount(), 2u);
|
||||
|
||||
auto* runtimeBall = m_context.GetSceneManager().GetEntity(ballId);
|
||||
ASSERT_NE(runtimeBall, nullptr);
|
||||
const float initialRuntimeY = runtimeBall->GetTransform()->GetLocalPosition().y;
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
m_controller.Update(m_context, 0.05f);
|
||||
}
|
||||
|
||||
runtimeBall = m_context.GetSceneManager().GetEntity(ballId);
|
||||
ASSERT_NE(runtimeBall, nullptr);
|
||||
const float simulatedRuntimeY = runtimeBall->GetTransform()->GetLocalPosition().y;
|
||||
EXPECT_LT(simulatedRuntimeY, initialRuntimeY);
|
||||
EXPECT_EQ(m_context.GetSceneManager().IsSceneDirty(), dirtyStateBeforePlay);
|
||||
|
||||
ASSERT_TRUE(m_controller.StopPlay(m_context));
|
||||
EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Edit);
|
||||
EXPECT_EQ(m_controller.GetRuntimePhysicsWorld(), nullptr);
|
||||
EXPECT_EQ(m_context.GetSceneManager().IsSceneDirty(), dirtyStateBeforePlay);
|
||||
|
||||
auto* restoredBall = m_context.GetSceneManager().GetEntity(ballId);
|
||||
ASSERT_NE(restoredBall, nullptr);
|
||||
const Math::Vector3 restoredPosition = restoredBall->GetTransform()->GetLocalPosition();
|
||||
EXPECT_NEAR(restoredPosition.x, 0.0f, 1e-4f);
|
||||
EXPECT_NEAR(restoredPosition.y, 5.0f, 1e-4f);
|
||||
EXPECT_NEAR(restoredPosition.z, 0.0f, 1e-4f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace XCEngine::Editor
|
||||
Reference in New Issue
Block a user