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}