From 101b455e51f9b702da5517cabe2c3b1086fcb2e7 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 14 Apr 2024 12:34:52 +0200 Subject: feat(engine): use ECS architecture --- engine/src/lib.rs | 369 ++++++++++-------------------------------------------- 1 file changed, 63 insertions(+), 306 deletions(-) (limited to 'engine/src/lib.rs') diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 332a011..f83d85b 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,367 +1,124 @@ #![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::lighting::LightSource; -use crate::object::{Id as ObjectId, Object}; -use crate::renderer::Renderer; -use crate::vector::Vec2; +#![allow(clippy::needless_pass_by_value)] + +use ecs::component::Sequence as ComponentSequence; +use ecs::event::Event; +use ecs::extension::Extension; +use ecs::sole::Sole; +use ecs::system::{Into, System}; +use ecs::{SoleAlreadyExistsError, World}; + +use crate::delta_time::{update as update_delta_time, DeltaTime, LastUpdate}; +use crate::event::{ + PostPresent as PostPresentEvent, + PreUpdate as PreUpdateEvent, + Present as PresentEvent, + Start as StartEvent, + Update as UpdateEvent, +}; mod opengl; mod projection; -mod renderer; mod shader_preprocessor; -mod transform; pub mod camera; pub mod data_types; +pub mod delta_time; +pub mod event; +pub mod input; pub mod lighting; pub mod material; pub mod math; pub mod mesh; -pub mod object; +pub mod renderer; pub mod shader; pub mod texture; +pub mod transform; pub mod vertex; +pub mod window; -pub use glfw::window::Key; -pub use glfw::WindowSize; +pub extern crate ecs; pub(crate) use crate::data_types::matrix; pub use crate::data_types::{color, vector}; +type EventOrder = (PreUpdateEvent, UpdateEvent, PresentEvent, PostPresentEvent); + #[derive(Debug)] -pub struct Engine +pub struct Engine { - /// Objects have to be dropped before window. Otherwise, UB. - objects: BTreeMap, - light_source: Option, - renderer: Renderer, - window: Window, - delta_time: Duration, + world: World, } -impl Engine -where - CameraT: Camera, +impl Engine { /// Creates and initializes a new engine. /// /// # Errors /// Will return `Err` if window creation or window configuration fails. - pub fn new(window_settings: &WindowSettings, camera: CameraT) -> 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_settings.size, &window_settings.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); - }); - - Self::set_window_input_modes(&window, window_settings)?; - - window - .set_cursor_mode(window_settings.cursor_mode) - .map_err(|err| Error::SetWindowCursorModeFailed { - source: err, - cursor_mode: window_settings.cursor_mode, - })?; - - let renderer = - Renderer::new(&window, camera).map_err(Error::InitializeRendererFailed)?; - - Ok(Self { - window, - renderer, - objects: BTreeMap::new(), - light_source: None, - 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) + #[must_use] + pub fn new() -> Self { - self.objects - .extend(objects.into_iter().map(|object| (object.id(), object))); - } + let mut world = World::new(); - pub fn set_light_source(&mut self, light_source: LightSource) - { - self.light_source = Some(light_source); - } + world.add_sole(DeltaTime::default()).ok(); - #[must_use] - pub fn light_source(&self) -> Option<&LightSource> - { - self.light_source.as_ref() - } + world.register_system( + PreUpdateEvent, + update_delta_time + .into_system() + .initialize((LastUpdate::default(),)), + ); - #[must_use] - pub fn light_source_mut(&mut self) -> Option<&mut LightSource> - { - self.light_source.as_mut() + Self { world } } - #[must_use] - pub fn get_object_by_id(&self, id: ObjectId) -> Option<&Object> + pub fn spawn(&mut self, components: Comps) + where + Comps: ComponentSequence, { - self.objects.get(&id) + self.world.create_entity(components); } - #[must_use] - pub fn get_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> + pub fn register_system<'this, SystemImpl>( + &'this mut self, + event: impl Event, + system: impl System<'this, SystemImpl>, + ) { - self.objects.get_mut(&id) + self.world.register_system(event, system); } - /// Starts the engine. + /// Adds a globally shared singleton value. /// /// # 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(), - self.light_source.as_ref(), - &window_size, - ) - .map_err(Error::RenderingFailed)?; - - self.window - .swap_buffers() - .map_err(Error::UpdateWindowFailed)?; - - self.window - .poll_events() - .map_err(Error::UpdateWindowFailed)?; - } - - Ok(()) - } - - #[must_use] - pub fn camera(&self) -> &CameraT - { - self.renderer.camera() - } - - pub fn camera_mut(&mut self) -> &mut CameraT - { - 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 + /// Returns `Err` if this [`Sole`] has already been added. + pub fn add_sole(&mut self, sole: impl Sole) -> Result<(), SoleAlreadyExistsError> { - &self.delta_time + self.world.add_sole(sole) } - /// 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 + pub fn add_extension(&mut self, extension: impl Extension) { - self.window - .get_key(key) - .map(|key_state| matches!(key_state, KeyState::Pressed)) - .map_err(Error::GetKeyStateFailed) + self.world.add_extension(extension); } - /// Returns the cursor position. + /// Starts the engine. /// /// # 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); - } - - fn set_window_input_modes( - window: &Window, - window_settings: &WindowSettings, - ) -> Result<(), Error> - { - for (input_mode, enabled) in &window_settings.input_modes { - window - .set_input_mode(*input_mode, *enabled) - .map_err(|err| Error::SetWindowInputModeFailed { - source: err, - input_mode: *input_mode, - enabled: *enabled, - })?; - } - - Ok(()) - } -} - -/// 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 set window input mode {input_mode:?} to {enabled}")] - SetWindowInputModeFailed - { - #[source] - source: glfw::Error, - input_mode: InputMode, - enabled: bool, - }, - - #[error("Failed to set window cursor mode to {cursor_mode:?}")] - SetWindowCursorModeFailed - { - #[source] - source: glfw::Error, - cursor_mode: CursorMode, - }, - - #[error("Failed to get key state")] - GetKeyStateFailed(#[source] glfw::Error), - - #[error("Failed to get cursor position")] - GetCursorPosFailed(#[source] glfw::Error), - - #[error("Rendering failed")] - RenderingFailed(#[source] renderer::Error), -} - -#[derive(Debug)] -pub struct WindowSettings -{ - size: WindowSize, - title: Box, - input_modes: BTreeMap, - cursor_mode: CursorMode, -} - -#[derive(Debug)] -pub struct WindowSettingsBuilder -{ - input_modes: BTreeMap, - cursor_mode: CursorMode, -} - -impl WindowSettingsBuilder -{ - #[must_use] - pub fn new() -> Self - { - Self::default() - } - - #[must_use] - pub fn input_mode(mut self, input_mode: InputMode, enabled: bool) -> Self - { - self.input_modes.insert(input_mode, enabled); - - self - } - - #[must_use] - pub fn input_modes( - mut self, - input_modes: impl IntoIterator, - ) -> Self - { - self.input_modes.extend(input_modes); - - self - } - - #[must_use] - pub fn cursor_mode(mut self, cursor_mode: CursorMode) -> Self + /// Will return `Err` if updating the window fails. + pub fn start(&self) { - self.cursor_mode = cursor_mode; - - self - } + self.world.emit(StartEvent); - pub fn build(&self, size: WindowSize, title: impl Into>) -> WindowSettings - { - WindowSettings { - size, - title: title.into(), - input_modes: self.input_modes.clone(), - cursor_mode: self.cursor_mode, - } + self.world.event_loop::(); } } -impl Default for WindowSettingsBuilder +impl Default for Engine { fn default() -> Self { - Self { - input_modes: BTreeMap::new(), - cursor_mode: CursorMode::Normal, - } + Self::new() } } - -pub use glfw::window::{CursorMode, InputMode}; -- cgit v1.2.3-18-g5258