bevy/tests/ecs/ambiguity_detection.rs
Alice Cecile 4ac2a63556
Remove all existing system order ambiguities in DefaultPlugins (#15031)
# Objective

As discussed in https://github.com/bevyengine/bevy/issues/7386, system
order ambiguities within `DefaultPlugins` are a source of bugs in the
engine and badly pollute diagnostic output for users.

We should eliminate them!

This PR is an alternative to #15027: with all external ambiguities
silenced, this should be much less prone to merge conflicts and the test
output should be much easier for authors to understand.

Note that system order ambiguities are still permitted in the
`RenderApp`: these need a bit of thought in terms of how to test them,
and will be fairly involved to fix. While these aren't *good*, they'll
generally only cause graphical bugs, not logic ones.

## Solution

All remaining system order ambiguities have been resolved.
Review this PR commit-by-commit to see how each of these problems were
fixed.

## Testing

`cargo run --example ambiguity_detection` passes with no panics or
logging!
2024-09-03 20:24:34 +00:00

83 lines
3.0 KiB
Rust

//! A test to confirm that `bevy` doesn't regress its system ambiguities count when using [`DefaultPlugins`].
//! This is run in CI.
//!
//! Note that because this test requires rendering, it isn't actually an integration test!
//! Instead, it's secretly an example: you can run this test manually using `cargo run --example ambiguity_detection`.
use bevy::{
ecs::schedule::{InternedScheduleLabel, LogLevel, ScheduleBuildSettings},
prelude::*,
utils::HashMap,
};
use bevy_render::pipelined_rendering::RenderExtractApp;
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins);
let main_app = app.main_mut();
configure_ambiguity_detection(main_app);
let render_extract_app = app.sub_app_mut(RenderExtractApp);
configure_ambiguity_detection(render_extract_app);
// Ambiguities in the RenderApp are currently allowed.
// Eventually, we should forbid these: see https://github.com/bevyengine/bevy/issues/7386
// Uncomment the lines below to show the current ambiguities in the RenderApp.
// let sub_app = app.sub_app_mut(bevy_render::RenderApp);
// configure_ambiguity_detection(sub_app);
app.finish();
app.cleanup();
app.update();
let main_app_ambiguities = count_ambiguities(app.main());
assert_eq!(
main_app_ambiguities.total(),
0,
"Main app has unexpected ambiguities among the following schedules: \n{:#?}.",
main_app_ambiguities,
);
// RenderApp is not checked here, because it is not within the App at this point.
let render_extract_ambiguities = count_ambiguities(app.sub_app(RenderExtractApp));
assert_eq!(
render_extract_ambiguities.total(),
0,
"RenderExtract app has unexpected ambiguities among the following schedules: \n{:#?}",
render_extract_ambiguities,
);
}
/// Contains the number of conflicting systems per schedule.
#[derive(Debug, Deref, DerefMut)]
struct AmbiguitiesCount(pub HashMap<InternedScheduleLabel, usize>);
impl AmbiguitiesCount {
fn total(&self) -> usize {
self.values().sum()
}
}
fn configure_ambiguity_detection(sub_app: &mut SubApp) {
let mut schedules = sub_app.world_mut().resource_mut::<Schedules>();
for (_, schedule) in schedules.iter_mut() {
schedule.set_build_settings(ScheduleBuildSettings {
// NOTE: you can change this to `LogLevel::Ignore` to easily see the current number of ambiguities.
ambiguity_detection: LogLevel::Warn,
use_shortnames: false,
..default()
});
}
}
/// Returns the number of conflicting systems per schedule.
fn count_ambiguities(sub_app: &SubApp) -> AmbiguitiesCount {
let schedules = sub_app.world().resource::<Schedules>();
let mut ambiguities = HashMap::new();
for (_, schedule) in schedules.iter() {
let ambiguities_in_schedule = schedule.graph().conflicting_systems().len();
ambiguities.insert(schedule.label(), ambiguities_in_schedule);
}
AmbiguitiesCount(ambiguities)
}