ngen 0.1.4

A very simple game engine using OpenGL
Documentation
#![warn(rust_2018_idioms)]

pub mod arena;
pub mod assets;
pub mod camera;
pub mod input;
pub mod math;
pub mod platform;
pub mod renderer;
pub mod time;

use math::Vec2;
use renderer::OpenGLRenderer;
use time::Timer;

pub use arena::TempArena;
pub use camera::Camera;
pub use input::Input;
pub use platform::Platform;
pub use renderer::RenderCommands;
pub use time::Tick;

pub fn builder() -> NgenBuilder {
    NgenBuilder {
        window_title: None,
        window_width: 1920,
        window_height: 1080,
        max_quads: 25_000,
        dt: 0.01,
        max_frame: 0.1,
        temp_memory: 0,
    }
}

/// A trait describing a simulation.
pub trait Simulation {
    /// The update method that is called every simulation frame.
    fn update(
        &mut self,
        platform: &mut Platform<'_>,
        input: &Input,
        tick: Tick,
    ) -> Result<bool, String>;

    /// The render method that is called every render frame.
    fn render(&mut self, render_commands: &mut RenderCommands) -> Result<(), String>;
}

#[rustfmt::skip]
fn handle_os_messages(
    event_pump: &mut sdl2::EventPump,
    renderer: &mut OpenGLRenderer,
    input: &mut Input,
) -> Result<bool, String> {
    for event in event_pump.poll_iter() {
        use sdl2::event::{Event, WindowEvent};
        use sdl2::keyboard::Keycode;
        use sdl2::mouse::MouseButton;

        match event {
            Event::Quit { .. } => return Ok(false),

            Event::MouseMotion { x, y, .. } => {
                input.mouse.screen_pos = Vec2::new(x as _, y as _);
            }

            Event::MouseButtonDown { mouse_btn, x, y, ..  } => {
                input.mouse.screen_pos = Vec2::new(x as _, y as _);
                match mouse_btn {
                    MouseButton::Left => input.mouse.left_button.set_is_down(true),
                    MouseButton::Right => input.mouse.right_button.set_is_down(true),
                    _ => (),
                }
            }

            Event::MouseButtonUp { mouse_btn, x, y, ..  } => {
                input.mouse.screen_pos = Vec2::new(x as _, y as _);
                match mouse_btn {
                    MouseButton::Left => input.mouse.left_button.set_is_down(false),
                    MouseButton::Right => input.mouse.right_button.set_is_down(false),
                    _ => (),
                }
            }

            Event::KeyDown { keycode: Some(key), ..  } => match key {
                Keycode::W => input.controller.up.set_is_down(true),
                Keycode::A => input.controller.left.set_is_down(true),
                Keycode::S => input.controller.down.set_is_down(true),
                Keycode::D => input.controller.right.set_is_down(true),
                Keycode::Q => input.controller.bumper_left.set_is_down(true),
                Keycode::E => input.controller.bumper_right.set_is_down(true),

                Keycode::Left => input.controller.action_a.set_is_down(true),
                Keycode::Up => input.controller.action_b.set_is_down(true),
                Keycode::Right => input.controller.action_c.set_is_down(true),
                Keycode::Down => input.controller.action_d.set_is_down(true),

                Keycode::Space => input.controller.select.set_is_down(true),
                Keycode::Return => {
                    input.controller.start.set_is_down(true);
                    input.keyboard.enter.set_is_down(true);
                }
                Keycode::Escape => input.keyboard.escape.set_is_down(true),

                Keycode::F1 => input.keyboard.f[01].set_is_down(true),
                Keycode::F2 => input.keyboard.f[02].set_is_down(true),
                Keycode::F3 => input.keyboard.f[03].set_is_down(true),
                Keycode::F4 => input.keyboard.f[04].set_is_down(true),
                Keycode::F5 => input.keyboard.f[05].set_is_down(true),
                Keycode::F6 => input.keyboard.f[06].set_is_down(true),
                Keycode::F7 => input.keyboard.f[07].set_is_down(true),
                Keycode::F8 => input.keyboard.f[08].set_is_down(true),
                Keycode::F9 => input.keyboard.f[09].set_is_down(true),
                Keycode::F10 => input.keyboard.f[10].set_is_down(true),
                Keycode::F11 => input.keyboard.f[11].set_is_down(true),
                Keycode::F12 => input.keyboard.f[12].set_is_down(true),

                _ => (),
            },

            Event::KeyUp {
                keycode: Some(key), ..
            } => match key {
                Keycode::W => input.controller.up.set_is_down(false),
                Keycode::A => input.controller.left.set_is_down(false),
                Keycode::S => input.controller.down.set_is_down(false),
                Keycode::D => input.controller.right.set_is_down(false),
                Keycode::Q => input.controller.bumper_left.set_is_down(false),
                Keycode::E => input.controller.bumper_right.set_is_down(false),

                Keycode::Left => input.controller.action_a.set_is_down(false),
                Keycode::Up => input.controller.action_b.set_is_down(false),
                Keycode::Right => input.controller.action_c.set_is_down(false),
                Keycode::Down => input.controller.action_d.set_is_down(false),

                Keycode::Space => input.controller.select.set_is_down(false),
                Keycode::Return => {
                    input.controller.start.set_is_down(false);
                    input.keyboard.enter.set_is_down(false);
                }
                Keycode::Escape => input.keyboard.escape.set_is_down(false),

                Keycode::F1 => input.keyboard.f[01].set_is_down(false),
                Keycode::F2 => input.keyboard.f[02].set_is_down(false),
                Keycode::F3 => input.keyboard.f[03].set_is_down(false),
                Keycode::F4 => input.keyboard.f[04].set_is_down(false),
                Keycode::F5 => input.keyboard.f[05].set_is_down(false),
                Keycode::F6 => input.keyboard.f[06].set_is_down(false),
                Keycode::F7 => input.keyboard.f[07].set_is_down(false),
                Keycode::F8 => input.keyboard.f[08].set_is_down(false),
                Keycode::F9 => input.keyboard.f[09].set_is_down(false),
                Keycode::F10 => input.keyboard.f[10].set_is_down(false),
                Keycode::F11 => input.keyboard.f[11].set_is_down(false),
                Keycode::F12 => input.keyboard.f[12].set_is_down(false),

                _ => (),
            },

            Event::Window { win_event: WindowEvent::Resized(width, height), ..  } => {
                renderer.set_window_dim(width, height);
            }

            _ => (),
        }
    }

    Ok(true)
}

pub struct NgenBuilder {
    window_title: Option<&'static str>,
    window_width: u32,
    window_height: u32,
    max_quads: u32,
    dt: f32,
    max_frame: f32,
    temp_memory: usize,
}

impl NgenBuilder {
    pub fn window(mut self, width: u32, height: u32, title: &'static str) -> Self {
        self.window_title = Some(title);
        self.window_width = width;
        self.window_height = height;

        self
    }

    pub fn dt(mut self, dt: f32) -> Self {
        self.dt = dt;

        self
    }

    pub fn max_quads(mut self, max_quads: u32) -> Self {
        self.max_quads = max_quads;

        self
    }

    pub fn max_frame(mut self, max_frame: f32) -> Self {
        self.max_frame = max_frame;

        self
    }

    pub fn temp_memory(mut self, memory: usize) -> Self {
        self.temp_memory = memory;

        self
    }

    pub fn mount_default<S: Simulation + Default>(self) -> Result<(), String> {
        self.mount(S::default())
    }

    pub fn mount<S: Simulation>(self, simulation: S) -> Result<(), String> {
        self.mount_init(move |_| simulation)
    }

    pub fn mount_init<S: Simulation, I: FnOnce(&mut Platform<'_>) -> S>(self, init: I) -> Result<(), String> {
        let mut context = sdl2::init()?;
        let mut event_pump = context.event_pump()?;

        let mut renderer = renderer::OpenGLRenderer::new(
            &mut context,
            self.window_width,
            self.window_height,
            self.window_title.unwrap_or("ngen"),
            self.max_quads as usize,
        )?;
        let mut timer = Timer::new(&mut context, 0.01, 0.1)?;
        let mut input = Input::default();
        let mut temp_arena = TempArena::with_capacity(self.temp_memory)?;

        let mut simulation = {
            let mut platform = Platform::new(&mut renderer, &mut temp_arena);
            init(&mut platform)
        };

        'running: loop {
            for tick in timer.begin_frame() {
                input.frame();
                temp_arena.reset();

                if !handle_os_messages(&mut event_pump, &mut renderer, &mut input)? {
                    break 'running;
                }

                let mut platform = Platform::new(&mut renderer, &mut temp_arena);
                if !simulation.update(&mut platform, &input, tick)? {
                    break 'running;
                }
            }

            let render_commands = renderer.begin_frame();
            simulation.render(render_commands)?;
            renderer.render();
        }

        Ok(())
    }
}