#include #include #include #include #include #include #include #include #include using namespace XCEngine::Components; using namespace XCEngine::Scripting; namespace { std::string LifecycleMethodToString(ScriptLifecycleMethod method) { switch (method) { case ScriptLifecycleMethod::Awake: return "Awake"; case ScriptLifecycleMethod::OnEnable: return "OnEnable"; case ScriptLifecycleMethod::Start: return "Start"; case ScriptLifecycleMethod::FixedUpdate: return "FixedUpdate"; case ScriptLifecycleMethod::Update: return "Update"; case ScriptLifecycleMethod::LateUpdate: return "LateUpdate"; case ScriptLifecycleMethod::OnDisable: return "OnDisable"; case ScriptLifecycleMethod::OnDestroy: return "OnDestroy"; } return "Unknown"; } class FakeScriptRuntime : public IScriptRuntime { public: void OnRuntimeStart(Scene* scene) override { events.push_back("RuntimeStart:" + (scene ? scene->GetName() : std::string("null"))); } void OnRuntimeStop(Scene* scene) override { events.push_back("RuntimeStop:" + (scene ? scene->GetName() : std::string("null"))); } bool CreateScriptInstance(const ScriptRuntimeContext& context) override { events.push_back("Create:" + Describe(context)); return context.component != nullptr; } void DestroyScriptInstance(const ScriptRuntimeContext& context) override { events.push_back("Destroy:" + Describe(context)); } void InvokeMethod( const ScriptRuntimeContext& context, ScriptLifecycleMethod method, float deltaTime) override { (void)deltaTime; events.push_back(LifecycleMethodToString(method) + ":" + Describe(context)); } void Clear() { events.clear(); } std::vector events; private: static std::string Describe(const ScriptRuntimeContext& context) { const std::string gameObjectName = context.gameObject ? context.gameObject->GetName() : "null"; const std::string className = context.component ? context.component->GetFullClassName() : "null"; return gameObjectName + ":" + className; } }; class ScriptEngineTest : public ::testing::Test { protected: void SetUp() override { engine = &ScriptEngine::Get(); engine->OnRuntimeStop(); engine->SetRuntime(&runtime); } void TearDown() override { engine->OnRuntimeStop(); engine->SetRuntime(nullptr); scene.reset(); } ScriptComponent* AddScriptComponent(GameObject* gameObject, const std::string& namespaceName, const std::string& className) { ScriptComponent* component = gameObject->AddComponent(); component->SetScriptClass("GameScripts", namespaceName, className); return component; } Scene* CreateScene(const std::string& sceneName) { scene = std::make_unique(sceneName); return scene.get(); } ScriptEngine* engine = nullptr; FakeScriptRuntime runtime; std::unique_ptr scene; }; TEST_F(ScriptEngineTest, RuntimeStartTracksActiveScriptsAndInvokesAwakeThenOnEnable) { Scene* runtimeScene = CreateScene("RuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScriptComponent(host, "Gameplay", "PlayerController"); engine->OnRuntimeStart(runtimeScene); EXPECT_TRUE(engine->IsRuntimeRunning()); EXPECT_EQ(engine->GetRuntimeScene(), runtimeScene); EXPECT_EQ(engine->GetTrackedScriptCount(), 1u); EXPECT_TRUE(engine->HasTrackedScriptComponent(component)); EXPECT_TRUE(engine->HasRuntimeInstance(component)); const std::vector expected = { "RuntimeStart:RuntimeScene", "Create:Host:Gameplay.PlayerController", "Awake:Host:Gameplay.PlayerController", "OnEnable:Host:Gameplay.PlayerController" }; EXPECT_EQ(runtime.events, expected); } TEST_F(ScriptEngineTest, UpdateInvokesStartOnlyOnceAndRunsPerFrameMethods) { Scene* runtimeScene = CreateScene("RuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); AddScriptComponent(host, "Gameplay", "Mover"); engine->OnRuntimeStart(runtimeScene); runtime.Clear(); engine->OnUpdate(0.016f); engine->OnFixedUpdate(0.02f); engine->OnUpdate(0.016f); engine->OnLateUpdate(0.016f); const std::vector expected = { "Start:Host:Gameplay.Mover", "Update:Host:Gameplay.Mover", "FixedUpdate:Host:Gameplay.Mover", "Update:Host:Gameplay.Mover", "LateUpdate:Host:Gameplay.Mover" }; EXPECT_EQ(runtime.events, expected); } TEST_F(ScriptEngineTest, InactiveObjectDefersCreationUntilActivated) { Scene* runtimeScene = CreateScene("RuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScriptComponent(host, "Gameplay", "SpawnedLater"); host->SetActive(false); engine->OnRuntimeStart(runtimeScene); ASSERT_EQ(engine->GetTrackedScriptCount(), 1u); EXPECT_TRUE(engine->HasTrackedScriptComponent(component)); EXPECT_FALSE(engine->HasRuntimeInstance(component)); ASSERT_EQ(runtime.events.size(), 1u); EXPECT_EQ(runtime.events[0], "RuntimeStart:RuntimeScene"); runtime.Clear(); host->SetActive(true); engine->OnUpdate(0.016f); const std::vector expected = { "Create:Host:Gameplay.SpawnedLater", "Awake:Host:Gameplay.SpawnedLater", "OnEnable:Host:Gameplay.SpawnedLater", "Start:Host:Gameplay.SpawnedLater", "Update:Host:Gameplay.SpawnedLater" }; EXPECT_EQ(runtime.events, expected); } TEST_F(ScriptEngineTest, RuntimeStopInvokesDisableDestroyAndRuntimeStop) { Scene* runtimeScene = CreateScene("RuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); AddScriptComponent(host, "Gameplay", "ShutdownWatcher"); engine->OnRuntimeStart(runtimeScene); runtime.Clear(); engine->OnRuntimeStop(); const std::vector expected = { "OnDisable:Host:Gameplay.ShutdownWatcher", "OnDestroy:Host:Gameplay.ShutdownWatcher", "Destroy:Host:Gameplay.ShutdownWatcher", "RuntimeStop:RuntimeScene" }; EXPECT_EQ(runtime.events, expected); EXPECT_FALSE(engine->IsRuntimeRunning()); EXPECT_EQ(engine->GetRuntimeScene(), nullptr); EXPECT_EQ(engine->GetTrackedScriptCount(), 0u); } TEST_F(ScriptEngineTest, ReEnablingScriptDoesNotInvokeStartTwice) { Scene* runtimeScene = CreateScene("RuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScriptComponent(host, "Gameplay", "ToggleWatcher"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); runtime.Clear(); component->SetEnabled(false); component->SetEnabled(true); engine->OnUpdate(0.016f); const std::vector expected = { "OnDisable:Host:Gameplay.ToggleWatcher", "OnEnable:Host:Gameplay.ToggleWatcher", "Update:Host:Gameplay.ToggleWatcher" }; EXPECT_EQ(runtime.events, expected); } TEST_F(ScriptEngineTest, DestroyingGameObjectWhileRuntimeRunningDestroysTrackedScript) { Scene* runtimeScene = CreateScene("RuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); AddScriptComponent(host, "Gameplay", "DestroyWatcher"); engine->OnRuntimeStart(runtimeScene); runtime.Clear(); runtimeScene->DestroyGameObject(host); const std::vector expected = { "OnDisable:Host:Gameplay.DestroyWatcher", "OnDestroy:Host:Gameplay.DestroyWatcher", "Destroy:Host:Gameplay.DestroyWatcher" }; EXPECT_EQ(runtime.events, expected); EXPECT_EQ(engine->GetTrackedScriptCount(), 0u); } } // namespace