Skip to main content

ambiguity_detection/
ambiguity_detection.rs

1//! A test to confirm that `bevy` doesn't regress its system ambiguities count when using [`DefaultPlugins`].
2//! This is run in CI.
3//!
4//! Note that because this test requires rendering, it isn't actually an integration test!
5//! Instead, it's secretly an example: you can run this test manually using `cargo run --example ambiguity_detection`.
6
7use bevy::{
8    ecs::schedule::{InternedScheduleLabel, LogLevel, ScheduleBuildSettings},
9    platform::collections::HashMap,
10    prelude::*,
11    render::{pipelined_rendering::PipelinedRenderingPlugin, RenderPlugin},
12};
13
14fn main() {
15    let mut app = App::new();
16    app.add_plugins(
17        DefaultPlugins
18            .build()
19            .set(RenderPlugin {
20                // llvmpipe driver can cause segfaults when aborting the binary while pipelines are being
21                // compiled (which happens very quickly in this example since we only run for a single
22                // frame). Synchronous pipeline compilation helps prevent these segfaults as the
23                // rendering thread blocks on these pipeline compilations.
24                synchronous_pipeline_compilation: true,
25                ..Default::default()
26            })
27            // We also have to disable pipelined rendering to ensure the test doesn't end while the
28            // rendering frame is still executing in another thread.
29            .disable::<PipelinedRenderingPlugin>(),
30    );
31
32    let main_app = app.main_mut();
33    configure_ambiguity_detection(main_app);
34
35    let sub_app = app.sub_app_mut(bevy_render::RenderApp);
36    configure_ambiguity_detection(sub_app);
37
38    // Make sure all the system stuff is added.
39    app.finish();
40    app.cleanup();
41
42    let main_app_ambiguities = count_ambiguities(app.main_mut());
43    assert_eq!(
44        main_app_ambiguities.total(),
45        0,
46        "Main app has unexpected ambiguities among the following schedules: \n{main_app_ambiguities:#?}.",
47    );
48
49    let render_app = app.sub_app_mut(bevy_render::RenderApp);
50    // Initialize the MainWorld so the render world systems don't fail initialization.
51    render_app.init_resource::<bevy_render::MainWorld>();
52    let render_app_ambiguities = count_ambiguities(render_app);
53    assert_eq!(
54        render_app_ambiguities.total(),
55        0,
56        "Render app has unexpected ambiguities among the following schedules: \n{render_app_ambiguities:#?}.",
57    );
58}
59
60/// Contains the number of conflicting systems per schedule.
61#[derive(Debug, Deref, DerefMut)]
62struct AmbiguitiesCount(pub HashMap<InternedScheduleLabel, usize>);
63
64impl AmbiguitiesCount {
65    fn total(&self) -> usize {
66        self.values().sum()
67    }
68}
69
70fn configure_ambiguity_detection(sub_app: &mut SubApp) {
71    let mut schedules = sub_app.world_mut().resource_mut::<Schedules>();
72    for (_, schedule) in schedules.iter_mut() {
73        schedule.set_build_settings(ScheduleBuildSettings {
74            // NOTE: you can change this to `LogLevel::Ignore` to easily see the current number of ambiguities.
75            ambiguity_detection: LogLevel::Warn,
76            // With auto-inserted apply_deferred stages, these can cause two ambiguous systems to
77            // become accidentally ordered by one of the apply_deferred stages. Disabling requires
78            // us to meet a higher bar. We don't just want no ambiguities - we also don't want
79            // changes to systems or the auto-insert code from "creating" new ambiguities (by
80            // reordering the graph). However, the cost is that the graph is no longer runnable,
81            // since Bevy crates often rely on auto-insert apply_deferred to not panic (e.g.,
82            // because a resource wasn't inserted).
83            auto_insert_apply_deferred: false,
84            use_shortnames: false,
85            ..default()
86        });
87    }
88}
89
90/// Returns the number of conflicting systems per schedule.
91fn count_ambiguities(sub_app: &mut SubApp) -> AmbiguitiesCount {
92    let schedule_labels = sub_app
93        .world()
94        .resource::<Schedules>()
95        .iter()
96        .map(|(_, schedule)| schedule.label())
97        .collect::<Vec<_>>();
98    let mut ambiguities = <HashMap<_, _>>::default();
99    for label in schedule_labels {
100        let ambiguities_in_schedule =
101            sub_app
102                .world_mut()
103                .schedule_scope(label, |world, schedule| {
104                    schedule.initialize(world).unwrap().unwrap();
105                    schedule.graph().conflicting_systems().len()
106                });
107        ambiguities.insert(label, ambiguities_in_schedule);
108    }
109    AmbiguitiesCount(ambiguities)
110}