diff --git a/tests/zecsy.cpp b/tests/zecsy.cpp index 77b3aaa..8bf4021 100644 --- a/tests/zecsy.cpp +++ b/tests/zecsy.cpp @@ -240,3 +240,185 @@ TEST_CASE("Test a systems ability to query and process only entities with a spec REQUIRE_FALSE(w.has(e1)); REQUIRE_FALSE(w.has(e2)); } + +TEST_CASE("Systems execute at correct frequencies") +{ + world w; + system_scheduler scheduler; + + int fast_count = 0; + int slow_count = 0; + + // Add a fast system (60 Hz) + scheduler.add_system(60, [&](float dt) { + fast_count++; + }); + + // Add a slow system (1 Hz) + scheduler.add_system(1, [&](float dt) { + slow_count++; + }); + + // Simulate 2 seconds of updates at 120 FPS + const float dt = 1.0f / 120.0f; + for (int i = 0; i < 240; ++i) { + scheduler.update(dt); + } + + // Verify counts + REQUIRE(fast_count == 120); // 60 Hz system should execute 60 times + REQUIRE(slow_count == 2); // 1 Hz system should execute 1 time +} + +TEST_CASE("Systems handle zero-frequency gracefully") +{ + world w; + system_scheduler scheduler; + + int zero_count = 0; + + // Add a zero-frequency system (should never execute) + scheduler.add_system(0, [&](float dt) { + zero_count++; + }); + + // Simulate 1 second of updates at 60 FPS + const float dt = 1.0f / 60.0f; + for (int i = 0; i < 60; ++i) { + scheduler.update(dt); + } + + // Verify zero-frequency system never executes + REQUIRE(zero_count == 0); +} + +TEST_CASE("Systems handle varying update rates") +{ + world w; + system_scheduler scheduler; + + int varying_count = 0; + + // Add a system with varying frequency (10 Hz) + scheduler.add_system(10, [&](float dt) { + varying_count++; + }); + + // Simulate 1 second of updates at 30 FPS + const float dt = 1.0f / 30.0f; + for (int i = 0; i < 30; ++i) { + scheduler.update(dt); + } + + // Verify varying-frequency system executes 10 times + REQUIRE(varying_count == 10); +} + +TEST_CASE("Systems handle large time steps") +{ + world w; + system_scheduler scheduler; + + int large_step_count = 0; + + // Add a system (1 Hz) + scheduler.add_system(1, [&](float dt) { + large_step_count++; + }); + + // Simulate a large time step (2 seconds) + scheduler.update(2.0f); + + // Verify system executes twice (accumulator handles large steps) + REQUIRE(large_step_count == 2); +} + +TEST_CASE("Systems handle multiple frequencies") +{ + world w; + system_scheduler scheduler; + + int fast_count = 0; + int medium_count = 0; + int slow_count = 0; + + // Add systems with different frequencies + scheduler.add_system(60, [&](float dt) { fast_count++; }); + scheduler.add_system(30, [&](float dt) { medium_count++; }); + scheduler.add_system(1, [&](float dt) { slow_count++; }); + + // Simulate 1 second of updates at 120 FPS + const float dt = 1.0f / 120; + for (int i = 0; i < 120; ++i) { + scheduler.update(dt); + } + + // Verify counts + REQUIRE(fast_count == 60); // 60 Hz system + REQUIRE(medium_count == 30); // 30 Hz system + REQUIRE(slow_count == 1); // 1 Hz system +} + +TEST_CASE("Systems handle fractional frequencies") +{ + world w; + system_scheduler scheduler; + + int fractional_count = 0; + + // Add a system with fractional frequency (0.5 Hz) + scheduler.add_system(0.5f, [&](float dt) { + fractional_count++; + }); + + // Simulate 4 seconds of updates at 60 FPS + const float dt = 1.0f / 60; + for (int i = 0; i < 240; ++i) { + scheduler.update(dt); + } + + // Verify fractional-frequency system executes twice (0.5 Hz = 2 times in 4 seconds) + REQUIRE(fractional_count == 2); +} + +TEST_CASE("Systems handle zero delta time") +{ + world w; + system_scheduler scheduler; + + int zero_dt_count = 0; + + // Add a system (1 Hz) + scheduler.add_system(1, [&](float dt) { + zero_dt_count++; + }); + + // Simulate zero delta time + scheduler.update(0.0f); + + // Verify system does not execute + REQUIRE(zero_dt_count == 0); +} + +TEST_CASE("Systems handle negative delta time") +{ + world w; + system_scheduler scheduler; + + int count = 0; + + // Add a system (1 Hz) + scheduler.add_system(1, [&](float dt) { + count++; + }); + + // Simulate negative delta time + scheduler.update(-1.0f); + + // Verify system does not execute + REQUIRE(count == 0); + + scheduler.update(2.0f); + + REQUIRE(count == 2); +} diff --git a/zecsy.hpp b/zecsy.hpp index 0a2faf7..377d8e7 100644 --- a/zecsy.hpp +++ b/zecsy.hpp @@ -1,6 +1,9 @@ #pragma once +#include #include +#include #include +#include #include #include #include @@ -70,6 +73,25 @@ namespace zecsy comp_to_reusable_ids reusable_id_queues; }; + class system_scheduler final + { + public: + void add_system(float freq, auto&& func); + + void add_system(int freq, auto&& func); + + void update(float dt); + private: + struct system_handler + { + double interval; + double accumulator = 0.0f; + std::function callback; + }; + + std::vector systems; + }; + class world final { public: @@ -267,4 +289,33 @@ namespace zecsy } } } + + inline void system_scheduler::add_system(float freq, auto&& func) + { + systems.push_back({ + 1.0f / freq, + 0.0f, + std::forward(func) + }); + } + + inline void system_scheduler::add_system(int freq, auto&& func) + { + add_system(float(freq), func); + } + + inline void system_scheduler::update(float dt) + { + dt = std::max(0.0f, dt); + + for(auto& s: systems) + { + s.accumulator += dt; + while(s.accumulator >= s.interval) + { + s.callback(dt); + s.accumulator -= s.interval; + } + } + } };