diff --git a/crates/bevy_legion/legion_fn_system_macro/src/lib.rs b/crates/bevy_legion/legion_fn_system_macro/src/lib.rs index 2de18145cc..8878c43a6b 100644 --- a/crates/bevy_legion/legion_fn_system_macro/src/lib.rs +++ b/crates/bevy_legion/legion_fn_system_macro/src/lib.rs @@ -130,7 +130,7 @@ pub fn impl_fn_systems(_input: TokenStream) -> TokenStream { #(#resource: ResourceSet + 'static + Clone,)* #(#view: for<'b> View<'b> + DefaultFilter + 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),* @@ -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 = 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 = 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 + 'static + Clone,)* + #(#view: for<'b> View<'b> + DefaultFilter + 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 { + let resource_access: Access = #resource_access; + let component_access: Access = #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 { + self.system_id(name.into()) + } + + fn system(self) -> Box { + self.system_id(std::any::type_name::().to_string().into()) + } + } + })); + } + } + } + tokens +} diff --git a/crates/bevy_legion/legion_systems/src/system_fn.rs b/crates/bevy_legion/legion_systems/src/system_fn.rs index 35e2e8a8e9..60cf1de03c 100644 --- a/crates/bevy_legion/legion_systems/src/system_fn.rs +++ b/crates/bevy_legion/legion_systems/src/system_fn.rs @@ -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 { +pub trait IntoSystem { fn system_id(self, id: SystemId) -> Box; fn system_named(self, name: &'static str) -> Box; fn system(self) -> Box; } impl_fn_systems!(); +impl_fn_query_systems!(); + +#[allow(type_alias_bounds)] +pub type Query +where + V: for<'a> View<'a> + DefaultFilter, += SystemQuery::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, Write)>) { + for (x, mut y) in query.iter_mut(world) { + y.0 = 2; + println!("{:?}", x); + } + } + + fn query_system2(world: &mut SubWorld, a: Res, query: &mut Query<(Read, Write)>, query2: &mut Query>) { + 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(); diff --git a/examples/ecs/ecs_guide.rs b/examples/ecs/ecs_guide.rs index a5bbf59910..05b943b2af 100644 --- a/examples/ecs/ecs_guide.rs +++ b/examples/ecs/ecs_guide.rs @@ -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, + mut game_state: ResMut, + query: &mut Query<(Read, Read)>, +) { + 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, player: Com, 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 { @@ -267,6 +285,7 @@ fn complex_system(resources: &mut Resources) -> Box { SystemBuilder::new("complex_system") .read_resource::() .write_resource::() + .read_component::() // this query is equivalent to the system we saw above: system(player: Com, mut score: ComMut) .with_query(<(Read, Write)>::query()) // this query only returns entities with a Player component that has changed since the last update diff --git a/src/prelude.rs b/src/prelude.rs index bb0ac0b517..c500a3f286 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -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}, };