add "query system functions"

This commit is contained in:
Carter Anderson 2020-05-28 13:36:48 -07:00
parent 1d4a574b52
commit 83d5275e10
4 changed files with 200 additions and 11 deletions

View file

@ -130,7 +130,7 @@ pub fn impl_fn_systems(_input: TokenStream) -> TokenStream {
#(#resource: ResourceSet<PreparedResources = #resource> + 'static + Clone,)*
#(#view: for<'b> View<'b> + DefaultFilter<Filter = #filter> + ViewElement,
#filter: EntityFilter + Sync + 'static),*
> IntoSystem<(#(#command_buffer)*), (#(#resource,)*), (#(#view,)*)> for Func
> IntoSystem<(#(#command_buffer)*), (#(#resource,)*), (#(#view,)*), (), ()> for Func
where
Func: FnMut(#(&mut #command_buffer,)* #(#resource,)* #(#view),*) + Send + Sync + 'static,
#(<#view as View<'a>>::Iter: Iterator<Item = #view>),*
@ -181,3 +181,135 @@ pub fn impl_fn_systems(_input: TokenStream) -> TokenStream {
tokens
}
#[proc_macro]
pub fn impl_fn_query_systems(_input: TokenStream) -> TokenStream {
let max_resources = 8;
let max_queries = 4;
let resources = get_idents(|i| format!("R{}", i), max_resources);
let resource_vars = get_idents(|i| format!("r{}", i), max_resources);
let views = get_idents(|i| format!("V{}", i), max_queries);
let filters = get_idents(|i| format!("VF{}", i), max_queries);
let query_vars = get_idents(|i| format!("q{}", i), max_queries);
let mut tokens = TokenStream::new();
let command_buffer = vec![Ident::new("CommandBuffer", Span::call_site())];
let command_buffer_var = vec![Ident::new("_command_buffer", Span::call_site())];
for resource_count in 0..=max_resources {
let resource = &resources[0..resource_count];
let resource_var = &resource_vars[0..resource_count];
let resource_tuple = tuple(resource);
let resource_var_tuple = tuple(resource_var);
let resource_access = if resource_count == 0 {
quote! { Access::default() }
}else {
quote! {{
let mut resource_access: Access<ResourceTypeId> = Access::default();
resource_access
.reads
.extend(<#resource_tuple as ResourceSet>::read_types().iter());
resource_access
.writes
.extend(<#resource_tuple as ResourceSet>::write_types().iter());
resource_access
}}
};
for query_count in 1..=max_queries {
let view = &views[0..query_count];
let filter = &filters[0..query_count];
let query_var = &query_vars[0..query_count];
let view_tuple = tuple(view);
let query_var_tuple = tuple(query_var);
let component_access = if query_count == 0 {
quote! { Access::default() }
}else {
quote! {{
let mut component_access: Access<ComponentTypeId> = Access::default();
component_access
.reads
.extend(<#view_tuple as View>::read_types().iter());
component_access
.writes
.extend(<#view_tuple as View>::write_types().iter());
component_access
}}
};
for command_buffer_index in 0..2 {
let command_buffer = &command_buffer[0..command_buffer_index];
let command_buffer_var = &command_buffer_var[0..command_buffer_index];
let view_tuple_avoid_type_collision = if query_count == 1 {
quote!{(#(#view)*,)}
} else {
quote!{(#(#view,)*)}
};
let filter_tuple_avoid_type_collision = if query_count == 1 {
quote!{(#(#filter)*,)}
} else {
quote!{(#(#filter,)*)}
};
tokens.extend(TokenStream::from(quote! {
impl<Func,
#(#resource: ResourceSet<PreparedResources = #resource> + 'static + Clone,)*
#(#view: for<'b> View<'b> + DefaultFilter<Filter = #filter> + ViewElement,
#filter: EntityFilter + Sync + 'static),*
> IntoSystem<(#(#command_buffer)*), (#(#resource,)*), (), #view_tuple_avoid_type_collision, #filter_tuple_avoid_type_collision> for Func
where
Func: FnMut(#(&mut #command_buffer,)* &mut SubWorld, #(#resource,)* #(&mut SystemQuery<#view, #filter>),*) + Send + Sync + 'static,
{
fn system_id(mut self, id: SystemId) -> Box<dyn Schedulable> {
let resource_access: Access<ResourceTypeId> = #resource_access;
let component_access: Access<ComponentTypeId> = #component_access;
let run_fn = FuncSystemFnWrapper(
move |_command_buffer,
_world,
_resources: #resource_tuple,
_queries: &mut (#(SystemQuery<#view, #filter>),*)
| {
let #resource_var_tuple = _resources;
let #query_var_tuple = _queries;
self(#(#command_buffer_var,)*_world,#(#resource_var,)* #(#query_var),*)
},
PhantomData,
);
Box::new(FuncSystem {
name: id,
queries: AtomicRefCell::new((#(<#view>::query()),*)),
access: SystemAccess {
resources: resource_access,
components: component_access,
tags: Access::default(),
},
archetypes: ArchetypeAccess::Some(BitSet::default()),
_resources: PhantomData::<#resource_tuple>,
command_buffer: FxHashMap::default(),
run_fn: AtomicRefCell::new(run_fn),
})
}
fn system_named(self, name: &'static str) -> Box<dyn Schedulable> {
self.system_id(name.into())
}
fn system(self) -> Box<dyn Schedulable> {
self.system_id(std::any::type_name::<Self>().to_string().into())
}
}
}));
}
}
}
tokens
}

View file

@ -2,7 +2,7 @@ use crate::{
resource::{ResourceSet, ResourceTypeId},
schedule::{ArchetypeAccess, Schedulable},
system_fn_types::{FuncSystem, FuncSystemFnWrapper},
Access, SystemAccess, SystemId, SystemQuery,
Access, SubWorld, SystemAccess, SystemId, SystemQuery,
};
use bit_set::BitSet;
use fxhash::FxHashMap;
@ -13,32 +13,38 @@ use legion_core::{
query::{DefaultFilter, IntoQuery, View, ViewElement},
storage::ComponentTypeId,
};
use legion_fn_system_macro::impl_fn_systems;
use legion_fn_system_macro::{impl_fn_query_systems, impl_fn_systems};
use std::marker::PhantomData;
// TODO: add params for component access
// TODO: add subworld to function parameters
// TODO: somehow support filters
pub trait IntoSystem<CommandBuffer, Resources, Components> {
pub trait IntoSystem<CommandBuffer, Resources, Views, Queries, Filters> {
fn system_id(self, id: SystemId) -> Box<dyn Schedulable>;
fn system_named(self, name: &'static str) -> Box<dyn Schedulable>;
fn system(self) -> Box<dyn Schedulable>;
}
impl_fn_systems!();
impl_fn_query_systems!();
#[allow(type_alias_bounds)]
pub type Query<V>
where
V: for<'a> View<'a> + DefaultFilter,
= SystemQuery<V, <V as DefaultFilter>::Filter>;
#[cfg(test)]
mod tests {
use crate::{
resource::Resources,
system_fn_types::{Res, ResMut},
IntoSystem,
IntoSystem, Query, SubWorld,
};
use legion_core::{
borrow::{Ref, RefMut},
command::CommandBuffer,
query::{Read, Write},
world::World,
};
use std::fmt::Debug;
#[derive(Debug, Eq, PartialEq)]
struct A(usize);
@ -49,6 +55,38 @@ mod tests {
#[derive(Debug, Eq, PartialEq)]
struct X(usize);
#[test]
fn test_query_system() {
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))]);
fn query_system(world: &mut SubWorld, query: &mut Query<(Read<X>, Write<Y>)>) {
for (x, mut y) in query.iter_mut(world) {
y.0 = 2;
println!("{:?}", x);
}
}
fn query_system2(world: &mut SubWorld, a: Res<A>, query: &mut Query<(Read<X>, Write<Y>)>, query2: &mut Query<Read<X>>) {
println!("{:?}", *a);
for (x, mut y) in query.iter_mut(world) {
y.0 = 2;
println!("{:?}", x);
}
for x in query2.iter(world) {
println!("{:?}", x);
}
}
let mut system = query_system.system();
let mut system2 = query_system2.system();
system.run(&mut world, &mut resources);
system2.run(&mut world, &mut resources);
}
#[test]
fn test_into_system() {
let mut world = World::new();

View file

@ -114,6 +114,22 @@ fn score_check_system(
}
}
// If you need more control over iteration or direct access to SubWorld, you can also use "query systems"
// This is how you would represent the system above with a "query system"
#[allow(dead_code)]
fn query_score_check_system(
world: &mut SubWorld,
game_rules: Res<GameRules>,
mut game_state: ResMut<GameState>,
query: &mut Query<(Read<Player>, Read<Score>)>,
) {
for (player, score) in query.iter(world) {
if score.value == game_rules.winning_score {
game_state.winning_player = Some(player.name.clone());
}
}
}
// This system ends the game if we meet the right conditions. This fires an AppExit event, which tells our
// App to quit. Check out the "event.rs" example if you want to learn more about using events.
fn game_over_system(
@ -257,7 +273,9 @@ fn stateful_system(mut state: ComMut<State>, player: Com<Player>, score: ComMut<
}
// If you need more flexibility, you can define complex systems using "system builders".
// SystemBuilder enables scenarios like "multiple queries" and "query filters"
// The main features SystemBuilder currently provides over "function style systems" are:
// * "query filters": filter components in your queries based on some criteria (ex: changed components)
// * "additional components": Enables access to a component in your SubWorld, even if it isn't in your queries,
// NOTE: this doesn't do anything relevant to our game, it is just here for illustrative purposes
#[allow(dead_code)]
fn complex_system(resources: &mut Resources) -> Box<dyn Schedulable> {
@ -267,6 +285,7 @@ fn complex_system(resources: &mut Resources) -> Box<dyn Schedulable> {
SystemBuilder::new("complex_system")
.read_resource::<GameState>()
.write_resource::<GameRules>()
.read_component::<Renderable>()
// this query is equivalent to the system we saw above: system(player: Com<Player>, mut score: ComMut<Score>)
.with_query(<(Read<Player>, Write<Score>)>::query())
// this query only returns entities with a Player component that has changed since the last update

View file

@ -60,12 +60,12 @@ pub use legion::{
entity::Entity,
event::Event as LegionEvent,
filter::filter_fns::*,
query::{IntoQuery, Query, Read, Tagged, TryRead, TryWrite, Write},
query::{IntoQuery, Read, Tagged, TryRead, TryWrite, Write},
systems::{
bit_set::BitSet,
resource::{ResourceSet, Resources},
schedule::{Executor, Runnable, Schedulable, Schedule},
IntoSystem, Res, ResMut, SubWorld, SystemBuilder,
IntoSystem, Res, ResMut, SubWorld, SystemBuilder, Query
},
world::{Universe, World},
};