#include #include #include #include #include #include #include using namespace XCEngine::Components; namespace { struct RuntimeLoopCounters { int startCount = 0; int fixedUpdateCount = 0; int updateCount = 0; int lateUpdateCount = 0; }; class RuntimeLoopObserverComponent : public Component { public: explicit RuntimeLoopObserverComponent(RuntimeLoopCounters* counters) : m_counters(counters) { } std::string GetName() const override { return "RuntimeLoopObserver"; } void Start() override { if (m_counters) { ++m_counters->startCount; } } void FixedUpdate() override { if (m_counters) { ++m_counters->fixedUpdateCount; } } void Update(float deltaTime) override { (void)deltaTime; if (m_counters) { ++m_counters->updateCount; } } void LateUpdate(float deltaTime) override { (void)deltaTime; if (m_counters) { ++m_counters->lateUpdateCount; } } private: RuntimeLoopCounters* m_counters = nullptr; }; class RuntimeLoopTest : public ::testing::Test { protected: Scene* CreateScene(const std::string& name = "RuntimeLoopScene") { m_scene = std::make_unique(name); return m_scene.get(); } RuntimeLoopCounters counters; std::unique_ptr m_scene; }; TEST_F(RuntimeLoopTest, AccumulatesFixedUpdatesAcrossFramesAndRunsVariableUpdatesEveryTick) { RuntimeLoop loop({0.02f, 0.1f, 4}); Scene* scene = CreateScene(); GameObject* host = scene->CreateGameObject("Host"); host->AddComponent(&counters); loop.Start(scene); loop.Tick(0.01f); EXPECT_EQ(counters.fixedUpdateCount, 0); EXPECT_EQ(counters.startCount, 1); EXPECT_EQ(counters.updateCount, 1); EXPECT_EQ(counters.lateUpdateCount, 1); loop.Tick(0.01f); EXPECT_EQ(counters.fixedUpdateCount, 1); EXPECT_EQ(counters.startCount, 1); EXPECT_EQ(counters.updateCount, 2); EXPECT_EQ(counters.lateUpdateCount, 2); } TEST_F(RuntimeLoopTest, ClampAndFixedStepLimitPreventExcessiveCatchUp) { RuntimeLoop loop({0.02f, 0.05f, 2}); Scene* scene = CreateScene(); GameObject* host = scene->CreateGameObject("Host"); host->AddComponent(&counters); loop.Start(scene); loop.Tick(1.0f); EXPECT_EQ(counters.fixedUpdateCount, 2); EXPECT_EQ(counters.startCount, 1); EXPECT_EQ(counters.updateCount, 1); EXPECT_EQ(counters.lateUpdateCount, 1); EXPECT_NEAR(loop.GetFixedAccumulator(), 0.01f, 1e-4f); } TEST_F(RuntimeLoopTest, PauseSkipsAutomaticTicksUntilStepFrameIsRequested) { RuntimeLoop loop({0.02f, 0.1f, 4}); Scene* scene = CreateScene(); GameObject* host = scene->CreateGameObject("Host"); host->AddComponent(&counters); loop.Start(scene); loop.Pause(); loop.Tick(0.025f); EXPECT_EQ(counters.fixedUpdateCount, 0); EXPECT_EQ(counters.startCount, 0); EXPECT_EQ(counters.updateCount, 0); EXPECT_EQ(counters.lateUpdateCount, 0); loop.StepFrame(); loop.Tick(0.025f); EXPECT_EQ(counters.fixedUpdateCount, 1); EXPECT_EQ(counters.startCount, 1); EXPECT_EQ(counters.updateCount, 1); EXPECT_EQ(counters.lateUpdateCount, 1); EXPECT_TRUE(loop.IsPaused()); } TEST_F(RuntimeLoopTest, ResumeRestoresAutomaticTickProgressionAfterPause) { RuntimeLoop loop({0.02f, 0.1f, 4}); Scene* scene = CreateScene(); GameObject* host = scene->CreateGameObject("Host"); host->AddComponent(&counters); loop.Start(scene); loop.Pause(); loop.Tick(0.025f); EXPECT_EQ(counters.updateCount, 0); loop.Resume(); EXPECT_FALSE(loop.IsPaused()); loop.Tick(0.025f); EXPECT_EQ(counters.startCount, 1); EXPECT_EQ(counters.fixedUpdateCount, 1); EXPECT_EQ(counters.updateCount, 1); EXPECT_EQ(counters.lateUpdateCount, 1); } } // namespace