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
122
123
124
125
//! Contains the `Engine` type, which manages all resources to do with the game engine, and calls
//! the functions defined in the `Application` trait.

use std::error::Error;

use sdl2::event::Event;

use crate::{
    input::{InputState, KeyboardState, MouseState},
    Application,
};

/// The main game engine, which manages the display and input.
pub struct Engine<'a> {
    app: &'a mut dyn Application,
    title: &'a str,
    width: u32,
    height: u32,
    ctx: sdl2::Sdl,
}

impl<'a> Engine<'a> {
    /// Create a new engine.
    /// # Parameters
    ///* `app`: Defines the application's logic.
    ///* `title`: Title of the window.
    /// * `width`: Width (in pixels) of the window.
    /// * `height`: Height (in pixels) of the window.
    pub fn new(
        app: &'a mut dyn Application,
        title: &'a str,
        width: u32,
        height: u32,
    ) -> Result<Engine<'a>, Box<dyn Error>> {
        Ok(Engine {
            app,
            title,
            width,
            height,
            ctx: sdl2::init()?,
        })
    }

    /// Create and show the window and start the main event loop.
    /// # Parameters
    /// * `present_vsync`: Whether to limit the frame rate of the application to the frame rate of
    ///   the display.
    pub fn start(&mut self, present_vsync: bool) -> Result<(), Box<dyn Error>> {
        let video = self.ctx.video()?;
        let timer = self.ctx.timer()?;
        let mut canvas = video
            .window(self.title, self.width, self.height)
            .position_centered()
            .build()?
            .into_canvas()
            .accelerated();
        if present_vsync {
            canvas = canvas.present_vsync();
        }
        let mut canvas = canvas.build()?;
        // Event handling
        let mut event_pump = self.ctx.event_pump()?;
        // Input state
        let mut input = InputState {
            keyboard: KeyboardState::new(event_pump.keyboard_state().scancodes()),
            mouse: MouseState::new(event_pump.mouse_state()),
        };

        // Call the app.on_create() function so the user can perform one-time initialisation of
        // their application.
        if !self.app.on_create(&mut canvas, &input)? {
            return self.app.on_quit();
        }

        // These variables are used to determine the elapsed time between frames, to allow for
        // time-regulated things like animation and to calculate average frame rates
        let mut now = timer.performance_counter();
        let mut time_acc = 0.0;
        let mut fps_acc = 0.0;
        let mut fps_counter = 0;
        loop {
            // Calculate and display FPS
            let last = now;
            now = timer.performance_counter();
            // Note: I have no idea whether this actually works, so if anyone would like to confirm
            // or deny this, please do
            let elapsed_time = (now - last) as f64 / timer.performance_frequency() as f64;
            time_acc += elapsed_time;
            fps_acc += 1.0 / elapsed_time;
            fps_counter += 1;
            if time_acc >= 1.0 {
                let fps = fps_acc / fps_counter as f64;
                let title = format!("{} ({} FPS)", self.title, fps.round() as u32);
                // This fails silently on error
                canvas.window_mut().set_title(title.as_str()).unwrap_or(());
                time_acc -= 1.0;
                fps_acc = 0.0;
                fps_counter = 0;
            }

            // Process next frame and exit if `Ok(false)` is returned
            if !self.app.on_update(&mut canvas, &input, elapsed_time)? {
                return self.app.on_quit();
            }

            // Handle events
            for event in event_pump.poll_iter() {
                match event {
                    Event::Quit { .. } => {
                        return self.app.on_quit();
                    }
                    _ => {}
                }
            }
            // Refresh the keyboard state
            input
                .keyboard
                .update(event_pump.keyboard_state().scancodes());
            input.mouse.update(event_pump.mouse_state());

            // Flip the double buffer
            canvas.present();
        }
    }
}