#![deny(clippy::all, clippy::pedantic)] use std::collections::BTreeMap; use std::time::{Duration, Instant}; use glfw::window::KeyState; use glfw::{Window, WindowBuilder}; use crate::camera::Camera; use crate::object::{Id as ObjectId, Object}; use crate::renderer::Renderer; use crate::vector::Vec2; mod matrix; mod opengl; mod projection; mod renderer; mod shader_preprocessor; mod transform; pub mod camera; pub mod color; pub mod object; pub mod texture; pub mod vector; pub mod vertex; pub use glfw::window::Key; pub use glfw::WindowSize; #[derive(Debug)] pub struct Engine { /// Objects have to be dropped before window. Otherwise, UB. objects: BTreeMap, window: Window, renderer: Renderer, delta_time: Duration, } impl Engine { /// Creates and initializes a new engine. /// /// # Errors /// Will return `Err` if window creation or window configuration fails. pub fn new(window_size: &WindowSize, window_title: &str) -> Result { let window_builder = WindowBuilder::new(); #[cfg(feature = "debug")] let window_builder = window_builder.hint(glfw::window::Hint::OpenGLDebugContext, 1); let window = window_builder .create(window_size, window_title) .map_err(Error::CreateWindowFailed)?; window .make_context_current() .map_err(Error::ConfigureWindowFailed)?; window.set_framebuffer_size_callback(move |new_window_size| { Renderer::set_viewport(&Vec2::ZERO, &new_window_size); }); let renderer = Renderer::new(&window).map_err(Error::InitializeRendererFailed)?; Ok(Self { window, renderer, objects: BTreeMap::new(), delta_time: Duration::ZERO, }) } pub fn add_object(&mut self, object: Object) { self.objects.insert(object.id(), object); } pub fn set_objecs(&mut self, objects: impl IntoIterator) { self.objects .extend(objects.into_iter().map(|object| (object.id(), object))); } #[must_use] pub fn get_object_by_id(&self, id: ObjectId) -> Option<&Object> { self.objects.get(&id) } #[must_use] pub fn get_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> { self.objects.get_mut(&id) } /// Starts the engine. /// /// # Errors /// Will return `Err` if updating the window fails. pub fn start(&mut self, mut func: impl FnMut(&mut Self)) -> Result<(), Error> { let mut prev_frame_start: Option = None; while !self.window.should_close() { self.update_delta_time(&mut prev_frame_start); func(self); let window_size = self.window.size().map_err(Error::GetWindowSizeFailed)?; self.renderer.render(self.objects.values(), &window_size); self.window .swap_buffers() .map_err(Error::UpdateWindowFailed)?; self.window .poll_events() .map_err(Error::UpdateWindowFailed)?; } Ok(()) } #[must_use] pub fn camera(&self) -> &Camera { self.renderer.camera() } pub fn camera_mut(&mut self) -> &mut Camera { self.renderer.camera_mut() } /// Returns the current delta time. Will be 0 if not called from the function /// passed to [`start`]. #[must_use] pub fn delta_time(&self) -> &Duration { &self.delta_time } /// Returns whether or not a keyboard key is currently pressed. /// /// # Errors /// Will return `Err` if getting the key state fails. pub fn is_key_pressed(&self, key: Key) -> Result { self.window .get_key(key) .map(|key_state| matches!(key_state, KeyState::Pressed)) .map_err(Error::GetKeyStateFailed) } /// Returns the cursor position. /// /// # Errors /// Will return `Err` if unable to get the cursor position from GLFW. pub fn get_cursor_pos(&self) -> Result, Error> { let pos = self .window .get_cursor_position() .map_err(Error::GetCursorPosFailed)?; Ok(Vec2 { x: pos.x, y: pos.y }) } fn update_delta_time(&mut self, prev_frame_start: &mut Option) { let frame_start_time = Instant::now(); if let Some(last_frame_start) = prev_frame_start { self.delta_time = frame_start_time.duration_since(*last_frame_start); } *prev_frame_start = Some(frame_start_time); } } /// Engine Error #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Failed to create window")] CreateWindowFailed(#[source] glfw::Error), #[error("Failed to configure window")] ConfigureWindowFailed(#[source] glfw::Error), #[error("Failed to initialize renderer")] InitializeRendererFailed(#[source] renderer::Error), #[error("Failed to update window")] UpdateWindowFailed(#[source] glfw::Error), #[error("Failed to get window size")] GetWindowSizeFailed(#[source] glfw::Error), #[error("Failed to get key state")] GetKeyStateFailed(#[source] glfw::Error), #[error("Failed to get cursor position")] GetCursorPosFailed(#[source] glfw::Error), }