Path: blob/main/examples/ecs/nondeterministic_system_order.rs
6592 views
//! By default, Bevy systems run in parallel with each other.1//! Unless the order is explicitly specified, their relative order is nondeterministic.2//!3//! In many cases, this doesn't matter and is in fact desirable!4//! Consider two systems, one which writes to resource A, and the other which writes to resource B.5//! By allowing their order to be arbitrary, we can evaluate them greedily, based on the data that is free.6//! Because their data accesses are **compatible**, there is no **observable** difference created based on the order they are run.7//!8//! But if instead we have two systems mutating the same data, or one reading it and the other mutating,9//! then the actual observed value will vary based on the nondeterministic order of evaluation.10//! These observable conflicts are called **system execution order ambiguities**.11//!12//! This example demonstrates how you might detect and resolve (or silence) these ambiguities.1314use bevy::{15ecs::schedule::{LogLevel, ScheduleBuildSettings},16prelude::*,17};1819fn main() {20App::new()21// We can modify the reporting strategy for system execution order ambiguities on a per-schedule basis.22// You must do this for each schedule you want to inspect; child schedules executed within an inspected23// schedule do not inherit this modification.24.edit_schedule(Update, |schedule| {25schedule.set_build_settings(ScheduleBuildSettings {26ambiguity_detection: LogLevel::Warn,27..default()28});29})30.init_resource::<A>()31.init_resource::<B>()32.add_systems(33Update,34(35// This pair of systems has an ambiguous order,36// as their data access conflicts, and there's no order between them.37reads_a,38writes_a,39// This pair of systems has conflicting data access,40// but it's resolved with an explicit ordering:41// the .after relationship here means that we will always double after adding.42adds_one_to_b,43doubles_b.after(adds_one_to_b),44// This system isn't ambiguous with adds_one_to_b,45// due to the transitive ordering created by our constraints:46// if A is before B is before C, then A must be before C as well.47reads_b.after(doubles_b),48// This system will conflict with all of our writing systems49// but we've silenced its ambiguity with adds_one_to_b.50// This should only be done in the case of clear false positives:51// leave a comment in your code justifying the decision!52reads_a_and_b.ambiguous_with(adds_one_to_b),53),54)55// Be mindful, internal ambiguities are reported too!56// If there are any ambiguities due solely to DefaultPlugins,57// or between DefaultPlugins and any of your third party plugins,58// please file a bug with the repo responsible!59// Only *you* can prevent nondeterministic bugs due to greedy parallelism.60.add_plugins(DefaultPlugins)61.run();62}6364#[derive(Resource, Debug, Default)]65struct A(usize);6667#[derive(Resource, Debug, Default)]68struct B(usize);6970// Data access is determined solely on the basis of the types of the system's parameters71// Every implementation of the `SystemParam` and `WorldQuery` traits must declare which data is used72// and whether or not it is mutably accessed.73fn reads_a(_a: Res<A>) {}7475fn writes_a(mut a: ResMut<A>) {76a.0 += 1;77}7879fn adds_one_to_b(mut b: ResMut<B>) {80b.0 = b.0.saturating_add(1);81}8283fn doubles_b(mut b: ResMut<B>) {84// This will overflow pretty rapidly otherwise85b.0 = b.0.saturating_mul(2);86}8788fn reads_b(b: Res<B>) {89// This invariant is always true,90// because we've fixed the system order so doubling always occurs after adding.91assert!((b.0.is_multiple_of(2)) || (b.0 == usize::MAX));92}9394fn reads_a_and_b(a: Res<A>, b: Res<B>) {95// Only display the first few steps to avoid burying the ambiguities in the console96if b.0 < 10 {97info!("{}, {}", a.0, b.0);98}99}100101102