mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 14:08:32 +00:00
add "query system functions"
This commit is contained in:
parent
1d4a574b52
commit
83d5275e10
4 changed files with 200 additions and 11 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue