mod commands; mod exclusive_system; mod function_system; mod query; #[allow(clippy::module_inception)] mod system; mod system_chaining; mod system_param; pub use commands::*; pub use exclusive_system::*; pub use function_system::*; pub use query::*; pub use system::*; pub use system_chaining::*; pub use system_param::*; #[cfg(test)] mod tests { use std::any::TypeId; use crate::{ archetype::Archetypes, bundle::Bundles, component::Components, entity::{Entities, Entity}, query::{Added, Changed, Or, With, Without}, schedule::{Schedule, Stage, SystemStage}, system::{ IntoExclusiveSystem, IntoSystem, Local, Query, QuerySet, RemovedComponents, Res, ResMut, System, SystemState, }, world::{FromWorld, World}, }; #[derive(Debug, Eq, PartialEq, Default)] struct A; struct B; struct C; struct D; struct E; struct F; #[test] fn simple_system() { fn sys(query: Query<&A>) { for a in query.iter() { println!("{:?}", a); } } let mut system = sys.system(); let mut world = World::new(); world.spawn().insert(A); system.initialize(&mut world); for archetype in world.archetypes.iter() { system.new_archetype(archetype); } system.run((), &mut world); } fn run_system>(world: &mut World, system: S) { let mut schedule = Schedule::default(); let mut update = SystemStage::parallel(); update.add_system(system); schedule.add_stage("update", update); schedule.run(world); } #[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(); world.insert_resource(false); world.spawn().insert_bundle((A,)); world.spawn().insert_bundle((A, B)); world.spawn().insert_bundle((A, C)); world.spawn().insert_bundle((A, D)); run_system(&mut world, query_system.system()); assert!(*world.get_resource::().unwrap(), "system ran"); } #[test] fn or_query_set_system() { // Regression test for issue #762 fn query_system( mut ran: ResMut, set: QuerySet<( Query<(), Or<(Changed, Changed)>>, Query<(), Or<(Added, Added)>>, )>, ) { let changed = set.q0().iter().count(); let added = set.q1().iter().count(); assert_eq!(changed, 1); assert_eq!(added, 1); *ran = true; } let mut world = World::default(); world.insert_resource(false); world.spawn().insert_bundle((A, B)); run_system(&mut world, query_system.system()); assert!(*world.get_resource::().unwrap(), "system ran"); } #[test] fn changed_resource_system() { struct Added(usize); struct Changed(usize); fn incr_e_on_flip( value: Res, mut changed: ResMut, mut added: ResMut, ) { if value.is_added() { added.0 += 1; } if value.is_changed() { changed.0 += 1; } } let mut world = World::default(); world.insert_resource(false); world.insert_resource(Added(0)); world.insert_resource(Changed(0)); let mut schedule = Schedule::default(); let mut update = SystemStage::parallel(); update.add_system(incr_e_on_flip.system()); schedule.add_stage("update", update); schedule.add_stage( "clear_trackers", SystemStage::single(World::clear_trackers.exclusive_system()), ); schedule.run(&mut world); assert_eq!(world.get_resource::().unwrap().0, 1); assert_eq!(world.get_resource::().unwrap().0, 1); schedule.run(&mut world); assert_eq!(world.get_resource::().unwrap().0, 1); assert_eq!(world.get_resource::().unwrap().0, 1); *world.get_resource_mut::().unwrap() = true; schedule.run(&mut world); assert_eq!(world.get_resource::().unwrap().0, 1); assert_eq!(world.get_resource::().unwrap().0, 2); } #[test] #[should_panic] fn conflicting_query_mut_system() { fn sys(_q1: Query<&mut A>, _q2: Query<&mut A>) {} let mut world = World::default(); run_system(&mut world, sys.system()); } #[test] fn disjoint_query_mut_system() { fn sys(_q1: Query<&mut A, With>, _q2: Query<&mut A, Without>) {} let mut world = World::default(); run_system(&mut world, sys.system()); } #[test] fn disjoint_query_mut_read_component_system() { fn sys(_q1: Query<(&mut A, &B)>, _q2: Query<&mut A, Without>) {} let mut world = World::default(); run_system(&mut world, sys.system()); } #[test] #[should_panic] fn conflicting_query_immut_system() { fn sys(_q1: Query<&A>, _q2: Query<&mut A>) {} let mut world = World::default(); run_system(&mut world, sys.system()); } #[test] fn query_set_system() { fn sys(mut _set: QuerySet<(Query<&mut A>, Query<&A>)>) {} let mut world = World::default(); run_system(&mut world, sys.system()); } #[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(); run_system(&mut world, sys.system()); } #[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(); run_system(&mut world, sys.system()); } #[derive(Default)] struct BufferRes { _buffer: Vec, } fn test_for_conflicting_resources>(sys: S) { let mut world = World::default(); world.insert_resource(BufferRes::default()); world.insert_resource(A); world.insert_resource(B); run_system(&mut world, sys.system()); } #[test] #[should_panic] fn conflicting_system_resources() { fn sys(_: ResMut, _: Res) {} test_for_conflicting_resources(sys.system()) } #[test] #[should_panic] fn conflicting_system_resources_reverse_order() { fn sys(_: Res, _: ResMut) {} test_for_conflicting_resources(sys.system()) } #[test] #[should_panic] fn conflicting_system_resources_multiple_mutable() { fn sys(_: ResMut, _: ResMut) {} test_for_conflicting_resources(sys.system()) } #[test] fn nonconflicting_system_resources() { fn sys(_: Local, _: ResMut, _: Local, _: ResMut) {} test_for_conflicting_resources(sys.system()) } #[test] fn local_system() { let mut world = World::default(); world.insert_resource(1u32); world.insert_resource(false); struct Foo { value: u32, } impl FromWorld for Foo { fn from_world(world: &mut World) -> Self { Foo { value: *world.get_resource::().unwrap() + 1, } } } fn sys(local: Local, mut modified: ResMut) { assert_eq!(local.value, 2); *modified = true; } run_system(&mut world, sys.system()); // ensure the system actually ran assert!(*world.get_resource::().unwrap()); } #[test] fn remove_tracking() { let mut world = World::new(); struct Despawned(Entity); let a = world.spawn().insert_bundle(("abc", 123)).id(); world.spawn().insert_bundle(("abc", 123)); world.insert_resource(false); world.insert_resource(Despawned(a)); world.entity_mut(a).despawn(); fn validate_removed( removed_i32: RemovedComponents, despawned: Res, mut ran: ResMut, ) { assert_eq!( removed_i32.iter().collect::>(), &[despawned.0], "despawning results in 'removed component' state" ); *ran = true; } run_system(&mut world, validate_removed.system()); assert!(*world.get_resource::().unwrap(), "system ran"); } #[test] fn configure_system_local() { let mut world = World::default(); world.insert_resource(false); fn sys(local: Local, mut modified: ResMut) { assert_eq!(*local, 42); *modified = true; } run_system( &mut world, sys.system().config(|config| config.0 = Some(42)), ); // ensure the system actually ran assert!(*world.get_resource::().unwrap()); } #[test] fn world_collections_system() { let mut world = World::default(); world.insert_resource(false); world.spawn().insert_bundle((42, true)); fn sys( archetypes: &Archetypes, components: &Components, entities: &Entities, bundles: &Bundles, query: Query>, mut modified: ResMut, ) { assert_eq!(query.iter().count(), 1, "entity exists"); for entity in query.iter() { let location = entities.get(entity).unwrap(); let archetype = archetypes.get(location.archetype_id).unwrap(); let archetype_components = archetype.components().collect::>(); let bundle_id = bundles .get_id(std::any::TypeId::of::<(i32, bool)>()) .expect("Bundle used to spawn entity should exist"); let bundle_info = bundles.get(bundle_id).unwrap(); let mut bundle_components = bundle_info.components().to_vec(); bundle_components.sort(); for component_id in bundle_components.iter() { assert!( components.get_info(*component_id).is_some(), "every bundle component exists in Components" ); } assert_eq!( bundle_components, archetype_components, "entity's bundle components exactly match entity's archetype components" ); } *modified = true; } run_system(&mut world, sys.system()); // ensure the system actually ran assert!(*world.get_resource::().unwrap()); } #[test] fn get_system_conflicts() { fn sys_x(_: Res, _: Res, _: Query<(&C, &D)>) {} fn sys_y(_: Res, _: ResMut, _: Query<(&C, &mut D)>) {} let mut world = World::default(); let mut x = sys_x.system(); let mut y = sys_y.system(); x.initialize(&mut world); y.initialize(&mut world); let conflicts = x.component_access().get_conflicts(y.component_access()); let b_id = world .components() .get_resource_id(TypeId::of::()) .unwrap(); let d_id = world.components().get_id(TypeId::of::()).unwrap(); assert_eq!(conflicts, vec![b_id, d_id]); } #[test] fn query_is_empty() { fn without_filter(not_empty: Query<&A>, empty: Query<&B>) { assert!(!not_empty.is_empty()); assert!(empty.is_empty()); } fn with_filter(not_empty: Query<&A, With>, empty: Query<&A, With>) { assert!(!not_empty.is_empty()); assert!(empty.is_empty()); } let mut world = World::default(); world.spawn().insert(A).insert(C); let mut without_filter = without_filter.system(); without_filter.initialize(&mut world); without_filter.run((), &mut world); let mut with_filter = with_filter.system(); with_filter.initialize(&mut world); with_filter.run((), &mut world); } #[test] #[allow(clippy::too_many_arguments)] fn can_have_16_parameters() { fn sys_x( _: Res, _: Res, _: Res, _: Res, _: Res, _: Res, _: Query<&A>, _: Query<&B>, _: Query<&C>, _: Query<&D>, _: Query<&E>, _: Query<&F>, _: Query<(&A, &B)>, _: Query<(&C, &D)>, _: Query<(&E, &F)>, ) { } fn sys_y( _: ( Res, Res, Res, Res, Res, Res, Query<&A>, Query<&B>, Query<&C>, Query<&D>, Query<&E>, Query<&F>, Query<(&A, &B)>, Query<(&C, &D)>, Query<(&E, &F)>, ), ) { } let mut world = World::default(); let mut x = sys_x.system(); let mut y = sys_y.system(); x.initialize(&mut world); y.initialize(&mut world); } #[test] fn read_system_state() { #[derive(Eq, PartialEq, Debug)] struct A(usize); #[derive(Eq, PartialEq, Debug)] struct B(usize); let mut world = World::default(); world.insert_resource(A(42)); world.spawn().insert(B(7)); let mut system_state: SystemState<(Res, Query<&B>, QuerySet<(Query<&C>, Query<&D>)>)> = SystemState::new(&mut world); let (a, query, _) = system_state.get(&world); assert_eq!(*a, A(42), "returned resource matches initial value"); assert_eq!( *query.single().unwrap(), B(7), "returned component matches initial value" ); } #[test] fn write_system_state() { #[derive(Eq, PartialEq, Debug)] struct A(usize); #[derive(Eq, PartialEq, Debug)] struct B(usize); let mut world = World::default(); world.insert_resource(A(42)); world.spawn().insert(B(7)); let mut system_state: SystemState<(ResMut, Query<&mut B>)> = SystemState::new(&mut world); // The following line shouldn't compile because the parameters used are not ReadOnlySystemParam // let (a, query) = system_state.get(&world); let (a, mut query) = system_state.get_mut(&mut world); assert_eq!(*a, A(42), "returned resource matches initial value"); assert_eq!( *query.single_mut().unwrap(), B(7), "returned component matches initial value" ); } #[test] fn system_state_change_detection() { #[derive(Eq, PartialEq, Debug)] struct A(usize); let mut world = World::default(); let entity = world.spawn().insert(A(1)).id(); let mut system_state: SystemState>> = SystemState::new(&mut world); { let query = system_state.get(&world); assert_eq!(*query.single().unwrap(), A(1)); } { let query = system_state.get(&world); assert!(query.single().is_err()); } world.entity_mut(entity).get_mut::().unwrap().0 = 2; { let query = system_state.get(&world); assert_eq!(*query.single().unwrap(), A(2)); } } #[test] #[should_panic] fn system_state_invalid_world() { let mut world = World::default(); let mut system_state = SystemState::>::new(&mut world); let mismatched_world = World::default(); system_state.get(&mismatched_world); } #[test] fn system_state_archetype_update() { #[derive(Eq, PartialEq, Debug)] struct A(usize); #[derive(Eq, PartialEq, Debug)] struct B(usize); let mut world = World::default(); world.spawn().insert(A(1)); let mut system_state = SystemState::>::new(&mut world); { let query = system_state.get(&world); assert_eq!( query.iter().collect::>(), vec![&A(1)], "exactly one component returned" ); } world.spawn().insert_bundle((A(2), B(2))); { let query = system_state.get(&world); assert_eq!( query.iter().collect::>(), vec![&A(1), &A(2)], "components from both archetypes returned" ); } } }