gemini_mainloop/
with_root.rs

1use std::time::{Duration, Instant};
2
3/// A trait to abstract away the main loop and simplify code writing
4///
5/// Check out the `game-loop-root.rs` example, a version of `quick-start.rs` rewritten using `MainLoopRoot`
6pub trait MainLoopRoot {
7    /// Return the FPS at which the main loop should run. A constant like `60.0` or `30.0` is sufficient
8    fn get_fps(&self) -> f32;
9
10    /// This is where the main logic of your game should go - handling input, moving objects, handling collisions, etc.
11    fn frame(&mut self);
12
13    /// All rendering code (drawing, printing to the screen, etc.) should be called in here. If the bool value returned by [`MainLoopRoot::sleep_and_get_input_data()`] is true, this won't run and nothing should be printed to the screen
14    /// ## Example
15    /// Here's an example of what a `render_frame` trait implementation might look like, assuming your root struct has a `view: View` property for your main view
16    /// ```
17    /// # use gemini_engine::{core::{CanDraw, Canvas}, view::View};
18    /// # struct Dummy {}
19    /// # impl CanDraw for Dummy {
20    /// #   fn draw_to(&self, canvas: &mut impl Canvas) {}
21    /// # }
22    /// # struct Game {
23    /// #   view: View,
24    /// #   player: Dummy,
25    /// #   enemies: Vec<Dummy>,
26    /// # }
27    /// # impl Game {
28    /// // --inside impl MainLoopRoot for Game--
29    /// fn render_frame(&mut self) {
30    ///     self.view.clear();
31    ///
32    ///     // Draw every enemy in a vector of `Enemies` (all of which would implement `CanDraw`)
33    ///     for enemy in &self.enemies {
34    ///         self.view.draw(enemy);
35    ///     }
36    ///     self.view.draw(&self.player);
37    ///
38    ///     self.view.display_render().unwrap();
39    /// }
40    /// # }
41    /// ```
42    fn render_frame(&mut self);
43
44    /// The function used to sleep for the appropriate amount based on the value returned by `get_fps`. Uses [`gameloop::sleep_fps`](crate::sleep_fps()) by default and will return None for the `InputDataType`. If the return value is `true`, `render_frame` will not be called on the next frame
45    fn sleep(&self, fps: f32, elapsed: Duration) -> bool {
46        // TODO: rename this function to just `sleep`
47        super::sleep_fps(fps, Some(elapsed))
48    }
49
50    /// The main loop function of the main loop root. This shouldn't be overriden. See the [`MainLoopRoot`] documentation for more info
51    /// ```no_run
52    /// # use gemini_engine::gameloop::MainLoopRoot;
53    /// # struct Game {}
54    /// # impl Game {
55    /// #   fn new() -> Game { Game {} }
56    /// # }
57    /// impl MainLoopRoot for Game {
58    ///     fn get_fps(&self) -> f32 { 30.0 }
59    ///     fn frame(&mut self) {
60    ///         // --snip--
61    ///     }
62    ///     fn render_frame(&mut self) {
63    ///         // --snip--
64    ///     }
65    /// }
66    /// let mut game = Game::new(); // `Game` implements `MainLoopRoot`. Its `new` method sets up all the game objects
67    ///
68    /// game.main_loop();
69    /// ```
70    fn main_loop(&mut self) {
71        let mut frame_skip = false;
72
73        loop {
74            let now = Instant::now();
75
76            self.frame();
77
78            if !frame_skip {
79                self.render_frame();
80            }
81
82            let elapsed = now.elapsed();
83            frame_skip = self.sleep(self.get_fps(), elapsed);
84        }
85    }
86}