diff options
-rw-r--r-- | src/crepe/api/LoopManager.cpp | 36 | ||||
-rw-r--r-- | src/crepe/api/LoopManager.h | 29 | ||||
-rw-r--r-- | src/crepe/api/LoopTimer.cpp | 45 | ||||
-rw-r--r-- | src/crepe/api/LoopTimer.h | 27 | ||||
-rw-r--r-- | src/crepe/manager/Mediator.h | 3 | ||||
-rw-r--r-- | src/example/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/example/gameloop.cpp | 7 | ||||
-rw-r--r-- | src/test/CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/test/LoopManagerTest.cpp | 44 | ||||
-rw-r--r-- | src/test/LoopTimerTest.cpp | 81 |
10 files changed, 195 insertions, 84 deletions
diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index 731cfb7..040cb93 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -1,9 +1,12 @@ +#include "../facade/SDLContext.h" + #include "../system/AnimatorSystem.h" #include "../system/CollisionSystem.h" #include "../system/ParticleSystem.h" #include "../system/PhysicsSystem.h" #include "../system/RenderSystem.h" #include "../system/ScriptSystem.h" +#include "../manager/EventManager.h" #include "LoopManager.h" @@ -11,15 +14,17 @@ using namespace crepe; using namespace std; LoopManager::LoopManager() { - this->mediator.component_manager = this->component_manager; - this->mediator.scene_manager = this->scene_manager; - this->load_system<AnimatorSystem>(); this->load_system<CollisionSystem>(); this->load_system<ParticleSystem>(); this->load_system<PhysicsSystem>(); this->load_system<RenderSystem>(); this->load_system<ScriptSystem>(); + EventManager::get_instance().subscribe<ShutDownEvent>([this](const ShutDownEvent& event) { + return this->on_shutdown(event); + }); + this->loop_timer = make_unique<LoopTimer>(); + this->mediator.loop_timer = *loop_timer; } void LoopManager::process_input() { this->sdl_context.handle_events(this->game_running); } @@ -28,36 +33,33 @@ void LoopManager::start() { this->setup(); this->loop(); } -void LoopManager::set_running(bool running) { this->game_running = running; } void LoopManager::fixed_update() {} void LoopManager::loop() { - LoopTimer & timer = this->loop_timer; - timer.start(); + this->loop_timer->start(); while (game_running) { - timer.update(); - - while (timer.get_lag() >= timer.get_fixed_delta_time()) { + this->loop_timer->update(); + + while (this->loop_timer->get_lag() >= this->loop_timer->get_fixed_delta_time()) { this->process_input(); this->fixed_update(); - timer.advance_fixed_update(); + this->loop_timer->advance_fixed_update(); } this->update(); this->render(); - - timer.enforce_frame_rate(); + this->loop_timer->enforce_frame_rate(); } } void LoopManager::setup() { - LoopTimer & timer = this->loop_timer; + this->game_running = true; - timer.start(); - timer.set_fps(200); + this->loop_timer->start(); + this->loop_timer->set_target_fps(200); } void LoopManager::render() { @@ -65,5 +67,9 @@ void LoopManager::render() { this->get_system<RenderSystem>().update(); } +bool LoopManager::on_shutdown(const ShutDownEvent & e){ + this->game_running = false; + return false; +} void LoopManager::update() {} diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h index d8910a0..17bddd1 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -6,11 +6,12 @@ #include "../manager/ComponentManager.h" #include "../manager/SceneManager.h" #include "../system/System.h" +#include "manager/SceneManager.h" -#include "LoopTimer.h" +#include "api/Event.h" +#include "api/LoopTimer.h" namespace crepe { - /** * \brief Main game loop manager * @@ -18,6 +19,12 @@ namespace crepe { */ class LoopManager { public: + /** + * \brief Start the gameloop + * + * This is the start of the engine where the setup is called and then the loop keeps running until the game stops running. + * Developers need to call this function to run the game. + */ void start(); LoopManager(); @@ -70,14 +77,6 @@ private: * This function updates physics and game logic based on LoopTimer's fixed_delta_time. */ void fixed_update(); - - /** - * \brief Set game running variable - * - * \param running running (false = game shutdown, true = game running) - */ - void set_running(bool running); - /** * \brief Function for executing render-related systems. * @@ -98,15 +97,17 @@ private: //! SDL context \todo no more singletons! SDLContext & sdl_context = SDLContext::get_instance(); - //! Loop timer \todo no more singletons! - LoopTimer & loop_timer = LoopTimer::get_instance(); - + //! loop timer instance + std::unique_ptr<LoopTimer> loop_timer; private: + + //! callback function for shutdown event + bool on_shutdown(const ShutDownEvent & e); /** * \brief Collection of System instances * * This map holds System instances indexed by the system's class typeid. It is filled in the - * constructor of \c LoopManager using LoopManager::load_system. + * constructor of LoopManager using LoopManager::load_system. */ std::unordered_map<std::type_index, std::unique_ptr<System>> systems; /** diff --git a/src/crepe/api/LoopTimer.cpp b/src/crepe/api/LoopTimer.cpp index 15a0e3a..07f0f75 100644 --- a/src/crepe/api/LoopTimer.cpp +++ b/src/crepe/api/LoopTimer.cpp @@ -1,4 +1,5 @@ #include <chrono> +#include <thread> #include "../facade/SDLContext.h" #include "../util/Log.h" @@ -9,15 +10,13 @@ using namespace crepe; LoopTimer::LoopTimer() { dbg_trace(); } -LoopTimer & LoopTimer::get_instance() { - static LoopTimer instance; - return instance; -} void LoopTimer::start() { this->last_frame_time = std::chrono::steady_clock::now(); + this->elapsed_time = std::chrono::milliseconds(0); - this->elapsed_fixed_time = std::chrono::milliseconds(0); + // by starting the elapsed_fixed_time at (0 - fixed_delta_time) in milliseconds it calls a fixed update at the start of the loop. + this->elapsed_fixed_time = -std::chrono::duration_cast<std::chrono::milliseconds>(fixed_delta_time); this->delta_time = std::chrono::milliseconds(0); } @@ -30,7 +29,8 @@ void LoopTimer::update() { if (this->delta_time > this->maximum_delta_time) { this->delta_time = this->maximum_delta_time; } - + this->actual_fps = 1.0 / this->delta_time.count(); + this->delta_time *= this->game_scale; this->elapsed_time += this->delta_time; this->last_frame_time = current_frame_time; @@ -44,34 +44,29 @@ void LoopTimer::advance_fixed_update() { this->elapsed_fixed_time += this->fixed double LoopTimer::get_fixed_delta_time() const { return this->fixed_delta_time.count(); } -void LoopTimer::set_fps(int fps) { - this->fps = fps; +void LoopTimer::set_target_fps(int fps) { + this->target_fps = fps; // target time per frame in seconds - this->frame_target_time = std::chrono::duration<double>(1.0) / fps; + this->frame_target_time = std::chrono::duration<double>(1.0) / target_fps; } -int LoopTimer::get_fps() const { return this->fps; } +int LoopTimer::get_fps() const { return this->actual_fps; } void LoopTimer::set_game_scale(double value) { this->game_scale = value; } double LoopTimer::get_game_scale() const { return this->game_scale; } void LoopTimer::enforce_frame_rate() { - std::chrono::steady_clock::time_point current_frame_time - = std::chrono::steady_clock::now(); - std::chrono::milliseconds frame_duration - = std::chrono::duration_cast<std::chrono::milliseconds>(current_frame_time - - this->last_frame_time); - - if (frame_duration < this->frame_target_time) { - std::chrono::milliseconds delay_time - = std::chrono::duration_cast<std::chrono::milliseconds>(this->frame_target_time - - frame_duration); - if (delay_time.count() > 0) { - SDLContext::get_instance().delay(delay_time.count()); - } - } + auto current_frame_time = std::chrono::steady_clock::now(); + auto frame_duration = current_frame_time - this->last_frame_time; - this->last_frame_time = current_frame_time; + // Check if frame duration is less than the target frame time + if (frame_duration < this->frame_target_time) { + auto delay_time = std::chrono::duration_cast<std::chrono::microseconds>(this->frame_target_time - frame_duration); + + if (delay_time.count() > 0) { + std::this_thread::sleep_for(delay_time); + } + } } double LoopTimer::get_lag() const { diff --git a/src/crepe/api/LoopTimer.h b/src/crepe/api/LoopTimer.h index 9393439..e348628 100644 --- a/src/crepe/api/LoopTimer.h +++ b/src/crepe/api/LoopTimer.h @@ -6,13 +6,7 @@ namespace crepe { class LoopTimer { public: - /** - * \brief Get the singleton instance of LoopTimer. - * - * \return A reference to the LoopTimer instance. - */ - static LoopTimer & get_instance(); - + LoopTimer(); /** * \brief Get the current delta time for the current frame. * @@ -35,7 +29,7 @@ public: * * \param fps The desired frames rendered per second. */ - void set_fps(int fps); + void set_target_fps(int fps); /** * \brief Get the current frames per second (FPS). @@ -68,7 +62,6 @@ private: * Initializes the timer to begin tracking frame times. */ void start(); - /** * \brief Enforce the frame rate limit. * @@ -97,12 +90,7 @@ private: */ double get_lag() const; - /** - * \brief Construct a new LoopTimer object. - * - * Private constructor for singleton pattern to restrict instantiation outside the class. - */ - LoopTimer(); + /** * \brief Update the timer to the current frame. @@ -121,8 +109,10 @@ private: void advance_fixed_update(); private: - //! Current frames per second - int fps = 50; + //! Target frames per second + int target_fps = 50; + //! Actual frames per second + int actual_fps = 0; //! Current game scale double game_scale = 1; //! Maximum delta time in seconds to avoid large jumps @@ -130,7 +120,7 @@ private: //! Delta time for the current frame in seconds std::chrono::duration<double> delta_time{0.0}; //! Target time per frame in seconds - std::chrono::duration<double> frame_target_time = std::chrono::duration<double>(1.0) / fps; + std::chrono::duration<double> frame_target_time = std::chrono::duration<double>(1.0) / target_fps; //! Fixed delta time for fixed updates in seconds std::chrono::duration<double> fixed_delta_time = std::chrono::duration<double>(1.0) / 50.0; //! Total elapsed game time in seconds @@ -139,6 +129,7 @@ private: std::chrono::duration<double> elapsed_fixed_time{0.0}; //! Time of the last frame std::chrono::steady_clock::time_point last_frame_time; + }; } // namespace crepe diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index 71bd1c9..cd96614 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -10,7 +10,7 @@ namespace crepe { class ComponentManager; class SceneManager; - +class LoopTimer; /** * Struct to pass references to classes that would otherwise need to be singletons down to * other classes within the engine hierarchy. Made to prevent constant changes to subclasses to @@ -28,6 +28,7 @@ struct Mediator { OptionalRef<SceneManager> scene_manager; OptionalRef<SaveManager> save_manager = SaveManager::get_instance(); OptionalRef<EventManager> event_manager = EventManager::get_instance(); + OptionalRef<LoopTimer> loop_timer; }; } // namespace crepe diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 560e2bc..6f92d45 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -19,5 +19,3 @@ endfunction() add_example(asset_manager) add_example(savemgr) add_example(rendering_particle) -add_example(gameloop) - diff --git a/src/example/gameloop.cpp b/src/example/gameloop.cpp deleted file mode 100644 index a676f20..0000000 --- a/src/example/gameloop.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "crepe/api/LoopManager.h" -using namespace crepe; -int main() { - LoopManager gameloop; - gameloop.start(); - return 1; -} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index d3e27b0..232c763 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -12,6 +12,7 @@ target_sources(test_main PUBLIC ValueBrokerTest.cpp DBTest.cpp Vector2Test.cpp - ScriptEventTest.cpp - ScriptSceneTest.cpp + LoopManagerTest.cpp + LoopTimerTest.cpp + ) diff --git a/src/test/LoopManagerTest.cpp b/src/test/LoopManagerTest.cpp new file mode 100644 index 0000000..af6cb1c --- /dev/null +++ b/src/test/LoopManagerTest.cpp @@ -0,0 +1,44 @@ +#include <gtest/gtest.h> +#include <chrono> +#include <thread> +#define private public +#define protected public +#include "api/LoopTimer.h" +#include "api/LoopManager.h" + +using namespace std::chrono; +using namespace crepe; + +class LoopManagerTest : public ::testing::Test { +protected: + LoopManager loop_manager; + + void SetUp() override { + // Setting up loop manager and start the loop + loop_manager.loop_timer->set_target_fps(60); + } +}; + +//Test to check if exactly 5 fixed updates are done every second (50Hz) +TEST_F(LoopManagerTest, FixedUpdate) { + loop_manager.loop_timer->fixed_delta_time = std::chrono::milliseconds(20); + loop_manager.loop_timer->set_target_fps(50); + int fixed_update_count = 0; + loop_manager.loop_timer->start(); + // We want to simulate the game loop for about 1 second + auto start_time = steady_clock::now(); + + // Simulate the game loop for 1 second + while (duration_cast<milliseconds>(steady_clock::now() - start_time) < std::chrono::milliseconds(1000)) { + loop_manager.loop_timer->update(); + // Simulate processing fixed updates while there's lag to advance + while (loop_manager.loop_timer->get_lag() >= loop_manager.loop_timer->get_fixed_delta_time()) { + fixed_update_count++; + loop_manager.loop_timer->advance_fixed_update(); + } + + loop_manager.loop_timer->enforce_frame_rate(); + } + // gameloop is 99 because it first takes 20 millisecond to build the lag to execute the fixed loop + ASSERT_EQ(fixed_update_count, 50); +} diff --git a/src/test/LoopTimerTest.cpp b/src/test/LoopTimerTest.cpp new file mode 100644 index 0000000..6e3f118 --- /dev/null +++ b/src/test/LoopTimerTest.cpp @@ -0,0 +1,81 @@ +#include <gtest/gtest.h> +#include <chrono> +#include <thread> +#define private public +#define protected public +#include "api/LoopTimer.h" + +using namespace std::chrono; +using namespace crepe; + +class LoopTimerTest : public ::testing::Test { +protected: + LoopTimer loop_timer; + + void SetUp() override { + loop_timer.start(); + } +}; +TEST_F(LoopTimerTest, EnforcesTargetFrameRate) { + // Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms) + loop_timer.set_target_fps(60); + + auto start_time = steady_clock::now(); + loop_timer.enforce_frame_rate(); + + auto elapsed_time = steady_clock::now() - start_time; + auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count(); + + // For 60 FPS, the target frame time is around 16.67ms + ASSERT_GE(elapsed_ms, 16); // Make sure it's at least 16 ms (could be slightly more) + ASSERT_LE(elapsed_ms, 18); // Ensure it's not too much longer +} +TEST_F(LoopTimerTest, SetTargetFps) { + // Set the target FPS to 120 + loop_timer.set_target_fps(120); + + // Calculate the expected frame time (~8.33ms per frame) + auto expected_frame_time = std::chrono::duration<double>(1.0 / 120.0); + + ASSERT_NEAR(loop_timer.frame_target_time.count(), expected_frame_time.count(), 0.001); +} +TEST_F(LoopTimerTest, DeltaTimeCalculation) { + // Set the target FPS to 60 (16.67 ms per frame) + loop_timer.set_target_fps(60); + + auto start_time = steady_clock::now(); + loop_timer.update(); + auto end_time = steady_clock::now(); + + // Check the delta time + double delta_time = loop_timer.get_delta_time(); + + auto elapsed_time = duration_cast<milliseconds>(end_time - start_time).count(); + + // Assert that delta_time is close to the elapsed time + ASSERT_GE(delta_time, elapsed_time / 1000.0); + ASSERT_LE(delta_time, (elapsed_time + 2) / 1000.0); +} + +TEST_F(LoopTimerTest, getCurrentTime) { + // Set the target FPS to 60 (16.67 ms per frame) + loop_timer.set_target_fps(60); + + auto start_time = steady_clock::now(); + + // Sleep for 500 milliseconds + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + loop_timer.update(); + + auto end_time = steady_clock::now(); + + // Get the elapsed time in seconds as a double + auto elapsed_time = duration_cast<std::chrono::duration<double>>(end_time - start_time).count(); + + ASSERT_NEAR(loop_timer.get_current_time(), elapsed_time, 0.001); + + +} + + |