simple_game_engine/engine/
mod.rs

1//! Contains the `Engine` type, which manages all resources to do with the game engine, and calls
2//! the functions defined in the `Application` trait.
3
4mod fps;
5use fps::FpsCounter;
6
7use std::error::Error;
8
9use sdl2::event::Event;
10
11use crate::{
12    input::{InputState, KeyboardState, MouseState},
13    Application, WindowCanvas,
14};
15
16/// The main game engine, which manages the display and input.
17pub struct Engine<'a> {
18    app: &'a mut dyn Application<WindowCanvas>,
19    title: &'a str,
20    width: u32,
21    height: u32,
22    /// Whether the FPS should be calculated and displayed in the titlebar of the window.
23    pub show_fps: bool,
24    ctx: sdl2::Sdl,
25}
26
27impl<'a> Engine<'a> {
28    /// Create a new engine.
29    /// # Parameters
30    ///* `app`: Defines the application's logic.
31    ///* `title`: Title of the window.
32    /// * `width`: Width (in pixels) of the window.
33    /// * `height`: Height (in pixels) of the window.
34    /// # Example
35    /// ```
36    /// # struct App;
37    /// # impl simple_game_engine::Application for App {}
38    /// use simple_game_engine::Engine;
39    /// # fn main() -> Result<(), String> {
40    /// let mut app = App {}; // Some `Application` implementation
41    /// let engine = Engine::new(&mut app, "Window Title", 640, 480)?;
42    /// # Ok(())
43    /// # }
44    /// ```
45    pub fn new(
46        app: &'a mut dyn Application,
47        title: &'a str,
48        width: u32,
49        height: u32,
50    ) -> Result<Engine<'a>, String> {
51        Ok(Engine {
52            app,
53            title,
54            width,
55            height,
56            show_fps: true,
57            ctx: sdl2::init()?,
58        })
59    }
60
61    /// Create and show the window and start the main event loop.
62    /// # Parameters
63    /// * `present_vsync`: Whether to limit the frame rate of the application to the frame rate of
64    ///   the display.
65    /// # Example
66    /// ```no_run
67    /// # struct App;
68    /// # impl simple_game_engine::Application for App {}
69    /// use simple_game_engine::Engine;
70    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
71    /// let mut app = App {}; // Some `Application` implementation
72    /// let mut engine = Engine::new(&mut app, "Window Title", 640, 480)?;
73    /// engine.start(true)?; // Starts with vsync enabled
74    /// # Ok(())
75    /// # }
76    /// ```
77    pub fn start(&mut self, present_vsync: bool) -> Result<(), Box<dyn Error>> {
78        let video = self.ctx.video()?;
79        let mut fps_counter = FpsCounter::new(self.ctx.timer()?);
80        let mut canvas = video
81            .window(self.title, self.width, self.height)
82            .position_centered()
83            .build()?
84            .into_canvas()
85            .accelerated();
86        if present_vsync {
87            canvas = canvas.present_vsync();
88        }
89        let mut canvas = WindowCanvas::new(canvas.build()?);
90        // Event handling
91        let mut event_pump = self.ctx.event_pump()?;
92        // Input state
93        let mut input = InputState {
94            keyboard: KeyboardState::new(event_pump.keyboard_state().scancodes()),
95            mouse: MouseState::new(event_pump.mouse_state()),
96        };
97
98        // Call the app.on_create() function so the user can perform one-time initialisation of
99        // their application.
100        if !self.app.on_create(&mut canvas, &input)? {
101            return self.app.on_quit();
102        }
103
104        // These variables are used to determine the elapsed time between frames, to allow for
105        // time-regulated things like animation and to calculate average frame rates
106        loop {
107            let elapsed_time = fps_counter.update(self.show_fps);
108            if self.show_fps && fps_counter.time_acc() >= 1.0 {
109                let fps = fps_counter.fps();
110                let title = format!("{} ({} FPS)", self.title, fps.round() as u32);
111                // This fails silently on error
112                canvas.window_mut().set_title(title.as_str()).ok();
113                fps_counter.reset_average();
114            }
115
116            // Process next frame and exit if `Ok(false)` is returned
117            if !self.app.on_update(&mut canvas, &input, elapsed_time)? {
118                return self.app.on_quit();
119            }
120
121            // Handle events
122            for event in event_pump.poll_iter() {
123                match event {
124                    Event::Quit { .. } => {
125                        return self.app.on_quit();
126                    }
127                    _ => {}
128                }
129            }
130            // Refresh the input state
131            input
132                .keyboard
133                .update(event_pump.keyboard_state().scancodes());
134            input.mouse.update(event_pump.mouse_state());
135
136            // Flip the double buffer
137            canvas.present();
138        }
139    }
140}