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}