aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-25 17:44:21 +0100
committertcmal <me@aria.rip>2024-08-25 17:44:21 +0100
commit95708732572431dc057a9fd71fa8bd8571a336a2 (patch)
tree3255e3407eb069c8124d41e1d6f60163b7c5091d
parent9bceddef62c48b56e7b93a2ca19636e1ff6486cb (diff)
feat(all): start using an ECS
-rw-r--r--examples/render-bsp/src/main.rs177
-rw-r--r--stockton-levels/src/features.rs4
-rw-r--r--stockton-render/Cargo.toml1
-rw-r--r--stockton-render/src/error.rs6
-rw-r--r--stockton-render/src/lib.rs74
-rw-r--r--stockton-render/src/window.rs (renamed from stockton-types/src/world.rs)24
-rw-r--r--stockton-types/Cargo.toml1
-rw-r--r--stockton-types/src/components/mod.rs23
-rw-r--r--stockton-types/src/lib.rs5
-rw-r--r--stockton-types/src/session.rs56
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);
+ }
+}