diff --git a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs b/crates/bevy_ecs/src/schedule/ambiguity_detection.rs index ce4a3360dec067..001f39530b7d38 100644 --- a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs +++ b/crates/bevy_ecs/src/schedule/ambiguity_detection.rs @@ -2,9 +2,11 @@ use bevy_utils::tracing::info; use fixedbitset::FixedBitSet; use crate::component::ComponentId; -use crate::schedule::{SystemContainer, SystemStage}; +use crate::schedule::{AmbiguityDetection, GraphNode, SystemContainer, SystemStage}; use crate::world::World; +use super::SystemLabelId; + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct SystemOrderAmbiguity { segment: SystemStageSegment, @@ -194,6 +196,24 @@ impl SystemStage { /// along with specific components that have triggered the warning. /// Systems must be topologically sorted beforehand. fn find_ambiguities(systems: &[SystemContainer]) -> Vec<(usize, usize, Vec)> { + // Check if we should ignore ambiguities between `system_a` and `system_b`. + fn should_ignore(system_a: &SystemContainer, system_b: &SystemContainer) -> bool { + fn should_ignore_inner( + system_a_detection: &AmbiguityDetection, + system_b_labels: &[SystemLabelId], + ) -> bool { + match system_a_detection { + AmbiguityDetection::Check => false, + AmbiguityDetection::IgnoreAll => true, + AmbiguityDetection::IgnoreWithLabel(labels) => { + labels.iter().any(|l| system_b_labels.contains(l)) + } + } + } + should_ignore_inner(&system_a.ambiguity_detection, system_b.labels()) + || should_ignore_inner(&system_b.ambiguity_detection, system_a.labels()) + } + 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() { @@ -235,7 +255,8 @@ fn find_ambiguities(systems: &[SystemContainer]) -> Vec<(usize, usize, Vec) {} + fn system_b(_res: ResMut) {} + fn system_c(_res: ResMut) {} + fn system_d(_res: ResMut) {} + fn system_e(_res: ResMut) {} + + // Tests that the correct ambiguities were reported in the correct order. + #[test] + fn correct_ambiguities() { + use super::*; + + let mut world = World::new(); + world.insert_resource(R); + + let mut test_stage = SystemStage::parallel(); + test_stage + .add_system(system_a) + .add_system(system_b) + .add_system(system_c.ignore_all_ambiguities()) + .add_system(system_d.ambiguous_with(system_b)) + .add_system(system_e.after(system_a)); + + test_stage.run(&mut world); + + let ambiguities = test_stage.ambiguities(&world); + assert_eq!( + ambiguities, + vec![ + SystemOrderAmbiguity { + system_names: [ + "bevy_ecs::schedule::ambiguity_detection::tests::system_a".to_string(), + "bevy_ecs::schedule::ambiguity_detection::tests::system_b".to_string() + ], + conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()], + segment: SystemStageSegment::Parallel, + }, + SystemOrderAmbiguity { + system_names: [ + "bevy_ecs::schedule::ambiguity_detection::tests::system_a".to_string(), + "bevy_ecs::schedule::ambiguity_detection::tests::system_d".to_string() + ], + conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()], + segment: SystemStageSegment::Parallel, + }, + SystemOrderAmbiguity { + system_names: [ + "bevy_ecs::schedule::ambiguity_detection::tests::system_b".to_string(), + "bevy_ecs::schedule::ambiguity_detection::tests::system_e".to_string() + ], + conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()], + segment: SystemStageSegment::Parallel, + }, + SystemOrderAmbiguity { + system_names: [ + "bevy_ecs::schedule::ambiguity_detection::tests::system_d".to_string(), + "bevy_ecs::schedule::ambiguity_detection::tests::system_e".to_string() + ], + conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()], + segment: SystemStageSegment::Parallel, + }, + ] + ); + } } diff --git a/crates/bevy_ecs/src/schedule/system_container.rs b/crates/bevy_ecs/src/schedule/system_container.rs index 721faa80f8ac83..9083ad37c5a2f2 100644 --- a/crates/bevy_ecs/src/schedule/system_container.rs +++ b/crates/bevy_ecs/src/schedule/system_container.rs @@ -1,7 +1,9 @@ use crate::{ component::ComponentId, query::Access, - schedule::{GraphNode, RunCriteriaLabelId, SystemDescriptor, SystemLabelId}, + schedule::{ + AmbiguityDetection, GraphNode, RunCriteriaLabelId, SystemDescriptor, SystemLabelId, + }, system::System, }; use std::borrow::Cow; @@ -16,6 +18,7 @@ pub struct SystemContainer { labels: Vec, before: Vec, after: Vec, + pub(crate) ambiguity_detection: AmbiguityDetection, } impl SystemContainer { @@ -29,6 +32,7 @@ impl SystemContainer { labels: descriptor.labels, before: descriptor.before, after: descriptor.after, + ambiguity_detection: descriptor.ambiguity_detection, is_exclusive: descriptor.exclusive_insertion_point.is_some(), } } diff --git a/crates/bevy_ecs/src/schedule/system_descriptor.rs b/crates/bevy_ecs/src/schedule/system_descriptor.rs index e673f12cc58ab8..8d0d87b9d9a47d 100644 --- a/crates/bevy_ecs/src/schedule/system_descriptor.rs +++ b/crates/bevy_ecs/src/schedule/system_descriptor.rs @@ -3,6 +3,16 @@ use crate::{ system::{AsSystemLabel, BoxedSystem, IntoSystem}, }; +/// Configures ambiguity detection for a single system. +#[derive(Default)] +pub(crate) enum AmbiguityDetection { + #[default] + Check, + IgnoreAll, + /// Ignore systems with any of these labels. + IgnoreWithLabel(Vec), +} + /// Encapsulates a system and information on when it run in a `SystemStage`. /// /// Systems can be inserted into 4 different groups within the stage: @@ -38,6 +48,7 @@ pub struct SystemDescriptor { pub(crate) labels: Vec, pub(crate) before: Vec, pub(crate) after: Vec, + pub(crate) ambiguity_detection: AmbiguityDetection, } impl SystemDescriptor { @@ -53,6 +64,7 @@ impl SystemDescriptor { run_criteria: None, before: Vec::new(), after: Vec::new(), + ambiguity_detection: Default::default(), } } } @@ -75,6 +87,15 @@ pub trait IntoSystemDescriptor { /// Specifies that the system should run after systems with the given label. fn after(self, label: impl AsSystemLabel) -> SystemDescriptor; + /// Marks this system as ambiguous with any system with the specified label. + /// This means that execution order between these systems does not matter, + /// which allows [some warnings](crate::schedule::ReportExecutionOrderAmbiguities) to be silenced. + fn ambiguous_with(self, label: impl AsSystemLabel) -> SystemDescriptor; + + /// Specifies that this system should opt out of + /// [execution order ambiguity detection](crate::schedule::ReportExecutionOrderAmbiguities). + fn ignore_all_ambiguities(self) -> SystemDescriptor; + /// Specifies that the system should run with other exclusive systems at the start of stage. fn at_start(self) -> SystemDescriptor; @@ -110,6 +131,26 @@ impl IntoSystemDescriptor<()> for SystemDescriptor { self } + fn ambiguous_with(mut self, label: impl AsSystemLabel) -> SystemDescriptor { + match &mut self.ambiguity_detection { + detection @ AmbiguityDetection::Check => { + *detection = + AmbiguityDetection::IgnoreWithLabel(vec![label.as_system_label().as_label()]); + } + AmbiguityDetection::IgnoreWithLabel(labels) => { + labels.push(label.as_system_label().as_label()); + } + // This descriptor is already ambiguous with everything. + AmbiguityDetection::IgnoreAll => {} + } + self + } + + fn ignore_all_ambiguities(mut self) -> SystemDescriptor { + self.ambiguity_detection = AmbiguityDetection::IgnoreAll; + self + } + fn at_start(mut self) -> SystemDescriptor { self.exclusive_insertion_point = Some(ExclusiveInsertionPoint::AtStart); self @@ -154,6 +195,14 @@ where SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).after(label) } + fn ambiguous_with(self, label: impl AsSystemLabel) -> SystemDescriptor { + SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).ambiguous_with(label) + } + + fn ignore_all_ambiguities(self) -> SystemDescriptor { + SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).ignore_all_ambiguities() + } + fn at_start(self) -> SystemDescriptor { SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).at_start() } @@ -191,6 +240,14 @@ impl IntoSystemDescriptor<()> for BoxedSystem<(), ()> { SystemDescriptor::new(self).after(label) } + fn ambiguous_with(self, label: impl AsSystemLabel) -> SystemDescriptor { + SystemDescriptor::new(self).ambiguous_with(label) + } + + fn ignore_all_ambiguities(self) -> SystemDescriptor { + SystemDescriptor::new(self).ignore_all_ambiguities() + } + fn at_start(self) -> SystemDescriptor { SystemDescriptor::new(self).at_start() }