diff options
Diffstat (limited to 'src')
| -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); + +	 +} + + |