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}