use crate::Entity;
use crate::Schedule;
use crate::dynamic::{DynQuery, DynWorld, QueryTuple};
use std::cell::RefCell;
use std::marker::PhantomData;
pub struct Res<'world, T> {
value: &'world T,
}
impl<T> std::ops::Deref for Res<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.value
}
}
pub struct ResMut<'world, T> {
value: &'world mut T,
}
impl<T> std::ops::Deref for ResMut<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.value
}
}
impl<T> std::ops::DerefMut for ResMut<'_, T> {
fn deref_mut(&mut self) -> &mut T {
self.value
}
}
pub trait ResourceParam {
type Resource: Send + Sync + 'static;
type Item<'world>;
fn build(resource: &mut Self::Resource) -> Self::Item<'_>;
}
impl<'a, T: Send + Sync + 'static> ResourceParam for Res<'a, T> {
type Resource = T;
type Item<'world> = Res<'world, T>;
fn build(resource: &mut T) -> Res<'_, T> {
Res { value: resource }
}
}
impl<'a, T: Send + Sync + 'static> ResourceParam for ResMut<'a, T> {
type Resource = T;
type Item<'world> = ResMut<'world, T>;
fn build(resource: &mut T) -> ResMut<'_, T> {
ResMut { value: resource }
}
}
pub trait QueryFilter {
fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q>;
}
impl QueryFilter for () {
fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
query
}
}
pub struct With<T>(PhantomData<fn() -> T>);
impl<T: Send + Sync + Default + 'static> QueryFilter for With<T> {
fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
query.with::<T>()
}
}
pub struct Without<T>(PhantomData<fn() -> T>);
impl<T: Send + Sync + Default + 'static> QueryFilter for Without<T> {
fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
query.without::<T>()
}
}
pub struct Changed<T>(PhantomData<fn() -> T>);
impl<T: Send + Sync + Default + 'static> QueryFilter for Changed<T> {
fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
query.changed::<T>()
}
}
pub struct Added<T>(PhantomData<fn() -> T>);
impl<T: Send + Sync + Default + 'static> QueryFilter for Added<T> {
fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
query.added::<T>()
}
}
pub struct WithTag<T>(PhantomData<fn() -> T>);
impl<T: 'static> QueryFilter for WithTag<T> {
fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
query.with_tag_type::<T>()
}
}
pub struct WithoutTag<T>(PhantomData<fn() -> T>);
impl<T: 'static> QueryFilter for WithoutTag<T> {
fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
query.without_tag_type::<T>()
}
}
macro_rules! impl_query_filter_tuple {
($($filter:ident),+) => {
impl<$($filter: QueryFilter),+> QueryFilter for ($($filter,)+) {
fn apply<Q: QueryTuple>(query: DynQuery<'_, Q>) -> DynQuery<'_, Q> {
$(let query = $filter::apply(query);)+
query
}
}
};
}
impl_query_filter_tuple!(A);
impl_query_filter_tuple!(A, B);
impl_query_filter_tuple!(A, B, C);
impl_query_filter_tuple!(A, B, C, D);
enum QueryState<'world, Q: QueryTuple> {
Eager(DynQuery<'world, Q>),
Lazy(&'world RefCell<&'world mut DynWorld>),
}
pub struct Query<'world, Q: QueryTuple, F: QueryFilter = ()> {
state: QueryState<'world, Q>,
filter: PhantomData<fn() -> F>,
}
impl<'world, Q: QueryTuple, F: QueryFilter> Query<'world, Q, F> {
pub fn for_each(self, f: impl for<'item> FnMut(Entity, Q::Item<'item>)) {
match self.state {
QueryState::Eager(query) => F::apply(query).for_each(f),
QueryState::Lazy(cell) => {
let mut guard = cell.borrow_mut();
let world: &mut DynWorld = &mut guard;
F::apply(world.query::<Q>()).for_each(f);
}
}
}
#[cfg(not(target_family = "wasm"))]
pub fn par_for_each<Fun>(self, f: Fun)
where
Fun: for<'item> Fn(Entity, Q::Item<'item>) + Send + Sync,
{
match self.state {
QueryState::Eager(query) => F::apply(query).par_for_each(f),
QueryState::Lazy(cell) => {
let mut guard = cell.borrow_mut();
let world: &mut DynWorld = &mut guard;
F::apply(world.query::<Q>()).par_for_each(f);
}
}
}
}
pub trait WorldParam {
type Item<'world>;
fn build(world: &mut DynWorld) -> Self::Item<'_>;
}
impl<'a, Q: QueryTuple, F: QueryFilter> WorldParam for Query<'a, Q, F> {
type Item<'world> = Query<'world, Q, F>;
fn build(world: &mut DynWorld) -> Query<'_, Q, F> {
Query {
state: QueryState::Eager(world.query::<Q>()),
filter: PhantomData,
}
}
}
pub trait MultiQueryParam {
type Item<'world>;
fn build_lazy<'world>(cell: &'world RefCell<&'world mut DynWorld>) -> Self::Item<'world>;
}
impl<'a, Q: QueryTuple, F: QueryFilter> MultiQueryParam for Query<'a, Q, F> {
type Item<'world> = Query<'world, Q, F>;
fn build_lazy<'world>(cell: &'world RefCell<&'world mut DynWorld>) -> Query<'world, Q, F> {
Query {
state: QueryState::Lazy(cell),
filter: PhantomData,
}
}
}
pub struct ParamSet<'world, T> {
world: &'world mut DynWorld,
marker: PhantomData<fn() -> T>,
}
impl<'a, T: 'static> WorldParam for ParamSet<'a, T>
where
ParamSet<'a, T>: ParamSetAccess,
{
type Item<'world> = ParamSet<'world, T>;
fn build(world: &mut DynWorld) -> ParamSet<'_, T> {
ParamSet {
world,
marker: PhantomData,
}
}
}
pub trait ParamSetAccess {}
impl<P0: WorldParam, P1: WorldParam> ParamSetAccess for ParamSet<'_, (P0, P1)> {}
impl<'world, P0: WorldParam, P1: WorldParam> ParamSet<'world, (P0, P1)> {
pub fn p0(&mut self) -> P0::Item<'_> {
P0::build(self.world)
}
pub fn p1(&mut self) -> P1::Item<'_> {
P1::build(self.world)
}
}
impl<P0: WorldParam, P1: WorldParam, P2: WorldParam> ParamSetAccess for ParamSet<'_, (P0, P1, P2)> {}
impl<'world, P0: WorldParam, P1: WorldParam, P2: WorldParam> ParamSet<'world, (P0, P1, P2)> {
pub fn p0(&mut self) -> P0::Item<'_> {
P0::build(self.world)
}
pub fn p1(&mut self) -> P1::Item<'_> {
P1::build(self.world)
}
pub fn p2(&mut self) -> P2::Item<'_> {
P2::build(self.world)
}
}
pub trait IntoSystem<Marker>: Sized {
fn into_runner(self) -> impl FnMut(&mut DynWorld) + Send + 'static;
}
pub trait ScheduleExt {
fn add_system<Marker>(
&mut self,
name: &'static str,
system: impl IntoSystem<Marker>,
) -> &mut Self;
}
impl ScheduleExt for Schedule<DynWorld> {
fn add_system<Marker>(
&mut self,
name: &'static str,
system: impl IntoSystem<Marker>,
) -> &mut Self {
self.push(name, system.into_runner())
}
}
pub struct ResourceSystemMarker<R>(PhantomData<fn() -> R>);
pub struct QuerySystemMarker<R, Q>(PhantomData<fn() -> (R, Q)>);
impl<Func, Q> IntoSystem<QuerySystemMarker<(), Q>> for Func
where
Q: WorldParam,
Func: FnMut(Q) + for<'world> FnMut(Q::Item<'world>) + Send + 'static,
{
fn into_runner(mut self) -> impl FnMut(&mut DynWorld) + Send + 'static {
move |world: &mut DynWorld| {
let query = Q::build(world);
self(query);
}
}
}
macro_rules! impl_resource_system {
($($resource:ident $binding:ident),+) => {
impl<Func, $($resource,)+> IntoSystem<ResourceSystemMarker<($($resource,)+)>>
for Func
where
$($resource: ResourceParam,)+
Func: FnMut($($resource,)+)
+ for<'world> FnMut($($resource::Item<'world>,)+)
+ Send
+ 'static,
{
#[allow(non_snake_case)]
fn into_runner(mut self) -> impl FnMut(&mut DynWorld) + Send + 'static {
move |world: &mut DynWorld| {
world.resources_scope::<($($resource::Resource,)+), _>(|_world, bundle| {
let ($($binding,)+) = bundle;
self($($resource::build($binding),)+);
});
}
}
}
};
}
macro_rules! impl_query_system {
($($resource:ident $binding:ident),+) => {
impl<Func, $($resource,)+ Q>
IntoSystem<QuerySystemMarker<($($resource,)+), Q>> for Func
where
$($resource: ResourceParam,)+
Q: WorldParam,
Func: FnMut($($resource,)+ Q)
+ for<'world> FnMut($($resource::Item<'world>,)+ Q::Item<'world>)
+ Send
+ 'static,
{
#[allow(non_snake_case)]
fn into_runner(mut self) -> impl FnMut(&mut DynWorld) + Send + 'static {
move |world: &mut DynWorld| {
world.resources_scope::<($($resource::Resource,)+), _>(|world, bundle| {
let ($($binding,)+) = bundle;
let query = Q::build(world);
self($($resource::build($binding),)+ query);
});
}
}
}
};
}
impl_resource_system!(R0 r0);
impl_resource_system!(R0 r0, R1 r1);
impl_resource_system!(R0 r0, R1 r1, R2 r2);
impl_resource_system!(R0 r0, R1 r1, R2 r2, R3 r3);
impl_resource_system!(R0 r0, R1 r1, R2 r2, R3 r3, R4 r4);
impl_resource_system!(R0 r0, R1 r1, R2 r2, R3 r3, R4 r4, R5 r5);
impl_resource_system!(R0 r0, R1 r1, R2 r2, R3 r3, R4 r4, R5 r5, R6 r6);
impl_resource_system!(R0 r0, R1 r1, R2 r2, R3 r3, R4 r4, R5 r5, R6 r6, R7 r7);
impl_query_system!(R0 r0);
impl_query_system!(R0 r0, R1 r1);
impl_query_system!(R0 r0, R1 r1, R2 r2);
impl_query_system!(R0 r0, R1 r1, R2 r2, R3 r3);
impl_query_system!(R0 r0, R1 r1, R2 r2, R3 r3, R4 r4);
impl_query_system!(R0 r0, R1 r1, R2 r2, R3 r3, R4 r4, R5 r5);
impl_query_system!(R0 r0, R1 r1, R2 r2, R3 r3, R4 r4, R5 r5, R6 r6);
pub struct MultiQuerySystemMarker<R, Q>(PhantomData<fn() -> (R, Q)>);
macro_rules! impl_multi_query_bare {
($($query:ident $qbind:ident),+) => {
impl<Func, $($query,)+> IntoSystem<MultiQuerySystemMarker<(), ($($query,)+)>> for Func
where
$($query: MultiQueryParam,)+
Func: FnMut($($query,)+)
+ for<'world> FnMut($($query::Item<'world>,)+)
+ Send
+ 'static,
{
#[allow(non_snake_case)]
fn into_runner(mut self) -> impl FnMut(&mut DynWorld) + Send + 'static {
move |world: &mut DynWorld| {
let cell = RefCell::new(world);
$(let $qbind = $query::build_lazy(&cell);)+
self($($qbind,)+);
}
}
}
};
}
macro_rules! impl_multi_query_resourced {
(($($resource:ident $rbind:ident),+), ($($query:ident $qbind:ident),+)) => {
impl<Func, $($resource,)+ $($query,)+>
IntoSystem<MultiQuerySystemMarker<($($resource,)+), ($($query,)+)>> for Func
where
$($resource: ResourceParam,)+
$($query: MultiQueryParam,)+
Func: FnMut($($resource,)+ $($query,)+)
+ for<'world> FnMut($($resource::Item<'world>,)+ $($query::Item<'world>,)+)
+ Send
+ 'static,
{
#[allow(non_snake_case)]
fn into_runner(mut self) -> impl FnMut(&mut DynWorld) + Send + 'static {
move |world: &mut DynWorld| {
world.resources_scope::<($($resource::Resource,)+), _>(|world, bundle| {
let ($($rbind,)+) = bundle;
let cell = RefCell::new(world);
$(let $qbind = $query::build_lazy(&cell);)+
self($($resource::build($rbind),)+ $($qbind,)+);
});
}
}
}
};
}
impl_multi_query_bare!(Q0 q0, Q1 q1);
impl_multi_query_bare!(Q0 q0, Q1 q1, Q2 q2);
impl_multi_query_bare!(Q0 q0, Q1 q1, Q2 q2, Q3 q3);
impl_multi_query_resourced!((R0 r0), (Q0 q0, Q1 q1));
impl_multi_query_resourced!((R0 r0), (Q0 q0, Q1 q1, Q2 q2));
impl_multi_query_resourced!((R0 r0, R1 r1), (Q0 q0, Q1 q1));
impl_multi_query_resourced!((R0 r0, R1 r1), (Q0 q0, Q1 q1, Q2 q2));
impl_multi_query_resourced!((R0 r0, R1 r1, R2 r2), (Q0 q0, Q1 q1));
impl_multi_query_resourced!((R0 r0, R1 r1, R2 r2), (Q0 q0, Q1 q1, Q2 q2));
#[cfg(test)]
mod tests {
use super::*;
use crate::Schedule;
use crate::dynamic::DynWorld;
#[derive(Default, Clone, Debug, PartialEq)]
struct Position {
x: f32,
y: f32,
}
#[derive(Default, Clone, Debug, PartialEq)]
struct Velocity {
x: f32,
y: f32,
}
#[derive(Default, Clone, Debug, PartialEq)]
struct Health {
value: f32,
}
struct DeltaTime(f32);
struct Score(u32);
struct Frozen;
fn run<Marker>(world: &mut DynWorld, system: impl IntoSystem<Marker>) {
let mut runner = system.into_runner();
runner(world);
}
#[test]
fn single_resource_param() {
let mut world = DynWorld::new();
world.insert_resource(Score(0));
run(&mut world, |mut score: ResMut<Score>| score.0 += 5);
assert_eq!(world.resource::<Score>().unwrap().0, 5);
}
#[test]
fn multiple_resource_params() {
let mut world = DynWorld::new();
world.insert_resource(DeltaTime(2.0));
world.insert_resource(Score(1));
run(
&mut world,
|delta: Res<DeltaTime>, mut score: ResMut<Score>| {
score.0 += delta.0 as u32;
},
);
assert_eq!(world.resource::<Score>().unwrap().0, 3);
}
#[test]
fn query_only_param() {
let mut world = DynWorld::new();
world.spawn((Position { x: 1.0, y: 2.0 },));
run(&mut world, |query: Query<&mut Position>| {
query.for_each(|_entity, position| position.x += 10.0);
});
let position = world.query_ref::<&Position>().single().unwrap().1.clone();
assert_eq!(position, Position { x: 11.0, y: 2.0 });
}
#[test]
fn resources_and_query_together() {
let mut world = DynWorld::new();
world.insert_resource(DeltaTime(0.5));
world.insert_resource(Score(0));
world.spawn((
Position { x: 0.0, y: 0.0 },
Velocity { x: 2.0, y: 4.0 },
Health { value: 100.0 },
));
world.spawn((Position { x: 5.0, y: 5.0 }, Velocity { x: 1.0, y: 1.0 }));
run(
&mut world,
|delta: Res<DeltaTime>,
mut score: ResMut<Score>,
query: Query<(&mut Position, &Velocity, Option<&mut Health>)>| {
query.for_each(|_entity, (position, velocity, health)| {
position.x += velocity.x * delta.0;
position.y += velocity.y * delta.0;
if let Some(health) = health {
health.value *= 0.9;
}
});
score.0 += 1;
},
);
assert_eq!(world.resource::<Score>().unwrap().0, 1);
let mut positions: Vec<(f32, f32)> = world
.query_ref::<&Position>()
.iter()
.map(|(_entity, position)| (position.x, position.y))
.collect();
positions.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
assert_eq!(positions, vec![(1.0, 2.0), (5.5, 5.5)]);
}
#[test]
fn with_filter_narrows_match() {
let mut world = DynWorld::new();
let frozen = world.spawn((Position { x: 0.0, y: 0.0 },));
world.add_tag_type::<Frozen>(frozen);
world.spawn((Position { x: 0.0, y: 0.0 },));
run(&mut world, |query: Query<&mut Position, With<Health>>| {
query.for_each(|_entity, position| position.x = 1.0);
});
assert!(
world
.query_ref::<&Position>()
.iter()
.all(|(_entity, position)| position.x == 0.0)
);
run(
&mut world,
|query: Query<&mut Position, WithoutTag<Frozen>>| {
query.for_each(|_entity, position| position.x = 2.0);
},
);
let moved = world
.query_ref::<&Position>()
.iter()
.filter(|(_entity, position)| position.x == 2.0)
.count();
assert_eq!(moved, 1);
}
#[test]
fn changed_filter_visits_only_mutated() {
let mut world = DynWorld::new();
let entity = world.spawn((Position { x: 0.0, y: 0.0 },));
world.spawn((Position { x: 0.0, y: 0.0 },));
world.step();
world.get_mut::<Position>(entity).unwrap().x = 1.0;
let mut visited = 0;
run(&mut world, |query: Query<&Position, Changed<Position>>| {
query.for_each(|_entity, _position| {});
});
world
.query_ref::<&Position>()
.changed::<Position>()
.iter()
.for_each(|_| visited += 1);
assert_eq!(visited, 1);
}
#[test]
fn param_set_lends_conflicting_queries() {
let mut world = DynWorld::new();
world.spawn((Position { x: 1.0, y: 1.0 }, Velocity { x: 3.0, y: 3.0 }));
run(
&mut world,
|mut set: ParamSet<(Query<&mut Position>, Query<&Velocity>)>| {
let mut velocity = (0.0, 0.0);
set.p1()
.for_each(|_entity, value| velocity = (value.x, value.y));
set.p0().for_each(|_entity, position| {
position.x += velocity.0;
position.y += velocity.1;
});
},
);
let position = world.query_ref::<&Position>().single().unwrap().1.clone();
assert_eq!(position, Position { x: 4.0, y: 4.0 });
}
#[test]
fn two_direct_queries_disjoint_components() {
let mut world = DynWorld::new();
world.spawn((Position { x: 1.0, y: 1.0 }, Velocity { x: 3.0, y: 4.0 }));
run(
&mut world,
|positions: Query<&mut Position>, velocities: Query<&Velocity>| {
let mut sample = (0.0, 0.0);
velocities.for_each(|_entity, velocity| sample = (velocity.x, velocity.y));
positions.for_each(|_entity, position| {
position.x += sample.0;
position.y += sample.1;
});
},
);
let position = world.query_ref::<&Position>().single().unwrap().1.clone();
assert_eq!(position, Position { x: 4.0, y: 5.0 });
}
#[test]
fn two_direct_queries_same_component_run_sequentially() {
let mut world = DynWorld::new();
world.spawn((Position { x: 0.0, y: 0.0 },));
run(
&mut world,
|first: Query<&mut Position>, second: Query<&mut Position>| {
first.for_each(|_entity, position| position.x += 1.0);
second.for_each(|_entity, position| position.x += 10.0);
},
);
assert_eq!(world.query_ref::<&Position>().single().unwrap().1.x, 11.0);
}
#[test]
fn resource_with_two_direct_queries() {
let mut world = DynWorld::new();
world.insert_resource(Score(0));
world.spawn((Position { x: 0.0, y: 0.0 }, Velocity { x: 2.0, y: 0.0 }));
run(
&mut world,
|mut score: ResMut<Score>,
positions: Query<&mut Position>,
velocities: Query<&Velocity>| {
let mut velocity_x = 0.0;
velocities.for_each(|_entity, velocity| velocity_x = velocity.x);
positions.for_each(|_entity, position| position.x += velocity_x);
score.0 += 1;
},
);
assert_eq!(world.resource::<Score>().unwrap().0, 1);
assert_eq!(world.query_ref::<&Position>().single().unwrap().1.x, 2.0);
}
#[test]
fn schedule_add_system_runs_param_systems() {
let mut world = DynWorld::new();
world.insert_resource(Score(0));
world.spawn((Position { x: 0.0, y: 0.0 }, Velocity { x: 1.0, y: 0.0 }));
fn movement(query: Query<(&mut Position, &Velocity)>) {
query.for_each(|_entity, (position, velocity)| position.x += velocity.x);
}
fn scoring(mut score: ResMut<Score>) {
score.0 += 1;
}
let mut schedule = Schedule::new();
schedule.add_system("movement", movement);
schedule.add_system("scoring", scoring);
schedule.run(&mut world);
schedule.run(&mut world);
assert_eq!(world.resource::<Score>().unwrap().0, 2);
let position = world.query_ref::<&Position>().single().unwrap().1.clone();
assert_eq!(position, Position { x: 2.0, y: 0.0 });
}
#[test]
fn added_filter_visits_only_new() {
let mut world = DynWorld::new();
world.spawn((Position { x: 0.0, y: 0.0 },));
world.step();
world.spawn((Position { x: 9.0, y: 9.0 },));
run(
&mut world,
|query: Query<&mut Position, Added<Position>>| {
query.for_each(|_entity, position| position.y = 100.0);
},
);
let marked = world
.query_ref::<&Position>()
.iter()
.filter(|(_entity, position)| position.y == 100.0)
.map(|(_entity, position)| position.x)
.collect::<Vec<_>>();
assert_eq!(marked, vec![9.0]);
}
#[test]
fn tuple_filter_combines_constraints() {
let mut world = DynWorld::new();
world.spawn((Position { x: 0.0, y: 0.0 }, Health { value: 1.0 }));
world.spawn((
Position { x: 0.0, y: 0.0 },
Health { value: 1.0 },
Velocity { x: 0.0, y: 0.0 },
));
run(
&mut world,
|query: Query<&mut Position, (With<Health>, Without<Velocity>)>| {
query.for_each(|_entity, position| position.x = 1.0);
},
);
let moved = world
.query_ref::<&Position>()
.iter()
.filter(|(_entity, position)| position.x == 1.0)
.count();
assert_eq!(moved, 1);
}
#[test]
fn par_for_each_runs_over_query() {
let mut world = DynWorld::new();
for index in 0..64 {
world.spawn((
Position {
x: index as f32,
y: 0.0,
},
Velocity { x: 1.0, y: 0.0 },
));
}
run(&mut world, |query: Query<(&mut Position, &Velocity)>| {
query.par_for_each(|_entity, (position, velocity)| position.x += velocity.x);
});
let total: f32 = world
.query_ref::<&Position>()
.iter()
.map(|(_entity, position)| position.x)
.sum();
assert_eq!(total, (0..64).map(|index| index as f32 + 1.0).sum());
}
#[test]
#[should_panic(expected = "DeltaTime")]
fn missing_resource_panics() {
let mut world = DynWorld::new();
run(&mut world, |_delta: Res<DeltaTime>| {});
}
}