#include #include "Core/EditorContext.h" #include "Core/EditorRuntimeMode.h" #include "Core/PlaySessionController.h" #include #include #include #include #include #include #include #include #include #include using namespace XCEngine::Components; using namespace XCEngine::Scripting; namespace XCEngine::Editor { namespace { MonoScriptRuntime::Settings CreateMonoSettings() { MonoScriptRuntime::Settings settings; settings.assemblyDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; settings.corlibDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; settings.coreAssemblyPath = XCENGINE_TEST_SCRIPT_CORE_DLL; settings.appAssemblyPath = XCENGINE_TEST_GAME_SCRIPTS_DLL; return settings; } void ExpectVector3Near(const Math::Vector3& actual, const Math::Vector3& expected, float tolerance = 0.001f) { EXPECT_NEAR(actual.x, expected.x, tolerance); EXPECT_NEAR(actual.y, expected.y, tolerance); EXPECT_NEAR(actual.z, expected.z, tolerance); } class CapturingLogSink final : public Debug::ILogSink { public: void Log(const Debug::LogEntry& entry) override { entries.push_back(entry); } void Flush() override { } std::vector CollectMessagesWithPrefix(const char* prefix) const { std::vector messages; for (const Debug::LogEntry& entry : entries) { const std::string message = entry.message.CStr(); if (message.rfind(prefix, 0) == 0) { messages.push_back(message); } } return messages; } std::vector entries; }; ScriptComponent* FindLifecycleProbe(GameObject* gameObject) { if (!gameObject) { return nullptr; } for (ScriptComponent* component : gameObject->GetComponents()) { if (!component) { continue; } if (component->GetAssemblyName() == "GameScripts" && component->GetNamespaceName() == "Gameplay" && component->GetClassName() == "LifecycleProbe") { return component; } } return nullptr; } ScriptComponent* FindInputProbe(GameObject* gameObject) { if (!gameObject) { return nullptr; } for (ScriptComponent* component : gameObject->GetComponents()) { if (!component) { continue; } if (component->GetAssemblyName() == "GameScripts" && component->GetNamespaceName() == "Gameplay" && component->GetClassName() == "InputProbe") { return component; } } return nullptr; } ScriptComponent* FindTickLogProbe(GameObject* gameObject) { if (!gameObject) { return nullptr; } for (ScriptComponent* component : gameObject->GetComponents()) { if (!component) { continue; } if (component->GetAssemblyName() == "GameScripts" && component->GetNamespaceName() == "Gameplay" && component->GetClassName() == "TickLogProbe") { return component; } } return nullptr; } 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 PlaySessionControllerScriptingTest : public ::testing::Test { protected: void SetUp() override { engine = &ScriptEngine::Get(); engine->OnRuntimeStop(); runtime = std::make_unique(CreateMonoSettings()); ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError(); engine->SetRuntime(runtime.get()); context.GetSceneManager().NewScene("Play Session Script Scene"); controller.Attach(context); } void TearDown() override { controller.Detach(context); engine->OnRuntimeStop(); engine->SetRuntime(nullptr); runtime.reset(); } ScriptComponent* AddLifecycleProbe(GameObject* gameObject) { ScriptComponent* component = gameObject->AddComponent(); component->SetScriptClass("GameScripts", "Gameplay", "LifecycleProbe"); return component; } ScriptComponent* AddInputProbe(GameObject* gameObject) { ScriptComponent* component = gameObject->AddComponent(); component->SetScriptClass("GameScripts", "Gameplay", "InputProbe"); return component; } ScriptComponent* AddTickLogProbe(GameObject* gameObject) { ScriptComponent* component = gameObject->AddComponent(); component->SetScriptClass("GameScripts", "Gameplay", "TickLogProbe"); return component; } ScriptEngine* engine = nullptr; std::unique_ptr runtime; EditorContext context; PlaySessionController controller; }; TEST_F(PlaySessionControllerScriptingTest, StartPlayAndRuntimeTickDriveManagedLifecycleThroughPlayController) { GameObject* host = context.GetSceneManager().CreateEntity("Host"); ASSERT_NE(host, nullptr); ScriptComponent* script = AddLifecycleProbe(host); ASSERT_NE(script, nullptr); script->GetFieldStorage().SetFieldValue("Label", "EditorLabel"); script->GetFieldStorage().SetFieldValue("Speed", 5.0f); script->GetFieldStorage().SetFieldValue("SpawnPoint", Math::Vector3(2.0f, 4.0f, 6.0f)); const uint64_t hostId = host->GetID(); ASSERT_TRUE(controller.StartPlay(context)); EXPECT_EQ(context.GetRuntimeMode(), EditorRuntimeMode::Play); GameObject* runtimeHost = context.GetSceneManager().GetEntity(hostId); ASSERT_NE(runtimeHost, nullptr); ScriptComponent* runtimeScript = FindLifecycleProbe(runtimeHost); ASSERT_NE(runtimeScript, nullptr); EXPECT_TRUE(engine->HasRuntimeInstance(runtimeScript)); EXPECT_TRUE(runtime->HasManagedInstance(runtimeScript)); int32_t awakeCount = 0; int32_t enableCount = 0; int32_t startCount = 0; int32_t fixedUpdateCount = 0; int32_t updateCount = 0; int32_t lateUpdateCount = 0; std::string label; ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "AwakeCount", awakeCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "EnableCount", enableCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "StartCount", startCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "FixedUpdateCount", fixedUpdateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "UpdateCount", updateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "LateUpdateCount", lateUpdateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "Label", label)); EXPECT_EQ(awakeCount, 1); EXPECT_EQ(enableCount, 1); EXPECT_EQ(startCount, 0); EXPECT_EQ(fixedUpdateCount, 0); EXPECT_EQ(updateCount, 0); EXPECT_EQ(lateUpdateCount, 0); EXPECT_EQ(label, "EditorLabel|Awake"); controller.Update(context, 0.036f); float observedFixedDeltaTime = 0.0f; float observedConfiguredFixedDeltaTime = 0.0f; float observedConfiguredFixedDeltaTimeInUpdate = 0.0f; float observedUpdateDeltaTime = 0.0f; float observedLateDeltaTime = 0.0f; float speed = 0.0f; Math::Vector3 spawnPoint; ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "StartCount", startCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "FixedUpdateCount", fixedUpdateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "UpdateCount", updateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "LateUpdateCount", lateUpdateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedFixedDeltaTime", observedFixedDeltaTime)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedConfiguredFixedDeltaTime", observedConfiguredFixedDeltaTime)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedConfiguredFixedDeltaTimeInUpdate", observedConfiguredFixedDeltaTimeInUpdate)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedUpdateDeltaTime", observedUpdateDeltaTime)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedLateDeltaTime", observedLateDeltaTime)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "Speed", speed)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "SpawnPoint", spawnPoint)); EXPECT_EQ(startCount, 1); EXPECT_EQ(fixedUpdateCount, 1); EXPECT_EQ(updateCount, 1); EXPECT_EQ(lateUpdateCount, 1); EXPECT_FLOAT_EQ(observedFixedDeltaTime, 0.02f); EXPECT_FLOAT_EQ(observedConfiguredFixedDeltaTime, 0.02f); EXPECT_FLOAT_EQ(observedConfiguredFixedDeltaTimeInUpdate, 0.02f); EXPECT_FLOAT_EQ(observedUpdateDeltaTime, 0.036f); EXPECT_FLOAT_EQ(observedLateDeltaTime, 0.036f); EXPECT_FLOAT_EQ(speed, 6.0f); EXPECT_EQ(runtimeHost->GetName(), "Host_Managed"); ExpectVector3Near(runtimeHost->GetTransform()->GetLocalPosition(), Math::Vector3(8.0f, 8.0f, 9.0f)); ExpectVector3Near(spawnPoint, Math::Vector3(3.0f, 4.0f, 6.0f)); } TEST_F(PlaySessionControllerScriptingTest, PauseAndStepGateManagedUpdatesInPlayMode) { GameObject* host = context.GetSceneManager().CreateEntity("Host"); ASSERT_NE(host, nullptr); ScriptComponent* script = AddLifecycleProbe(host); ASSERT_NE(script, nullptr); script->GetFieldStorage().SetFieldValue("Label", "EditorLabel"); const uint64_t hostId = host->GetID(); ASSERT_TRUE(controller.StartPlay(context)); GameObject* runtimeHost = context.GetSceneManager().GetEntity(hostId); ASSERT_NE(runtimeHost, nullptr); ScriptComponent* runtimeScript = FindLifecycleProbe(runtimeHost); ASSERT_NE(runtimeScript, nullptr); controller.Update(context, 0.02f); int32_t startCount = 0; int32_t fixedUpdateCount = 0; int32_t updateCount = 0; int32_t lateUpdateCount = 0; ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "StartCount", startCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "FixedUpdateCount", fixedUpdateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "UpdateCount", updateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "LateUpdateCount", lateUpdateCount)); EXPECT_EQ(startCount, 1); EXPECT_EQ(fixedUpdateCount, 1); EXPECT_EQ(updateCount, 1); EXPECT_EQ(lateUpdateCount, 1); ASSERT_TRUE(controller.PausePlay(context)); EXPECT_EQ(context.GetRuntimeMode(), EditorRuntimeMode::Paused); controller.Update(context, 0.02f); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "FixedUpdateCount", fixedUpdateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "UpdateCount", updateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "LateUpdateCount", lateUpdateCount)); EXPECT_EQ(fixedUpdateCount, 1); EXPECT_EQ(updateCount, 1); EXPECT_EQ(lateUpdateCount, 1); ASSERT_TRUE(controller.StepPlay(context)); controller.Update(context, 0.02f); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "FixedUpdateCount", fixedUpdateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "UpdateCount", updateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "LateUpdateCount", lateUpdateCount)); EXPECT_EQ(context.GetRuntimeMode(), EditorRuntimeMode::Paused); EXPECT_EQ(fixedUpdateCount, 2); EXPECT_EQ(updateCount, 2); EXPECT_EQ(lateUpdateCount, 2); ASSERT_TRUE(controller.ResumePlay(context)); EXPECT_EQ(context.GetRuntimeMode(), EditorRuntimeMode::Play); controller.Update(context, 0.02f); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "FixedUpdateCount", fixedUpdateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "UpdateCount", updateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "LateUpdateCount", lateUpdateCount)); EXPECT_EQ(fixedUpdateCount, 3); EXPECT_EQ(updateCount, 3); EXPECT_EQ(lateUpdateCount, 3); } TEST_F(PlaySessionControllerScriptingTest, StopPlayDestroysManagedInstancesAndRestoresEditorSnapshot) { GameObject* host = context.GetSceneManager().CreateEntity("Host"); ASSERT_NE(host, nullptr); host->GetTransform()->SetLocalPosition(Math::Vector3(1.0f, 2.0f, 3.0f)); ScriptComponent* script = AddLifecycleProbe(host); ASSERT_NE(script, nullptr); script->GetFieldStorage().SetFieldValue("Label", "EditorLabel"); script->GetFieldStorage().SetFieldValue("Speed", 5.0f); script->GetFieldStorage().SetFieldValue("SpawnPoint", Math::Vector3(2.0f, 4.0f, 6.0f)); const uint64_t hostId = host->GetID(); const uint64_t scriptComponentUUID = script->GetScriptComponentUUID(); ASSERT_TRUE(controller.StartPlay(context)); GameObject* runtimeHost = context.GetSceneManager().GetEntity(hostId); ASSERT_NE(runtimeHost, nullptr); ScriptComponent* runtimeScript = FindLifecycleProbe(runtimeHost); ASSERT_NE(runtimeScript, nullptr); controller.Update(context, 0.02f); EXPECT_EQ(runtime->GetManagedInstanceCount(), 1u); EXPECT_EQ(runtimeHost->GetName(), "Host_Managed"); ExpectVector3Near(runtimeHost->GetTransform()->GetLocalPosition(), Math::Vector3(8.0f, 8.0f, 9.0f)); ASSERT_TRUE(controller.StopPlay(context)); EXPECT_EQ(context.GetRuntimeMode(), EditorRuntimeMode::Edit); EXPECT_FALSE(engine->IsRuntimeRunning()); EXPECT_EQ(runtime->GetManagedInstanceCount(), 0u); GameObject* restoredHost = context.GetSceneManager().GetEntity(hostId); ASSERT_NE(restoredHost, nullptr); EXPECT_EQ(restoredHost->GetName(), "Host"); ExpectVector3Near(restoredHost->GetTransform()->GetLocalPosition(), Math::Vector3(1.0f, 2.0f, 3.0f)); ScriptComponent* restoredScript = FindLifecycleProbe(restoredHost); ASSERT_NE(restoredScript, nullptr); EXPECT_EQ(restoredScript->GetScriptComponentUUID(), scriptComponentUUID); std::string label; float speed = 0.0f; Math::Vector3 spawnPoint; int32_t awakeCount = 0; ASSERT_TRUE(engine->TryGetScriptFieldValue(restoredScript, "Label", label)); ASSERT_TRUE(engine->TryGetScriptFieldValue(restoredScript, "Speed", speed)); ASSERT_TRUE(engine->TryGetScriptFieldValue(restoredScript, "SpawnPoint", spawnPoint)); EXPECT_EQ(label, "EditorLabel"); EXPECT_FLOAT_EQ(speed, 5.0f); ExpectVector3Near(spawnPoint, Math::Vector3(2.0f, 4.0f, 6.0f)); EXPECT_FALSE(engine->TryGetScriptFieldValue(restoredScript, "AwakeCount", awakeCount)); EXPECT_FALSE(restoredScript->GetFieldStorage().Contains("AwakeCount")); } TEST_F(PlaySessionControllerScriptingTest, GameViewInputBridgeFeedsManagedInputApiDuringPlayMode) { GameObject* host = context.GetSceneManager().CreateEntity("Host"); ASSERT_NE(host, nullptr); ScriptComponent* script = AddInputProbe(host); ASSERT_NE(script, nullptr); const uint64_t hostId = host->GetID(); controller.Attach(context); ASSERT_TRUE(controller.StartPlay(context)); GameObject* runtimeHost = context.GetSceneManager().GetEntity(hostId); ASSERT_NE(runtimeHost, nullptr); ScriptComponent* runtimeScript = FindInputProbe(runtimeHost); ASSERT_NE(runtimeScript, nullptr); context.GetEventBus().Publish(CreateGameViewInputFrame( true, true, {XCEngine::Input::KeyCode::A, XCEngine::Input::KeyCode::Space, XCEngine::Input::KeyCode::LeftCtrl}, {XCEngine::Input::MouseButton::Left}, XCEngine::Math::Vector2(120.0f, 48.0f), XCEngine::Math::Vector2(3.0f, -2.0f), 1.0f)); controller.Update(context, 0.016f); int32_t updateCount = 0; bool observedKeyA = false; bool observedKeyADown = false; bool observedKeyAUp = false; bool observedKeySpace = false; bool observedJump = false; bool observedJumpDown = false; bool observedJumpUp = false; bool observedFire1 = false; bool observedFire1Down = false; bool observedFire1Up = false; bool observedAnyKey = false; bool observedAnyKeyDown = false; bool observedLeftMouse = false; bool observedLeftMouseDown = false; bool observedLeftMouseUp = false; float observedHorizontal = 0.0f; float observedHorizontalRaw = 0.0f; XCEngine::Math::Vector2 observedMouseScrollDelta; XCEngine::Math::Vector3 observedMousePosition; ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "UpdateCount", updateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeyA", observedKeyA)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeyADown", observedKeyADown)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeyAUp", observedKeyAUp)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeySpace", observedKeySpace)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedJump", observedJump)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedJumpDown", observedJumpDown)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedJumpUp", observedJumpUp)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedFire1", observedFire1)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedFire1Down", observedFire1Down)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedFire1Up", observedFire1Up)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedAnyKey", observedAnyKey)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedAnyKeyDown", observedAnyKeyDown)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedLeftMouse", observedLeftMouse)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedLeftMouseDown", observedLeftMouseDown)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedLeftMouseUp", observedLeftMouseUp)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedHorizontal", observedHorizontal)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedHorizontalRaw", observedHorizontalRaw)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedMousePosition", observedMousePosition)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedMouseScrollDelta", observedMouseScrollDelta)); EXPECT_EQ(updateCount, 1); EXPECT_TRUE(observedKeyA); EXPECT_TRUE(observedKeyADown); EXPECT_FALSE(observedKeyAUp); EXPECT_TRUE(observedKeySpace); EXPECT_TRUE(observedJump); EXPECT_TRUE(observedJumpDown); EXPECT_FALSE(observedJumpUp); EXPECT_TRUE(observedFire1); EXPECT_TRUE(observedFire1Down); EXPECT_FALSE(observedFire1Up); EXPECT_TRUE(observedAnyKey); EXPECT_TRUE(observedAnyKeyDown); EXPECT_TRUE(observedLeftMouse); EXPECT_TRUE(observedLeftMouseDown); EXPECT_FALSE(observedLeftMouseUp); EXPECT_FLOAT_EQ(observedHorizontal, -1.0f); EXPECT_FLOAT_EQ(observedHorizontalRaw, -1.0f); EXPECT_EQ(observedMousePosition, XCEngine::Math::Vector3(120.0f, 48.0f, 0.0f)); EXPECT_EQ(observedMouseScrollDelta, XCEngine::Math::Vector2(0.0f, 1.0f)); context.GetEventBus().Publish(CreateGameViewInputFrame( true, true, {XCEngine::Input::KeyCode::A, XCEngine::Input::KeyCode::Space, XCEngine::Input::KeyCode::LeftCtrl}, {XCEngine::Input::MouseButton::Left}, XCEngine::Math::Vector2(120.0f, 48.0f))); controller.Update(context, 0.016f); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "UpdateCount", updateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeyA", observedKeyA)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeyADown", observedKeyADown)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeyAUp", observedKeyAUp)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedJump", observedJump)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedJumpDown", observedJumpDown)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedJumpUp", observedJumpUp)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedFire1", observedFire1)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedFire1Down", observedFire1Down)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedFire1Up", observedFire1Up)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedAnyKey", observedAnyKey)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedAnyKeyDown", observedAnyKeyDown)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedLeftMouse", observedLeftMouse)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedLeftMouseDown", observedLeftMouseDown)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedLeftMouseUp", observedLeftMouseUp)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedHorizontalRaw", observedHorizontalRaw)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedMouseScrollDelta", observedMouseScrollDelta)); EXPECT_EQ(updateCount, 2); EXPECT_TRUE(observedKeyA); EXPECT_FALSE(observedKeyADown); EXPECT_FALSE(observedKeyAUp); EXPECT_TRUE(observedJump); EXPECT_FALSE(observedJumpDown); EXPECT_FALSE(observedJumpUp); EXPECT_TRUE(observedFire1); EXPECT_FALSE(observedFire1Down); EXPECT_FALSE(observedFire1Up); EXPECT_TRUE(observedAnyKey); EXPECT_FALSE(observedAnyKeyDown); EXPECT_TRUE(observedLeftMouse); EXPECT_FALSE(observedLeftMouseDown); EXPECT_FALSE(observedLeftMouseUp); EXPECT_FLOAT_EQ(observedHorizontalRaw, -1.0f); EXPECT_EQ(observedMouseScrollDelta, XCEngine::Math::Vector2(0.0f, 0.0f)); context.GetEventBus().Publish(CreateGameViewInputFrame( true, true, {XCEngine::Input::KeyCode::Space}, {}, XCEngine::Math::Vector2(120.0f, 48.0f))); controller.Update(context, 0.016f); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "UpdateCount", updateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeyA", observedKeyA)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeyAUp", observedKeyAUp)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeySpace", observedKeySpace)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedJump", observedJump)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedJumpUp", observedJumpUp)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedFire1", observedFire1)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedFire1Up", observedFire1Up)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedAnyKey", observedAnyKey)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedAnyKeyDown", observedAnyKeyDown)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedLeftMouse", observedLeftMouse)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedLeftMouseUp", observedLeftMouseUp)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedHorizontal", observedHorizontal)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedHorizontalRaw", observedHorizontalRaw)); EXPECT_EQ(updateCount, 3); EXPECT_FALSE(observedKeyA); EXPECT_TRUE(observedKeyAUp); EXPECT_TRUE(observedKeySpace); EXPECT_TRUE(observedJump); EXPECT_FALSE(observedJumpUp); EXPECT_FALSE(observedFire1); EXPECT_TRUE(observedFire1Up); EXPECT_TRUE(observedAnyKey); EXPECT_FALSE(observedAnyKeyDown); EXPECT_FALSE(observedLeftMouse); EXPECT_TRUE(observedLeftMouseUp); EXPECT_FLOAT_EQ(observedHorizontal, 0.0f); EXPECT_FLOAT_EQ(observedHorizontalRaw, 0.0f); context.GetEventBus().Publish(CreateGameViewInputFrame( true, true, {}, {}, XCEngine::Math::Vector2(120.0f, 48.0f))); controller.Update(context, 0.016f); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "UpdateCount", updateCount)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedKeySpace", observedKeySpace)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedJump", observedJump)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedJumpUp", observedJumpUp)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedAnyKey", observedAnyKey)); ASSERT_TRUE(engine->TryGetScriptFieldValue(runtimeScript, "ObservedAnyKeyDown", observedAnyKeyDown)); EXPECT_EQ(updateCount, 4); EXPECT_FALSE(observedKeySpace); EXPECT_FALSE(observedJump); EXPECT_TRUE(observedJumpUp); EXPECT_FALSE(observedAnyKey); EXPECT_FALSE(observedAnyKeyDown); } TEST_F(PlaySessionControllerScriptingTest, PlayModeTickWritesManagedDebugLogsToNativeLogger) { auto sink = std::make_unique(); CapturingLogSink* sinkPtr = sink.get(); Debug::Logger::Get().AddSink(std::move(sink)); GameObject* host = context.GetSceneManager().CreateEntity("Host"); ASSERT_NE(host, nullptr); ScriptComponent* script = AddTickLogProbe(host); ASSERT_NE(script, nullptr); const uint64_t hostId = host->GetID(); ASSERT_TRUE(controller.StartPlay(context)); GameObject* runtimeHost = context.GetSceneManager().GetEntity(hostId); ASSERT_NE(runtimeHost, nullptr); ASSERT_NE(FindTickLogProbe(runtimeHost), nullptr); controller.Update(context, 0.036f); const std::vector messages = sinkPtr->CollectMessagesWithPrefix("[TickLogProbe]"); const std::vector expected = { "[TickLogProbe] Awake", "[TickLogProbe] FixedUpdate 1", "[TickLogProbe] Start", "[TickLogProbe] Update 1", "[TickLogProbe] LateUpdate 1", }; EXPECT_EQ(messages, expected); Debug::Logger::Get().RemoveSink(sinkPtr); } } // namespace } // namespace XCEngine::Editor