Change the ambiguity_detection test to be more strict. (#23846)

# Objective

- Fixes #23843 by preventing these systems from recurring inside Bevy.
- Note this does not fix this for users of Bevy. They would need to
setup their own "strict" checking.

## Solution

- Disable `auto_insert_apply_deferred` in the `ambiguity_detection`
example. Now these stages won't accidentally resolve these ambiguities,
so they will be detected!
- Stop running the app in `ambiguity_detection` and just manually
initialize the schedules.
- We have to do this otherwise the schedules just panic because they
depend on various resource initializations to be executed by commands
(which were previously being applied by the automatically inserted
`apply_deferred`).

### Caveats:

- The ambiguity_detection CI test now won't detect ambiguities in
runtime-added systems.
- This is not a pattern we use today, and I'd encourage us to **delete**
systems instead so that tooling can grab the systems before the app
runs.
- To clarify, this is **not** ambiguity detection in general - just
Bevy's CI test that is affected.

## Testing

- Ran the examples and all the ambiguities have been fixed in previous
PRs.
This commit is contained in:
andriyDev
2026-04-22 09:19:28 -07:00
committed by GitHub
parent c7f2038d09
commit 4dc8fb4919
+31 -8
View File
@@ -35,17 +35,21 @@ fn main() {
let sub_app = app.sub_app_mut(bevy_render::RenderApp); let sub_app = app.sub_app_mut(bevy_render::RenderApp);
configure_ambiguity_detection(sub_app); configure_ambiguity_detection(sub_app);
// Make sure all the system stuff is added.
app.finish(); app.finish();
app.cleanup(); app.cleanup();
app.update();
let main_app_ambiguities = count_ambiguities(app.main()); let main_app_ambiguities = count_ambiguities(app.main_mut());
assert_eq!( assert_eq!(
main_app_ambiguities.total(), main_app_ambiguities.total(),
0, 0,
"Main app has unexpected ambiguities among the following schedules: \n{main_app_ambiguities:#?}.", "Main app has unexpected ambiguities among the following schedules: \n{main_app_ambiguities:#?}.",
); );
let render_app_ambiguities = count_ambiguities(app.sub_app(bevy_render::RenderApp));
let render_app = app.sub_app_mut(bevy_render::RenderApp);
// Initialize the MainWorld so the render world systems don't fail initialization.
render_app.init_resource::<bevy_render::MainWorld>();
let render_app_ambiguities = count_ambiguities(render_app);
assert_eq!( assert_eq!(
render_app_ambiguities.total(), render_app_ambiguities.total(),
0, 0,
@@ -69,6 +73,14 @@ fn configure_ambiguity_detection(sub_app: &mut SubApp) {
schedule.set_build_settings(ScheduleBuildSettings { schedule.set_build_settings(ScheduleBuildSettings {
// NOTE: you can change this to `LogLevel::Ignore` to easily see the current number of ambiguities. // NOTE: you can change this to `LogLevel::Ignore` to easily see the current number of ambiguities.
ambiguity_detection: LogLevel::Warn, ambiguity_detection: LogLevel::Warn,
// With auto-inserted apply_deferred stages, these can cause two ambiguous systems to
// become accidentally ordered by one of the apply_deferred stages. Disabling requires
// us to meet a higher bar. We don't just want no ambiguities - we also don't want
// changes to systems or the auto-insert code from "creating" new ambiguities (by
// reordering the graph). However, the cost is that the graph is no longer runnable,
// since Bevy crates often rely on auto-insert apply_deferred to not panic (e.g.,
// because a resource wasn't inserted).
auto_insert_apply_deferred: false,
use_shortnames: false, use_shortnames: false,
..default() ..default()
}); });
@@ -76,12 +88,23 @@ fn configure_ambiguity_detection(sub_app: &mut SubApp) {
} }
/// Returns the number of conflicting systems per schedule. /// Returns the number of conflicting systems per schedule.
fn count_ambiguities(sub_app: &SubApp) -> AmbiguitiesCount { fn count_ambiguities(sub_app: &mut SubApp) -> AmbiguitiesCount {
let schedules = sub_app.world().resource::<Schedules>(); let schedule_labels = sub_app
.world()
.resource::<Schedules>()
.iter()
.map(|(_, schedule)| schedule.label())
.collect::<Vec<_>>();
let mut ambiguities = <HashMap<_, _>>::default(); let mut ambiguities = <HashMap<_, _>>::default();
for (_, schedule) in schedules.iter() { for label in schedule_labels {
let ambiguities_in_schedule = schedule.graph().conflicting_systems().len(); let ambiguities_in_schedule =
ambiguities.insert(schedule.label(), ambiguities_in_schedule); sub_app
.world_mut()
.schedule_scope(label, |world, schedule| {
schedule.initialize(world).unwrap().unwrap();
schedule.graph().conflicting_systems().len()
});
ambiguities.insert(label, ambiguities_in_schedule);
} }
AmbiguitiesCount(ambiguities) AmbiguitiesCount(ambiguities)
} }