Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 67 additions & 73 deletions examples/macroquad.rs
Original file line number Diff line number Diff line change
@@ -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<bool>,
paused: bool,
}

struct Position {
Expand Down Expand Up @@ -38,36 +36,34 @@ enum Shape {
Circle,
}

fn move_system(game_state: Rc<GameState>) -> 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;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core part of this PR. it becomes much rarer that you need to use closures, though it's still very practical for things like textures that do not change and are only loaded once, as they can just be captured by immutable reference and do not have to be part of the mutable resource argument (here GameState).

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soo much cleaner than passing around individual resources to each system.. Looks solid to me 😄

}

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| {
Expand All @@ -93,46 +89,44 @@ fn collision_system(world: &World) {
)
}

fn render_system(game_state: Rc<GameState>) -> 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")]
Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ mod scheduler;
mod sparse_set;
mod world;

pub use scheduler::Scheduler;
pub use scheduler::SysId;
pub use world::{Entity, World};
128 changes: 24 additions & 104 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,115 +92,35 @@ impl<C: 'static> SparseSetGetter for &mut C {
}
}

impl<T: SparseSetGetter + Always, F> Query<(T,)> for F
macro_rules! impl_query {
($($T:ident),*) => {
impl<A: SparseSetGetter + Always, $($T: SparseSetGetter,)* Z> 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<T: SparseSetGetter + Always, U: SparseSetGetter, F> 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<T: SparseSetGetter + Always, U: SparseSetGetter, V: SparseSetGetter, F> 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<T: SparseSetGetter + Always, U: SparseSetGetter, V: SparseSetGetter, W: SparseSetGetter, F>
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);
31 changes: 19 additions & 12 deletions src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Box<dyn FnMut(&World, &mut RES) + 'a>>);

impl<T: FnMut(&World) + 'static> SystemFn for T {}

pub type System = (SysId, Option<Box<dyn SystemFn>>);

#[derive(Default)]
pub struct Scheduler {
pub struct Scheduler<'a, RES> {
next_id: Cell<u64>,
systems: RefCell<Vec<System>>,
systems: RefCell<Vec<System<'a, RES>>>,
}

impl<RES> 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
Expand All @@ -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();
Expand All @@ -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;
Expand Down
Loading