game_engine_core/
lib.rs

1//! A simple crate that takes care of updating the main loop of a game engine.
2//! It is based on a stack-based state machine and is agnostic of the engine used.
3//! It does not rely on an ECS nor on other libraries. Sleeping is configurable.
4//! When sleeping is disabled, this crate is compatible with WASM. When enabled,
5//! the game loop will run at the target framerate.
6#![deny(missing_docs)]
7pub use game_clock::*;
8pub use game_state_machine::*;
9use spin_sleep::LoopHelper;
10
11/// The main structure of the engine core loop.
12/// It holds the data necessary to the execution of a game engine.
13/// # Generics:
14/// - SD: Type of the data passed to states.
15/// - F: Post update function.
16pub struct Engine<SD, F: Fn(&mut SD, &Time)> {
17    loop_helper: LoopHelper,
18    /// The inner state machine.
19    /// You can verify that it is still running using
20    /// ```rust,ignore
21    /// engine.state_machine.is_running();
22    /// ```
23    pub state_machine: StateMachine<SD>,
24    /// The game data that we are using to drive the execution.
25    pub state_data: SD,
26    /// The inner clock keeping track of time.
27    pub time: Time,
28    post_update: F,
29}
30
31impl<SD, F: Fn(&mut SD, &Time)> Engine<SD, F> {
32    /// Creates a new `Engine`.
33    /// The initial state and state data will be used to initialize the state machine.
34    /// The post update function will be stored. It is called at the end of game frames.
35    /// `max_fps` specifies the maximum number of frames that can happen within a second.
36    /// # Generics:
37    /// I: Initial state.
38    pub fn new<I: State<SD> + 'static>(
39        init_state: I,
40        mut init_state_data: SD,
41        post_update: F,
42        max_fps: f32,
43    ) -> Self {
44        let loop_helper = LoopHelper::builder().build_with_target_rate(max_fps);
45        let mut state_machine = StateMachine::default();
46        let time = Time::default();
47        state_machine.push(Box::new(init_state), &mut init_state_data);
48        Self {
49            loop_helper,
50            state_machine,
51            state_data: init_state_data,
52            time,
53            post_update,
54        }
55    }
56
57    /// Runs a single frame of the engine. Returns false if this was the last
58    /// frame the engine will run and returns true if the engine can be run again.
59    /// The sleep argument specifies if this function should take care of sleeping
60    /// the thread. It is recommended to always have it to true, except in the
61    /// case where you are using the engine in an async context. If set to false,
62    /// the `Time` argument in the post_update callback will be meaningless and you
63    /// will have to calculate the time difference yourself.
64    ///
65    /// This function is most useful when called from WASM or in the context of
66    /// another loop. For instance, winit and bracket-lib are both libraries that
67    /// require control of the main loop, for compatibility with mobile and web platforms.
68    /// Here, we can let them take care of the main loop and simple call `engine_frame`.
69    pub fn engine_frame(&mut self, sleep: bool) -> bool {
70        if sleep {
71            let delta = self.loop_helper.loop_start();
72            {
73                self.time.advance_frame(delta);
74            }
75        }
76
77        self.state_machine.update(&mut self.state_data);
78        if sleep {
79            self.loop_helper.loop_sleep();
80        }
81        (self.post_update)(&mut self.state_data, &self.time);
82        self.state_machine.is_running()
83    }
84
85    /// Runs the engine until the state machine quits.
86    /// Generics:
87    /// - SD: The type of the data that is passed to states when updating.
88    /// - I: The type of the initial state. This is the first state that it started
89    /// when the engine is started.
90    /// - F: The post update function. This function is called after each loop of
91    /// of the engine. It receives the state data mutable and a reference to the
92    /// structure keeping track of the time. This function is called *after* sleeping
93    /// at the end of the frame, which means it is equivalent to the start of the next
94    /// frame.
95    pub fn engine_loop(&mut self) {
96        while self.engine_frame(true) {}
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use crate::*;
103    #[test]
104    fn test_loop() {
105        struct MyState;
106        impl State<i32> for MyState {
107            fn update(&mut self, state_data: &mut i32) -> StateTransition<i32> {
108                *state_data += 1;
109                StateTransition::Quit
110            }
111        }
112        Engine::new(
113            MyState,
114            0,
115            |s, _| {
116                *s += 1;
117                assert_eq!(*s, 2);
118            },
119            1000.0,
120        )
121        .engine_loop();
122    }
123}