1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! An object oriented approach to the gameloop, separate from [`fps_gameloop!`](crate::fps_gameloop). Read the [`MainLoopRoot`] documentation for more info

use std::time::{Duration, Instant};

/// This is an alternative way to handle the gameloop, separate from [`fps_gameloop!`](crate::fps_gameloop). It takes on a more object oriented approach where everything related to the game is stored inside a single struct which implements this trait.
///
/// Check out the [example](https://docs.rs/crate/gemini-engine/latest/source/examples/game-loop-root.rs), a version of `quick-start.rs` (the example from the [elements](crate::elements) doc page) rewritten using `MainLoopRoot`. While in that particular case it might appear to have a lot of boilerplate code, it can make your game much easier to manage as you add more things and scale it
///
/// This trait's separate functions have more documentation to explain further details
pub trait MainLoopRoot {
    /// This type should be generated by [`MainLoopRoot::sleep_and_get_input_data()`] and will be passed to [`MainLoopRoot::frame()`]
    type InputDataType;

    /// This is where the main logic of your game - handling input, moving objects, handling collisions, etc.
    ///
    /// # Example
    /// ```
    /// # use gemini_engine::elements::view::{Wrapping, ViewElement, View, Point, Vec2D };
    /// # use gemini_engine::gameloop::MainLoopRoot;
    /// # struct Dummy { pos: Vec2D }
    /// # impl ViewElement for Dummy {
    /// #   fn active_pixels(&self) -> Vec<Point> { vec![] }
    /// # }
    /// # struct Game {
    /// #   view: View,
    /// #   player: Dummy,
    /// #   enemies: Vec<Dummy>,
    /// #
    /// # }
    /// # impl MainLoopRoot for Game {
    /// # type InputDataType = u32;
    /// // --inside impl MainLoopRoot for Game--
    /// fn frame(&mut self, input_data: Option<Self::InputDataType>) {
    ///     self.player.pos.x += 1; // player has a pos: Vec2D field
    /// }
    /// # fn render_frame(&mut self) {}
    /// # }
    /// ```
    fn frame(&mut self, input_data: Option<Self::InputDataType>);

    /// All rendering code (blitting, 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
    /// # Example
    /// 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
    /// ```
    /// # use gemini_engine::elements::view::{Wrapping, ViewElement, View, Point};
    /// # struct Dummy {}
    /// # impl ViewElement for Dummy {
    /// #   fn active_pixels(&self) -> Vec<Point> { vec![] }
    /// # }
    /// # struct Game {
    /// #   view: View,
    /// #   player: Dummy,
    /// #   enemies: Vec<Dummy>,
    /// #
    /// # }
    /// # impl Game {
    /// // --inside impl MainLoopRoot for Game--
    /// fn render_frame(&mut self) {
    ///     self.view.clear();
    ///
    ///     for enemy in &self.enemies { // Go through a vector of Enemy structs (all of which would implement ViewElement)
    ///         self.view.blit(enemy, Wrapping::Ignore);
    ///     }
    ///     self.view.blit(&self.player, Wrapping::Ignore);
    ///
    ///     self.view.display_render().unwrap();
    /// }
    /// # }
    /// ```
    fn render_frame(&mut self);

    /// The function used to sleep for the appropriate amount based on the FPS. Uses [`gameloop::sleep_fps`](super::sleep_fps()) by default and will return None for the `InputDataType`. The returned bool value should represent whether or not to skip rendering on the next frame
    fn sleep_and_get_input_data(
        &self,
        fps: f32,
        elapsed: Duration,
    ) -> (bool, Option<Self::InputDataType>) {
        (super::sleep_fps(fps, Some(elapsed)), None)
    }

    /// The main loop function of the main loop root. This shouldnt be overriden. The `fps` parameter will be passed straight to [`sleep_and_get_input()`](MainLoopRoot::sleep_and_get_input_data()). See the [`MainLoopRoot`] documentation for more info
    /// ```rust, no_run
    /// # use gemini_engine::gameloop::MainLoopRoot;
    /// # struct Game {}
    /// # impl Game {
    /// #   fn new() -> Game { Game {} }
    /// # }
    /// # impl MainLoopRoot for Game {
    /// #   type InputDataType = u32;
    /// #   fn frame(&mut self, input_data: Option<Self::InputDataType>) {}
    /// #   fn render_frame(&mut self) {}
    /// # }
    /// const FPS: f32 = 30.0;
    /// let mut game = Game::new(); // Implements MainLoopRoot and has a function that sets up all the game objects
    ///
    /// game.main_loop(FPS);
    /// ```
    fn main_loop(&mut self, fps: f32) {
        let mut elapsed = Duration::ZERO;

        loop {
            let (frame_skip, input_data) = self.sleep_and_get_input_data(fps, elapsed);
            let now = Instant::now();

            self.frame(input_data);

            if !frame_skip {
                self.render_frame();
            }

            elapsed = now.elapsed();
        }
    }
}