summaryrefslogtreecommitdiff
path: root/engine/src
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src')
-rw-r--r--engine/src/camera.rs62
-rw-r--r--engine/src/data_types.rs1
-rw-r--r--engine/src/data_types/dimens.rs7
-rw-r--r--engine/src/delta_time.rs32
-rw-r--r--engine/src/event.rs26
-rw-r--r--engine/src/input.rs110
-rw-r--r--engine/src/lib.rs369
-rw-r--r--engine/src/lighting.rs4
-rw-r--r--engine/src/material.rs4
-rw-r--r--engine/src/mesh.rs4
-rw-r--r--engine/src/object.rs229
-rw-r--r--engine/src/opengl/buffer.rs15
-rw-r--r--engine/src/opengl/mod.rs4
-rw-r--r--engine/src/renderer/mod.rs354
-rw-r--r--engine/src/shader.rs4
-rw-r--r--engine/src/texture.rs32
-rw-r--r--engine/src/transform.rs16
-rw-r--r--engine/src/window.rs312
18 files changed, 814 insertions, 771 deletions
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");
+}