From 6b935739ef4dc797d30dcda54679c6dbf1430249 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 29 Oct 2023 17:54:04 -0400 Subject: [PATCH] Add system criteria --- examples/testbed/src/free_fly_camera.rs | 8 +- examples/testbed/src/main.rs | 173 ++++++++++-------------- src/ecs/components/delta_time.rs | 10 +- src/ecs/mod.rs | 148 +------------------- src/ecs/system/batched.rs | 75 ++++++++++ src/ecs/system/criteria.rs | 23 ++++ src/ecs/system/mod.rs | 127 +++++++++++++++++ 7 files changed, 308 insertions(+), 256 deletions(-) create mode 100644 src/ecs/system/batched.rs create mode 100644 src/ecs/system/criteria.rs create mode 100644 src/ecs/system/mod.rs diff --git a/examples/testbed/src/free_fly_camera.rs b/examples/testbed/src/free_fly_camera.rs index fb0bce7..fafb145 100644 --- a/examples/testbed/src/free_fly_camera.rs +++ b/examples/testbed/src/free_fly_camera.rs @@ -39,9 +39,9 @@ impl FreeFlyCamera { } } -pub struct FreeFlyCameraController; +pub struct FreeFlyCameraPlugin; -impl SimpleSystem for FreeFlyCameraController { +impl SimpleSystem for FreeFlyCameraPlugin { fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { let mut camera_rot = Vec3::default(); @@ -147,8 +147,8 @@ impl SimpleSystem for FreeFlyCameraController { } } -impl Plugin for FreeFlyCameraController { +impl Plugin for FreeFlyCameraPlugin { fn setup(&self, game: &mut Game) { - game.with_system("free_fly_camera_controller", FreeFlyCameraController, &[]); + game.with_system("free_fly_camera_system", FreeFlyCameraPlugin, &[]); } } diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index b7ab30b..af37fa4 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -1,24 +1,60 @@ -use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent}, EventQueue, SimpleSystem, Component}, math::Transform, input::{KeyCode, InputButtons, MouseMotion}, game::Game, plugin::Plugin, render::window::{CursorGrabMode, WindowOptions}, change_tracker::Ct}; +use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent, DeltaTime}, EventQueue, SimpleSystem, Component, Criteria, CriteriaSchedule, BatchedSystem}, math::Transform, input::{KeyCode, InputButtons, MouseMotion}, game::Game, plugin::Plugin, render::window::{CursorGrabMode, WindowOptions}, change_tracker::Ct}; use lyra_engine::assets::{ResourceManager, Model}; -use tracing::debug; - mod free_fly_camera; -use free_fly_camera::{FreeFlyCameraController, FreeFlyCamera}; +use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera}; -/* pub const VERTICES: &[Vertex] = &[ - Vertex { position: [-0.0868241, 0.49240386, 0.0], tex_coords: [0.4131759, 0.00759614], }, // A - Vertex { position: [-0.49513406, 0.06958647, 0.0], tex_coords: [0.0048659444, 0.43041354], }, // B - Vertex { position: [-0.21918549, -0.44939706, 0.0], tex_coords: [0.28081453, 0.949397], }, // C - Vertex { position: [0.35966998, -0.3473291, 0.0], tex_coords: [0.85967, 0.84732914], }, // D - Vertex { position: [0.44147372, 0.2347359, 0.0], tex_coords: [0.9414737, 0.2652641], }, // E -]; */ +struct FixedTimestep { + max_tps: u32, + fixed_time: f32, + accumulator: f32, +} -pub const INDICES: &[u16] = &[ - 0, 1, 4, - 1, 2, 4, - 2, 3, 4, -]; +impl FixedTimestep { + pub fn new(max_tps: u32) -> Self { + Self { + max_tps, + fixed_time: Self::calc_fixed_time(max_tps), + accumulator: 0.0, + } + } + + fn calc_fixed_time(max_tps: u32) -> f32 { + 1.0 / max_tps as f32 + } + + fn set_tps(&mut self, tps: u32) { + self.max_tps = tps; + self.fixed_time = Self::calc_fixed_time(tps); + } + + fn tps(&self) -> u32 { + self.max_tps + } + + fn fixed_time(&self) -> f32 { + self.fixed_time + } +} + +impl Criteria for FixedTimestep { + fn can_run(&mut self, world: &mut edict::World, check_count: u32) -> CriteriaSchedule { + if check_count == 0 { + let delta_time = world.get_resource::().unwrap(); + self.accumulator += **delta_time; + } + + if self.accumulator >= self.fixed_time { + self.accumulator -= self.fixed_time; + return CriteriaSchedule::YesAndLoop; + } + + CriteriaSchedule::No + } +} + +#[derive(Clone)] +struct TpsAccumulator(f32); #[async_std::main] async fn main() { @@ -54,111 +90,42 @@ async fn main() { Ok(()) }; - /* let fps_system = |world: &mut World| -> anyhow::Result<()> { - let mut counter: RefMut = world.get_resource_mut().unwrap(); + let fps_system = |world: &mut World| -> anyhow::Result<()> { + let mut counter = world.get_resource_mut::().unwrap(); let fps = counter.tick(); - debug!("FPS: {fps}"); + println!("FPS: {fps}"); Ok(()) }; let fps_plugin = move |game: &mut Game| { let world = game.world(); world.insert_resource(fps_counter::FPSCounter::new()); + }; - game.with_system("fps", fps_system, &["input"]); - }; */ - - let jiggle_system = |world: &mut World| -> anyhow::Result<()> { - let keys = world.get_resource::>(); - if keys.is_none() { - return Ok(()); - } - let keys = keys.unwrap(); - - let speed = 0.01; - let rot_speed = 1.0; - - let mut dir_x = 0.0; - let mut dir_y = 0.0; - let mut dir_z = 0.0; - - let mut rot_x = 0.0; - let mut rot_y = 0.0; - - if keys.is_pressed(KeyCode::A) { - dir_x += speed; - } - - if keys.is_pressed(KeyCode::D) { - dir_x -= speed; - } - - if keys.is_pressed(KeyCode::S) { - dir_y += speed; - } - - if keys.is_pressed(KeyCode::W) { - dir_y -= speed; - } - - if keys.is_pressed(KeyCode::E) { - dir_z += speed; - } - - if keys.is_pressed(KeyCode::Q) { - dir_z -= speed; - } - - if keys.is_pressed(KeyCode::Left) { - rot_y -= rot_speed; - } - - if keys.is_pressed(KeyCode::Right) { - rot_y += rot_speed; - } - - if keys.is_pressed(KeyCode::Up) { - rot_x -= rot_speed; - } - - if keys.is_pressed(KeyCode::Down) { - rot_x += rot_speed; - } - - drop(keys); - - if dir_x == 0.0 && dir_y == 0.0 && dir_z == 0.0 && rot_x == 0.0 && rot_y == 0.0 { - return Ok(()); - } - - //debug!("moving by ({}, {})", dir_x, dir_y); + let spin_system = |world: &mut World| -> anyhow::Result<()> { + const SPEED: f32 = 5.0; + let delta_time = **world.get_resource::().unwrap(); for transform in world.query_mut::<(&mut TransformComponent,)>().iter_mut() { let t = &mut transform.transform; - //debug!("Translation: {}", t.translation); - //debug!("Rotation: {}", t.rotation); - - /* t.translation += glam::Vec3::new(0.0, 0.001, 0.0); - t.translation.x *= -1.0; */ - t.translation.x += dir_x; - t.translation.y += dir_y; - t.translation.z += dir_z; - t.rotate_x(math::Angle::Degrees(rot_x)); - t.rotate_y(math::Angle::Degrees(rot_y)); - } - - let events = world.get_resource_mut::().unwrap(); - if let Some(mm) = events.read_events::() { - debug!("Mouse motion: {:?}", mm); + t.rotate_y(math::Angle::Degrees(SPEED * delta_time)); } Ok(()) }; let jiggle_plugin = move |game: &mut Game| { - //game.with_system("jiggle", jiggle_system, &["input"]); + game.world().insert_resource(TpsAccumulator(0.0)); + + let mut sys = BatchedSystem::new(); + sys.with_criteria(FixedTimestep::new(60)); + sys.with_system(spin_system); + sys.with_system(fps_system); + + game.with_system("fixed", sys, &[]); + fps_plugin(game); }; Game::initialize().await @@ -166,6 +133,6 @@ async fn main() { .with_startup_system(setup_sys) //.with_plugin(fps_plugin) .with_plugin(jiggle_plugin) - .with_plugin(FreeFlyCameraController) + .with_plugin(FreeFlyCameraPlugin) .run().await; } diff --git a/src/ecs/components/delta_time.rs b/src/ecs/components/delta_time.rs index 92b6d71..a1f13c2 100644 --- a/src/ecs/components/delta_time.rs +++ b/src/ecs/components/delta_time.rs @@ -6,7 +6,7 @@ use instant::Instant; use crate::plugin::Plugin; #[derive(Clone, Component)] -pub struct DeltaTime(f32, Instant); +pub struct DeltaTime(f32, Option); impl std::ops::Deref for DeltaTime { type Target = f32; @@ -25,8 +25,10 @@ impl std::ops::DerefMut for DeltaTime { fn delta_time_system(world: &mut World) -> anyhow::Result<()> { let now = Instant::now(); let mut delta = world.get_resource_mut::().unwrap(); - delta.0 = delta.1.elapsed().as_secs_f32(); - delta.1 = now; + delta.0 = delta.1.unwrap_or(now).elapsed().as_secs_f32(); + delta.1 = Some(now); + + //println!("delta: {}", delta.0); Ok(()) } @@ -35,7 +37,7 @@ pub struct DeltaTimePlugin; impl Plugin for DeltaTimePlugin { fn setup(&self, game: &mut crate::game::Game) { - game.world().insert_resource(DeltaTime(0.0, Instant::now())); + game.world().insert_resource(DeltaTime(0.0, None)); game.with_system("delta_time", delta_time_system, &[]); } } \ No newline at end of file diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs index a61f49b..569acd0 100755 --- a/src/ecs/mod.rs +++ b/src/ecs/mod.rs @@ -1,151 +1,9 @@ pub use edict::*; pub mod components; + pub mod events; pub use events::*; -use std::collections::HashMap; - -use petgraph::{stable_graph::{StableDiGraph, NodeIndex}, visit::Topo}; -use tracing::warn; - -/// A trait that represents a simple system -pub trait SimpleSystem { - fn setup(&mut self, _world: &mut World) -> anyhow::Result<()> { - Ok(()) - } - - // todo: make async? - fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()>; -} - -impl SimpleSystem for S - where S: FnMut(&mut edict::World) -> anyhow::Result<()> -{ - fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { - self(world) - } -} - -/// A system that executes a batch of systems in order that they were given. -pub struct BatchedSystem { - systems: Vec>, -} - -impl BatchedSystem { - /// Create a new BatchedSystems. The systems will be executed in the order given. - pub fn new(systems: Vec>) -> Self { - Self { - systems - } - } -} - -impl SimpleSystem for BatchedSystem { - fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { - for system in self.systems.iter_mut() { - system.execute_mut(world)?; - } - - Ok(()) - } -} - -struct SystemGraphNode { - name: String, - #[allow(dead_code)] - dependent_on: Vec, // TODO - system: Box, -} - -struct SystemGraphEdge { - -} - -/// Dispatcher for multiple systems. -/// -/// This struct uses a graph for finding executing systems with dependencies. -/// At some point this will be multithreaded. -pub struct SystemDispatcher { - graph: StableDiGraph, - node_refs: HashMap, -} - -impl Default for SystemDispatcher { - fn default() -> Self { - Self { - graph: StableDiGraph::new(), - node_refs: HashMap::new(), - } - } -} - -impl SystemDispatcher { - pub fn new() -> Self { - Self::default() - } - - /// Adds a SimpleSystem to the SystemGraph. - /// - /// WARN: Ensure that no cycles are created, this will cause the systems graph to not be sortable by topological sort, - /// and will cause a panic later on in the engine. - /// - /// TODO: Return false if a cycle was created, making it impossible to know when to dispatch them. This will mean that the System would not of had been added. - pub fn add_system(&mut self, name: &str, system: S, dependencies: &[&str]) -> bool - where - S: SimpleSystem + 'static - { - let name = name.to_string(); - let dependencies: Vec = dependencies.iter().map(|s| s.to_string()).collect(); - let system = Box::new(system) as Box; - - let added_index = - self.graph.add_node(SystemGraphNode { - name: name.to_string(), - dependent_on: dependencies.clone(), - system, - }); - - // store name ref to the graph node - self.node_refs.insert(name, added_index); - - // find the dependencies in node_refs and add edges directed towards the new node - for depend in dependencies.into_iter() { - let idx = self.node_refs - .get(&depend) - .expect("Dependency not found!"); - - self.graph.add_edge(*idx, added_index, SystemGraphEdge { }); - } - - true - } - - pub(crate) fn execute_systems(&mut self, world: &mut World) { - let mut topo = Topo::new(&self.graph); - - while let Some(nx) = topo.next(&self.graph) { - let node = self.graph.node_weight_mut(nx).unwrap(); - - match node.system.execute_mut(world) { - Ok(()) => {}, - Err(e) => { - warn!("System execution of {} resulted in an error! '{}'.", node.name, e); - - // TODO: Find some way to stop traversing down a topopath in the graph executor and continue down a different one. - // This might require a custom topopath implementation (preferably iterative). It would have to ensure that it - // doesn't run other systems that are dependent on that failed system. - return; - } - } - } - } -} - -impl SimpleSystem for SystemDispatcher { - fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { - self.execute_systems(world); - - Ok(()) - } -} \ No newline at end of file +pub mod system; +pub use system::*; \ No newline at end of file diff --git a/src/ecs/system/batched.rs b/src/ecs/system/batched.rs new file mode 100644 index 0000000..ca67b9f --- /dev/null +++ b/src/ecs/system/batched.rs @@ -0,0 +1,75 @@ +use super::{SimpleSystem, Criteria}; +use edict::World; + +/// A system that executes a batch of systems in order that they were given. +/// You can optionally add criteria that must pass before the systems are +/// executed. +pub struct BatchedSystem { + systems: Vec>, + criteria: Vec>, + criteria_checks: u32, +} + +impl BatchedSystem { + /// Create a new BatchedSystem + pub fn new() -> Self { + Self { + systems: Vec::new(), + criteria: Vec::new(), + criteria_checks: 0, + } + } + + pub fn with_system(&mut self, system: S) -> &mut Self + where + S: SimpleSystem + 'static + { + self.systems.push(Box::new(system)); + self + } + + pub fn with_criteria(&mut self, criteria: C) -> &mut Self + where + C: Criteria + 'static + { + self.criteria.push(Box::new(criteria)); + self + } +} + +impl SimpleSystem for BatchedSystem { + fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { + let mut can_run = true; + let mut check_again = false; + + for criteria in self.criteria.iter_mut() { + match criteria.can_run(world, self.criteria_checks) { + super::CriteriaSchedule::Yes => can_run = can_run && true, + super::CriteriaSchedule::No => can_run = false, + super::CriteriaSchedule::YesAndLoop => { + can_run = can_run && true; + check_again = can_run && true; + }, + super::CriteriaSchedule::NoAndLoop => { + can_run = false; + check_again = true; + }, + } + } + + if can_run { + for system in self.systems.iter_mut() { + system.execute_mut(world)?; + } + } + + if check_again { + self.criteria_checks += 1; + self.execute_mut(world)?; + } + + self.criteria_checks = 0; + + Ok(()) + } +} \ No newline at end of file diff --git a/src/ecs/system/criteria.rs b/src/ecs/system/criteria.rs new file mode 100644 index 0000000..dec6e58 --- /dev/null +++ b/src/ecs/system/criteria.rs @@ -0,0 +1,23 @@ +pub enum CriteriaSchedule { + Yes, + No, + YesAndLoop, + NoAndLoop, +} + +pub trait Criteria { + /// Checks if this Criteria can run, and if it should check it again. + /// + /// Parameters: + /// * `world` - The ecs world. + /// * `check_count` - The amount of times the Criteria has been checked this tick. + fn can_run(&mut self, world: &mut edict::World, check_count: u32) -> CriteriaSchedule; +} + +impl Criteria for F + where F: FnMut(&mut edict::World, u32) -> CriteriaSchedule +{ + fn can_run(&mut self, world: &mut edict::World, check_count: u32) -> CriteriaSchedule { + self(world, check_count) + } +} \ No newline at end of file diff --git a/src/ecs/system/mod.rs b/src/ecs/system/mod.rs new file mode 100644 index 0000000..21a4dfd --- /dev/null +++ b/src/ecs/system/mod.rs @@ -0,0 +1,127 @@ +pub mod batched; +pub use batched::*; + +pub mod criteria; +pub use criteria::*; + +use std::collections::HashMap; +use edict::World; +use petgraph::{stable_graph::{StableDiGraph, NodeIndex}, visit::Topo}; +use tracing::warn; + +/// A trait that represents a simple system +pub trait SimpleSystem { + fn setup(&mut self, _world: &mut World) -> anyhow::Result<()> { + Ok(()) + } + + // todo: make async? + fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()>; +} + +impl SimpleSystem for S + where S: FnMut(&mut edict::World) -> anyhow::Result<()> +{ + fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { + self(world) + } +} + +struct SystemGraphNode { + name: String, + #[allow(dead_code)] + dependent_on: Vec, // TODO + system: Box, +} + +struct SystemGraphEdge { + +} + +/// Dispatcher for multiple systems. +/// +/// This struct uses a graph for finding executing systems with dependencies. +/// At some point this will be multithreaded. +pub struct SystemDispatcher { + graph: StableDiGraph, + node_refs: HashMap, +} + +impl Default for SystemDispatcher { + fn default() -> Self { + Self { + graph: StableDiGraph::new(), + node_refs: HashMap::new(), + } + } +} + +impl SystemDispatcher { + pub fn new() -> Self { + Self::default() + } + + /// Adds a SimpleSystem to the SystemGraph. + /// + /// WARN: Ensure that no cycles are created, this will cause the systems graph to not be sortable by topological sort, + /// and will cause a panic later on in the engine. + /// + /// TODO: Return false if a cycle was created, making it impossible to know when to dispatch them. This will mean that the System would not of had been added. + pub fn add_system(&mut self, name: &str, system: S, dependencies: &[&str]) -> bool + where + S: SimpleSystem + 'static + { + let name = name.to_string(); + let dependencies: Vec = dependencies.iter().map(|s| s.to_string()).collect(); + let system = Box::new(system) as Box; + + let added_index = + self.graph.add_node(SystemGraphNode { + name: name.to_string(), + dependent_on: dependencies.clone(), + system, + }); + + // store name ref to the graph node + self.node_refs.insert(name, added_index); + + // find the dependencies in node_refs and add edges directed towards the new node + for depend in dependencies.into_iter() { + let idx = self.node_refs + .get(&depend) + .expect("Dependency not found!"); + + self.graph.add_edge(*idx, added_index, SystemGraphEdge { }); + } + + true + } + + pub(crate) fn execute_systems(&mut self, world: &mut World) { + let mut topo = Topo::new(&self.graph); + + while let Some(nx) = topo.next(&self.graph) { + let node = self.graph.node_weight_mut(nx).unwrap(); + + match node.system.execute_mut(world) { + Ok(()) => {}, + Err(e) => { + warn!("System execution of {} resulted in an error! '{}'.", node.name, e); + + // TODO: Find some way to stop traversing down a topopath in the graph executor and continue down a different one. + // This might require a custom topopath implementation (preferably iterative). It would have to ensure that it + // doesn't run other systems that are dependent on that failed system. + return; + } + } + } + } +} + +impl SimpleSystem for SystemDispatcher { + fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { + self.execute_systems(world); + + Ok(()) + } +} \ No newline at end of file