diff options
-rw-r--r-- | contributing.md | 79 | ||||
-rw-r--r-- | src/crepe/api/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/crepe/api/LoopManager.cpp | 55 | ||||
-rw-r--r-- | src/crepe/api/LoopManager.h | 79 | ||||
-rw-r--r-- | src/crepe/api/LoopTimer.cpp | 86 | ||||
-rw-r--r-- | src/crepe/api/LoopTimer.h | 147 | ||||
-rw-r--r-- | src/crepe/facade/SDLContext.cpp | 1 | ||||
-rw-r--r-- | src/crepe/facade/SDLContext.h | 13 | ||||
-rw-r--r-- | src/example/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/example/gameloop.cpp | 7 |
10 files changed, 468 insertions, 4 deletions
diff --git a/contributing.md b/contributing.md index a80f2b4..3090727 100644 --- a/contributing.md +++ b/contributing.md @@ -54,8 +54,6 @@ that you can click on to open them. - Implementation details (if applicable) - Header files (`.h`) contain the following types of comments: - [Usage documentation](#documentation) (required) - > [!NOTE] - > Constructors/destructors aren't required to have a `\brief` description - Implementation details (if they affect the header) - Design/data structure decisions (if applicable) - <details><summary> @@ -436,7 +434,8 @@ that you can click on to open them. ``` </td></tr></table></details> - <details><summary> - Variables that are being moved always use the fully qualified <code>std::move</code> + Variables that are being moved always use the fully qualified + <code>std::move</code> </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> ```cpp @@ -666,6 +665,19 @@ that you can click on to open them. friend class ComponentManager; ``` </td></tr></table></details> +- <details><summary> + Do not <i>pick</i> fixed-width integer types (unless required) + </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + + ```cpp + unsigned long long foo(); + ``` + </td><td> + + ```cpp + uint64_t foo(); + ``` + </td></tr></table></details> ## CMakeLists-specific @@ -716,6 +728,67 @@ that you can click on to open them. void foo(); ``` </td></tr></table></details> +- <details><summary> + The default constructor and destructor aren't required to have a + <code>\brief</code> description + </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + + ```cpp + Foo(); + virtual ~Foo(); + ``` + </td><td> + + ```cpp + //! Create instance of Foo + Foo(); + //! Destroy instance of Foo + virtual ~Foo(); + ``` + </td></tr></table></details> +- <details><summary> + Parameter direction shouldn't be specified using Doxygen + </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + + ```cpp + /** + * \param bar Reference to Bar + */ + void foo(const Bar & bar); + ``` + </td><td> + + ```cpp + /** + * \param[in] bar Reference to Bar + */ + void foo(const Bar & bar); + ``` + </td></tr></table></details> +- <details><summary> + Deleted functions shouldn't have Doxygen comments + </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> + + ```cpp + // singleton + Foo(const Foo &) = delete; + Foo(Foo &&) = delete; + Foo & operator=(const Foo &) = delete; + Foo & operator=(Foo &&) = delete; + ``` + </td><td> + + ```cpp + //! Deleted copy constructor + Foo(const Foo &) = delete; + //! Deleted move constructor + Foo(Foo &&) = delete; + //! Deleted copy assignment operator + Foo & operator=(const Foo &) = delete; + //! Deleted move assignment operator + Foo & operator=(Foo &&) = delete; + ``` + </td></tr></table></details> # Libraries diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 87cbb09..85696c4 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -18,6 +18,8 @@ target_sources(crepe PUBLIC Vector2.cpp Camera.cpp Animator.cpp + LoopManager.cpp + LoopTimer.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -42,4 +44,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES SceneManager.hpp Camera.h Animator.h + LoopManager.h + LoopTimer.h ) diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp new file mode 100644 index 0000000..2e9823f --- /dev/null +++ b/src/crepe/api/LoopManager.cpp @@ -0,0 +1,55 @@ + +#include "../facade/SDLContext.h" +#include "../system/RenderSystem.h" +#include "../system/ScriptSystem.h" + +#include "LoopManager.h" +#include "LoopTimer.h" + +using namespace crepe; + +LoopManager::LoopManager() {} +void LoopManager::process_input() { + SDLContext::get_instance().handle_events(this->game_running); +} +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 = LoopTimer::get_instance(); + timer.start(); + + while (game_running) { + timer.update(); + + while (timer.get_lag() >= timer.get_fixed_delta_time()) { + this->process_input(); + this->fixed_update(); + timer.advance_fixed_update(); + } + + this->update(); + this->render(); + + timer.enforce_frame_rate(); + } +} + +void LoopManager::setup() { + this->game_running = true; + LoopTimer::get_instance().start(); + LoopTimer::get_instance().set_fps(60); +} + +void LoopManager::render() { + if (this->game_running) { + RenderSystem::get_instance().update(); + } +} + +void LoopManager::update() { LoopTimer & timer = LoopTimer::get_instance(); } diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h new file mode 100644 index 0000000..2f03193 --- /dev/null +++ b/src/crepe/api/LoopManager.h @@ -0,0 +1,79 @@ +#pragma once + +#include <memory> + +class RenderSystem; +class SDLContext; +class LoopTimer; +class ScriptSystem; +class SoundSystem; +class ParticleSystem; +class PhysicsSystem; +class AnimatorSystem; +class CollisionSystem; +namespace crepe { + +class LoopManager { +public: + void start(); + LoopManager(); + +private: + /** + * \brief Setup function for one-time initialization. + * + * This function initializes necessary components for the game. + */ + void setup(); + /** + * \brief Main game loop function. + * + * This function runs the main loop, handling game updates and rendering. + */ + void loop(); + + /** + * \brief Function for handling input-related system calls. + * + * Processes user inputs from keyboard and mouse. + */ + void process_input(); + + /** + * \brief Per-frame update. + * + * Updates the game state based on the elapsed time since the last frame. + */ + void update(); + + /** + * \brief Late update which is called after update(). + * + * This function can be used for final adjustments before rendering. + */ + void late_update(); + + /** + * \brief Fixed update executed at a fixed rate. + * + * 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. + * + * Renders the current state of the game to the screen. + */ + void render(); + + bool game_running = false; + //#TODO add system instances +}; + +} // namespace crepe diff --git a/src/crepe/api/LoopTimer.cpp b/src/crepe/api/LoopTimer.cpp new file mode 100644 index 0000000..8f09e41 --- /dev/null +++ b/src/crepe/api/LoopTimer.cpp @@ -0,0 +1,86 @@ +#include <chrono> + +#include "../facade/SDLContext.h" +#include "../util/log.h" + +#include "LoopTimer.h" + +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); + this->delta_time = std::chrono::milliseconds(0); +} + +void LoopTimer::update() { + auto current_frame_time = std::chrono::steady_clock::now(); + // Convert to duration in seconds for delta time + this->delta_time + = std::chrono::duration_cast<std::chrono::duration<double>>( + current_frame_time - last_frame_time); + + if (this->delta_time > this->maximum_delta_time) { + this->delta_time = this->maximum_delta_time; + } + + this->delta_time *= this->game_scale; + this->elapsed_time += this->delta_time; + this->last_frame_time = current_frame_time; +} + +double LoopTimer::get_delta_time() const { return this->delta_time.count(); } + +double LoopTimer::get_current_time() const { + return this->elapsed_time.count(); +} + +void LoopTimer::advance_fixed_update() { + this->elapsed_fixed_time += this->fixed_delta_time; +} + +double LoopTimer::get_fixed_delta_time() const { + return this->fixed_delta_time.count(); +} + +void LoopTimer::set_fps(int fps) { + this->fps = fps; + // target time per frame in seconds + this->frame_target_time = std::chrono::seconds(1) / fps; +} + +int LoopTimer::get_fps() const { return this->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()); + } + } + + this->last_frame_time = current_frame_time; +} + +double LoopTimer::get_lag() const { + return (this->elapsed_time - this->elapsed_fixed_time).count(); +} diff --git a/src/crepe/api/LoopTimer.h b/src/crepe/api/LoopTimer.h new file mode 100644 index 0000000..85687be --- /dev/null +++ b/src/crepe/api/LoopTimer.h @@ -0,0 +1,147 @@ +#pragma once + +#include <chrono> +#include <cstdint> + +namespace crepe { + +class LoopTimer { +public: + /** + * \brief Get the singleton instance of LoopTimer. + * + * \return A reference to the LoopTimer instance. + */ + static LoopTimer & get_instance(); + + /** + * \brief Get the current delta time for the current frame. + * + * \return Delta time in seconds since the last frame. + */ + double get_delta_time() const; + + /** + * \brief Get the current game time. + * + * \note The current game time may vary from real-world elapsed time. + * It is the cumulative sum of each frame's delta time. + * + * \return Elapsed game time in seconds. + */ + double get_current_time() const; + + /** + * \brief Set the target frames per second (FPS). + * + * \param fps The desired frames rendered per second. + */ + void set_fps(int fps); + + /** + * \brief Get the current frames per second (FPS). + * + * \return Current FPS. + */ + int get_fps() const; + + /** + * \brief Get the current game scale. + * + * \return The current game scale, where 0 = paused, 1 = normal speed, and values > 1 speed up the game. + */ + double get_game_scale() const; + + /** + * \brief Set the game scale. + * + * \param game_scale The desired game scale (0 = pause, 1 = normal speed, > 1 = speed up). + */ + void set_game_scale(double game_scale); + +private: + friend class LoopManager; + + /** + * \brief Start the loop timer. + * + * Initializes the timer to begin tracking frame times. + */ + void start(); + + /** + * \brief Enforce the frame rate limit. + * + * Ensures that the game loop does not exceed the target FPS by delaying + * frame updates as necessary. + */ + void enforce_frame_rate(); + + /** + * \brief Get the fixed delta time for consistent updates. + * + * Fixed delta time is used for operations that require uniform time steps, + * such as physics calculations. + * + * \return Fixed delta time in seconds. + */ + double get_fixed_delta_time() const; + + /** + * \brief Get the accumulated lag in the game loop. + * + * Lag represents the difference between the target frame time and the + * actual frame time, useful for managing fixed update intervals. + * + * \return Accumulated lag in seconds. + */ + 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. + * + * Calculates and updates the delta time for the current frame and adds it to + * the cumulative game time. + */ + void update(); + + /** + * \brief Advance the game loop by a fixed update interval. + * + * This method progresses the game state by a consistent, fixed time step, + * allowing for stable updates independent of frame rate fluctuations. + */ + void advance_fixed_update(); + +private: + //! Current frames per second + int fps = 50; + //! Current game scale + double game_scale = 1; + //! Maximum delta time in seconds to avoid large jumps + std::chrono::duration<double> maximum_delta_time{0.25}; + //! 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::seconds(1) / fps; + //! Fixed delta time for fixed updates in seconds + std::chrono::duration<double> fixed_delta_time + = std::chrono::seconds(1) / 50; + //! Total elapsed game time in seconds + std::chrono::duration<double> elapsed_time{0.0}; + //! Total elapsed time for fixed updates in seconds + 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/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index c4c96e2..39d0d4d 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -191,3 +191,4 @@ int SDLContext::get_height(const Texture & ctx) const { SDL_QueryTexture(ctx.texture.get(), NULL, NULL, NULL, &h); return h; } +void SDLContext::delay(int ms) const { SDL_Delay(ms); } diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index e358c21..536dec5 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -58,12 +58,23 @@ private: private: //! Will only use get_ticks friend class AnimatorSystem; - + //! Will only use delay + friend class LoopTimer; /** * \brief Gets the current SDL ticks since the program started. * \return Current ticks in milliseconds as a constant uint64_t. */ uint64_t get_ticks() const; + /** + * \brief Pauses the execution for a specified duration. + * + * This function uses SDL's delay function to halt the program execution + * for a given number of milliseconds, allowing for frame rate control + * or other timing-related functionality. + * + * \param ms Duration of the delay in milliseconds. + */ + void delay(int ms) const; private: /** diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 36f9d4d..c8a4b85 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -28,4 +28,5 @@ add_example(proxy) add_example(db) add_example(ecs) add_example(scene_manager) +add_example(gameloop) diff --git a/src/example/gameloop.cpp b/src/example/gameloop.cpp new file mode 100644 index 0000000..a676f20 --- /dev/null +++ b/src/example/gameloop.cpp @@ -0,0 +1,7 @@ +#include "crepe/api/LoopManager.h" +using namespace crepe; +int main() { + LoopManager gameloop; + gameloop.start(); + return 1; +} |