Add methods for silencing system-order ambiguity warnings (#6158)

# Background

Incremental implementation of #4299. The code is heavily borrowed from that PR.

# Objective

The execution order ambiguity checker often emits false positives, since bevy is not aware of invariants upheld by the user.

## Solution

Title

---

## Changelog

+ Added methods `SystemDescriptor::ignore_all_ambiguities` and `::ambiguous_with`. These allow you to silence warnings for specific system-order ambiguities.

## Migration Guide

***Note for maintainers**: This should replace the migration guide for #5916*

Ambiguity sets have been replaced with a simpler API.

```rust
// These systems technically conflict, but we don't care which order they run in.
fn jump_on_click(mouse: Res<Input<MouseButton>>, mut transforms: Query<&mut Transform>) { ... }
fn jump_on_spacebar(keys: Res<Input<KeyCode>>, mut transforms: Query<&mut Transform>) { ... }

//
// Before

#[derive(AmbiguitySetLabel)]
struct JumpSystems;

app
  .add_system(jump_on_click.in_ambiguity_set(JumpSystems))
  .add_system(jump_on_spacebar.in_ambiguity_set(JumpSystems));

//
// After

app
  .add_system(jump_on_click.ambiguous_with(jump_on_spacebar))
  .add_system(jump_on_spacebar);

```
This commit is contained in:
JoJoJet 2022-10-10 16:34:21 +00:00
parent f5322cd757
commit 3321d68a75
3 changed files with 198 additions and 3 deletions

View file

@ -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<ComponentId>)> {
// 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::<FixedBitSet>::with_capacity(systems.len());
let mut all_dependants = Vec::<FixedBitSet>::with_capacity(systems.len());
for (index, container) in systems.iter().enumerate() {
@ -235,7 +255,8 @@ fn find_ambiguities(systems: &[SystemContainer]) -> Vec<(usize, usize, Vec<Compo
for index_b in full_bitset.difference(&relations)
// .take(index_a)
{
if !processed.contains(index_b) {
if !processed.contains(index_b) && !should_ignore(&systems[index_a], &systems[index_b])
{
let system_a = &systems[index_a];
let system_b = &systems[index_b];
if system_a.is_exclusive() || system_b.is_exclusive() {
@ -471,4 +492,117 @@ mod tests {
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
#[test]
fn ignore_all_ambiguities() {
let mut world = World::new();
world.insert_resource(R);
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(resmut_system.ignore_all_ambiguities())
.add_system(res_system)
.add_system(nonsend_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
#[test]
fn ambiguous_with_label() {
let mut world = World::new();
world.insert_resource(R);
#[derive(SystemLabel)]
struct IgnoreMe;
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(resmut_system.ambiguous_with(IgnoreMe))
.add_system(res_system.label(IgnoreMe))
.add_system(nonsend_system.label(IgnoreMe));
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
#[test]
fn ambiguous_with_system() {
let mut world = World::new();
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(write_component_system.ambiguous_with(read_component_system))
.add_system(read_component_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
fn system_a(_res: ResMut<R>) {}
fn system_b(_res: ResMut<R>) {}
fn system_c(_res: ResMut<R>) {}
fn system_d(_res: ResMut<R>) {}
fn system_e(_res: ResMut<R>) {}
// 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,
},
]
);
}
}

View file

@ -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<SystemLabelId>,
before: Vec<SystemLabelId>,
after: Vec<SystemLabelId>,
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(),
}
}

View file

@ -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<SystemLabelId>),
}
/// 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<SystemLabelId>,
pub(crate) before: Vec<SystemLabelId>,
pub(crate) after: Vec<SystemLabelId>,
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<Params> {
/// Specifies that the system should run after systems with the given label.
fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> 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<Marker>(self, label: impl AsSystemLabel<Marker>) -> 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<Marker>(mut self, label: impl AsSystemLabel<Marker>) -> 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<Marker>(self, label: impl AsSystemLabel<Marker>) -> 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<Marker>(self, label: impl AsSystemLabel<Marker>) -> 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()
}