diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 9c53a1c9ab95f5..0e73ef4c28cf50 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -12,9 +12,7 @@ pub use world::*; pub mod prelude { pub use crate::{ resource::{ChangedRes, FromResources, Local, OrRes, Res, ResMut, Resource, Resources}, - system::{ - Commands, IntoForEachSystem, IntoSystem, IntoThreadLocalSystem, Query, System, - }, + system::{Commands, IntoSystem, IntoThreadLocalSystem, Query, System}, world::WorldBuilderSource, Added, Bundle, Changed, Component, Entity, Mut, Mutated, Or, QuerySet, Ref, RefMut, With, Without, World, diff --git a/crates/bevy_ecs/src/system/into_system.rs b/crates/bevy_ecs/src/system/into_system.rs index 65fdfa1e0d3a1b..a16c90a665196a 100644 --- a/crates/bevy_ecs/src/system/into_system.rs +++ b/crates/bevy_ecs/src/system/into_system.rs @@ -1,484 +1,494 @@ -pub use super::Query; use crate::{ - resource::{FetchResource, ResourceQuery, Resources, UnsafeClone}, - system::{Commands, System, SystemId, ThreadLocalExecution}, - QueryAccess, TypeAccess, + Commands, FetchResource, Query, QuerySet, QueryTuple, ResourceFetchSelf, ResourceQuery, + Resources, System, SystemId, ThreadLocalExecution, }; -use bevy_hecs::{ArchetypeComponent, Fetch, Query as HecsQuery, World}; +use bevy_hecs::{ArchetypeComponent, Fetch, Query as HecsQuery, QueryAccess, TypeAccess, World}; use std::{any::TypeId, borrow::Cow}; -#[derive(Debug)] -pub(crate) struct SystemFn +pub struct SystemState { + id: SystemId, + name: Cow<'static, str>, + is_initialized: bool, + archetype_component_access: TypeAccess, + resource_access: TypeAccess, + query_archetype_component_accesses: Vec>, + query_accesses: Vec>, + query_type_names: Vec<&'static str>, + commands: Commands, + current_query_index: usize, +} + +impl SystemState { + pub fn reset_indices(&mut self) { + self.current_query_index = 0; + } + + pub fn update(&mut self, world: &World) { + self.archetype_component_access.clear(); + let mut conflict_index = None; + let mut conflict_name = None; + for (i, (query_accesses, component_access)) in self + .query_accesses + .iter() + .zip(self.query_archetype_component_accesses.iter_mut()) + .enumerate() + { + component_access.clear(); + for query_access in query_accesses.iter() { + query_access.get_world_archetype_access(world, Some(component_access)); + } + if !component_access.is_compatible(&self.archetype_component_access) { + conflict_index = Some(i); + conflict_name = component_access + .get_conflict(&self.archetype_component_access) + .and_then(|archetype_component| { + query_accesses + .iter() + .filter_map(|query_access| { + query_access.get_type_name(archetype_component.component) + }) + .next() + }); + break; + } + self.archetype_component_access.union(component_access); + } + if let Some(conflict_index) = conflict_index { + let mut conflicts_with_index = None; + for prior_index in 0..conflict_index { + if !self.query_archetype_component_accesses[conflict_index] + .is_compatible(&self.query_archetype_component_accesses[prior_index]) + { + conflicts_with_index = Some(prior_index); + } + } + panic!("System {} has conflicting queries. {} conflicts with the component access [{}] in this prior query: {}", + core::any::type_name::(), + self.query_type_names[conflict_index], + conflict_name.unwrap_or("Unknown"), + conflicts_with_index.map(|index| self.query_type_names[index]).unwrap_or("Unknown")); + } + } +} + +pub struct FuncSystem where - F: FnMut(&World, &Resources, &mut State) + Send + Sync, - ThreadLocalF: FnMut(&mut World, &mut Resources, &mut State) + Send + Sync, - Init: FnMut(&mut World, &mut Resources, &mut State) + Send + Sync, - Update: FnMut(&World, &mut TypeAccess, &mut State) + Send + Sync, - State: Send + Sync, + F: FnMut(&mut SystemState, &World, &Resources) + Send + Sync + 'static, + Init: FnMut(&mut SystemState, &World, &mut Resources) + Send + Sync + 'static, + ThreadLocalFunc: FnMut(&mut SystemState, &mut World, &mut Resources) + Send + Sync + 'static, { - pub state: State, - pub func: F, - pub thread_local_func: ThreadLocalF, - pub init_func: Init, - pub thread_local_execution: ThreadLocalExecution, - pub resource_access: TypeAccess, - pub name: Cow<'static, str>, - pub id: SystemId, - pub archetype_component_access: TypeAccess, - pub update_func: Update, + func: F, + thread_local_func: ThreadLocalFunc, + init_func: Init, + state: SystemState, } -impl System for SystemFn +impl System for FuncSystem where - F: FnMut(&World, &Resources, &mut State) + Send + Sync, - ThreadLocalF: FnMut(&mut World, &mut Resources, &mut State) + Send + Sync, - Init: FnMut(&mut World, &mut Resources, &mut State) + Send + Sync, - Update: FnMut(&World, &mut TypeAccess, &mut State) + Send + Sync, - State: Send + Sync, + F: FnMut(&mut SystemState, &World, &Resources) + Send + Sync + 'static, + Init: FnMut(&mut SystemState, &World, &mut Resources) + Send + Sync + 'static, + ThreadLocalFunc: FnMut(&mut SystemState, &mut World, &mut Resources) + Send + Sync + 'static, { - fn name(&self) -> Cow<'static, str> { - self.name.clone() + fn name(&self) -> std::borrow::Cow<'static, str> { + self.state.name.clone() + } + + fn id(&self) -> SystemId { + self.state.id } fn update(&mut self, world: &World) { - (self.update_func)(world, &mut self.archetype_component_access, &mut self.state); + self.state.update(world); } fn archetype_component_access(&self) -> &TypeAccess { - &self.archetype_component_access + &self.state.archetype_component_access } - fn resource_access(&self) -> &TypeAccess { - &self.resource_access + fn resource_access(&self) -> &TypeAccess { + &self.state.resource_access } fn thread_local_execution(&self) -> ThreadLocalExecution { - self.thread_local_execution + ThreadLocalExecution::NextFlush } - #[inline] fn run(&mut self, world: &World, resources: &Resources) { - (self.func)(world, resources, &mut self.state); + (self.func)(&mut self.state, world, resources) } fn run_thread_local(&mut self, world: &mut World, resources: &mut Resources) { - (self.thread_local_func)(world, resources, &mut self.state); + (self.thread_local_func)(&mut self.state, world, resources) } fn initialize(&mut self, world: &mut World, resources: &mut Resources) { - (self.init_func)(world, resources, &mut self.state); + (self.init_func)(&mut self.state, world, resources); + self.state.is_initialized = true; } - fn id(&self) -> SystemId { - self.id + fn is_initialized(&self) -> bool { + self.state.is_initialized } +} - fn is_initialized(&self) -> bool { - // TODO: either make this correct or remove everything in this file :) - false +pub trait SystemParam { + fn init(system_state: &mut SystemState, world: &World, resources: &mut Resources); + fn get_param(system_state: &mut SystemState, world: &World, resources: &Resources) -> Self; +} + +impl<'a, Q: HecsQuery> SystemParam for Query<'a, Q> { + #[inline] + fn get_param(system_state: &mut SystemState, world: &World, _resources: &Resources) -> Self { + let query_index = system_state.current_query_index; + unsafe { + let world: &'a World = std::mem::transmute(world); + let archetype_component_access: &'a TypeAccess = + std::mem::transmute(&system_state.query_archetype_component_accesses[query_index]); + system_state.current_query_index += 1; + Query::new(world, archetype_component_access) + } + } + + fn init(system_state: &mut SystemState, _world: &World, _resources: &mut Resources) { + system_state + .query_archetype_component_accesses + .push(TypeAccess::default()); + system_state + .query_accesses + .push(vec![::access()]); + system_state + .query_type_names + .push(std::any::type_name::()); } } -/// Converts `Self` into a For-Each system -pub trait IntoForEachSystem { - fn system(self) -> Box; +impl SystemParam for QuerySet { + #[inline] + fn get_param(system_state: &mut SystemState, world: &World, _resources: &Resources) -> Self { + let query_index = system_state.current_query_index; + system_state.current_query_index += 1; + unsafe { + QuerySet::new( + world, + &system_state.query_archetype_component_accesses[query_index], + ) + } + } + + fn init(system_state: &mut SystemState, _world: &World, _resources: &mut Resources) { + system_state + .query_archetype_component_accesses + .push(TypeAccess::default()); + system_state.query_accesses.push(T::get_accesses()); + system_state + .query_type_names + .push(std::any::type_name::()); + } } -struct ForEachState { - commands: Commands, - query_access: QueryAccess, +impl SystemParam for R +where + R: ResourceQuery + ResourceFetchSelf, +{ + fn init(system_state: &mut SystemState, _world: &World, resources: &mut Resources) { + R::initialize(resources, Some(system_state.id)); + system_state + .resource_access + .union(&::access()); + } + + #[inline] + fn get_param(system_state: &mut SystemState, _world: &World, resources: &Resources) -> Self { + unsafe { ::get(resources, Some(system_state.id)) } + } } -macro_rules! impl_into_foreach_system { - (($($commands: ident)*), ($($resource: ident),*), ($($component: ident),*)) => { - impl IntoForEachSystem<($($commands,)*), ($($resource,)*), ($($component,)*)> for Func - where - Func: - FnMut($($commands,)* $($resource,)* $($component,)*) + - FnMut( - $($commands,)* - $(<<$resource as ResourceQuery>::Fetch as FetchResource>::Item,)* - $(<<$component as HecsQuery>::Fetch as Fetch>::Item,)*)+ - Send + Sync + 'static, - $($component: HecsQuery,)* - $($resource: ResourceQuery,)* +impl SystemParam for Commands { + fn init(system_state: &mut SystemState, world: &World, _resources: &mut Resources) { + system_state + .commands + .set_entity_reserver(world.get_entity_reserver()) + } + + #[inline] + fn get_param(system_state: &mut SystemState, _world: &World, _resources: &Resources) -> Self { + system_state.commands.clone() + } +} + +pub trait IntoSystem { + fn system(self) -> Box; +} + +macro_rules! impl_into_system { + (($($param: ident),*)) => { + impl IntoSystem<($($param,)*)> for Func + where Func: FnMut($($param),*) + Send + Sync + 'static, { - #[allow(non_snake_case)] #[allow(unused_variables)] - #[allow(unused_unsafe)] fn system(mut self) -> Box { - let id = SystemId::new(); - Box::new(SystemFn { - state: ForEachState { + Box::new(FuncSystem { + state: SystemState { + name: std::any::type_name::().into(), + archetype_component_access: TypeAccess::default(), + resource_access: TypeAccess::default(), + is_initialized: false, + id: SystemId::new(), commands: Commands::default(), - query_access: <($($component,)*) as HecsQuery>::Fetch::access(), + query_archetype_component_accesses: Vec::new(), + query_accesses: Vec::new(), + query_type_names: Vec::new(), + current_query_index: 0, }, - thread_local_execution: ThreadLocalExecution::NextFlush, - name: core::any::type_name::().into(), - id, - func: move |world, resources, state| { - { - let state_commands = &state.commands; - if let Some(($($resource,)*)) = resources.query_system::<($($resource,)*)>(id) { - // SAFE: the scheduler has ensured that there is no archetype clashing here - unsafe { - for ($($component,)*) in world.query_unchecked::<($($component,)*)>() { - fn_call!(self, ($($commands, state_commands)*), ($($resource),*), ($($component),*)) - } - } - } - } + func: move |state, world, resources| { + state.reset_indices(); + self($($param::get_param(state, world, resources)),*); }, - thread_local_func: move |world, resources, state| { + thread_local_func: |state, world, resources| { state.commands.apply(world, resources); }, - init_func: move |world, resources, state| { - <($($resource,)*)>::initialize(resources, Some(id)); - state.commands.set_entity_reserver(world.get_entity_reserver()) - }, - resource_access: <<($($resource,)*) as ResourceQuery>::Fetch as FetchResource>::access(), - archetype_component_access: TypeAccess::default(), - update_func: |world, archetype_component_access, state| { - archetype_component_access.clear(); - state.query_access.get_world_archetype_access(world, Some(archetype_component_access)); + init_func: |state, world, resources| { + $($param::init(state, world, resources);)* }, }) } } - }; -} -struct QuerySystemState { - query_accesses: Vec>, - query_type_names: Vec<&'static str>, - archetype_component_accesses: Vec>, - commands: Commands, + }; } -macro_rules! fn_call { - ($self:ident, ($($commands: ident, $commands_var: ident)*), ($($resource: ident),*), ($($a: ident),*), ($($b: ident),*)) => { - unsafe { $self($($commands_var.clone(),)* $($resource.unsafe_clone(),)* $($a,)* $($b,)*) } - }; - ($self:ident, ($($commands: ident, $commands_var: ident)*), ($($resource: ident),*), ($($a: ident),*)) => { - unsafe { $self($($commands_var.clone(),)* $($resource.unsafe_clone(),)* $($a,)*) } +impl_into_system!(()); +impl_into_system!((A)); +impl_into_system!((A, B)); +impl_into_system!((A, B, C)); +impl_into_system!((A, B, C, D)); +impl_into_system!((A, B, C, D, E)); +impl_into_system!((A, B, C, D, E, F)); +impl_into_system!((A, B, C, D, E, F, G)); +impl_into_system!((A, B, C, D, E, F, G, H)); +impl_into_system!((A, B, C, D, E, F, G, H, I)); +impl_into_system!((A, B, C, D, E, F, G, H, I, J)); + +#[cfg(test)] +mod tests { + use super::{IntoSystem, Query}; + use crate::{ + resource::{ResMut, Resources}, + schedule::Schedule, + ChangedRes, QuerySet, }; - ($self:ident, (), ($($resource: ident),*), ($($a: ident),*)) => { - unsafe { $self($($resource.unsafe_clone(),)* $($a,)*) } - }; -} + use bevy_hecs::{Entity, With, World}; + + #[derive(Debug, Eq, PartialEq)] + struct A; + struct B; + struct C; + struct D; + + #[test] + fn query_system_gets() { + fn query_system( + mut ran: ResMut, + entity_query: Query>, + b_query: Query<&B>, + a_c_query: Query<(&A, &C)>, + d_query: Query<&D>, + ) { + let entities = entity_query.iter().collect::>(); + assert!( + b_query.get_component::(entities[0]).is_err(), + "entity 0 should not have B" + ); + assert!( + b_query.get_component::(entities[1]).is_ok(), + "entity 1 should have B" + ); + assert!( + b_query.get_component::(entities[1]).is_err(), + "entity 1 should have A, but b_query shouldn't have access to it" + ); + assert!( + b_query.get_component::(entities[3]).is_err(), + "entity 3 should have D, but it shouldn't be accessible from b_query" + ); + assert!( + b_query.get_component::(entities[2]).is_err(), + "entity 2 has C, but it shouldn't be accessible from b_query" + ); + assert!( + a_c_query.get_component::(entities[2]).is_ok(), + "entity 2 has C, and it should be accessible from a_c_query" + ); + assert!( + a_c_query.get_component::(entities[3]).is_err(), + "entity 3 should have D, but it shouldn't be accessible from b_query" + ); + assert!( + d_query.get_component::(entities[3]).is_ok(), + "entity 3 should have D" + ); + + *ran = true; + } + + let mut world = World::default(); + let mut resources = Resources::default(); + resources.insert(false); + world.spawn((A,)); + world.spawn((A, B)); + world.spawn((A, C)); + world.spawn((A, D)); -macro_rules! impl_into_foreach_systems { - (($($resource: ident,)*), ($($component: ident),*)) => { - #[rustfmt::skip] - impl_into_foreach_system!((), ($($resource),*), ($($component),*)); - #[rustfmt::skip] - impl_into_foreach_system!((Commands), ($($resource),*), ($($component),*)); + let mut schedule = Schedule::default(); + schedule.add_stage("update"); + schedule.add_system_to_stage("update", query_system.system()); + + schedule.run(&mut world, &mut resources); + + assert!(*resources.get::().unwrap(), "system ran"); } -} -macro_rules! impl_into_systems { - ($($resource: ident),*) => { - #[rustfmt::skip] - impl_into_foreach_systems!(($($resource,)*), (A)); - #[rustfmt::skip] - impl_into_foreach_systems!(($($resource,)*), (A,B)); - #[rustfmt::skip] - impl_into_foreach_systems!(($($resource,)*), (A,B,C)); - #[rustfmt::skip] - impl_into_foreach_systems!(($($resource,)*), (A,B,C,D)); - #[rustfmt::skip] - impl_into_foreach_systems!(($($resource,)*), (A,B,C,D,E)); - #[rustfmt::skip] - impl_into_foreach_systems!(($($resource,)*), (A,B,C,D,E,F)); - #[rustfmt::skip] - impl_into_foreach_systems!(($($resource,)*), (A,B,C,D,E,F,G)); - #[rustfmt::skip] - impl_into_foreach_systems!(($($resource,)*), (A,B,C,D,E,F,G,H)); - }; -} + #[test] + fn or_query_set_system() { + // Regression test for issue #762 + use crate::{Added, Changed, Mutated, Or}; + fn query_system( + mut ran: ResMut, + set: QuerySet<( + Query, Changed)>>, + Query, Added)>>, + Query, Mutated)>>, + )>, + ) { + let changed = set.q0().iter().count(); + let added = set.q1().iter().count(); + let mutated = set.q2().iter().count(); + + assert_eq!(changed, 1); + assert_eq!(added, 1); + assert_eq!(mutated, 0); + + *ran = true; + } -#[rustfmt::skip] -impl_into_systems!(); -#[rustfmt::skip] -impl_into_systems!(Ra); -#[rustfmt::skip] -impl_into_systems!(Ra,Rb); -#[rustfmt::skip] -impl_into_systems!(Ra,Rb,Rc); -#[rustfmt::skip] -impl_into_systems!(Ra,Rb,Rc,Rd); -#[rustfmt::skip] -impl_into_systems!(Ra,Rb,Rc,Rd,Re); -#[rustfmt::skip] -impl_into_systems!(Ra,Rb,Rc,Rd,Re,Rf); -#[rustfmt::skip] -impl_into_systems!(Ra,Rb,Rc,Rd,Re,Rf,Rg); -#[rustfmt::skip] -impl_into_systems!(Ra,Rb,Rc,Rd,Re,Rf,Rg,Rh); -#[rustfmt::skip] -impl_into_systems!(Ra,Rb,Rc,Rd,Re,Rf,Rg,Rh,Ri); -#[rustfmt::skip] -impl_into_systems!(Ra,Rb,Rc,Rd,Re,Rf,Rg,Rh,Ri,Rj); - -/// Converts `Self` into a thread local system -pub trait IntoThreadLocalSystem { - fn thread_local_system(self) -> Box; -} + let mut world = World::default(); + let mut resources = Resources::default(); + resources.insert(false); + world.spawn((A, B)); -impl IntoThreadLocalSystem for F -where - F: ThreadLocalSystemFn, -{ - fn thread_local_system(mut self) -> Box { - Box::new(SystemFn { - state: (), - thread_local_func: move |world, resources, _| { - self.run(world, resources); - }, - func: |_, _, _| {}, - init_func: |_, _, _| {}, - update_func: |_, _, _| {}, - thread_local_execution: ThreadLocalExecution::Immediate, - name: core::any::type_name::().into(), - id: SystemId::new(), - resource_access: TypeAccess::default(), - archetype_component_access: TypeAccess::default(), - }) + let mut schedule = Schedule::default(); + schedule.add_stage("update"); + schedule.add_system_to_stage("update", query_system.system()); + + schedule.run(&mut world, &mut resources); + + assert!(*resources.get::().unwrap(), "system ran"); } -} -/// A thread local system function -pub trait ThreadLocalSystemFn: Send + Sync + 'static { - fn run(&mut self, world: &mut World, resource: &mut Resources); -} + #[test] + fn changed_resource_system() { + fn incr_e_on_flip(_run_on_flip: ChangedRes, mut query: Query<&mut i32>) { + for mut i in query.iter_mut() { + *i += 1; + } + } -impl ThreadLocalSystemFn for F -where - F: FnMut(&mut World, &mut Resources) + Send + Sync + 'static, -{ - fn run(&mut self, world: &mut World, resources: &mut Resources) { - self(world, resources); + let mut world = World::default(); + let mut resources = Resources::default(); + resources.insert(false); + let ent = world.spawn((0,)); + + let mut schedule = Schedule::default(); + schedule.add_stage("update"); + schedule.add_system_to_stage("update", incr_e_on_flip.system()); + + schedule.run(&mut world, &mut resources); + assert_eq!(*(world.get::(ent).unwrap()), 1); + + schedule.run(&mut world, &mut resources); + assert_eq!(*(world.get::(ent).unwrap()), 1); + + *resources.get_mut::().unwrap() = true; + schedule.run(&mut world, &mut resources); + assert_eq!(*(world.get::(ent).unwrap()), 2); } -} -// #[cfg(test)] -// mod tests { -// use super::{IntoForEachSystem, IntoSystem, Query}; -// use crate::{ -// resource::{ResMut, Resources}, -// schedule::Schedule, -// ChangedRes, Mut, QuerySet, -// }; -// use bevy_hecs::{Entity, With, World}; - -// #[derive(Debug, Eq, PartialEq)] -// struct A; -// struct B; -// struct C; -// struct D; - -// #[test] -// fn query_system_gets() { -// fn query_system( -// mut ran: ResMut, -// entity_query: Query>, -// b_query: Query<&B>, -// a_c_query: Query<(&A, &C)>, -// d_query: Query<&D>, -// ) { -// let entities = entity_query.iter().collect::>(); -// assert!( -// b_query.get_component::(entities[0]).is_err(), -// "entity 0 should not have B" -// ); -// assert!( -// b_query.get_component::(entities[1]).is_ok(), -// "entity 1 should have B" -// ); -// assert!( -// b_query.get_component::(entities[1]).is_err(), -// "entity 1 should have A, but b_query shouldn't have access to it" -// ); -// assert!( -// b_query.get_component::(entities[3]).is_err(), -// "entity 3 should have D, but it shouldn't be accessible from b_query" -// ); -// assert!( -// b_query.get_component::(entities[2]).is_err(), -// "entity 2 has C, but it shouldn't be accessible from b_query" -// ); -// assert!( -// a_c_query.get_component::(entities[2]).is_ok(), -// "entity 2 has C, and it should be accessible from a_c_query" -// ); -// assert!( -// a_c_query.get_component::(entities[3]).is_err(), -// "entity 3 should have D, but it shouldn't be accessible from b_query" -// ); -// assert!( -// d_query.get_component::(entities[3]).is_ok(), -// "entity 3 should have D" -// ); - -// *ran = true; -// } - -// let mut world = World::default(); -// let mut resources = Resources::default(); -// resources.insert(false); -// world.spawn((A,)); -// world.spawn((A, B)); -// world.spawn((A, C)); -// world.spawn((A, D)); - -// let mut schedule = Schedule::default(); -// schedule.add_stage("update"); -// schedule.add_system_to_stage("update", query_system.system()); - -// schedule.run(&mut world, &mut resources); - -// assert!(*resources.get::().unwrap(), "system ran"); -// } - -// #[test] -// fn or_query_set_system() { -// // Regression test for issue #762 -// use crate::{Added, Changed, Mutated, Or}; -// fn query_system( -// mut ran: ResMut, -// set: QuerySet<( -// Query, Changed)>>, -// Query, Added)>>, -// Query, Mutated)>>, -// )>, -// ) { -// let changed = set.q0().iter().count(); -// let added = set.q1().iter().count(); -// let mutated = set.q2().iter().count(); - -// assert_eq!(changed, 1); -// assert_eq!(added, 1); -// assert_eq!(mutated, 0); - -// *ran = true; -// } - -// let mut world = World::default(); -// let mut resources = Resources::default(); -// resources.insert(false); -// world.spawn((A, B)); - -// let mut schedule = Schedule::default(); -// schedule.add_stage("update"); -// schedule.add_system_to_stage("update", query_system.system()); - -// schedule.run(&mut world, &mut resources); - -// assert!(*resources.get::().unwrap(), "system ran"); -// } - -// #[test] -// fn changed_resource_system() { -// fn incr_e_on_flip(_run_on_flip: ChangedRes, mut i: Mut) { -// *i += 1; -// } - -// let mut world = World::default(); -// let mut resources = Resources::default(); -// resources.insert(false); -// let ent = world.spawn((0,)); - -// let mut schedule = Schedule::default(); -// schedule.add_stage("update"); -// schedule.add_system_to_stage("update", incr_e_on_flip.system()); - -// schedule.run(&mut world, &mut resources); -// assert_eq!(*(world.get::(ent).unwrap()), 1); - -// schedule.run(&mut world, &mut resources); -// assert_eq!(*(world.get::(ent).unwrap()), 1); - -// *resources.get_mut::().unwrap() = true; -// schedule.run(&mut world, &mut resources); -// assert_eq!(*(world.get::(ent).unwrap()), 2); -// } - -// #[test] -// #[should_panic] -// fn conflicting_query_mut_system() { -// fn sys(_q1: Query<&mut A>, _q2: Query<&mut A>) {} - -// let mut world = World::default(); -// let mut resources = Resources::default(); -// world.spawn((A,)); - -// let mut schedule = Schedule::default(); -// schedule.add_stage("update"); -// schedule.add_system_to_stage("update", sys.system()); - -// schedule.run(&mut world, &mut resources); -// } - -// #[test] -// #[should_panic] -// fn conflicting_query_immut_system() { -// fn sys(_q1: Query<&A>, _q2: Query<&mut A>) {} - -// let mut world = World::default(); -// let mut resources = Resources::default(); -// world.spawn((A,)); - -// let mut schedule = Schedule::default(); -// schedule.add_stage("update"); -// schedule.add_system_to_stage("update", sys.system()); - -// schedule.run(&mut world, &mut resources); -// } - -// #[test] -// fn query_set_system() { -// fn sys(_set: QuerySet<(Query<&mut A>, Query<&B>)>) {} - -// let mut world = World::default(); -// let mut resources = Resources::default(); -// world.spawn((A,)); - -// let mut schedule = Schedule::default(); -// schedule.add_stage("update"); -// schedule.add_system_to_stage("update", sys.system()); - -// schedule.run(&mut world, &mut resources); -// } - -// #[test] -// #[should_panic] -// fn conflicting_query_with_query_set_system() { -// fn sys(_query: Query<&mut A>, _set: QuerySet<(Query<&mut A>, Query<&B>)>) {} + #[test] + #[should_panic] + fn conflicting_query_mut_system() { + fn sys(_q1: Query<&mut A>, _q2: Query<&mut A>) {} + + let mut world = World::default(); + let mut resources = Resources::default(); + world.spawn((A,)); + + let mut schedule = Schedule::default(); + schedule.add_stage("update"); + schedule.add_system_to_stage("update", sys.system()); + + schedule.run(&mut world, &mut resources); + } + + #[test] + #[should_panic] + fn conflicting_query_immut_system() { + fn sys(_q1: Query<&A>, _q2: Query<&mut A>) {} + + let mut world = World::default(); + let mut resources = Resources::default(); + world.spawn((A,)); + + let mut schedule = Schedule::default(); + schedule.add_stage("update"); + schedule.add_system_to_stage("update", sys.system()); -// let mut world = World::default(); -// let mut resources = Resources::default(); -// world.spawn((A,)); + schedule.run(&mut world, &mut resources); + } + + #[test] + fn query_set_system() { + fn sys(_set: QuerySet<(Query<&mut A>, Query<&B>)>) {} -// let mut schedule = Schedule::default(); -// schedule.add_stage("update"); -// schedule.add_system_to_stage("update", sys.system()); + let mut world = World::default(); + let mut resources = Resources::default(); + world.spawn((A,)); -// schedule.run(&mut world, &mut resources); -// } + let mut schedule = Schedule::default(); + schedule.add_stage("update"); + schedule.add_system_to_stage("update", sys.system()); + + schedule.run(&mut world, &mut resources); + } -// #[test] -// #[should_panic] -// fn conflicting_query_sets_system() { -// fn sys(_set_1: QuerySet<(Query<&mut A>,)>, _set_2: QuerySet<(Query<&mut A>, Query<&B>)>) {} + #[test] + #[should_panic] + fn conflicting_query_with_query_set_system() { + fn sys(_query: Query<&mut A>, _set: QuerySet<(Query<&mut A>, Query<&B>)>) {} -// let mut world = World::default(); -// let mut resources = Resources::default(); -// world.spawn((A,)); - -// let mut schedule = Schedule::default(); -// schedule.add_stage("update"); -// schedule.add_system_to_stage("update", sys.system()); - -// schedule.run(&mut world, &mut resources); -// } -// } + let mut world = World::default(); + let mut resources = Resources::default(); + world.spawn((A,)); + + let mut schedule = Schedule::default(); + schedule.add_stage("update"); + schedule.add_system_to_stage("update", sys.system()); + + schedule.run(&mut world, &mut resources); + } + + #[test] + #[should_panic] + fn conflicting_query_sets_system() { + fn sys(_set_1: QuerySet<(Query<&mut A>,)>, _set_2: QuerySet<(Query<&mut A>, Query<&B>)>) {} + + let mut world = World::default(); + let mut resources = Resources::default(); + world.spawn((A,)); + + let mut schedule = Schedule::default(); + schedule.add_stage("update"); + schedule.add_system_to_stage("update", sys.system()); + + schedule.run(&mut world, &mut resources); + } +} diff --git a/crates/bevy_ecs/src/system/into_thread_local.rs b/crates/bevy_ecs/src/system/into_thread_local.rs new file mode 100644 index 00000000000000..b83e4df720b9e9 --- /dev/null +++ b/crates/bevy_ecs/src/system/into_thread_local.rs @@ -0,0 +1,121 @@ +pub use super::Query; +use crate::{ + resource::Resources, + system::{System, SystemId, ThreadLocalExecution}, + TypeAccess, +}; +use bevy_hecs::{ArchetypeComponent, World}; +use std::{any::TypeId, borrow::Cow}; + +#[derive(Debug)] +pub(crate) struct SystemFn +where + F: FnMut(&World, &Resources, &mut State) + Send + Sync, + ThreadLocalF: FnMut(&mut World, &mut Resources, &mut State) + Send + Sync, + Init: FnMut(&mut World, &mut Resources, &mut State) + Send + Sync, + Update: FnMut(&World, &mut TypeAccess, &mut State) + Send + Sync, + State: Send + Sync, +{ + pub state: State, + pub func: F, + pub thread_local_func: ThreadLocalF, + pub init_func: Init, + pub thread_local_execution: ThreadLocalExecution, + pub resource_access: TypeAccess, + pub name: Cow<'static, str>, + pub id: SystemId, + pub archetype_component_access: TypeAccess, + pub update_func: Update, +} + +impl System for SystemFn +where + F: FnMut(&World, &Resources, &mut State) + Send + Sync, + ThreadLocalF: FnMut(&mut World, &mut Resources, &mut State) + Send + Sync, + Init: FnMut(&mut World, &mut Resources, &mut State) + Send + Sync, + Update: FnMut(&World, &mut TypeAccess, &mut State) + Send + Sync, + State: Send + Sync, +{ + fn name(&self) -> Cow<'static, str> { + self.name.clone() + } + + fn update(&mut self, world: &World) { + (self.update_func)(world, &mut self.archetype_component_access, &mut self.state); + } + + fn archetype_component_access(&self) -> &TypeAccess { + &self.archetype_component_access + } + + fn resource_access(&self) -> &TypeAccess { + &self.resource_access + } + + fn thread_local_execution(&self) -> ThreadLocalExecution { + self.thread_local_execution + } + + #[inline] + fn run(&mut self, world: &World, resources: &Resources) { + (self.func)(world, resources, &mut self.state); + } + + fn run_thread_local(&mut self, world: &mut World, resources: &mut Resources) { + (self.thread_local_func)(world, resources, &mut self.state); + } + + fn initialize(&mut self, world: &mut World, resources: &mut Resources) { + (self.init_func)(world, resources, &mut self.state); + } + + fn id(&self) -> SystemId { + self.id + } + + fn is_initialized(&self) -> bool { + // TODO: either make this correct or remove everything in this file :) + false + } +} + +/// Converts `Self` into a thread local system +pub trait IntoThreadLocalSystem { + fn thread_local_system(self) -> Box; +} + +impl IntoThreadLocalSystem for F +where + F: ThreadLocalSystemFn, +{ + fn thread_local_system(mut self) -> Box { + Box::new(SystemFn { + state: (), + thread_local_func: move |world, resources, _| { + self.run(world, resources); + }, + func: |_, _, _| {}, + init_func: |_, _, _| {}, + update_func: |_, _, _| {}, + thread_local_execution: ThreadLocalExecution::Immediate, + name: core::any::type_name::().into(), + id: SystemId::new(), + resource_access: TypeAccess::default(), + archetype_component_access: TypeAccess::default(), + }) + } +} + +/// A thread local system function +pub trait ThreadLocalSystemFn: Send + Sync + 'static { + fn run(&mut self, world: &mut World, resource: &mut Resources); +} + +impl ThreadLocalSystemFn for F +where + F: FnMut(&mut World, &mut Resources) + Send + Sync + 'static, +{ + fn run(&mut self, world: &mut World, resources: &mut Resources) { + self(world, resources); + } +} \ No newline at end of file diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index ef5e9f20436fd2..975580b390bb67 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1,5 +1,6 @@ mod commands; mod into_system; +mod into_thread_local; #[cfg(feature = "profiler")] mod profiler; mod query; @@ -8,6 +9,7 @@ mod system; pub use commands::*; pub use into_system::*; +pub use into_thread_local::*; #[cfg(feature = "profiler")] pub use profiler::*; pub use query::*; diff --git a/crates/bevy_ecs/src/system/query/mod.rs b/crates/bevy_ecs/src/system/query/mod.rs index 1e2d56867ce21e..f7494ad3d8e643 100644 --- a/crates/bevy_ecs/src/system/query/mod.rs +++ b/crates/bevy_ecs/src/system/query/mod.rs @@ -1,8 +1,6 @@ mod query_set; -mod param; pub use query_set::*; -pub use param::*; use bevy_hecs::{ ArchetypeComponent, Batch, BatchedIter, Component, ComponentError, Entity, Fetch, Mut, diff --git a/crates/bevy_ecs/src/system/query/param.rs b/crates/bevy_ecs/src/system/query/param.rs deleted file mode 100644 index 992228fc2a816e..00000000000000 --- a/crates/bevy_ecs/src/system/query/param.rs +++ /dev/null @@ -1,354 +0,0 @@ -use crate::{ - Commands, FetchResource, Query, QuerySet, QueryTuple, ResourceFetchSelf, ResourceQuery, - Resources, System, SystemId, ThreadLocalExecution, -}; -use bevy_hecs::{ArchetypeComponent, Fetch, Query as HecsQuery, QueryAccess, TypeAccess, World}; -use std::{any::TypeId, borrow::Cow}; - -pub struct SystemState { - id: SystemId, - name: Cow<'static, str>, - is_initialized: bool, - archetype_component_access: TypeAccess, - resource_access: TypeAccess, - query_archetype_component_accesses: Vec>, - query_accesses: Vec>, - query_type_names: Vec<&'static str>, - commands: Commands, - current_query_index: usize, -} - -impl SystemState { - pub fn reset_indices(&mut self) { - self.current_query_index = 0; - } - - pub fn update(&mut self, world: &World) { - self.archetype_component_access.clear(); - let mut conflict_index = None; - let mut conflict_name = None; - for (i, (query_accesses, component_access)) in self - .query_accesses - .iter() - .zip(self.query_archetype_component_accesses.iter_mut()) - .enumerate() - { - component_access.clear(); - for query_access in query_accesses.iter() { - query_access.get_world_archetype_access(world, Some(component_access)); - } - if !component_access.is_compatible(&self.archetype_component_access) { - conflict_index = Some(i); - conflict_name = component_access - .get_conflict(&self.archetype_component_access) - .and_then(|archetype_component| { - query_accesses - .iter() - .filter_map(|query_access| { - query_access.get_type_name(archetype_component.component) - }) - .next() - }); - break; - } - self.archetype_component_access.union(component_access); - } - if let Some(conflict_index) = conflict_index { - let mut conflicts_with_index = None; - for prior_index in 0..conflict_index { - if !self.query_archetype_component_accesses[conflict_index] - .is_compatible(&self.query_archetype_component_accesses[prior_index]) - { - conflicts_with_index = Some(prior_index); - } - } - panic!("System {} has conflicting queries. {} conflicts with the component access [{}] in this prior query: {}", - core::any::type_name::(), - self.query_type_names[conflict_index], - conflict_name.unwrap_or("Unknown"), - conflicts_with_index.map(|index| self.query_type_names[index]).unwrap_or("Unknown")); - } - } -} - -pub struct FuncSystem -where - F: FnMut(&mut SystemState, &World, &Resources) + Send + Sync + 'static, - Init: FnMut(&mut SystemState, &World, &mut Resources) + Send + Sync + 'static, - ThreadLocalFunc: FnMut(&mut SystemState, &mut World, &mut Resources) + Send + Sync + 'static, -{ - func: F, - thread_local_func: ThreadLocalFunc, - init_func: Init, - state: SystemState, -} - -impl System for FuncSystem -where - F: FnMut(&mut SystemState, &World, &Resources) + Send + Sync + 'static, - Init: FnMut(&mut SystemState, &World, &mut Resources) + Send + Sync + 'static, - ThreadLocalFunc: FnMut(&mut SystemState, &mut World, &mut Resources) + Send + Sync + 'static, -{ - fn name(&self) -> std::borrow::Cow<'static, str> { - self.state.name.clone() - } - - fn id(&self) -> SystemId { - self.state.id - } - - fn update(&mut self, world: &World) { - self.state.update(world); - } - - fn archetype_component_access(&self) -> &TypeAccess { - &self.state.archetype_component_access - } - - fn resource_access(&self) -> &TypeAccess { - &self.state.resource_access - } - - fn thread_local_execution(&self) -> ThreadLocalExecution { - ThreadLocalExecution::NextFlush - } - - fn run(&mut self, world: &World, resources: &Resources) { - (self.func)(&mut self.state, world, resources) - } - - fn run_thread_local(&mut self, world: &mut World, resources: &mut Resources) { - (self.thread_local_func)(&mut self.state, world, resources) - } - - fn initialize(&mut self, world: &mut World, resources: &mut Resources) { - (self.init_func)(&mut self.state, world, resources); - self.state.is_initialized = true; - } - - fn is_initialized(&self) -> bool { - self.state.is_initialized - } -} - -pub trait SystemParam { - fn init(system_state: &mut SystemState, world: &World, resources: &mut Resources); - fn get_param(system_state: &mut SystemState, world: &World, resources: &Resources) -> Self; -} - -impl<'a, Q: HecsQuery> SystemParam for Query<'a, Q> { - #[inline] - fn get_param(system_state: &mut SystemState, world: &World, _resources: &Resources) -> Self { - let query_index = system_state.current_query_index; - unsafe { - let world: &'a World = std::mem::transmute(world); - let archetype_component_access: &'a TypeAccess = - std::mem::transmute(&system_state.query_archetype_component_accesses[query_index]); - system_state.current_query_index += 1; - Query::new(world, archetype_component_access) - } - } - - fn init(system_state: &mut SystemState, _world: &World, _resources: &mut Resources) { - system_state - .query_archetype_component_accesses - .push(TypeAccess::default()); - system_state - .query_accesses - .push(vec![::access()]); - system_state - .query_type_names - .push(std::any::type_name::()); - } -} - -impl SystemParam for QuerySet { - #[inline] - fn get_param(system_state: &mut SystemState, world: &World, _resources: &Resources) -> Self { - let query_index = system_state.current_query_index; - system_state.current_query_index += 1; - unsafe { - QuerySet::new( - world, - &system_state.query_archetype_component_accesses[query_index], - ) - } - } - - fn init(system_state: &mut SystemState, _world: &World, _resources: &mut Resources) { - system_state - .query_archetype_component_accesses - .push(TypeAccess::default()); - system_state.query_accesses.push(T::get_accesses()); - system_state - .query_type_names - .push(std::any::type_name::()); - } -} - -impl SystemParam for R -where - R: ResourceQuery + ResourceFetchSelf, -{ - fn init(system_state: &mut SystemState, _world: &World, resources: &mut Resources) { - R::initialize(resources, Some(system_state.id)); - system_state - .resource_access - .union(&::access()); - } - - #[inline] - fn get_param(system_state: &mut SystemState, _world: &World, resources: &Resources) -> Self { - unsafe { ::get(resources, Some(system_state.id)) } - } -} - -impl SystemParam for Commands { - fn init(system_state: &mut SystemState, world: &World, _resources: &mut Resources) { - system_state - .commands - .set_entity_reserver(world.get_entity_reserver()) - } - - #[inline] - fn get_param(system_state: &mut SystemState, _world: &World, _resources: &Resources) -> Self { - system_state.commands.clone() - } -} - -pub trait IntoSystem { - fn system(self) -> Box; -} - -macro_rules! impl_into_system { - (($($param: ident),*)) => { - impl IntoSystem<($($param,)*), ()> for Func - where Func: FnMut($($param),*) + Send + Sync + 'static, - { - #[allow(unused_variables)] - fn system(mut self) -> Box { - Box::new(FuncSystem { - state: SystemState { - name: std::any::type_name::().into(), - archetype_component_access: TypeAccess::default(), - resource_access: TypeAccess::default(), - is_initialized: false, - id: SystemId::new(), - commands: Commands::default(), - query_archetype_component_accesses: Vec::new(), - query_accesses: Vec::new(), - query_type_names: Vec::new(), - current_query_index: 0, - }, - func: move |state, world, resources| { - state.reset_indices(); - self($($param::get_param(state, world, resources)),*); - }, - thread_local_func: |state, world, resources| { - state.commands.apply(world, resources); - }, - init_func: |state, world, resources| { - $($param::init(state, world, resources);)* - }, - }) - } - } - - }; -} - -impl_into_system!(()); -impl_into_system!((A)); -impl_into_system!((A, B)); -impl_into_system!((A, B, C)); -impl_into_system!((A, B, C, D)); -impl_into_system!((A, B, C, D, E)); -impl_into_system!((A, B, C, D, E, F)); -impl_into_system!((A, B, C, D, E, F, G)); -impl_into_system!((A, B, C, D, E, F, G, H)); -impl_into_system!((A, B, C, D, E, F, G, H, I)); -impl_into_system!((A, B, C, D, E, F, G, H, I, J)); - -// macro_rules! impl_into_foreach_system { -// (($($param: ident),*), ($($query: ident),*)) => { -// impl IntoSystem<($($param,)*), ($($query,)*)> for Func -// where -// Func: FnMut($($param,)* $($query),*) + Send + Sync + 'static, -// Func: FnMut($($param,)* $(<<$query as HecsQuery>::Fetch as Fetch>::Item),*) + Send + Sync + 'static, -// { -// #[allow(unused_variables)] -// fn system(mut self) -> Box { -// Box::new(FuncSystem { -// state: SystemState { -// name: std::any::type_name::().into(), -// archetype_component_access: TypeAccess::default(), -// resource_access: TypeAccess::default(), -// is_initialized: false, -// id: SystemId::new(), -// commands: Commands::default(), -// query_archetype_component_accesses: Vec::new(), -// query_accesses: Vec::new(), -// query_type_names: Vec::new(), -// current_query_index: 0, -// }, -// func: move |state, world, resources| { -// state.reset_indices(); -// let ($($param),*) = ($($param::get_param(state, world, resources),)*); -// unsafe { -// for ($($query,)*) in world.query_unchecked::<($($query,)*)>() { -// self($($param,)* $($query),*); -// } -// } -// }, -// thread_local_func: |state, world, resources| { -// state.commands.apply(world, resources); -// }, -// init_func: |state, world, resources| { -// $($param::init(state, world, resources);)* -// }, -// }) -// } -// } - -// }; -// } - -// impl_into_foreach_system!((A), (Q1)); - -#[cfg(test)] -mod tests { - use bevy_hecs::World; - - use crate::{Commands, Query, QuerySet, Res, Resources, Schedule}; - - use super::IntoSystem; - struct A; - struct B; - struct X(usize); - #[test] - fn into_system() { - fn system( - a: Query<&A>, - x: Res, - commands: Commands, - s: QuerySet<(Query<&B>, Query<(&A, &B)>)>, - ) { - println!("{}", x.0); - - for a in a.iter() { - println!("a"); - } - } - - let mut world = World::default(); - world.spawn((A,)); - world.spawn((B,)); - let mut resources = Resources::default(); - resources.insert(X(10)); - let mut schedule = Schedule::default(); - schedule.add_stage("update"); - schedule.add_system_to_stage("update", system.system()); - schedule.initialize(&mut world, &mut resources); - schedule.run(&mut world, &mut resources); - } -}