Implement IntoSystem trait for flat functions using macros

This commit is contained in:
Carter Anderson 2020-04-28 23:02:21 -07:00
parent f1a03a7a3a
commit 9230c370ba
5 changed files with 325 additions and 71 deletions

View File

@ -2,7 +2,7 @@ use legion::{
filter::EntityFilter,
prelude::{
into_resource_system, IntoQuery, ResourceSet, Resources, Runnable,
Schedulable, World, into_resource_for_each_system, into_for_each_system,
Schedulable, World, into_resource_for_each_system,
},
query::{DefaultFilter, View},
};
@ -34,16 +34,6 @@ where
}
impl System {
pub fn for_each<'a, Q, F, R>(name: &'static str, system: F) -> Self
where
Q: IntoQuery + DefaultFilter<Filter = R>,
<Q as View<'a>>::Iter: Iterator<Item = Q> + 'a,
F: FnMut(Q) + Send + Sync + 'static,
R: EntityFilter + Sync + 'static,
{
into_for_each_system(name, system).into()
}
pub fn resource_for_each<'a, Q, F, R, X>(name: &'static str, system: F) -> Self
where
Q: IntoQuery + DefaultFilter<Filter = R>,

View File

@ -1,3 +1,4 @@
#![feature(trace_macros)]
pub mod resource;
pub mod schedule;
@ -16,6 +17,7 @@ pub mod prelude {
resource::{ResourceSet, Resources, PreparedRead as Resource, PreparedWrite as ResourceMut},
schedule::{Executor, Runnable, Schedulable, Schedule},
System, SystemBuilder,
into_for_each_system, into_resource_system, into_resource_for_each_system,
into_resource_system, into_resource_for_each_system,
IntoSystem
};
}

View File

@ -1,5 +1,5 @@
use crate::{
resource::{ResourceSet, ResourceTypeId},
resource::{PreparedRead, Resource, ResourceSet, ResourceTypeId},
schedule::{ArchetypeAccess, Schedulable},
Access, System, SystemAccess, SystemFnWrapper, SystemQuery,
};
@ -7,47 +7,12 @@ use bit_set::BitSet;
use fxhash::FxHashMap;
use legion_core::{
borrow::AtomicRefCell,
filter::EntityFilter,
query::{DefaultFilter, IntoQuery, View},
filter::{And, EntityFilter, EntityFilterTuple},
query::{DefaultFilter, IntoQuery, View, ViewElement},
storage::ComponentTypeId,
};
use std::marker::PhantomData;
pub fn into_for_each_system<'a, Q, F, R>(name: &'static str, mut system: F) -> Box<dyn Schedulable>
where
Q: IntoQuery + DefaultFilter<Filter = R>,
<Q as View<'a>>::Iter: Iterator<Item = Q> + 'a,
F: FnMut(Q) + Send + Sync + 'static,
R: EntityFilter + Sync + 'static,
{
let resource_access: Access<ResourceTypeId> = Access::default();
let mut component_access: Access<ComponentTypeId> = Access::default();
component_access.reads.extend(Q::read_types().iter());
component_access.writes.extend(Q::write_types().iter());
let run_fn = SystemFnWrapper(
move |_, world, _, query: &mut SystemQuery<Q, <Q as DefaultFilter>::Filter>| {
for components in query.iter_mut(world) {
system(components);
}
},
PhantomData,
);
Box::new(System {
name: name.into(),
queries: AtomicRefCell::new(Q::query()),
access: SystemAccess {
resources: resource_access,
components: component_access,
tags: Access::default(),
},
archetypes: ArchetypeAccess::Some(BitSet::default()),
_resources: PhantomData::<()>,
command_buffer: FxHashMap::default(),
run_fn: AtomicRefCell::new(run_fn),
})
}
pub fn into_resource_for_each_system<'a, Q, F, R, X>(
name: &'static str,
@ -127,11 +92,274 @@ where
})
}
pub trait IntoSystem<'a, ResourceArgs, ComponentArgs>
where
ComponentArgs: IntoQuery + DefaultFilter,
{
fn into_system(self, name: &'static str) -> Box<dyn Schedulable>;
}
// impl<F, X: Resource + Send + Sync + 'static, A: Component, B: Component> IntoSystem<(X,), (A, B)> for F
// where
// F: for<'a> FnMut(&X, Ref<'a, A>, Ref<'a, B>) + Send + Sync + 'static,
// {
// fn into_system(mut self, name: &'static str) -> Box<dyn Schedulable> {
// let mut resource_access: Access<ResourceTypeId> = Access::default();
// resource_access
// .reads
// .extend(<PreparedRead<X>>::read_types().iter());
// resource_access
// .writes
// .extend(<PreparedRead<X>>::write_types().iter());
// let mut component_access: Access<ComponentTypeId> = Access::default();
// component_access
// .reads
// .extend(<(Ref<A>, Ref<B>) as View>::read_types().iter());
// component_access
// .writes
// .extend(<(Ref<A>, Ref<B>) as View>::write_types().iter());
// let run_fn = SystemFnWrapper(
// move |_,
// world,
// x: &mut PreparedRead<X>,
// query: &mut SystemQuery<
// (Ref<A>, Ref<B>),
// EntityFilterTuple<
// And<(ComponentFilter<A>, ComponentFilter<B>)>,
// And<(Passthrough, Passthrough)>,
// And<(Passthrough, Passthrough)>,
// >,
// >| {
// for (a, b) in query.iter_mut(world) {
// self(&*x, a, b);
// }
// },
// PhantomData,
// );
// Box::new(System {
// name: name.into(),
// queries: AtomicRefCell::new(<(Ref<A>, Ref<B>)>::query()),
// access: SystemAccess {
// resources: resource_access,
// components: component_access,
// tags: Access::default(),
// },
// archetypes: ArchetypeAccess::Some(BitSet::default()),
// _resources: PhantomData::<PreparedRead<X>>,
// command_buffer: FxHashMap::default(),
// run_fn: AtomicRefCell::new(run_fn),
// })
// }
// }
impl<
'a,
F,
X: Resource + Send + Sync + 'static,
A: for<'b> View<'b> + DefaultFilter<Filter = AF> + ViewElement,
AF: EntityFilter + Sync + 'static,
B: for<'b> View<'b> + DefaultFilter<Filter = BF> + ViewElement,
BF: EntityFilter + Sync + 'static,
> IntoSystem<'a, (X,), (A, B)> for F
where
F: FnMut(&X, A, B) + Send + Sync + 'static,
<A as View<'a>>::Iter: Iterator<Item = A>,
<B as View<'a>>::Iter: Iterator<Item = B>,
{
fn into_system(mut self, name: &'static str) -> Box<dyn Schedulable> {
let mut resource_access: Access<ResourceTypeId> = Access::default();
resource_access
.reads
.extend(<PreparedRead<X>>::read_types().iter());
resource_access
.writes
.extend(<PreparedRead<X>>::write_types().iter());
let mut component_access: Access<ComponentTypeId> = Access::default();
component_access
.reads
.extend(<(A, B) as View>::read_types().iter());
component_access
.writes
.extend(<(A, B) as View>::write_types().iter());
let run_fn = SystemFnWrapper(
move |_,
world,
x: &mut PreparedRead<X>,
query: &mut SystemQuery<
(A, B),
EntityFilterTuple<
And<(
<AF as EntityFilter>::ArchetypeFilter,
<BF as EntityFilter>::ArchetypeFilter,
)>,
And<(
<AF as EntityFilter>::ChunksetFilter,
<BF as EntityFilter>::ChunksetFilter,
)>,
And<(
<AF as EntityFilter>::ChunkFilter,
<BF as EntityFilter>::ChunkFilter,
)>,
>,
>| {
for (a, b) in query.iter_mut(world) {
self(&*x, a, b);
}
},
PhantomData,
);
Box::new(System {
name: name.into(),
queries: AtomicRefCell::new(<(A, B)>::query()),
access: SystemAccess {
resources: resource_access,
components: component_access,
tags: Access::default(),
},
archetypes: ArchetypeAccess::Some(BitSet::default()),
_resources: PhantomData::<PreparedRead<X>>,
command_buffer: FxHashMap::default(),
run_fn: AtomicRefCell::new(run_fn),
})
}
}
macro_rules! impl_system {
($(($view:ident, $filter:ident, $var:ident)),+) => {
impl<'a,
Func,
$($view: for<'b> View<'b> + DefaultFilter<Filter = $filter> + ViewElement,
$filter: EntityFilter + Sync + 'static),+
> IntoSystem<'a, (), ($($view,)+)> for Func
where
Func: FnMut($($view),+) + Send + Sync + 'static,
$(<$view as View<'a>>::Iter: Iterator<Item = $view>),+
{
fn into_system(mut self, name: &'static str) -> Box<dyn Schedulable> {
let resource_access: Access<ResourceTypeId> = Access::default();
let component_access: Access<ComponentTypeId> = component_access!(($($view),+));
let run_fn = SystemFnWrapper(
move |_,
world,
_: &mut (),
query: &mut system_query!($($view, $filter),+)
,
| {
for tuple!($($var),+) in query.iter_mut(world) {
self($($var),+);
}
},
PhantomData,
);
Box::new(System {
name: name.into(),
queries: AtomicRefCell::new(query!($($view),+)),
access: SystemAccess {
resources: resource_access,
components: component_access,
tags: Access::default(),
},
archetypes: ArchetypeAccess::Some(BitSet::default()),
_resources: PhantomData::<()>,
command_buffer: FxHashMap::default(),
run_fn: AtomicRefCell::new(run_fn),
})
}
}
}
}
macro_rules! tuple {
// single value: v1
($value:ident) => { $value };
// multiple values: (v1, v2, v3)
($($value:ident),+) => { ($($value),+) }
}
macro_rules! component_access {
(()) => {Access::default()};
(($($view:ident),+)) => {{
let mut component_access: Access<ComponentTypeId> = Access::default();
component_access
.reads
.extend(<tuple!($($view),+) as View>::read_types().iter());
component_access
.writes
.extend(<tuple!($($view),+) as View>::write_types().iter());
component_access
}}
}
macro_rules! system_query {
($view:ident, $filter:ident) => {
SystemQuery<
$view,
$filter
>
};
($($view:ident, $filter:ident),+) => {
SystemQuery<
($($view),+),
EntityFilterTuple<
And<(
$(<$filter as EntityFilter>::ArchetypeFilter),+
)>,
And<(
$(<$filter as EntityFilter>::ChunksetFilter),+
)>,
And<(
$(<$filter as EntityFilter>::ChunkFilter),+
)>,
>
>
}
}
macro_rules! query {
(()) => { () };
($($query:ident),+) => {
<tuple!($($query),+)>::query()
}
}
#[rustfmt::skip]
impl_system![(A, AF, a)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b), (C, CF, c)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b), (C, CF, c), (D, DF, d)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b), (C, CF, c), (D, DF, d), (E, EF, e)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b), (C, CF, c), (D, DF, d), (E, EF, e), (F, FF, f)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b), (C, CF, c), (D, DF, d), (E, EF, e), (F, FF, f), (G, GF, g)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b), (C, CF, c), (D, DF, d), (E, EF, e), (F, FF, f), (G, GF, g), (H, HF, h)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b), (C, CF, c), (D, DF, d), (E, EF, e), (F, FF, f), (G, GF, g), (H, HF, h), (I, IF, i)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b), (C, CF, c), (D, DF, d), (E, EF, e), (F, FF, f), (G, GF, g), (H, HF, h), (I, IF, i), (J, JF, j)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b), (C, CF, c), (D, DF, d), (E, EF, e), (F, FF, f), (G, GF, g), (H, HF, h), (I, IF, i), (J, JF, j), (K, KF, k)];
#[rustfmt::skip]
impl_system![(A, AF, a), (B, BF, b), (C, CF, c), (D, DF, d), (E, EF, e), (F, FF, f), (G, GF, g), (H, HF, h), (I, IF, i), (J, JF, j), (K, KF, k), (L, LF, l)];
#[cfg(test)]
mod tests {
use crate::{
into_resource_for_each_system,
resource::{PreparedRead, PreparedWrite, Resources},
IntoSystem,
};
use legion_core::{
borrow::{Ref, RefMut},
@ -147,6 +375,29 @@ mod tests {
#[derive(Debug, Eq, PartialEq)]
struct X(usize);
#[test]
fn test_into_system() {
// fn read_system(a: &A, x: Ref<X>, y: Ref<Y>) {
// println!("{} {} {}", a.0, x.0, y.0);
// }
// fn read_system(x: Ref<X>) {
// println!("{}", x.0);
// }
fn read_system(x: Ref<X>, y: Ref<Y>, mut z: RefMut<A>) {
z.0 += 1;
println!("{} {} {}", x.0, y.0, z.0);
}
let mut world = World::new();
let mut resources = Resources::default();
resources.insert(A(0));
world.insert((), vec![(X(1), Y(1)), (X(2), Y(2))]);
let mut system = read_system.into_system("hi");
system.run(&mut world, &mut resources);
}
#[test]
fn test_system_fn() {
fn read_write_system(_: &mut (), (_x, mut y): (Ref<X>, RefMut<Y>)) { y.0 += 1; }

View File

@ -6,8 +6,9 @@ fn main() {
.add_default_plugins()
.add_event::<MyEvent>()
.add_startup_system(setup)
.add_system_init(system_b)
.add_system(System::for_each("system_a", system_a))
.add_system_init(built_system)
.add_system(simple_system.into_system("simple_system"))
.add_system(closure_system())
.run();
}
@ -26,31 +27,40 @@ fn setup(world: &mut World, resources: &mut Resources) {
world.insert((), vec![(X(0), Y(1)), (X(2), Y(3))]);
}
fn system_a((x, y): (Ref<X>, RefMut<Y>)) {
// runs once for each entity with the X and Y component
fn simple_system(x: Ref<X>, mut y: RefMut<Y>) {
y.0 += 1;
println!("processed entity: {} {}", x.0, y.0);
}
// fn system_a(x: Ref<X>, mut y: RefMut<Y>) {
// does the same thing as the first system, but also captures the "counter" variable and uses it as internal state
fn closure_system() -> Box<dyn Schedulable> {
let mut counter = 0;
(move |x: Ref<X>, mut y: RefMut<Y>| {
y.0 += 1;
println!("processed entity: {} {}", x.0, y.0);
println!("ran {} times", counter);
counter += 1;
}).into_system("closure_system")
}
// }
// fn system_a((my_events, a): &mut (Resource<Events<MyEvent>>, Resource<A>), (x, mut y): (Ref<X>, RefMut<Y>)) {
// }
fn system_b(resources: &mut Resources) -> Box<dyn Schedulable> {
// if you need more flexibility, you can define complex systems using the system builder
fn built_system(resources: &mut Resources) -> Box<dyn Schedulable> {
let mut my_event_reader = resources.get_event_reader::<MyEvent>();
SystemBuilder::new("example")
.read_resource::<Events<MyEvent>>()
.write_resource::<A>()
.with_query(<(Read<X>, Write<Y>)>::query())
.build(move |_command_buffer, world, (my_events, ref mut a), query| {
for event in my_event_reader.iter(&my_events) {
a.0 += event.0;
println!("modified resource A with event: {}", event.0);
}
for (x, mut y) in query.iter_mut(world) {
y.0 += 1;
println!("processed entity: {} {}", x.0, y.0);
}
})
.build(
move |_command_buffer, world, (my_events, ref mut a), query| {
for event in my_event_reader.iter(&my_events) {
a.0 += event.0;
println!("modified resource A with event: {}", event.0);
}
for (x, mut y) in query.iter_mut(world) {
y.0 += 1;
println!("processed entity: {} {}", x.0, y.0);
}
},
)
}

View File

@ -55,6 +55,7 @@ pub use legion::{
resource::{ResourceSet, Resources, PreparedRead as Resource, PreparedWrite as ResourceMut},
schedule::{Executor, Runnable, Schedulable, Schedule},
SubWorld, SystemBuilder,
IntoSystem
},
world::{Universe, World},
};