diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:21 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:21 +0100 |
commit | 95708732572431dc057a9fd71fa8bd8571a336a2 (patch) | |
tree | 3255e3407eb069c8124d41e1d6f60163b7c5091d | |
parent | 9bceddef62c48b56e7b93a2ca19636e1ff6486cb (diff) |
feat(all): start using an ECS
-rw-r--r-- | examples/render-bsp/src/main.rs | 177 | ||||
-rw-r--r-- | stockton-levels/src/features.rs | 4 | ||||
-rw-r--r-- | stockton-render/Cargo.toml | 1 | ||||
-rw-r--r-- | stockton-render/src/error.rs | 6 | ||||
-rw-r--r-- | stockton-render/src/lib.rs | 74 | ||||
-rw-r--r-- | stockton-render/src/window.rs (renamed from stockton-types/src/world.rs) | 24 | ||||
-rw-r--r-- | stockton-types/Cargo.toml | 1 | ||||
-rw-r--r-- | stockton-types/src/components/mod.rs | 23 | ||||
-rw-r--r-- | stockton-types/src/lib.rs | 5 | ||||
-rw-r--r-- | stockton-types/src/session.rs | 56 |
10 files changed, 187 insertions, 184 deletions
diff --git a/examples/render-bsp/src/main.rs b/examples/render-bsp/src/main.rs index 112edd8..8278f1e 100644 --- a/examples/render-bsp/src/main.rs +++ b/examples/render-bsp/src/main.rs @@ -37,75 +37,13 @@ extern crate stockton_render; extern crate stockton_types; extern crate winit; -use std::f32::consts::PI; -use std::time::SystemTime; - -use winit::{ - event::{ElementState, Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; +use winit::{event::Event, event_loop::EventLoop, window::WindowBuilder}; use stockton_levels::{prelude::*, q3::Q3BSPFile}; -use stockton_render::Renderer; -use stockton_types::{Vector2, Vector3, World}; - -/// Movement speed, world units per second -const SPEED: f32 = 100.0; - -/// Pixels required to rotate 90 degrees -const PIXELS_PER_90D: f32 = 100.0; - -/// Sensitivity, derived from above -const SENSITIVITY: f32 = PI / (2.0 * PIXELS_PER_90D); - -#[derive(Debug)] -struct KeyState { - pub up: bool, - pub left: bool, - pub right: bool, - pub down: bool, - pub inwards: bool, - pub out: bool, -} - -impl KeyState { - pub fn new() -> KeyState { - KeyState { - up: false, - left: false, - right: false, - down: false, - inwards: false, - out: false, - } - } - - /// Helper function to get our movement request as a normalized vector - pub fn as_vector(&self) -> Vector3 { - let mut vec = Vector3::new(0.0, 0.0, 0.0); - - if self.up { - vec.y = 1.0; - } else if self.down { - vec.y = -1.0; - } - - if self.right { - vec.x = 1.0; - } else if self.left { - vec.x = -1.0; - } - - if self.inwards { - vec.z = 1.0; - } else if self.out { - vec.z = -1.0; - } - - vec - } -} +use stockton_render::{ + do_render_system, window::process_window_events_system, Renderer, WindowEvent, +}; +use stockton_types::Session; fn main() { // Initialise logger @@ -129,89 +67,42 @@ fn main() { let bsp: Q3BSPFile<Q3System> = bsp.unwrap(); let bsp: Q3BSPFile<VulkanSystem> = bsp.swizzle_to(); - // Load into a world and create the new renderer - let world = World::new(bsp); - let mut renderer = Renderer::new(world, &window).unwrap(); + // Create the renderer + let (renderer, tx) = Renderer::new(&window, &bsp); + let new_control_flow = renderer.update_control_flow.clone(); + + // Load everything into the session + let mut session = Session::new( + move |resources| { + resources.insert(renderer); + resources.insert(bsp); + }, + move |schedule| { + schedule + .add_system(process_window_events_system()) + .add_thread_local(do_render_system::<Q3BSPFile<VulkanSystem>>()); + }, + ); // Done loading - This is our main loop. - - let mut last_update = SystemTime::now(); - let mut key_state = KeyState::new(); - let mut last_cursor_pos = Vector2::new(0.0, 0.0); - - // Keep rendering the world + // It just communicates events to the session and continuously ticks event_loop.run(move |event, _, flow| { - *flow = ControlFlow::Poll; match event { - Event::WindowEvent { event, .. } => match event { - // Close when requested - WindowEvent::CloseRequested => *flow = ControlFlow::Exit, - - WindowEvent::Resized(_) => { - unsafe { renderer.context.handle_surface_change().unwrap() }; - } - - // Update our keystates - WindowEvent::KeyboardInput { input, .. } => match input.scancode { - // A - 30 => key_state.left = input.state == ElementState::Pressed, - // D - 32 => key_state.right = input.state == ElementState::Pressed, - // W (in) - 17 => key_state.inwards = input.state == ElementState::Pressed, - // S (out) - 31 => key_state.out = input.state == ElementState::Pressed, - // Space (up) - 57 => key_state.up = input.state == ElementState::Pressed, - // Ctrl (down) - 42 => key_state.down = input.state == ElementState::Pressed, - _ => (), - }, - - // Look around with mouse - WindowEvent::CursorMoved { position, .. } => { - // Don't do anything on the first frame - if last_cursor_pos.x != 0.0 || last_cursor_pos.y == 0.0 { - // Figure out how much to rotate by - let x_offset = (position.x as f32 - last_cursor_pos.x) * SENSITIVITY; - let y_offset = (position.y as f32 - last_cursor_pos.y) * SENSITIVITY; - - // Rotate - renderer - .context - .rotate(Vector3::new(-y_offset, x_offset, 0.0)); - } - - // For tracking how much the mouse has moved - last_cursor_pos.x = position.x as f32; - last_cursor_pos.y = position.y as f32; - } - _ => (), - }, - - // Nothing new happened Event::MainEventsCleared => { - // Draw as many frames as we can - // This isn't ideal, but it'll do for now. - window.request_redraw() + window.request_redraw(); } - - // Redraw - either from above or resizing, etc - Event::RedrawRequested(_) => { - // Time since last frame drawn. Again, not ideal. - let delta = last_update.elapsed().unwrap().as_secs_f32(); - last_update = SystemTime::now(); - - // Move our camera if needed - let delta_pos = key_state.as_vector() * delta * SPEED; - if delta_pos.x != 0.0 || delta_pos.y != 0.0 || delta_pos.z != 0.0 { - renderer.context.move_camera_relative(delta_pos); - } - - // Render the frame - renderer.render_frame().unwrap() + Event::RedrawRequested(_) => session.do_update(), + _ => { + tx.send(WindowEvent::from(&event)).unwrap(); } - _ => (), } + + // Update the control flow if the session has requested it. + { + let new_control_flow = new_control_flow.read().unwrap(); + if *new_control_flow != *flow { + *flow = *new_control_flow; + } + }; }); } diff --git a/stockton-levels/src/features.rs b/stockton-levels/src/features.rs index dea1d15..f9e6455 100644 --- a/stockton-levels/src/features.rs +++ b/stockton-levels/src/features.rs @@ -32,5 +32,5 @@ use crate::coords::CoordSystem; use crate::traits::*; -pub trait MinBSPFeatures<S: CoordSystem>: HasBSPTree<S> {} -impl<T, S: CoordSystem> MinBSPFeatures<S> for T where T: HasBSPTree<S> {} +pub trait MinBSPFeatures<S: CoordSystem>: HasBSPTree<S> + Send + Sync {} +impl<T, S: CoordSystem> MinBSPFeatures<S> for T where T: HasBSPTree<S> + Send + Sync {} diff --git a/stockton-render/Cargo.toml b/stockton-render/Cargo.toml index 852d174..5d46e66 100644 --- a/stockton-render/Cargo.toml +++ b/stockton-render/Cargo.toml @@ -13,6 +13,7 @@ nalgebra-glm = "^0.6" shaderc = "0.6.1" log = "0.4.0" image = "0.23.2" +legion = { version = "^0.3" } [features] default = ["vulkan"] diff --git a/stockton-render/src/error.rs b/stockton-render/src/error.rs index cd1aa71..e0de55d 100644 --- a/stockton-render/src/error.rs +++ b/stockton-render/src/error.rs @@ -50,8 +50,4 @@ pub enum CreationError { /// Usually this is out of memory or something happened to the device/surface. /// You'll likely need to exit or create a new context. #[derive(Debug, Clone)] -pub enum FrameError { - AcquireError(hal::window::AcquireError), - SyncObjectError, - PresentError, -} +pub enum FrameError {} diff --git a/stockton-render/src/lib.rs b/stockton-render/src/lib.rs index b7bc8ab..ae902df 100644 --- a/stockton-render/src/lib.rs +++ b/stockton-render/src/lib.rs @@ -32,48 +32,80 @@ extern crate stockton_types; extern crate arrayvec; +#[macro_use] +extern crate legion; + mod culling; pub mod draw; mod error; mod types; - -use stockton_levels::prelude::*; -use stockton_types::World; +pub mod window; use culling::get_visible_faces; use draw::RenderingContext; -use error::{CreationError, FrameError}; +use std::sync::mpsc::{Receiver, Sender}; +use std::sync::Arc; +use std::sync::RwLock; +pub use window::WindowEvent; + +use stockton_levels::prelude::*; +use winit::event_loop::ControlFlow; +use winit::window::Window; + +use std::sync::mpsc::channel; /// Renders a world to a window when you tell it to. -pub struct Renderer<'a, T: MinBSPFeatures<VulkanSystem>> { - world: World<T>, - pub context: RenderingContext<'a>, +/// Also takes ownership of the window and channels window events to be processed outside winit's event loop. +pub struct Renderer<'a> { + /// All the vulkan stuff + context: RenderingContext<'a>, + + /// For getting events from the winit event loop + pub window_events: Receiver<WindowEvent>, + + /// For updating the control flow of the winit event loop + pub update_control_flow: Arc<RwLock<ControlFlow>>, } -impl<'a, T: MinBSPFeatures<VulkanSystem>> Renderer<'a, T> { +impl<'a> Renderer<'a> { /// Create a new Renderer. - /// This initialises all the vulkan context, etc needed. - pub fn new(world: World<T>, window: &winit::window::Window) -> Result<Self, CreationError> { - let context = RenderingContext::new(window, &world.map)?; + pub fn new<T: MinBSPFeatures<VulkanSystem>>( + window: &Window, + file: &T, + ) -> (Self, Sender<WindowEvent>) { + let (tx, rx) = channel(); + let update_control_flow = Arc::new(RwLock::new(ControlFlow::Poll)); - Ok(Renderer { world, context }) + ( + Renderer { + context: RenderingContext::new(window, file).unwrap(), + window_events: rx, + update_control_flow, + }, + tx, + ) } - /// Render a single frame of the world - pub fn render_frame(&mut self) -> Result<(), FrameError> { + /// Render a single frame of the given map. + fn render<T: MinBSPFeatures<VulkanSystem>>(&mut self, map: &T) { // Get visible faces - let faces = get_visible_faces(self.context.camera_pos(), &self.world.map); + let faces = get_visible_faces(self.context.camera_pos(), map); // Then draw them - if self.context.draw_vertices(&self.world.map, &faces).is_err() { + if self.context.draw_vertices(map, &faces).is_err() { unsafe { self.context.handle_surface_change().unwrap() }; // If it fails twice, then error - self.context - .draw_vertices(&self.world.map, &faces) - .map_err(|_| FrameError::PresentError)?; + self.context.draw_vertices(map, &faces).unwrap(); } - - Ok(()) } } + +/// A system that just renders the world. +#[system] +pub fn do_render<T: 'static + MinBSPFeatures<VulkanSystem>>( + #[resource] renderer: &mut Renderer<'static>, + #[resource] map: &T, +) { + renderer.render(map); +} diff --git a/stockton-types/src/world.rs b/stockton-render/src/window.rs index 3db567a..4520ae8 100644 --- a/stockton-types/src/world.rs +++ b/stockton-render/src/window.rs @@ -15,19 +15,21 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -//! The thing you play on and all the associated state. +use Renderer; -use stockton_levels::prelude::*; +use winit::event::Event as WinitEvent; -/// A loaded world. -pub struct World<T: MinBSPFeatures<VulkanSystem>> { - pub map: T, -} +pub struct WindowEvent {} -impl<T: MinBSPFeatures<VulkanSystem>> World<T> { - /// Create a new world from a level. - /// The level can be any format, as long as it has the required features of a bsp. - pub fn new(map: T) -> World<T> { - World { map } +impl WindowEvent { + pub fn from(_winit_event: &WinitEvent<()>) -> WindowEvent { + // TODO + WindowEvent {} } } + +#[system] +/// A system to process the window events sent to renderer by the winit event loop. +pub fn process_window_events(#[resource] _renderer: &mut Renderer<'static>) { + println!("processing window events..."); +} diff --git a/stockton-types/Cargo.toml b/stockton-types/Cargo.toml index 02205ca..f473c5f 100644 --- a/stockton-types/Cargo.toml +++ b/stockton-types/Cargo.toml @@ -6,4 +6,5 @@ edition = "2018" [dependencies] nalgebra-glm = "^0.6" +legion = { version = "^0.3" } stockton-levels = { path = "../stockton-levels" } diff --git a/stockton-types/src/components/mod.rs b/stockton-types/src/components/mod.rs index c96417e..0dd9fe9 100644 --- a/stockton-types/src/components/mod.rs +++ b/stockton-types/src/components/mod.rs @@ -14,3 +14,26 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see <http://www.gnu.org/licenses/>. */ + +use crate::Vector3; + +#[derive(Clone, Copy, Debug, PartialEq)] +struct Transform { + /// Position of the object + pub position: Vector3, + + /// Rotation of the object (euler angles in radians) + pub rotation: Vector3, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +struct CameraSettings { + /// FOV (radians) + pub fov: f32, + + /// Near clipping plane (world units) + pub near: f32, + + /// Far clipping plane (world units) + pub far: f32, +} diff --git a/stockton-types/src/lib.rs b/stockton-types/src/lib.rs index a53d4fa..1fda444 100644 --- a/stockton-types/src/lib.rs +++ b/stockton-types/src/lib.rs @@ -17,13 +17,14 @@ //! Common types for all stockton crates. +extern crate legion; extern crate nalgebra_glm as na; extern crate stockton_levels; pub mod components; -pub mod world; +pub mod session; -pub use world::World; +pub use session::Session; /// Alias for convenience pub type Vector2 = na::Vec2; diff --git a/stockton-types/src/session.rs b/stockton-types/src/session.rs new file mode 100644 index 0000000..8e48b59 --- /dev/null +++ b/stockton-types/src/session.rs @@ -0,0 +1,56 @@ +/* + * Copyright (C) Oscar Shrimpton 2020 + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +//! The thing you play on and all the associated state. + +use legion::systems::Builder; +use legion::*; + +/// A loaded world. +pub struct Session { + world: World, + resources: Resources, + schedule: Schedule, +} + +impl Session { + /// Create a new world from a level. + /// The level can be any format, as long as it has the required features of a bsp. + pub fn new<R: FnOnce(&mut Resources), S: FnOnce(&mut Builder)>( + add_resources: R, + add_systems: S, + ) -> Session { + let world = World::default(); + + let mut resources = Resources::default(); + add_resources(&mut resources); + + let mut schedule = Schedule::builder(); + add_systems(&mut schedule); + let schedule = schedule.build(); + + Session { + world, + resources, + schedule, + } + } + + pub fn do_update(&mut self) { + self.schedule.execute(&mut self.world, &mut self.resources); + } +} |