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
115
116
117
118
119
120
121
//! A simple crate that takes care of updating the main loop of a game engine.
//! It is based on a stack-based state machine and is agnostic of the engine used.
//! It does not rely on an ECS nor on other libraries. Sleeping is configurable.
//! When sleeping is disabled, this crate is compatible with WASM. When enabled,
//! the game loop will run at the target framerate.
#![deny(missing_docs)]
pub use game_clock::*;
pub use game_state_machine::*;
use spin_sleep::LoopHelper;

/// The main structure of the engine core loop.
/// It holds the data necessary to the execution of a game engine.
/// # Generics:
/// - SD: Type of the data passed to states.
/// - F: Post update function.
pub struct Engine<SD, F: Fn(&mut SD, &Time)> {
    loop_helper: LoopHelper,
    /// The inner state machine.
    /// You can verify that it is still running using
    /// ```rust,ignore
    /// engine.state_machine.is_running();
    /// ```
    pub state_machine: StateMachine<SD>,
    state_data: SD,
    time: Time,
    post_update: F,
}

impl<SD, F: Fn(&mut SD, &Time)> Engine<SD, F> {
    /// Creates a new `Engine`.
    /// The initial state and state data will be used to initialize the state machine.
    /// The post update function will be stored. It is called at the end of game frames.
    /// `max_fps` specifies the maximum number of frames that can happen within a second.
    /// # Generics:
    /// I: Initial state.
    pub fn new<I: State<SD> + 'static>(
        init_state: I,
        mut init_state_data: SD,
        post_update: F,
        max_fps: f32,
    ) -> Self {
        let loop_helper = LoopHelper::builder().build_with_target_rate(max_fps);
        let mut state_machine = StateMachine::default();
        let time = Time::default();
        state_machine.push(Box::new(init_state), &mut init_state_data);
        Self {
            loop_helper,
            state_machine,
            state_data: init_state_data,
            time,
            post_update,
        }
    }

    /// Runs a single frame of the engine. Returns false if this was the last
    /// frame the engine will run and returns true if the engine can be run again.
    /// The sleep argument specifies if this function should take care of sleeping
    /// the thread. It is recommended to always have it to true, except in the
    /// case where you are using the engine in an async context. If set to false,
    /// the `Time` argument in the post_update callback will be meaningless and you
    /// will have to calculate the time difference yourself.
    ///
    /// This function is most useful when called from WASM or in the context of
    /// another loop. For instance, winit and bracket-lib are both libraries that
    /// require control of the main loop, for compatibility with mobile and web platforms.
    /// Here, we can let them take care of the main loop and simple call `engine_frame`.
    pub fn engine_frame(&mut self, sleep: bool) -> bool {
        if sleep {
            let delta = self.loop_helper.loop_start();
            {
                self.time.advance_frame(delta);
            }
        }

        self.state_machine.update(&mut self.state_data);
        if sleep {
            self.loop_helper.loop_sleep();
        }
        (self.post_update)(&mut self.state_data, &self.time);
        self.state_machine.is_running()
    }

    /// Runs the engine until the state machine quits.
    /// Generics:
    /// - SD: The type of the data that is passed to states when updating.
    /// - I: The type of the initial state. This is the first state that it started
    /// when the engine is started.
    /// - F: The post update function. This function is called after each loop of
    /// of the engine. It receives the state data mutable and a reference to the
    /// structure keeping track of the time. This function is called *after* sleeping
    /// at the end of the frame, which means it is equivalent to the start of the next
    /// frame.
    pub fn engine_loop(&mut self) {
        while self.engine_frame(true) {}
    }
}

#[cfg(test)]
mod tests {
    use crate::*;
    #[test]
    fn test_loop() {
        struct MyState;
        impl State<i32> for MyState {
            fn update(&mut self, state_data: &mut i32) -> StateTransition<i32> {
                *state_data += 1;
                StateTransition::Quit
            }
        }
        Engine::new(
            MyState,
            0,
            |s, _| {
                *s += 1;
                assert_eq!(*s, 2);
            },
            1000.0,
        )
        .engine_loop();
    }
}