diff options
author | HampusM <hampus@hampusmat.com> | 2024-04-14 12:34:52 +0200 |
---|---|---|
committer | HampusM <hampus@hampusmat.com> | 2024-04-14 12:35:28 +0200 |
commit | 101b455e51f9b702da5517cabe2c3b1086fcb2e7 (patch) | |
tree | 470e28acd7a3777dbb4be0208f9cd3177bba52a9 | |
parent | ef7b76ff39d501028852835649f618fcbe17a003 (diff) |
feat(engine): use ECS architecture
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | engine/Cargo.toml | 1 | ||||
-rw-r--r-- | engine/src/camera.rs | 62 | ||||
-rw-r--r-- | engine/src/data_types.rs | 1 | ||||
-rw-r--r-- | engine/src/data_types/dimens.rs | 7 | ||||
-rw-r--r-- | engine/src/delta_time.rs | 32 | ||||
-rw-r--r-- | engine/src/event.rs | 26 | ||||
-rw-r--r-- | engine/src/input.rs | 110 | ||||
-rw-r--r-- | engine/src/lib.rs | 369 | ||||
-rw-r--r-- | engine/src/lighting.rs | 4 | ||||
-rw-r--r-- | engine/src/material.rs | 4 | ||||
-rw-r--r-- | engine/src/mesh.rs | 4 | ||||
-rw-r--r-- | engine/src/object.rs | 229 | ||||
-rw-r--r-- | engine/src/opengl/buffer.rs | 15 | ||||
-rw-r--r-- | engine/src/opengl/mod.rs | 4 | ||||
-rw-r--r-- | engine/src/renderer/mod.rs | 354 | ||||
-rw-r--r-- | engine/src/shader.rs | 4 | ||||
-rw-r--r-- | engine/src/texture.rs | 32 | ||||
-rw-r--r-- | engine/src/transform.rs | 16 | ||||
-rw-r--r-- | engine/src/window.rs | 312 |
20 files changed, 816 insertions, 771 deletions
@@ -146,6 +146,7 @@ version = "0.1.0" dependencies = [ "bitflags 2.4.0", "cstr", + "ecs", "gl", "glfw", "image", diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 00fe7d0..13426b5 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -14,6 +14,7 @@ bitflags = "2.4.0" cstr = "0.2.11" tracing = { version = "0.1.39", optional = true } seq-macro = "0.3.5" +ecs = { path = "../ecs" } [dependencies.image] version = "0.24.7" diff --git a/engine/src/camera.rs b/engine/src/camera.rs index 362e0d6..5347e83 100644 --- a/engine/src/camera.rs +++ b/engine/src/camera.rs @@ -1,51 +1,25 @@ +use ecs::Component; + use crate::vector::Vec3; -pub trait Camera +#[derive(Debug, Component)] +pub struct Camera { - /// Returns the camera position. - fn position(&self) -> Vec3<f32>; - - /// Returns the position of the camera target. - fn target(&self) -> Vec3<f32>; - - /// Returns the direction the camera is looking. - fn direction(&self) -> Vec3<f32> - { - self.target() - self.position() - } - - /// Returns the right direction relative from where the camera is looking. - fn right(&self) -> Vec3<f32> - { - self.direction().cross(&self.global_up()).normalize() - } - - /// Returns the left direction relative from where the camera is looking. - fn left(&self) -> Vec3<f32> - { - -self.right() - } - - /// Returns the upwards direction relative from where the camera is looking. - fn up(&self) -> Vec3<f32> - { - let rev_direction = -self.direction(); - - rev_direction.cross(&self.right()) - } - - /// Returns the downwards direction relative from where the camera is looking. - fn down(&self) -> Vec3<f32> - { - -self.up() - } + pub position: Vec3<f32>, + pub target: Vec3<f32>, + pub global_up: Vec3<f32>, + pub current: bool, +} - /// Returns the global direction upwards. - /// - /// The default implementation which returns [`Vec3::UP`] should be fine in most - /// cases. - fn global_up(&self) -> Vec3<f32> +impl Default for Camera +{ + fn default() -> Self { - Vec3::UP + Self { + position: Vec3 { x: 0.0, y: 0.0, z: 3.0 }, + target: Vec3::default(), + global_up: Vec3::UP, + current: false, + } } } diff --git a/engine/src/data_types.rs b/engine/src/data_types.rs index 01d0a1e..5cf15e4 100644 --- a/engine/src/data_types.rs +++ b/engine/src/data_types.rs @@ -1,4 +1,5 @@ pub mod color; +pub mod dimens; pub mod vector; pub(crate) mod matrix; diff --git a/engine/src/data_types/dimens.rs b/engine/src/data_types/dimens.rs new file mode 100644 index 0000000..b395627 --- /dev/null +++ b/engine/src/data_types/dimens.rs @@ -0,0 +1,7 @@ +/// Dimensions. +#[derive(Debug, Clone, Copy)] +pub struct Dimens<Value> +{ + pub width: Value, + pub height: Value, +} diff --git a/engine/src/delta_time.rs b/engine/src/delta_time.rs new file mode 100644 index 0000000..33a2fc8 --- /dev/null +++ b/engine/src/delta_time.rs @@ -0,0 +1,32 @@ +use std::time::{Duration, Instant}; + +use ecs::component::local::Local; +use ecs::sole::Single; +use ecs::{Component, Sole}; + +#[derive(Debug, Clone, Default, Sole)] +pub struct DeltaTime +{ + pub duration: Duration, +} + +#[derive(Debug, Clone, Default, Component)] +pub struct LastUpdate +{ + pub time: Option<Instant>, +} + +/// Updates the current delta time. +/// +/// # Panics +/// Will panic if no delta time component exists. +pub fn update(mut delta_time: Single<DeltaTime>, mut last_update: Local<LastUpdate>) +{ + let current_time = Instant::now(); + + if let Some(last_update_time) = last_update.time { + delta_time.duration = current_time.duration_since(last_update_time); + } + + last_update.time = Some(current_time); +} diff --git a/engine/src/event.rs b/engine/src/event.rs new file mode 100644 index 0000000..89c441f --- /dev/null +++ b/engine/src/event.rs @@ -0,0 +1,26 @@ +use ecs::event::Event; + +#[derive(Debug)] +pub struct Start; + +impl Event for Start {} + +#[derive(Debug)] +pub struct Update; + +impl Event for Update {} + +#[derive(Debug)] +pub struct PreUpdate; + +impl Event for PreUpdate {} + +#[derive(Debug)] +pub struct Present; + +impl Event for Present {} + +#[derive(Debug)] +pub struct PostPresent; + +impl Event for PostPresent {} diff --git a/engine/src/input.rs b/engine/src/input.rs new file mode 100644 index 0000000..78c8270 --- /dev/null +++ b/engine/src/input.rs @@ -0,0 +1,110 @@ +use std::collections::HashMap; + +use ecs::extension::Collector as ExtensionCollector; +use ecs::sole::Single; +use ecs::Sole; + +use crate::event::Start as StartEvent; +use crate::vector::Vec2; +use crate::window::{Key, KeyState, Window}; + +#[derive(Debug, Clone, Default, Sole)] +pub struct Keys +{ + map: HashMap<Key, KeyState>, +} + +impl Keys +{ + #[must_use] + pub fn new() -> Self + { + Self::default() + } + + #[must_use] + pub fn get_key_state(&self, key: Key) -> KeyState + { + self.map.get(&key).copied().unwrap_or(KeyState::Released) + } + + pub fn set_key_state(&mut self, key: Key, key_state: KeyState) + { + if matches!(key_state, KeyState::Released) { + self.map.remove(&key); + return; + } + + if matches!(key_state, KeyState::Repeat) { + return; + } + + self.map.insert(key, key_state); + } + + #[must_use] + pub fn is_anything_pressed(&self) -> bool + { + !self.map.is_empty() + } +} + +#[derive(Debug, Clone, Default, Sole)] +pub struct Cursor +{ + pub position: Vec2<f64>, +} + +impl Cursor +{ + #[must_use] + pub fn new() -> Self + { + Self::default() + } +} + +/// Input extension. +#[derive(Debug, Default)] +pub struct Extension {} + +impl ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: ExtensionCollector<'_>) + { + collector.add_system(StartEvent, initialize); + + collector.add_sole(Keys::default()).ok(); + collector.add_sole(Cursor::default()).ok(); + } +} + +fn initialize(keys: Single<Keys>, cursor: Single<Cursor>, window: Single<Window>) +{ + let keys_weak_ref = keys.to_weak_ref(); + + window.set_key_callback(move |key, _scancode, key_state, _modifiers| { + let keys_weak_ref = keys_weak_ref.clone(); + + let keys_ref = keys_weak_ref.access().expect("No world"); + + let mut keys = keys_ref.to_single(); + + keys.set_key_state(key, key_state); + }); + + let cursor_weak_ref = cursor.to_weak_ref(); + + window.set_cursor_pos_callback(move |cursor_position| { + let cursor_weak_ref = cursor_weak_ref.clone(); + + let cursor_ref = cursor_weak_ref.access().expect("No world"); + + let mut cursor = cursor_ref.to_single(); + + cursor.position = Vec2 { + x: cursor_position.x, + y: cursor_position.y, + }; + }); +} 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<CameraT> +pub struct Engine { - /// Objects have to be dropped before window. Otherwise, UB. - objects: BTreeMap<ObjectId, Object>, - light_source: Option<LightSource>, - renderer: Renderer<CameraT>, - window: Window, - delta_time: Duration, + world: World, } -impl<CameraT> Engine<CameraT> -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<Self, Error> - { - 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::<CameraT>::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<Item = Object>) + #[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<Comps>(&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<Instant> = 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<bool, Error> + 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<Vec2<f64>, 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<Instant>) - { - 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<str>, - input_modes: BTreeMap<InputMode, bool>, - cursor_mode: CursorMode, -} - -#[derive(Debug)] -pub struct WindowSettingsBuilder -{ - input_modes: BTreeMap<InputMode, bool>, - 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<Item = (InputMode, bool)>, - ) -> 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<Box<str>>) -> WindowSettings - { - WindowSettings { - size, - title: title.into(), - input_modes: self.input_modes.clone(), - cursor_mode: self.cursor_mode, - } + self.world.event_loop::<EventOrder>(); } } -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}; diff --git a/engine/src/lighting.rs b/engine/src/lighting.rs index 20f89c3..e285739 100644 --- a/engine/src/lighting.rs +++ b/engine/src/lighting.rs @@ -1,7 +1,9 @@ +use ecs::Component; + use crate::color::Color; use crate::vector::Vec3; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Component)] pub struct LightSource { position: Vec3<f32>, diff --git a/engine/src/material.rs b/engine/src/material.rs index 5af3282..7d8c5db 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -1,6 +1,8 @@ +use ecs::Component; + use crate::texture::Id as TextureId; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Component)] pub struct Material { ambient_map: TextureId, diff --git a/engine/src/mesh.rs b/engine/src/mesh.rs index 67b26cc..e07ed35 100644 --- a/engine/src/mesh.rs +++ b/engine/src/mesh.rs @@ -1,6 +1,8 @@ +use ecs::Component; + use crate::vertex::Vertex; -#[derive(Debug)] +#[derive(Debug, Clone, Component)] pub struct Mesh { vertices: Vec<Vertex>, diff --git a/engine/src/object.rs b/engine/src/object.rs deleted file mode 100644 index 0fd049c..0000000 --- a/engine/src/object.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::collections::HashMap; -use std::path::Path; - -use crate::material::Material; -use crate::mesh::Mesh; -use crate::shader::{ - Error as ShaderError, - Kind as ShaderKind, - Program as ShaderProgram, - Shader, -}; -use crate::texture::{Id as TextureId, Texture}; -use crate::transform::Transform; -use crate::vector::Vec3; -use crate::vertex::Vertex; - -const VERTEX_SHADER_FILE: &str = "vertex.glsl"; -const FRAGMENT_SHADER_FILE: &str = "fragment.glsl"; - -const SHADER_DIR: &str = "engine"; - -#[derive(Debug)] -pub struct Object -{ - id: Id, - mesh: Mesh, - shader: ShaderProgram, - transform: Transform, - textures: HashMap<TextureId, Texture>, - material: Material, -} - -impl Object -{ - /// Returns the object ID. - #[must_use] - pub fn id(&self) -> Id - { - self.id - } - - #[must_use] - pub fn position(&self) -> &Vec3<f32> - { - self.transform.position() - } - - pub fn translate(&mut self, translation: Vec3<f32>) - { - self.transform - .set_position(self.transform.position().clone() + translation); - } - - pub fn scale(&mut self, scaling: Vec3<f32>) - { - self.transform.set_scale(scaling); - } - - pub fn textures(&self) -> impl Iterator<Item = (&TextureId, &Texture)> - { - self.textures.iter() - } - - #[must_use] - pub fn material(&self) -> &Material - { - &self.material - } - - #[must_use] - pub fn mesh(&self) -> &Mesh - { - &self.mesh - } - - #[must_use] - pub fn shader(&self) -> &ShaderProgram - { - &self.shader - } - - pub(crate) fn transform(&self) -> &Transform - { - &self.transform - } -} - -/// Object builder. -#[derive(Debug)] -pub struct Builder -{ - vertices: Vec<Vertex>, - indices: Option<Vec<u32>>, - textures: HashMap<TextureId, Texture>, - material: Option<Material>, -} - -impl Builder -{ - /// Returns a new [`Object`] builder. - #[must_use] - pub fn new() -> Self - { - Self { - vertices: Vec::new(), - indices: None, - textures: HashMap::new(), - material: None, - } - } - - #[must_use] - pub fn vertices(mut self, vertices: impl IntoIterator<Item = Vertex>) -> Self - { - self.vertices = vertices.into_iter().collect(); - - self - } - - #[must_use] - pub fn indices(mut self, indices: impl IntoIterator<Item = u32>) -> Self - { - self.indices = Some(indices.into_iter().collect()); - - self - } - - #[must_use] - pub fn texture(mut self, id: TextureId, texture: Texture) -> Self - { - self.textures.insert(id, texture); - - self - } - - #[must_use] - pub fn textures<Textures>(mut self, textures: Textures) -> Self - where - Textures: IntoIterator<Item = (TextureId, Texture)>, - { - self.textures.extend(textures); - - self - } - - #[must_use] - pub fn material(mut self, material: Material) -> Self - { - self.material = Some(material); - - self - } - - /// Builds a new [`Object`]. - /// - /// # Errors - /// Will return `Err` if: - /// - Creating shaders fails - /// - Linking the shader program fails - pub fn build(self, id: Id) -> Result<Object, Error> - { - let mut shader_program = ShaderProgram::new(); - - shader_program.push_shader( - Shader::read_shader_file( - ShaderKind::Vertex, - &Path::new(SHADER_DIR).join(VERTEX_SHADER_FILE), - )? - .preprocess()?, - ); - - shader_program.push_shader( - Shader::read_shader_file( - ShaderKind::Fragment, - &Path::new(SHADER_DIR).join(FRAGMENT_SHADER_FILE), - )? - .preprocess()?, - ); - - let mesh = Mesh::new(self.vertices, self.indices); - - let transform = Transform::new(); - - Ok(Object { - id, - mesh, - shader: shader_program, - transform, - textures: self.textures, - material: self.material.ok_or(Error::NoMaterial)?, - }) - } -} - -impl Default for Builder -{ - fn default() -> Self - { - Self::new() - } -} - -/// Object error -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error(transparent)] - ShaderError(#[from] ShaderError), - - #[error("Object builder has no material")] - NoMaterial, -} - -/// Object ID. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Id -{ - id: u16, -} - -impl Id -{ - /// Returns a new object ID. - #[must_use] - pub fn new(id: u16) -> Self - { - Self { id } - } -} diff --git a/engine/src/opengl/buffer.rs b/engine/src/opengl/buffer.rs index 3a4ecf0..1622804 100644 --- a/engine/src/opengl/buffer.rs +++ b/engine/src/opengl/buffer.rs @@ -6,9 +6,8 @@ use crate::opengl::currently_bound::CurrentlyBound; #[derive(Debug)] pub struct Buffer<Item, ModeT: Mode> { - buffer: gl::types::GLuint, - _item_pd: PhantomData<Item>, - _mode_pd: PhantomData<ModeT>, + buf: gl::types::GLuint, + _pd: PhantomData<(Item, ModeT)>, } impl<Item, ModeT: Mode> Buffer<Item, ModeT> @@ -21,11 +20,7 @@ impl<Item, ModeT: Mode> Buffer<Item, ModeT> gl::GenBuffers(1, &mut buffer); }; - Self { - buffer, - _item_pd: PhantomData, - _mode_pd: PhantomData, - } + Self { buf: buffer, _pd: PhantomData } } #[allow(clippy::inline_always)] @@ -33,7 +28,7 @@ impl<Item, ModeT: Mode> Buffer<Item, ModeT> pub fn bind(&self, cb: impl FnOnce(CurrentlyBound<'_, Self>)) { unsafe { - gl::BindBuffer(ModeT::GL_ENUM, self.buffer); + gl::BindBuffer(ModeT::GL_ENUM, self.buf); } // SAFETY: The buffer object is bound above @@ -63,7 +58,7 @@ impl<Item, ModeT: Mode> Drop for Buffer<Item, ModeT> { #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] unsafe { - gl::DeleteBuffers(1, &self.buffer); + gl::DeleteBuffers(1, &self.buf); } } } diff --git a/engine/src/opengl/mod.rs b/engine/src/opengl/mod.rs index d58ca50..6898c49 100644 --- a/engine/src/opengl/mod.rs +++ b/engine/src/opengl/mod.rs @@ -1,6 +1,6 @@ use bitflags::bitflags; -use glfw::WindowSize; +use crate::data_types::dimens::Dimens; use crate::vector::Vec2; pub mod buffer; @@ -14,7 +14,7 @@ mod util; #[cfg(feature = "debug")] pub mod debug; -pub fn set_viewport(position: &Vec2<u32>, size: &WindowSize) +pub fn set_viewport(position: &Vec2<u32>, size: Dimens<u32>) { unsafe { #[allow(clippy::cast_possible_wrap)] diff --git a/engine/src/renderer/mod.rs b/engine/src/renderer/mod.rs index 839c0d3..304c38b 100644 --- a/engine/src/renderer/mod.rs +++ b/engine/src/renderer/mod.rs @@ -1,15 +1,22 @@ use std::collections::HashMap; -use std::ffi::{c_void, CString}; +use std::ffi::c_void; use std::process::abort; use cstr::cstr; -use glfw::WindowSize; +use ecs::component::local::Local; +use ecs::sole::Single; +use ecs::system::{Into as _, System}; +use ecs::{Component, Query}; +use tracing::warn; use crate::camera::Camera; use crate::color::Color; +use crate::data_types::dimens::Dimens; +use crate::event::{Present as PresentEvent, Start as StartEvent}; use crate::lighting::LightSource; +use crate::material::Material; use crate::matrix::Matrix; -use crate::object::Object; +use crate::mesh::Mesh; use crate::opengl::buffer::{ ArrayKind as ArrayBufferKind, Buffer, @@ -33,168 +40,184 @@ use crate::opengl::vertex_array::{PrimitiveKind, VertexArray}; use crate::opengl::{clear_buffers, enable, BufferClearMask, Capability}; use crate::projection::new_perspective; use crate::shader::Program as ShaderProgram; -use crate::texture::{Id as TextureId, Texture}; +use crate::texture::{Id as TextureId, Map as TextureMap, Texture}; +use crate::transform::Transform; use crate::vector::{Vec2, Vec3}; use crate::vertex::Vertex; +use crate::window::Window; -#[derive(Debug)] -pub struct Renderer<CameraT> +#[derive(Debug, Default)] +pub struct Extension {} + +impl ecs::extension::Extension for Extension { - camera: CameraT, - shader_programs: HashMap<u64, GlShaderProgram>, - textures: HashMap<TextureId, GlTexture>, + fn collect(self, mut collector: ecs::extension::Collector<'_>) + { + collector.add_system(StartEvent, initialize); + + collector.add_system( + PresentEvent, + render.into_system().initialize((GlObjects::default(),)), + ); + } } -impl<CameraT> Renderer<CameraT> -where - CameraT: Camera, +fn initialize(window: Single<Window>) { - pub fn new(window: &glfw::Window, camera: CameraT) -> Result<Self, Error> - { - gl::load_with(|symbol| { - let proc_name = unsafe { CString::from_vec_unchecked(symbol.into()) }; - - match window.get_proc_address(proc_name.as_c_str()) { - Ok(addr) => addr as *const c_void, - Err(err) => { - println!( - "FATAL ERROR: Failed to get adress of OpenGL function {}. {}", - symbol, err - ); - - abort(); - } - } - }); + window + .make_context_current() + .expect("Failed to make window context current"); + + gl::load_with(|symbol| match window.get_proc_address(symbol) { + Ok(addr) => addr as *const c_void, + Err(err) => { + println!( + "FATAL ERROR: Failed to get adress of OpenGL function {symbol}: {err}", + ); + + abort(); + } + }); - #[cfg(feature = "debug")] - Self::initialize_debug(); + #[cfg(feature = "debug")] + initialize_debug(); - let window_size = window.size().map_err(Error::GetWindowSizeFailed)?; + let window_size = window.size().expect("Failed to get window size"); - Self::set_viewport(&Vec2 { x: 0, y: 0 }, &window_size); + set_viewport(&Vec2 { x: 0, y: 0 }, window_size); - enable(Capability::DepthTest); + window.set_framebuffer_size_callback(|new_window_size| { + set_viewport(&Vec2::ZERO, new_window_size); + }); - Ok(Self { - camera, - shader_programs: HashMap::new(), - textures: HashMap::new(), - }) - } + enable(Capability::DepthTest); +} - pub fn render<'obj>( - &mut self, - objects: impl IntoIterator<Item = &'obj Object>, - light_source: Option<&LightSource>, - window_size: &WindowSize, - ) -> Result<(), Error> - { - clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); - - for obj in objects { - let shader_program = self - .shader_programs - .entry(obj.shader().u64_hash()) - .or_insert_with(|| create_gl_shader_program(obj.shader()).unwrap()); - - shader_program.activate(|shader_program_curr_bound| { - apply_transformation_matrices( - obj, - shader_program, - &self.camera, - window_size, - &shader_program_curr_bound, - ); - - apply_light( - obj, - shader_program, - light_source, - &self.camera, - &shader_program_curr_bound, - ); - - for (texture_id, texture) in obj.textures() { - let gl_texture = self - .textures - .entry(*texture_id) - .or_insert_with(|| create_gl_texture(texture)); - - let texture_unit = TextureUnit::from_texture_id(*texture_id) - .ok_or(Error::TextureIdIsInvalidTextureUnit)?; - - set_active_texture_unit(texture_unit); - - gl_texture.bind(|_| {}); - } - - Self::draw_object(obj); - - Ok::<_, Error>(()) - })?; +fn render( + query: Query<(Mesh, TextureMap, ShaderProgram, Material, Transform)>, + light_source_query: Query<(LightSource,)>, + camera_query: Query<(Camera,)>, + window: Single<Window>, + mut gl_objects: Local<GlObjects>, +) +{ + let Some(camera) = camera_query.iter().find_map(|(camera,)| { + if !camera.current { + return None; } - Ok(()) - } + Some(camera) + }) else { + warn!("No current camera. Nothing will be rendered"); + return; + }; - pub fn set_viewport(position: &Vec2<u32>, size: &WindowSize) - { - crate::opengl::set_viewport(position, size); - } + let light_source = light_source_query + .iter() + .next() + .map(|(light_source,)| light_source); + + let GlObjects { + shader_programs: gl_shader_programs, + textures: gl_textures, + } = &mut *gl_objects; + + clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); + + for (mesh, texture_map, shader_program, material, transform) in &query { + let shader_program = gl_shader_programs + .entry(shader_program.u64_hash()) + .or_insert_with(|| create_gl_shader_program(&shader_program).unwrap()); + + shader_program.activate(|shader_program_curr_bound| { + apply_transformation_matrices( + &transform, + shader_program, + &camera, + window.size().expect("Failed to get window size"), + &shader_program_curr_bound, + ); + + apply_light( + &material, + shader_program, + light_source.as_deref(), + &camera, + &shader_program_curr_bound, + ); + + for (texture_id, texture) in &texture_map.inner { + let gl_texture = gl_textures + .entry(*texture_id) + .or_insert_with(|| create_gl_texture(texture)); + + let texture_unit = TextureUnit::from_texture_id(*texture_id) + .unwrap_or_else(|| { + panic!("Texture id {texture_id} is a invalid texture unit"); + }); + + set_active_texture_unit(texture_unit); + + gl_texture.bind(|_| {}); + } - #[must_use] - pub fn camera(&self) -> &CameraT - { - &self.camera + draw_mesh(&mesh); + }); } +} - pub fn camera_mut(&mut self) -> &mut CameraT - { - &mut self.camera - } +#[derive(Debug, Default, Component)] +struct GlObjects +{ + shader_programs: HashMap<u64, GlShaderProgram>, + textures: HashMap<TextureId, GlTexture>, +} - #[cfg(feature = "debug")] - fn initialize_debug() - { - use crate::opengl::debug::{ - enable_debug_output, - set_debug_message_callback, - set_debug_message_control, - MessageIdsAction, - }; +fn set_viewport(position: &Vec2<u32>, size: Dimens<u32>) +{ + crate::opengl::set_viewport(position, size); +} - enable_debug_output(); +#[cfg(feature = "debug")] +fn initialize_debug() +{ + use crate::opengl::debug::{ + enable_debug_output, + set_debug_message_callback, + set_debug_message_control, + MessageIdsAction, + }; - set_debug_message_callback(opengl_debug_message_cb); + enable_debug_output(); - set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable); - } + set_debug_message_callback(opengl_debug_message_cb); - fn draw_object(obj: &Object) - { - // TODO: Creating a new vertex buffer each draw is really dumb and slow this - // should be rethinked - let renderable = Renderable::new(obj.mesh().vertices(), obj.mesh().indices()); - - renderable.vertex_arr.bind(|vert_arr_curr_bound| { - if let Some(index_info) = &renderable.index_info { - VertexArray::draw_elements( - &vert_arr_curr_bound, - PrimitiveKind::Triangles, - 0, - index_info.cnt, - ); - } else { - VertexArray::draw_arrays( - &vert_arr_curr_bound, - PrimitiveKind::Triangles, - 0, - 3, - ); - } - }); - } + set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable); +} + +fn draw_mesh(mesh: &Mesh) +{ + // TODO: Creating a new vertex buffer each draw is really dumb and slow this + // should be rethinked + let renderable = Renderable::new(mesh.vertices(), mesh.indices()); + + renderable.vertex_arr.bind(|vert_arr_curr_bound| { + if let Some(index_info) = &renderable.index_info { + VertexArray::draw_elements( + &vert_arr_curr_bound, + PrimitiveKind::Triangles, + 0, + index_info.cnt, + ); + } else { + VertexArray::draw_arrays( + &vert_arr_curr_bound, + PrimitiveKind::Triangles, + 0, + 3, + ); + } + }); } fn create_gl_texture(texture: &Texture) -> GlTexture @@ -204,7 +227,7 @@ fn create_gl_texture(texture: &Texture) -> GlTexture gl_texture.bind(|texture_curr_bound| { GlTexture::generate( &texture_curr_bound, - &texture.dimensions(), + texture.dimensions(), texture.image().as_bytes(), texture.pixel_data_format(), ); @@ -244,7 +267,7 @@ fn create_gl_shader_program( } #[derive(Debug)] -pub struct Renderable +struct Renderable { vertex_arr: VertexArray, @@ -255,7 +278,7 @@ pub struct Renderable impl Renderable { - pub fn new(vertices: &[Vertex], indices: Option<&[u32]>) -> Self + fn new(vertices: &[Vertex], indices: Option<&[u32]>) -> Self { let vertex_arr = VertexArray::new(); let vertex_buffer = Buffer::new(); @@ -291,35 +314,18 @@ impl Renderable } } -/// Renderer error. -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error("Failed to get window size")] - GetWindowSizeFailed(#[source] glfw::Error), - - #[error("Texture ID is a invalid texture unit")] - TextureIdIsInvalidTextureUnit, - - #[error(transparent)] - GlShader(#[from] GlShaderError), - - #[error("No shader program object was found for object")] - MissingShaderProgram, -} - fn apply_transformation_matrices( - object: &Object, + transform: &Transform, gl_shader_program: &GlShaderProgram, - camera: &impl Camera, - window_size: &WindowSize, + camera: &Camera, + window_size: Dimens<u32>, shader_program_curr_bound: &CurrentlyBound<GlShaderProgram>, ) { gl_shader_program.set_uniform_matrix_4fv( shader_program_curr_bound, cstr!("model"), - &object.transform().as_matrix(), + &transform.as_matrix(), ); let view = create_view(camera); @@ -346,10 +352,10 @@ fn apply_transformation_matrices( } fn apply_light( - obj: &Object, + material: &Material, gl_shader_program: &GlShaderProgram, light_source: Option<&LightSource>, - camera: &impl Camera, + camera: &Camera, shader_program_curr_bound: &CurrentlyBound<GlShaderProgram>, ) { @@ -395,41 +401,41 @@ fn apply_light( gl_shader_program.set_uniform_1i( shader_program_curr_bound, cstr!("material.ambient"), - obj.material().ambient_map().into_inner() as i32, + material.ambient_map().into_inner() as i32, ); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform_1i( shader_program_curr_bound, cstr!("material.diffuse"), - obj.material().diffuse_map().into_inner() as i32, + material.diffuse_map().into_inner() as i32, ); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform_1i( shader_program_curr_bound, cstr!("material.specular"), - obj.material().specular_map().into_inner() as i32, + material.specular_map().into_inner() as i32, ); gl_shader_program.set_uniform_1fv( shader_program_curr_bound, cstr!("material.shininess"), - obj.material().shininess(), + material.shininess(), ); gl_shader_program.set_uniform_vec_3fv( shader_program_curr_bound, cstr!("view_pos"), - &camera.position(), + &camera.position, ); } -fn create_view(camera: &impl Camera) -> Matrix<f32, 4, 4> +fn create_view(camera: &Camera) -> Matrix<f32, 4, 4> { let mut view = Matrix::new(); - view.look_at(&camera.position(), &camera.target(), &camera.global_up()); + view.look_at(&camera.position, &camera.target, &camera.global_up); view } diff --git a/engine/src/shader.rs b/engine/src/shader.rs index 428e5a8..a0066f1 100644 --- a/engine/src/shader.rs +++ b/engine/src/shader.rs @@ -3,10 +3,12 @@ use std::fs::read_to_string; use std::hash::{Hash, Hasher}; use std::path::{Path, PathBuf}; +use ecs::Component; + use crate::shader_preprocessor::{Error as ShaderPreprocessorError, ShaderPreprocessor}; /// Shader program -#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Component)] pub struct Program { shaders: Vec<Shader>, diff --git a/engine/src/texture.rs b/engine/src/texture.rs index 2d7ba51..b0f0278 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -1,5 +1,8 @@ +use std::collections::HashMap; +use std::fmt::Display; use std::path::Path; +use ecs::Component; use image::io::Reader as ImageReader; use image::{DynamicImage, ImageError, Rgb, RgbImage}; @@ -74,6 +77,7 @@ impl Texture } } + #[must_use] pub fn properties(&self) -> &Properties { &self.properties @@ -84,16 +88,19 @@ impl Texture &mut self.properties } + #[must_use] pub fn dimensions(&self) -> &Vec2<u32> { &self.dimensions } + #[must_use] pub fn pixel_data_format(&self) -> PixelDataFormat { self.pixel_data_format } + #[must_use] pub fn image(&self) -> &DynamicImage { &self.image @@ -157,3 +164,28 @@ impl Id self.id } } + +impl Display for Id +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + self.id.fmt(formatter) + } +} + +/// Texture map. +#[derive(Component)] +pub struct Map +{ + pub inner: HashMap<Id, Texture>, +} + +impl FromIterator<(Id, Texture)> for Map +{ + fn from_iter<Iter>(iter: Iter) -> Self + where + Iter: IntoIterator<Item = (Id, Texture)>, + { + Self { inner: iter.into_iter().collect() } + } +} diff --git a/engine/src/transform.rs b/engine/src/transform.rs index a9d9980..f55f44e 100644 --- a/engine/src/transform.rs +++ b/engine/src/transform.rs @@ -1,7 +1,9 @@ +use ecs::Component; + use crate::matrix::Matrix; use crate::vector::Vec3; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Component)] pub struct Transform { position: Vec3<f32>, @@ -10,6 +12,7 @@ pub struct Transform impl Transform { + #[must_use] pub fn new() -> Self { Self { @@ -18,6 +21,7 @@ impl Transform } } + #[must_use] pub fn position(&self) -> &Vec3<f32> { &self.position @@ -33,7 +37,7 @@ impl Transform self.scale = scale; } - pub fn as_matrix(&self) -> Matrix<f32, 4, 4> + pub(crate) fn as_matrix(&self) -> Matrix<f32, 4, 4> { let mut matrix = Matrix::new_identity(); @@ -44,3 +48,11 @@ impl Transform matrix } } + +impl Default for Transform +{ + fn default() -> Self + { + Self::new() + } +} diff --git a/engine/src/window.rs b/engine/src/window.rs new file mode 100644 index 0000000..fde2cca --- /dev/null +++ b/engine/src/window.rs @@ -0,0 +1,312 @@ +use std::borrow::Cow; +use std::ffi::{CStr, CString}; + +use ecs::actions::Actions; +use ecs::extension::Collector as ExtensionCollector; +use ecs::sole::Single; +use ecs::Sole; +use glfw::WindowSize; + +use crate::data_types::dimens::Dimens; +use crate::event::{PostPresent as PostPresentEvent, Start as StartEvent}; +use crate::vector::Vec2; + +mod reexports +{ + pub use glfw::window::{ + CursorMode, + Hint as CreationHint, + InputMode, + Key, + KeyModifiers, + KeyState, + }; +} + +pub use reexports::*; + +#[derive(Debug, Sole)] +/// Has to be dropped last since it holds the OpenGL context. +#[sole(drop_last)] +pub struct Window +{ + inner: glfw::Window, +} + +impl Window +{ + /// Returns a new Window builder. + #[must_use] + pub fn builder() -> Builder + { + Builder::default() + } + + /// Sets the value of a input mode. + /// + /// # Errors + /// Returns `Err` if the input mode is unsupported on the current system. + pub fn set_input_mode( + &self, + input_mode: InputMode, + enabled: bool, + ) -> Result<(), Error> + { + Ok(self.inner.set_input_mode(input_mode, enabled)?) + } + + /// Sets the cursor mode. + /// + /// # Errors + /// If a platform error occurs. + pub fn set_cursor_mode(&self, cursor_mode: CursorMode) -> Result<(), Error> + { + Ok(self.inner.set_cursor_mode(cursor_mode)?) + } + + /// Returns whether or not the window should close. Will return true when the user has + /// attempted to close the window. + #[must_use] + pub fn should_close(&self) -> bool + { + self.inner.should_close() + } + + /// Processes all pending events. + /// + /// # Errors + /// If a platform error occurs. + pub fn poll_events(&self) -> Result<(), Error> + { + Ok(self.inner.poll_events()?) + } + + /// Swaps the front and back buffers of the window. + /// + /// # Errors + /// Will return `Err` if a platform error occurs or if no OpenGL window context + /// is present. + pub fn swap_buffers(&self) -> Result<(), Error> + { + Ok(self.inner.swap_buffers()?) + } + + /// Returns the size of the window. + /// + /// # Errors + /// Will return `Err` if a platform error occurs. + pub fn size(&self) -> Result<Dimens<u32>, Error> + { + let size = self.inner.size()?; + + Ok(Dimens { + width: size.width, + height: size.height, + }) + } + + /// Returns the address of the specified OpenGL function, if it is supported by the + /// current OpenGL context. + /// + /// # Errors + /// Will return `Err` if a platform error occurs or if no current context has + /// been set. + /// + /// # Panics + /// Will panic if the `proc_name` argument contains a nul byte. + pub fn get_proc_address( + &self, + proc_name: &str, + ) -> Result<unsafe extern "C" fn(), Error> + { + let proc_name_c: Cow<CStr> = CStr::from_bytes_with_nul(proc_name.as_bytes()) + .map(Cow::Borrowed) + .or_else(|_| CString::new(proc_name).map(Cow::Owned)) + .expect("OpenGL function name contains a nul byte"); + + Ok(self.inner.get_proc_address(&proc_name_c)?) + } + + /// Makes the OpenGL context of the window current for the calling thread. + /// + /// # Errors + /// Will return `Err` if a platform error occurs or if no OpenGL context is + /// present. + pub fn make_context_current(&self) -> Result<(), Error> + { + Ok(self.inner.make_context_current()?) + } + + /// Sets the window's framebuffer size callback. + pub fn set_framebuffer_size_callback(&self, callback: impl Fn(Dimens<u32>) + 'static) + { + self.inner.set_framebuffer_size_callback(move |size| { + callback(Dimens { + width: size.width, + height: size.height, + }); + }); + } + + /// Sets the window's key size callback. + pub fn set_key_callback( + &self, + callback: impl Fn(Key, i32, KeyState, KeyModifiers) + 'static, + ) + { + self.inner.set_key_callback(callback); + } + + /// Sets the window's cursor position callback. + pub fn set_cursor_pos_callback(&self, callback: impl Fn(Vec2<f64>) + 'static) + { + self.inner + .set_cursor_pos_callback(move |pos| callback(Vec2 { x: pos.x, y: pos.y })); + } + + /// Sets the window's close callback. + pub fn set_close_callback(&self, callback: impl Fn() + 'static) + { + self.inner.set_close_callback(callback); + } +} + +/// [`Window`] builder. +#[derive(Debug, Clone, Default)] +pub struct Builder +{ + inner: glfw::WindowBuilder, +} + +impl Builder +{ + #[must_use] + pub fn creation_hint(mut self, creation_hint: CreationHint, value: i32) -> Self + { + self.inner = self.inner.hint( + match creation_hint { + CreationHint::OpenGLDebugContext => { + glfw::window::Hint::OpenGLDebugContext + } + }, + value, + ); + + self + } + + /// Creates a new window. + /// + /// # Errors + /// Will return `Err` if the title contains a internal nul byte or if a platform error + /// occurs. + pub fn create(&self, size: Dimens<u32>, title: &str) -> Result<Window, Error> + { + let window = self.inner.create( + &WindowSize { + width: size.width, + height: size.height, + }, + title, + )?; + + Ok(Window { inner: window }) + } +} + +#[derive(Debug)] +pub struct Extension +{ + window_builder: Builder, + window_size: Dimens<u32>, + window_title: String, +} + +impl Extension +{ + #[must_use] + pub fn new(window_builder: Builder) -> Self + { + Self { window_builder, ..Default::default() } + } + + #[must_use] + pub fn window_size(mut self, window_size: Dimens<u32>) -> Self + { + self.window_size = window_size; + + self + } + + #[must_use] + pub fn window_title(mut self, window_title: impl Into<String>) -> Self + { + self.window_title = window_title.into(); + + self + } +} + +impl ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: ExtensionCollector<'_>) + { + collector.add_system(StartEvent, initialize); + collector.add_system(PostPresentEvent, update); + + let window = self + .window_builder + .create(self.window_size, &self.window_title) + .unwrap(); + + window.set_cursor_mode(CursorMode::Normal).unwrap(); + + collector.add_sole(window).ok(); + } +} + +impl Default for Extension +{ + fn default() -> Self + { + Self { + window_builder: Builder::default(), + window_size: Dimens { width: 1920, height: 1080 }, + window_title: String::new(), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct Error(glfw::Error); + +impl From<glfw::Error> for Error +{ + fn from(err: glfw::Error) -> Self + { + Self(err) + } +} + +fn initialize(window: Single<Window>, actions: Actions) +{ + let actions_weak_ref = actions.to_weak_ref(); + + window.set_close_callback(move || { + let actions_weak_ref = actions_weak_ref.clone(); + + let actions_ref = actions_weak_ref.access().expect("No world"); + + actions_ref.to_actions().stop(); + }); +} + +fn update(window: Single<Window>) +{ + window + .swap_buffers() + .expect("Failed to swap window buffers"); + + window.poll_events().expect("Failed to poll window events"); +} |