use bevy_utils::tracing::info; use fixedbitset::FixedBitSet; use crate::component::ComponentId; use crate::schedule::{SystemContainer, SystemStage}; use crate::world::World; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct SystemOrderAmbiguity { segment: SystemStageSegment, // Note: In order for comparisons to work correctly, // `system_names` and `conflicts` must be sorted at all times. system_names: [String; 2], conflicts: Vec, } /// Which part of a [`SystemStage`] was a [`SystemOrderAmbiguity`] detected in? #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] enum SystemStageSegment { Parallel, ExclusiveAtStart, ExclusiveBeforeCommands, ExclusiveAtEnd, } impl SystemStageSegment { pub fn desc(&self) -> &'static str { match self { SystemStageSegment::Parallel => "Parallel systems", SystemStageSegment::ExclusiveAtStart => "Exclusive systems at start of stage", SystemStageSegment::ExclusiveBeforeCommands => { "Exclusive systems before commands of stage" } SystemStageSegment::ExclusiveAtEnd => "Exclusive systems at end of stage", } } } impl SystemOrderAmbiguity { fn from_raw( system_a_index: usize, system_b_index: usize, component_ids: Vec, segment: SystemStageSegment, stage: &SystemStage, world: &World, ) -> Self { use SystemStageSegment::*; // TODO: blocked on https://github.com/bevyengine/bevy/pull/4166 // We can't grab the system container generically, because .parallel_systems() // and the exclusive equivalent return a different type, // and SystemContainer is not object-safe let (system_a_name, system_b_name) = match segment { Parallel => { let system_container = stage.parallel_systems(); ( system_container[system_a_index].name(), system_container[system_b_index].name(), ) } ExclusiveAtStart => { let system_container = stage.exclusive_at_start_systems(); ( system_container[system_a_index].name(), system_container[system_b_index].name(), ) } ExclusiveBeforeCommands => { let system_container = stage.exclusive_before_commands_systems(); ( system_container[system_a_index].name(), system_container[system_b_index].name(), ) } ExclusiveAtEnd => { let system_container = stage.exclusive_at_end_systems(); ( system_container[system_a_index].name(), system_container[system_b_index].name(), ) } }; let mut system_names = [system_a_name.to_string(), system_b_name.to_string()]; system_names.sort(); let mut conflicts: Vec<_> = component_ids .iter() .map(|id| world.components().get_info(*id).unwrap().name().to_owned()) .collect(); conflicts.sort(); Self { system_names, conflicts, segment, } } } impl SystemStage { /// Logs execution order ambiguities between systems. /// /// The output may be incorrect if this stage has not been initialized with `world`. pub fn report_ambiguities(&self, world: &World) { debug_assert!(!self.systems_modified); use std::fmt::Write; let ambiguities = self.ambiguities(world); if !ambiguities.is_empty() { let mut string = "Execution order ambiguities detected, you might want to \ add an explicit dependency relation between some of these systems:\n" .to_owned(); let mut last_segment_kind = None; for SystemOrderAmbiguity { system_names: [system_a, system_b], conflicts, segment, } in &ambiguities { // If the ambiguity occurred in a different segment than the previous one, write a header for the segment. if last_segment_kind != Some(segment) { writeln!(string, " * {}:", segment.desc()).unwrap(); last_segment_kind = Some(segment); } writeln!(string, " -- {:?} and {:?}", system_a, system_b).unwrap(); if !conflicts.is_empty() { writeln!(string, " conflicts: {conflicts:?}").unwrap(); } } info!("{}", string); } } /// Returns all execution order ambiguities between systems. /// /// Returns 4 vectors of ambiguities for each stage, in the following order: /// - parallel /// - exclusive at start, /// - exclusive before commands /// - exclusive at end /// /// The result may be incorrect if this stage has not been initialized with `world`. fn ambiguities(&self, world: &World) -> Vec { let parallel = find_ambiguities(&self.parallel).into_iter().map( |(system_a_index, system_b_index, component_ids)| { SystemOrderAmbiguity::from_raw( system_a_index, system_b_index, component_ids.to_vec(), SystemStageSegment::Parallel, self, world, ) }, ); let at_start = find_ambiguities(&self.exclusive_at_start).into_iter().map( |(system_a_index, system_b_index, component_ids)| { SystemOrderAmbiguity::from_raw( system_a_index, system_b_index, component_ids, SystemStageSegment::ExclusiveAtStart, self, world, ) }, ); let before_commands = find_ambiguities(&self.exclusive_before_commands) .into_iter() .map(|(system_a_index, system_b_index, component_ids)| { SystemOrderAmbiguity::from_raw( system_a_index, system_b_index, component_ids, SystemStageSegment::ExclusiveBeforeCommands, self, world, ) }); let at_end = find_ambiguities(&self.exclusive_at_end).into_iter().map( |(system_a_index, system_b_index, component_ids)| { SystemOrderAmbiguity::from_raw( system_a_index, system_b_index, component_ids, SystemStageSegment::ExclusiveAtEnd, self, world, ) }, ); let mut ambiguities: Vec<_> = at_start .chain(parallel) .chain(before_commands) .chain(at_end) .collect(); ambiguities.sort(); ambiguities } /// Returns the number of system order ambiguities between systems in this stage. /// /// The result may be incorrect if this stage has not been initialized with `world`. #[cfg(test)] fn ambiguity_count(&self, world: &World) -> usize { self.ambiguities(world).len() } } /// Returns vector containing all pairs of indices of systems with ambiguous execution order, /// along with specific components that have triggered the warning. /// Systems must be topologically sorted beforehand. fn find_ambiguities(systems: &[SystemContainer]) -> Vec<(usize, usize, Vec)> { let mut all_dependencies = Vec::::with_capacity(systems.len()); let mut all_dependants = Vec::::with_capacity(systems.len()); for (index, container) in systems.iter().enumerate() { let mut dependencies = FixedBitSet::with_capacity(systems.len()); for &dependency in container.dependencies() { dependencies.union_with(&all_dependencies[dependency]); dependencies.insert(dependency); all_dependants[dependency].insert(index); } all_dependants.push(FixedBitSet::with_capacity(systems.len())); all_dependencies.push(dependencies); } for index in (0..systems.len()).rev() { let mut dependants = FixedBitSet::with_capacity(systems.len()); for dependant in all_dependants[index].ones() { dependants.union_with(&all_dependants[dependant]); dependants.insert(dependant); } all_dependants[index] = dependants; } let mut all_relations = all_dependencies .drain(..) .zip(all_dependants.drain(..)) .enumerate() .map(|(index, (dependencies, dependants))| { let mut relations = FixedBitSet::with_capacity(systems.len()); relations.union_with(&dependencies); relations.union_with(&dependants); relations.insert(index); relations }) .collect::>(); let mut ambiguities = Vec::new(); let full_bitset: FixedBitSet = (0..systems.len()).collect(); let mut processed = FixedBitSet::with_capacity(systems.len()); for (index_a, relations) in all_relations.drain(..).enumerate() { // TODO: prove that `.take(index_a)` would be correct here, and uncomment it if so. for index_b in full_bitset.difference(&relations) // .take(index_a) { if !processed.contains(index_b) { let system_a = &systems[index_a]; let system_b = &systems[index_b]; if system_a.is_exclusive() || system_b.is_exclusive() { ambiguities.push((index_a, index_b, Vec::new())); } else { let a_access = systems[index_a].component_access(); let b_access = systems[index_b].component_access(); let conflicts = a_access.get_conflicts(b_access); if !conflicts.is_empty() { ambiguities.push((index_a, index_b, conflicts)); } } } } processed.insert(index_a); } ambiguities } #[cfg(test)] mod tests { // Required to make the derive macro behave use crate as bevy_ecs; use crate::event::Events; use crate::prelude::*; #[derive(Resource)] struct R; #[derive(Component)] struct A; #[derive(Component)] struct B; // An event type struct E; fn empty_system() {} fn res_system(_res: Res) {} fn resmut_system(_res: ResMut) {} fn nonsend_system(_ns: NonSend) {} fn nonsendmut_system(_ns: NonSendMut) {} fn read_component_system(_query: Query<&A>) {} fn write_component_system(_query: Query<&mut A>) {} fn with_filtered_component_system(_query: Query<&mut A, With>) {} fn without_filtered_component_system(_query: Query<&mut A, Without>) {} fn event_reader_system(_reader: EventReader) {} fn event_writer_system(_writer: EventWriter) {} fn event_resource_system(_events: ResMut>) {} fn read_world_system(_world: &World) {} fn write_world_system(_world: &mut World) {} // Tests for conflict detection #[test] fn one_of_everything() { let mut world = World::new(); world.insert_resource(R); world.spawn(A); world.init_resource::>(); let mut test_stage = SystemStage::parallel(); test_stage // nonsendmut system deliberately conflicts with resmut system .add_system(resmut_system) .add_system(write_component_system) .add_system(event_writer_system); test_stage.run(&mut world); assert_eq!(test_stage.ambiguity_count(&world), 0); } #[test] fn read_only() { let mut world = World::new(); world.insert_resource(R); world.spawn(A); world.init_resource::>(); let mut test_stage = SystemStage::parallel(); test_stage .add_system(empty_system) .add_system(empty_system) .add_system(res_system) .add_system(res_system) .add_system(nonsend_system) .add_system(nonsend_system) .add_system(read_component_system) .add_system(read_component_system) .add_system(event_reader_system) .add_system(event_reader_system) .add_system(read_world_system) .add_system(read_world_system); test_stage.run(&mut world); assert_eq!(test_stage.ambiguity_count(&world), 0); } #[test] fn read_world() { let mut world = World::new(); world.insert_resource(R); world.spawn(A); world.init_resource::>(); let mut test_stage = SystemStage::parallel(); test_stage .add_system(resmut_system) .add_system(write_component_system) .add_system(event_writer_system) .add_system(read_world_system); test_stage.run(&mut world); assert_eq!(test_stage.ambiguity_count(&world), 3); } #[test] fn resources() { let mut world = World::new(); world.insert_resource(R); let mut test_stage = SystemStage::parallel(); test_stage.add_system(resmut_system).add_system(res_system); test_stage.run(&mut world); assert_eq!(test_stage.ambiguity_count(&world), 1); } #[test] fn nonsend() { let mut world = World::new(); world.insert_resource(R); let mut test_stage = SystemStage::parallel(); test_stage .add_system(nonsendmut_system) .add_system(nonsend_system); test_stage.run(&mut world); assert_eq!(test_stage.ambiguity_count(&world), 1); } #[test] fn components() { let mut world = World::new(); world.spawn(A); let mut test_stage = SystemStage::parallel(); test_stage .add_system(read_component_system) .add_system(write_component_system); test_stage.run(&mut world); assert_eq!(test_stage.ambiguity_count(&world), 1); } #[test] #[ignore = "Known failing but fix is non-trivial: https://github.com/bevyengine/bevy/issues/4381"] fn filtered_components() { let mut world = World::new(); world.spawn(A); let mut test_stage = SystemStage::parallel(); test_stage .add_system(with_filtered_component_system) .add_system(without_filtered_component_system); test_stage.run(&mut world); assert_eq!(test_stage.ambiguity_count(&world), 0); } #[test] fn events() { let mut world = World::new(); world.init_resource::>(); let mut test_stage = SystemStage::parallel(); test_stage // All of these systems clash .add_system(event_reader_system) .add_system(event_writer_system) .add_system(event_resource_system); test_stage.run(&mut world); assert_eq!(test_stage.ambiguity_count(&world), 3); } #[test] fn exclusive() { let mut world = World::new(); world.insert_resource(R); world.spawn(A); world.init_resource::>(); let mut test_stage = SystemStage::parallel(); test_stage // All 3 of these conflict with each other .add_system(write_world_system) .add_system(write_world_system.at_end()) .add_system(res_system.at_start()) // These do not, as they're in different segments of the stage .add_system(write_world_system.at_start()) .add_system(write_world_system.before_commands()); test_stage.run(&mut world); assert_eq!(test_stage.ambiguity_count(&world), 3); } // Tests for silencing and resolving ambiguities #[test] fn before_and_after() { let mut world = World::new(); world.init_resource::>(); let mut test_stage = SystemStage::parallel(); test_stage .add_system(event_reader_system.before(event_writer_system)) .add_system(event_writer_system) .add_system(event_resource_system.after(event_writer_system)); test_stage.run(&mut world); assert_eq!(test_stage.ambiguity_count(&world), 0); } }