diff --git a/examples/macroquad.rs b/examples/macroquad.rs index 2aeca21..93beaad 100644 --- a/examples/macroquad.rs +++ b/examples/macroquad.rs @@ -1,10 +1,8 @@ use macroquad::{prelude::*, rand, ui::root_ui}; use secs::World; -use std::cell::Cell; -use std::rc::Rc; struct GameState { - paused: Cell, + paused: bool, } struct Position { @@ -38,36 +36,34 @@ enum Shape { Circle, } -fn move_system(game_state: Rc) -> impl Fn(&World) { - move |world| { - if game_state.paused.get() { - return; +fn move_system(world: &World, game_state: &mut GameState) { + if game_state.paused { + return; + } + + world.query(|_entity, pos: &mut Position, vel: &mut Velocity| { + vel.x = 0.; + vel.y = 0.; + + if is_key_down(KeyCode::Right) || is_key_down(KeyCode::D) { + vel.x = 2.; + } + if is_key_down(KeyCode::Left) || is_key_down(KeyCode::A) { + vel.x = -2.; + } + if is_key_down(KeyCode::Down) || is_key_down(KeyCode::S) { + vel.y = 2.; + } + if is_key_down(KeyCode::Up) || is_key_down(KeyCode::W) { + vel.y = -2.; } - world.query(|_entity, pos: &mut Position, vel: &mut Velocity| { - vel.x = 0.; - vel.y = 0.; - - if is_key_down(KeyCode::Right) || is_key_down(KeyCode::D) { - vel.x = 2.; - } - if is_key_down(KeyCode::Left) || is_key_down(KeyCode::A) { - vel.x = -2.; - } - if is_key_down(KeyCode::Down) || is_key_down(KeyCode::S) { - vel.y = 2.; - } - if is_key_down(KeyCode::Up) || is_key_down(KeyCode::W) { - vel.y = -2.; - } - - pos.x += vel.x; - pos.y += vel.y; - }); - } + pos.x += vel.x; + pos.y += vel.y; + }); } -fn collision_system(world: &World) { +fn collision_system(world: &World, _: &mut GameState) { world.query( |_, player_center: &Position, player: &mut Sprite, player_score: &mut Score| { world.query(|_, powerup_center: &Position, powerup: &mut Powerup| { @@ -93,46 +89,44 @@ fn collision_system(world: &World) { ) } -fn render_system(game_state: Rc) -> impl Fn(&World) { - move |world| { - if game_state.paused.get() { - let text = "PAUSED"; - let font_size = 100.; - let text_width = measure_text(text, None, font_size as u16, 1.).width; - let (x, y) = ((screen_width() - text_width) / 2., screen_height() / 2.); +fn render_system(world: &World, game_state: &mut GameState) { + if game_state.paused { + let text = "PAUSED"; + let font_size = 100.; + let text_width = measure_text(text, None, font_size as u16, 1.).width; + let (x, y) = ((screen_width() - text_width) / 2., screen_height() / 2.); - draw_text(text, x, y, font_size, RED); + draw_text(text, x, y, font_size, RED); - return; + return; + } + + world.query(|_, pos: &Position, sprite: &Sprite| match sprite.shape { + Shape::Square => draw_rectangle( + pos.x - (sprite.width * 0.5), + pos.y - (sprite.width * 0.5), + sprite.width, + sprite.height, + ORANGE, + ), + Shape::Circle => draw_circle(pos.x, pos.y, sprite.width * 0.5, PURPLE), + }); + + world.query(|_, powerup: &Powerup, pos: &Position| { + if powerup.active { + draw_rectangle( + pos.x - (powerup.width * 0.5), + pos.y - (powerup.width * 0.5), + powerup.width, + powerup.height, + RED, + ); } + }); - world.query(|_, pos: &Position, sprite: &Sprite| match sprite.shape { - Shape::Square => draw_rectangle( - pos.x - (sprite.width * 0.5), - pos.y - (sprite.width * 0.5), - sprite.width, - sprite.height, - ORANGE, - ), - Shape::Circle => draw_circle(pos.x, pos.y, sprite.width * 0.5, PURPLE), - }); - - world.query(|_, powerup: &Powerup, pos: &Position| { - if powerup.active { - draw_rectangle( - pos.x - (powerup.width * 0.5), - pos.y - (powerup.width * 0.5), - powerup.width, - powerup.height, - RED, - ); - } - }); - - world.query(|_, score: &Score| { - root_ui().label(None, &format!("Player Score: {}", score.value)); - }); - } + world.query(|_, score: &Score| { + root_ui().label(None, &format!("Player Score: {}", score.value)); + }); } #[macroquad::main("secs_macroquad")] @@ -164,24 +158,24 @@ async fn main() { )); } - let game_state = Rc::new(GameState { - paused: Cell::new(false), - }); + let scheduler = secs::Scheduler::default(); + + let mut game_state = GameState { paused: false }; - world.add_system(move_system(game_state.clone())); - world.add_system(collision_system); + scheduler.register(move_system); + scheduler.register(collision_system); - world.add_system(render_system(game_state.clone())); + scheduler.register(render_system); loop { clear_background(SKYBLUE); if is_key_pressed(KeyCode::P) { - game_state.paused.set(!game_state.paused.get()); + game_state.paused = !game_state.paused; } // run all parallel and sequential systems - world.run_systems(); + scheduler.run(&world, &mut game_state); next_frame().await; } diff --git a/src/lib.rs b/src/lib.rs index 1c47668..279d748 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,5 +8,6 @@ mod scheduler; mod sparse_set; mod world; +pub use scheduler::Scheduler; pub use scheduler::SysId; pub use world::{Entity, World}; diff --git a/src/query.rs b/src/query.rs index 689e433..f7795d1 100644 --- a/src/query.rs +++ b/src/query.rs @@ -92,115 +92,35 @@ impl SparseSetGetter for &mut C { } } -impl Query<(T,)> for F +macro_rules! impl_query { + ($($T:ident),*) => { + impl Query<(A, $($T,)*)> for Z where - F: FnMut(Entity, T::Short<'_>) + FnMut(Entity, T), -{ - #[track_caller] - fn get_components(world: &World, mut f: F) { - if let Some(mut s1) = T::get_set(world) { - for (entity, c1) in T::iter(&mut s1) { - f(entity, c1); - } - } - } -} + Z: FnMut(Entity, A::Short<'_>, $($T::Short<'_>,)*) + FnMut(Entity, A, $($T,)*),{ + #[track_caller] + fn get_components(world: &World, mut f: Z) { + #[allow(non_snake_case)] + if let (Some(mut a), $(Some(mut $T),)*) = (A::get_set(world), $($T::get_set(world),)*) { + for (entity, a) in A::iter(&mut a) { + $(let Some($T) = $T::get_entity(&mut $T, entity) else { continue };)* + f(entity, a, $($T,)*); -impl Query<(T, U)> for F -where - F: FnMut(Entity, T::Short<'_>, U::Short<'_>) + FnMut(Entity, T, U), -{ - #[track_caller] - fn get_components(world: &World, mut f: F) { - if let (Some(mut s1), Some(mut s2)) = (T::get_set(world), U::get_set(world)) { - for (entity, c1) in T::iter(&mut s1) { - if let Some(c2) = U::get_entity(&mut s2, entity) { - f(entity, c1, c2); - } - } - } - } -} - -impl Query<(T, U, V)> for F -where - F: FnMut(Entity, T::Short<'_>, U::Short<'_>, V::Short<'_>) + FnMut(Entity, T, U, V), -{ - #[track_caller] - fn get_components(world: &World, mut f: F) { - if let (Some(mut s1), Some(mut s2), Some(mut s3)) = - (T::get_set(world), U::get_set(world), V::get_set(world)) - { - for (entity, c1) in T::iter(&mut s1) { - if let Some(c2) = U::get_entity(&mut s2, entity) { - if let Some(c3) = V::get_entity(&mut s3, entity) { - f(entity, c1, c2, c3); } } } } - } + }; } -impl - Query<(T, U, V, W)> for F -where - F: FnMut(Entity, T::Short<'_>, U::Short<'_>, V::Short<'_>, W::Short<'_>) - + FnMut(Entity, T, U, V, W), -{ - #[track_caller] - fn get_components(world: &World, mut f: F) { - if let (Some(mut s1), Some(mut s2), Some(mut s3), Some(mut s4)) = ( - T::get_set(world), - U::get_set(world), - V::get_set(world), - W::get_set(world), - ) { - for (entity, c1) in T::iter(&mut s1) { - if let Some(c2) = U::get_entity(&mut s2, entity) { - if let Some(c3) = V::get_entity(&mut s3, entity) { - if let Some(c4) = W::get_entity(&mut s4, entity) { - f(entity, c1, c2, c3, c4); - } - } - } - } - } - } -} - -impl< - T: SparseSetGetter + Always, - U: SparseSetGetter, - V: SparseSetGetter, - W: SparseSetGetter, - X: SparseSetGetter, - F, -> Query<(T, U, V, W, X)> for F -where - F: FnMut(Entity, T::Short<'_>, U::Short<'_>, V::Short<'_>, W::Short<'_>, X::Short<'_>) - + FnMut(Entity, T, U, V, W, X), -{ - #[track_caller] - fn get_components(world: &World, mut f: F) { - if let (Some(mut s1), Some(mut s2), Some(mut s3), Some(mut s4), Some(mut s5)) = ( - T::get_set(world), - U::get_set(world), - V::get_set(world), - W::get_set(world), - X::get_set(world), - ) { - for (entity, c1) in T::iter(&mut s1) { - if let Some(c2) = U::get_entity(&mut s2, entity) { - if let Some(c3) = V::get_entity(&mut s3, entity) { - if let Some(c4) = W::get_entity(&mut s4, entity) { - if let Some(c5) = X::get_entity(&mut s5, entity) { - f(entity, c1, c2, c3, c4, c5); - } - } - } - } - } - } - } -} +impl_query!(); +impl_query!(B); +impl_query!(B, C); +impl_query!(B, C, D); +impl_query!(B, C, D, E); +impl_query!(B, C, D, E, F); +impl_query!(B, C, D, E, F, G); +impl_query!(B, C, D, E, F, G, H); +impl_query!(B, C, D, E, F, G, H, I); +impl_query!(B, C, D, E, F, G, H, I, J); +impl_query!(B, C, D, E, F, G, H, I, J, K); +impl_query!(B, C, D, E, F, G, H, I, J, K, L); diff --git a/src/scheduler.rs b/src/scheduler.rs index 7a0258d..7b8bdeb 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -7,26 +7,32 @@ use std::cell::{Cell, RefCell}; #[derive(Copy, Clone)] pub struct SysId(u64); -pub trait SystemFn: FnMut(&World) + 'static {} +pub type System<'a, RES> = (SysId, Option>); -impl SystemFn for T {} - -pub type System = (SysId, Option>); - -#[derive(Default)] -pub struct Scheduler { +pub struct Scheduler<'a, RES> { next_id: Cell, - systems: RefCell>, + systems: RefCell>>, +} + +impl Default for Scheduler<'_, RES> { + fn default() -> Self { + Self { + next_id: Default::default(), + systems: Default::default(), + } + } } -impl Scheduler { - pub fn register(&self, system: impl SystemFn) -> SysId { +impl<'a, RES> Scheduler<'a, RES> { + /// Add a system that will run after all systems that were added before it. + pub fn register(&self, system: impl FnMut(&World, &mut RES) + 'a) -> SysId { let id = SysId(self.next_id.get()); self.next_id.set(id.0 + 1); self.systems.borrow_mut().push((id, Some(Box::new(system)))); id } + /// Remove a previously inserted system. Will silently do nothing if the system was already removed. pub fn deregister(&self, system: SysId) { let position = self .systems @@ -38,7 +44,8 @@ impl Scheduler { } } - pub fn run(&self, world: &World) { + /// Run all systems once. + pub fn run(&self, world: &World, res: &mut RES) { let len = self.systems.borrow().len(); for i in 0..len { let mut guard = self.systems.borrow_mut(); @@ -47,7 +54,7 @@ impl Scheduler { }; let mut sys = sys.take().unwrap(); drop(guard); - sys(world); + sys(world, res); let mut guard = self.systems.borrow_mut(); let Some((_, entry)) = guard.get_mut(i) else { break; diff --git a/src/world.rs b/src/world.rs index a6b9228..8e674f0 100644 --- a/src/world.rs +++ b/src/world.rs @@ -12,7 +12,7 @@ use std::{ use crate::{ query::Query, - scheduler::{Scheduler, SysId, SystemFn}, + scheduler::{Scheduler, SysId}, sparse_set::{RemoveType, SparseSets}, }; @@ -58,7 +58,7 @@ pub struct World { #[cfg(any(debug_assertions, feature = "track_dead_entities"))] dead_entities: RefCell, String)>>, pub(crate) sparse_sets: SparseSets, - scheduler: Scheduler, + scheduler: Scheduler<'static, ()>, } impl World { @@ -235,12 +235,11 @@ impl World { } /// Add a system that will run after all systems that were added before it. - pub fn add_system(&self, system: impl SystemFn) -> SysId { - self.scheduler.register(system) + pub fn add_system(&self, mut system: impl FnMut(&World) + 'static) -> SysId { + self.scheduler.register(move |world, _| system(world)) } - /// Remove a system. Note that due to how compilers work this may not - /// work if the system is declared in another crate. + /// Remove a previously inserted system. pub fn remove_system(&self, system: SysId) { self.scheduler.deregister(system); } @@ -249,7 +248,7 @@ impl World { /// /// Note: it is not recommended to run this from within a system, as that will usually result in infinite recursion. pub fn run_systems(&self) { - self.scheduler.run(self); + self.scheduler.run(self, &mut ()); } } pub trait AttachComponents { diff --git a/tests/system.rs b/tests/system.rs index 618a936..e072f3a 100644 --- a/tests/system.rs +++ b/tests/system.rs @@ -1,6 +1,6 @@ use std::{cell::Cell, panic::AssertUnwindSafe, rc::Rc}; -use secs::{SysId, World}; +use secs::{Scheduler, SysId, World}; #[test] fn remove() { @@ -85,3 +85,25 @@ fn mut_system() { world.run_systems(); } } + +#[test] +fn scheduler_resource() { + let world = World::default(); + + let mut state = 5_u32; + + let immut_resource = "foo".to_string(); + + let scheduler = Scheduler::default(); + + scheduler.register(|_world, state| { + *state += 1; + assert_eq!(immut_resource, "foo"); + assert!(*state <= 8); + }); + + for _ in 0..3 { + scheduler.run(&world, &mut state); + } + assert_eq!(state, 8); +} diff --git a/tests/ui/too_many_tuple_elements.rs b/tests/ui/too_many_tuple_elements.rs index 32ffc58..7085958 100644 --- a/tests/ui/too_many_tuple_elements.rs +++ b/tests/ui/too_many_tuple_elements.rs @@ -15,6 +15,30 @@ fn optional_components() { _: &u16, _: &i16, _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, + _: &u128, _: &i128| {}, ); } diff --git a/tests/ui/too_many_tuple_elements.stderr b/tests/ui/too_many_tuple_elements.stderr index e1f848b..9e44dc3 100644 --- a/tests/ui/too_many_tuple_elements.stderr +++ b/tests/ui/too_many_tuple_elements.stderr @@ -1,4 +1,4 @@ -error[E0277]: `{closure@tests/ui/too_many_tuple_elements.rs:7:9: 18:19}` is not a valid query +error[E0277]: `{closure@tests/ui/too_many_tuple_elements.rs:7:9: 42:19}` is not a valid query --> tests/ui/too_many_tuple_elements.rs:7:9 | 6 | world.query( @@ -8,11 +8,11 @@ error[E0277]: `{closure@tests/ui/too_many_tuple_elements.rs:7:9: 18:19}` is not 9 | | _: &u32, 10 | | _: &i32, ... | -17 | | _: &u128, -18 | | _: &i128| {}, +41 | | _: &u128, +42 | | _: &i128| {}, | |_____________________^ | - = help: the trait `secs::query::Query<_>` is not implemented for closure `{closure@tests/ui/too_many_tuple_elements.rs:7:9: 18:19}` + = help: the trait `secs::query::Query<_>` is not implemented for closure `{closure@tests/ui/too_many_tuple_elements.rs:7:9: 42:19}` = note: only tuples with 1 or up to 5 elements can be used as queries note: required by a bound in `World::query` --> $DIR/src/world.rs