bevy/crates/bevy_ecs/src/schedule/mod.rs
Zachary Harrold 5241e09671
Upgrade to Rust Edition 2024 (#17967)
# Objective

- Fixes #17960

## Solution

- Followed the [edition upgrade
guide](https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html)

## Testing

- CI

---

## Summary of Changes

### Documentation Indentation

When using lists in documentation, proper indentation is now linted for.
This means subsequent lines within the same list item must start at the
same indentation level as the item.

```rust
/* Valid */
/// - Item 1
///   Run-on sentence.
/// - Item 2
struct Foo;

/* Invalid */
/// - Item 1
///     Run-on sentence.
/// - Item 2
struct Foo;
```

### Implicit `!` to `()` Conversion

`!` (the never return type, returned by `panic!`, etc.) no longer
implicitly converts to `()`. This is particularly painful for systems
with `todo!` or `panic!` statements, as they will no longer be functions
returning `()` (or `Result<()>`), making them invalid systems for
functions like `add_systems`. The ideal fix would be to accept functions
returning `!` (or rather, _not_ returning), but this is blocked on the
[stabilisation of the `!` type
itself](https://doc.rust-lang.org/std/primitive.never.html), which is
not done.

The "simple" fix would be to add an explicit `-> ()` to system
signatures (e.g., `|| { todo!() }` becomes `|| -> () { todo!() }`).
However, this is _also_ banned, as there is an existing lint which (IMO,
incorrectly) marks this as an unnecessary annotation.

So, the "fix" (read: workaround) is to put these kinds of `|| -> ! { ...
}` closuers into variables and give the variable an explicit type (e.g.,
`fn()`).

```rust
// Valid
let system: fn() = || todo!("Not implemented yet!");
app.add_systems(..., system);

// Invalid
app.add_systems(..., || todo!("Not implemented yet!"));
```

### Temporary Variable Lifetimes

The order in which temporary variables are dropped has changed. The
simple fix here is _usually_ to just assign temporaries to a named
variable before use.

### `gen` is a keyword

We can no longer use the name `gen` as it is reserved for a future
generator syntax. This involved replacing uses of the name `gen` with
`r#gen` (the raw-identifier syntax).

### Formatting has changed

Use statements have had the order of imports changed, causing a
substantial +/-3,000 diff when applied. For now, I have opted-out of
this change by amending `rustfmt.toml`

```toml
style_edition = "2021"
```

This preserves the original formatting for now, reducing the size of
this PR. It would be a simple followup to update this to 2024 and run
`cargo fmt`.

### New `use<>` Opt-Out Syntax

Lifetimes are now implicitly included in RPIT types. There was a handful
of instances where it needed to be added to satisfy the borrow checker,
but there may be more cases where it _should_ be added to avoid
breakages in user code.

### `MyUnitStruct { .. }` is an invalid pattern

Previously, you could match against unit structs (and unit enum
variants) with a `{ .. }` destructuring. This is no longer valid.

### Pretty much every use of `ref` and `mut` are gone

Pattern binding has changed to the point where these terms are largely
unused now. They still serve a purpose, but it is far more niche now.

### `iter::repeat(...).take(...)` is bad

New lint recommends using the more explicit `iter::repeat_n(..., ...)`
instead.

## Migration Guide

The lifetimes of functions using return-position impl-trait (RPIT) are
likely _more_ conservative than they had been previously. If you
encounter lifetime issues with such a function, please create an issue
to investigate the addition of `+ use<...>`.

## Notes

- Check the individual commits for a clearer breakdown for what
_actually_ changed.

---------

Co-authored-by: François Mockers <francois.mockers@vleue.com>
2025-02-24 03:54:47 +00:00

1237 lines
42 KiB
Rust

//! Contains APIs for ordering systems and executing them on a [`World`](crate::world::World)
mod auto_insert_apply_deferred;
mod condition;
mod config;
mod executor;
mod pass;
mod schedule;
mod set;
mod stepping;
use self::graph::*;
pub use self::{condition::*, config::*, executor::*, schedule::*, set::*};
pub use pass::ScheduleBuildPass;
pub use self::graph::NodeId;
/// An implementation of a graph data structure.
pub mod graph;
/// Included optional schedule build passes.
pub mod passes {
pub use crate::schedule::auto_insert_apply_deferred::*;
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::{string::ToString, vec, vec::Vec};
use core::sync::atomic::{AtomicU32, Ordering};
pub use crate::{
prelude::World,
resource::Resource,
schedule::{Schedule, SystemSet},
system::{Res, ResMut},
};
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
enum TestSet {
A,
B,
C,
D,
X,
}
#[derive(Resource, Default)]
struct SystemOrder(Vec<u32>);
#[derive(Resource, Default)]
struct RunConditionBool(pub bool);
#[derive(Resource, Default)]
struct Counter(pub AtomicU32);
fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) {
move |world| world.resource_mut::<SystemOrder>().0.push(tag)
}
fn make_function_system(tag: u32) -> impl FnMut(ResMut<SystemOrder>) {
move |mut resource: ResMut<SystemOrder>| resource.0.push(tag)
}
fn named_system(mut resource: ResMut<SystemOrder>) {
resource.0.push(u32::MAX);
}
fn named_exclusive_system(world: &mut World) {
world.resource_mut::<SystemOrder>().0.push(u32::MAX);
}
fn counting_system(counter: Res<Counter>) {
counter.0.fetch_add(1, Ordering::Relaxed);
}
mod system_execution {
use super::*;
#[test]
fn run_system() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<SystemOrder>();
schedule.add_systems(make_function_system(0));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
}
#[test]
fn run_exclusive_system() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<SystemOrder>();
schedule.add_systems(make_exclusive_system(0));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
}
#[test]
#[cfg(not(miri))]
fn parallel_execution() {
use alloc::sync::Arc;
use bevy_tasks::{ComputeTaskPool, TaskPool};
use std::sync::Barrier;
let mut world = World::default();
let mut schedule = Schedule::default();
let thread_count = ComputeTaskPool::get_or_init(TaskPool::default).thread_num();
let barrier = Arc::new(Barrier::new(thread_count));
for _ in 0..thread_count {
let inner = barrier.clone();
schedule.add_systems(move || {
inner.wait();
});
}
schedule.run(&mut world);
}
}
mod system_ordering {
use super::*;
#[test]
fn order_systems() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<SystemOrder>();
schedule.add_systems((
named_system,
make_function_system(1).before(named_system),
make_function_system(0)
.after(named_system)
.in_set(TestSet::A),
));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![1, u32::MAX, 0]);
world.insert_resource(SystemOrder::default());
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
// modify the schedule after it's been initialized and test ordering with sets
schedule.configure_sets(TestSet::A.after(named_system));
schedule.add_systems((
make_function_system(3)
.before(TestSet::A)
.after(named_system),
make_function_system(4).after(TestSet::A),
));
schedule.run(&mut world);
assert_eq!(
world.resource::<SystemOrder>().0,
vec![1, u32::MAX, 3, 0, 4]
);
}
#[test]
fn order_exclusive_systems() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<SystemOrder>();
schedule.add_systems((
named_exclusive_system,
make_exclusive_system(1).before(named_exclusive_system),
make_exclusive_system(0).after(named_exclusive_system),
));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![1, u32::MAX, 0]);
}
#[test]
fn add_systems_correct_order() {
let mut world = World::new();
let mut schedule = Schedule::default();
world.init_resource::<SystemOrder>();
schedule.add_systems(
(
make_function_system(0),
make_function_system(1),
make_exclusive_system(2),
make_function_system(3),
)
.chain(),
);
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![0, 1, 2, 3]);
}
#[test]
fn add_systems_correct_order_nested() {
let mut world = World::new();
let mut schedule = Schedule::default();
world.init_resource::<SystemOrder>();
schedule.add_systems(
(
(make_function_system(0), make_function_system(1)).chain(),
make_function_system(2),
(make_function_system(3), make_function_system(4)).chain(),
(
make_function_system(5),
(make_function_system(6), make_function_system(7)),
),
(
(make_function_system(8), make_function_system(9)).chain(),
make_function_system(10),
),
)
.chain(),
);
schedule.run(&mut world);
let order = &world.resource::<SystemOrder>().0;
assert_eq!(
&order[0..5],
&[0, 1, 2, 3, 4],
"first five items should be exactly ordered"
);
let unordered = &order[5..8];
assert!(
unordered.contains(&5) && unordered.contains(&6) && unordered.contains(&7),
"unordered must be 5, 6, and 7 in any order"
);
let partially_ordered = &order[8..11];
assert!(
partially_ordered == [8, 9, 10] || partially_ordered == [10, 8, 9],
"partially_ordered must be [8, 9, 10] or [10, 8, 9]"
);
assert_eq!(order.len(), 11, "must have exactly 11 order entries");
}
}
mod conditions {
use crate::change_detection::DetectChanges;
use super::*;
#[test]
fn system_with_condition() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<RunConditionBool>();
world.init_resource::<SystemOrder>();
schedule.add_systems(
make_function_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
);
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
world.resource_mut::<RunConditionBool>().0 = true;
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
}
#[test]
fn systems_with_distributive_condition() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.insert_resource(RunConditionBool(true));
world.init_resource::<SystemOrder>();
fn change_condition(mut condition: ResMut<RunConditionBool>) {
condition.0 = false;
}
schedule.add_systems(
(
make_function_system(0),
change_condition,
make_function_system(1),
)
.chain()
.distributive_run_if(|condition: Res<RunConditionBool>| condition.0),
);
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
}
#[test]
fn run_exclusive_system_with_condition() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<RunConditionBool>();
world.init_resource::<SystemOrder>();
schedule.add_systems(
make_exclusive_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
);
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
world.resource_mut::<RunConditionBool>().0 = true;
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
}
#[test]
fn multiple_conditions_on_system() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<Counter>();
schedule.add_systems((
counting_system.run_if(|| false).run_if(|| false),
counting_system.run_if(|| true).run_if(|| false),
counting_system.run_if(|| false).run_if(|| true),
counting_system.run_if(|| true).run_if(|| true),
));
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
}
#[test]
fn multiple_conditions_on_system_sets() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<Counter>();
schedule.configure_sets(TestSet::A.run_if(|| false).run_if(|| false));
schedule.add_systems(counting_system.in_set(TestSet::A));
schedule.configure_sets(TestSet::B.run_if(|| true).run_if(|| false));
schedule.add_systems(counting_system.in_set(TestSet::B));
schedule.configure_sets(TestSet::C.run_if(|| false).run_if(|| true));
schedule.add_systems(counting_system.in_set(TestSet::C));
schedule.configure_sets(TestSet::D.run_if(|| true).run_if(|| true));
schedule.add_systems(counting_system.in_set(TestSet::D));
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
}
#[test]
fn systems_nested_in_system_sets() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<Counter>();
schedule.configure_sets(TestSet::A.run_if(|| false));
schedule.add_systems(counting_system.in_set(TestSet::A).run_if(|| false));
schedule.configure_sets(TestSet::B.run_if(|| true));
schedule.add_systems(counting_system.in_set(TestSet::B).run_if(|| false));
schedule.configure_sets(TestSet::C.run_if(|| false));
schedule.add_systems(counting_system.in_set(TestSet::C).run_if(|| true));
schedule.configure_sets(TestSet::D.run_if(|| true));
schedule.add_systems(counting_system.in_set(TestSet::D).run_if(|| true));
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
}
#[test]
fn system_conditions_and_change_detection() {
#[derive(Resource, Default)]
struct Bool2(pub bool);
let mut world = World::default();
world.init_resource::<Counter>();
world.init_resource::<RunConditionBool>();
world.init_resource::<Bool2>();
let mut schedule = Schedule::default();
schedule.add_systems(
counting_system
.run_if(|res1: Res<RunConditionBool>| res1.is_changed())
.run_if(|res2: Res<Bool2>| res2.is_changed()),
);
// both resource were just added.
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// nothing has changed
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// RunConditionBool has changed, but counting_system did not run
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// internal state for the bool2 run criteria was updated in the
// previous run, so system still does not run
world.get_resource_mut::<Bool2>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// internal state for bool2 was updated, so system still does not run
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// now check that it works correctly changing Bool2 first and then RunConditionBool
world.get_resource_mut::<Bool2>().unwrap().0 = false;
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 2);
}
#[test]
fn system_set_conditions_and_change_detection() {
#[derive(Resource, Default)]
struct Bool2(pub bool);
let mut world = World::default();
world.init_resource::<Counter>();
world.init_resource::<RunConditionBool>();
world.init_resource::<Bool2>();
let mut schedule = Schedule::default();
schedule.configure_sets(
TestSet::A
.run_if(|res1: Res<RunConditionBool>| res1.is_changed())
.run_if(|res2: Res<Bool2>| res2.is_changed()),
);
schedule.add_systems(counting_system.in_set(TestSet::A));
// both resource were just added.
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// nothing has changed
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// RunConditionBool has changed, but counting_system did not run
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// internal state for the bool2 run criteria was updated in the
// previous run, so system still does not run
world.get_resource_mut::<Bool2>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// internal state for bool2 was updated, so system still does not run
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// the system only runs when both are changed on the same run
world.get_resource_mut::<Bool2>().unwrap().0 = false;
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 2);
}
#[test]
fn mixed_conditions_and_change_detection() {
#[derive(Resource, Default)]
struct Bool2(pub bool);
let mut world = World::default();
world.init_resource::<Counter>();
world.init_resource::<RunConditionBool>();
world.init_resource::<Bool2>();
let mut schedule = Schedule::default();
schedule
.configure_sets(TestSet::A.run_if(|res1: Res<RunConditionBool>| res1.is_changed()));
schedule.add_systems(
counting_system
.run_if(|res2: Res<Bool2>| res2.is_changed())
.in_set(TestSet::A),
);
// both resource were just added.
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// nothing has changed
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// RunConditionBool has changed, but counting_system did not run
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// we now only change bool2 and the system also should not run
world.get_resource_mut::<Bool2>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// internal state for the bool2 run criteria was updated in the
// previous run, so system still does not run
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
// the system only runs when both are changed on the same run
world.get_resource_mut::<Bool2>().unwrap().0 = false;
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 2);
}
}
mod schedule_build_errors {
use super::*;
#[test]
#[should_panic]
fn dependency_loop() {
let mut schedule = Schedule::default();
schedule.configure_sets(TestSet::X.after(TestSet::X));
}
#[test]
fn dependency_cycle() {
let mut world = World::new();
let mut schedule = Schedule::default();
schedule.configure_sets(TestSet::A.after(TestSet::B));
schedule.configure_sets(TestSet::B.after(TestSet::A));
let result = schedule.initialize(&mut world);
assert!(matches!(
result,
Err(ScheduleBuildError::DependencyCycle(_))
));
fn foo() {}
fn bar() {}
let mut world = World::new();
let mut schedule = Schedule::default();
schedule.add_systems((foo.after(bar), bar.after(foo)));
let result = schedule.initialize(&mut world);
assert!(matches!(
result,
Err(ScheduleBuildError::DependencyCycle(_))
));
}
#[test]
#[should_panic]
fn hierarchy_loop() {
let mut schedule = Schedule::default();
schedule.configure_sets(TestSet::X.in_set(TestSet::X));
}
#[test]
fn hierarchy_cycle() {
let mut world = World::new();
let mut schedule = Schedule::default();
schedule.configure_sets(TestSet::A.in_set(TestSet::B));
schedule.configure_sets(TestSet::B.in_set(TestSet::A));
let result = schedule.initialize(&mut world);
assert!(matches!(result, Err(ScheduleBuildError::HierarchyCycle(_))));
}
#[test]
fn system_type_set_ambiguity() {
// Define some systems.
fn foo() {}
fn bar() {}
let mut world = World::new();
let mut schedule = Schedule::default();
// Schedule `bar` to run after `foo`.
schedule.add_systems((foo, bar.after(foo)));
// There's only one `foo`, so it's fine.
let result = schedule.initialize(&mut world);
assert!(result.is_ok());
// Schedule another `foo`.
schedule.add_systems(foo);
// When there are multiple instances of `foo`, dependencies on
// `foo` are no longer allowed. Too much ambiguity.
let result = schedule.initialize(&mut world);
assert!(matches!(
result,
Err(ScheduleBuildError::SystemTypeSetAmbiguity(_))
));
// same goes for `ambiguous_with`
let mut schedule = Schedule::default();
schedule.add_systems(foo);
schedule.add_systems(bar.ambiguous_with(foo));
let result = schedule.initialize(&mut world);
assert!(result.is_ok());
schedule.add_systems(foo);
let result = schedule.initialize(&mut world);
assert!(matches!(
result,
Err(ScheduleBuildError::SystemTypeSetAmbiguity(_))
));
}
#[test]
#[should_panic]
fn configure_system_type_set() {
fn foo() {}
let mut schedule = Schedule::default();
schedule.configure_sets(foo.into_system_set());
}
#[test]
fn hierarchy_redundancy() {
let mut world = World::new();
let mut schedule = Schedule::default();
schedule.set_build_settings(ScheduleBuildSettings {
hierarchy_detection: LogLevel::Error,
..Default::default()
});
// Add `A`.
schedule.configure_sets(TestSet::A);
// Add `B` as child of `A`.
schedule.configure_sets(TestSet::B.in_set(TestSet::A));
// Add `X` as child of both `A` and `B`.
schedule.configure_sets(TestSet::X.in_set(TestSet::A).in_set(TestSet::B));
// `X` cannot be the `A`'s child and grandchild at the same time.
let result = schedule.initialize(&mut world);
assert!(matches!(
result,
Err(ScheduleBuildError::HierarchyRedundancy(_))
));
}
#[test]
fn cross_dependency() {
let mut world = World::new();
let mut schedule = Schedule::default();
// Add `B` and give it both kinds of relationships with `A`.
schedule.configure_sets(TestSet::B.in_set(TestSet::A));
schedule.configure_sets(TestSet::B.after(TestSet::A));
let result = schedule.initialize(&mut world);
assert!(matches!(
result,
Err(ScheduleBuildError::CrossDependency(_, _))
));
}
#[test]
fn sets_have_order_but_intersect() {
let mut world = World::new();
let mut schedule = Schedule::default();
fn foo() {}
// Add `foo` to both `A` and `C`.
schedule.add_systems(foo.in_set(TestSet::A).in_set(TestSet::C));
// Order `A -> B -> C`.
schedule.configure_sets((
TestSet::A,
TestSet::B.after(TestSet::A),
TestSet::C.after(TestSet::B),
));
let result = schedule.initialize(&mut world);
// `foo` can't be in both `A` and `C` because they can't run at the same time.
assert!(matches!(
result,
Err(ScheduleBuildError::SetsHaveOrderButIntersect(_, _))
));
}
#[test]
fn ambiguity() {
#[derive(Resource)]
struct X;
fn res_ref(_x: Res<X>) {}
fn res_mut(_x: ResMut<X>) {}
let mut world = World::new();
let mut schedule = Schedule::default();
schedule.set_build_settings(ScheduleBuildSettings {
ambiguity_detection: LogLevel::Error,
..Default::default()
});
schedule.add_systems((res_ref, res_mut));
let result = schedule.initialize(&mut world);
assert!(matches!(result, Err(ScheduleBuildError::Ambiguity(_))));
}
}
mod system_ambiguity {
use alloc::collections::BTreeSet;
use super::*;
use crate::prelude::*;
#[derive(Resource)]
struct R;
#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
// An event type
#[derive(Event)]
struct E;
#[derive(Resource, Component)]
struct RC;
fn empty_system() {}
fn res_system(_res: Res<R>) {}
fn resmut_system(_res: ResMut<R>) {}
fn nonsend_system(_ns: NonSend<R>) {}
fn nonsendmut_system(_ns: NonSendMut<R>) {}
fn read_component_system(_query: Query<&A>) {}
fn write_component_system(_query: Query<&mut A>) {}
fn with_filtered_component_system(_query: Query<&mut A, With<B>>) {}
fn without_filtered_component_system(_query: Query<&mut A, Without<B>>) {}
fn entity_ref_system(_query: Query<EntityRef>) {}
fn entity_mut_system(_query: Query<EntityMut>) {}
fn event_reader_system(_reader: EventReader<E>) {}
fn event_writer_system(_writer: EventWriter<E>) {}
fn event_resource_system(_events: ResMut<Events<E>>) {}
fn read_world_system(_world: &World) {}
fn write_world_system(_world: &mut World) {}
// Tests for conflict detection
#[test]
fn one_of_everything() {
let mut world = World::new();
world.insert_resource(R);
world.spawn(A);
world.init_resource::<Events<E>>();
let mut schedule = Schedule::default();
schedule
// nonsendmut system deliberately conflicts with resmut system
.add_systems((resmut_system, write_component_system, event_writer_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn read_only() {
let mut world = World::new();
world.insert_resource(R);
world.spawn(A);
world.init_resource::<Events<E>>();
let mut schedule = Schedule::default();
schedule.add_systems((
empty_system,
empty_system,
res_system,
res_system,
nonsend_system,
nonsend_system,
read_component_system,
read_component_system,
entity_ref_system,
entity_ref_system,
event_reader_system,
event_reader_system,
read_world_system,
read_world_system,
));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn read_world() {
let mut world = World::new();
world.insert_resource(R);
world.spawn(A);
world.init_resource::<Events<E>>();
let mut schedule = Schedule::default();
schedule.add_systems((
resmut_system,
write_component_system,
event_writer_system,
read_world_system,
));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
}
#[test]
fn resources() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((resmut_system, res_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
}
#[test]
fn nonsend() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((nonsendmut_system, nonsend_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
}
#[test]
fn components() {
let mut world = World::new();
world.spawn(A);
let mut schedule = Schedule::default();
schedule.add_systems((read_component_system, write_component_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
}
#[test]
#[ignore = "Known failing but fix is non-trivial: https://github.com/bevyengine/bevy/issues/4381"]
fn filtered_components() {
let mut world = World::new();
world.spawn(A);
let mut schedule = Schedule::default();
schedule.add_systems((
with_filtered_component_system,
without_filtered_component_system,
));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn events() {
let mut world = World::new();
world.init_resource::<Events<E>>();
let mut schedule = Schedule::default();
schedule.add_systems((
// All of these systems clash
event_reader_system,
event_writer_system,
event_resource_system,
));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
}
/// Test that when a struct is both a Resource and a Component, they do not
/// conflict with each other.
#[test]
fn shared_resource_mut_component() {
let mut world = World::new();
world.insert_resource(RC);
let mut schedule = Schedule::default();
schedule.add_systems((|_: ResMut<RC>| {}, |_: Query<&mut RC>| {}));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn resource_mut_and_entity_ref() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((resmut_system, entity_ref_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn resource_and_entity_mut() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((res_system, nonsend_system, entity_mut_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn write_component_and_entity_ref() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((write_component_system, entity_ref_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
}
#[test]
fn read_component_and_entity_mut() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((read_component_system, entity_mut_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
}
#[test]
fn exclusive() {
let mut world = World::new();
world.insert_resource(R);
world.spawn(A);
world.init_resource::<Events<E>>();
let mut schedule = Schedule::default();
schedule.add_systems((
// All 3 of these conflict with each other
write_world_system,
write_world_system,
res_system,
));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
}
// Tests for silencing and resolving ambiguities
#[test]
fn before_and_after() {
let mut world = World::new();
world.init_resource::<Events<E>>();
let mut schedule = Schedule::default();
schedule.add_systems((
event_reader_system.before(event_writer_system),
event_writer_system,
event_resource_system.after(event_writer_system),
));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn ignore_all_ambiguities() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((
resmut_system.ambiguous_with_all(),
res_system,
nonsend_system,
));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn ambiguous_with_label() {
let mut world = World::new();
world.insert_resource(R);
#[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
struct IgnoreMe;
let mut schedule = Schedule::default();
schedule.add_systems((
resmut_system.ambiguous_with(IgnoreMe),
res_system.in_set(IgnoreMe),
nonsend_system.in_set(IgnoreMe),
));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn ambiguous_with_system() {
let mut world = World::new();
let mut schedule = Schedule::default();
schedule.add_systems((
write_component_system.ambiguous_with(read_component_system),
read_component_system,
));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
struct TestSchedule;
// Tests that the correct ambiguities were reported in the correct order.
#[test]
fn correct_ambiguities() {
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>) {}
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::new(TestSchedule);
schedule.add_systems((
system_a,
system_b,
system_c.ambiguous_with_all(),
system_d.ambiguous_with(system_b),
system_e.after(system_a),
));
schedule.graph_mut().initialize(&mut world);
let _ = schedule.graph_mut().build_schedule(
&mut world,
TestSchedule.intern(),
&BTreeSet::new(),
);
let ambiguities: Vec<_> = schedule
.graph()
.conflicts_to_string(schedule.graph().conflicting_systems(), world.components())
.collect();
let expected = &[
(
"system_d".to_string(),
"system_a".to_string(),
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
),
(
"system_d".to_string(),
"system_e".to_string(),
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
),
(
"system_b".to_string(),
"system_a".to_string(),
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
),
(
"system_b".to_string(),
"system_e".to_string(),
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
),
];
// ordering isn't stable so do this
for entry in expected {
assert!(ambiguities.contains(entry));
}
}
// Test that anonymous set names work properly
// Related issue https://github.com/bevyengine/bevy/issues/9641
#[test]
fn anonymous_set_name() {
let mut schedule = Schedule::new(TestSchedule);
schedule.add_systems((resmut_system, resmut_system).run_if(|| true));
let mut world = World::new();
schedule.graph_mut().initialize(&mut world);
let _ = schedule.graph_mut().build_schedule(
&mut world,
TestSchedule.intern(),
&BTreeSet::new(),
);
let ambiguities: Vec<_> = schedule
.graph()
.conflicts_to_string(schedule.graph().conflicting_systems(), world.components())
.collect();
assert_eq!(
ambiguities[0],
(
"resmut_system (in set (resmut_system, resmut_system))".to_string(),
"resmut_system (in set (resmut_system, resmut_system))".to_string(),
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
)
);
}
#[test]
fn ignore_component_resource_ambiguities() {
let mut world = World::new();
world.insert_resource(R);
world.allow_ambiguous_resource::<R>();
let mut schedule = Schedule::new(TestSchedule);
// check resource
schedule.add_systems((resmut_system, res_system));
schedule.initialize(&mut world).unwrap();
assert!(schedule.graph().conflicting_systems().is_empty());
// check components
world.allow_ambiguous_component::<A>();
schedule.add_systems((write_component_system, read_component_system));
schedule.initialize(&mut world).unwrap();
assert!(schedule.graph().conflicting_systems().is_empty());
}
}
#[cfg(feature = "bevy_debug_stepping")]
mod stepping {
use super::*;
use bevy_ecs::system::SystemState;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct TestSchedule;
macro_rules! assert_executor_supports_stepping {
($executor:expr) => {
// create a test schedule
let mut schedule = Schedule::new(TestSchedule);
schedule
.set_executor_kind($executor)
.add_systems(|| -> () { panic!("Executor ignored Stepping") });
// Add our schedule to stepping & and enable stepping; this should
// prevent any systems in the schedule from running
let mut stepping = Stepping::default();
stepping.add_schedule(TestSchedule).enable();
// create a world, and add the stepping resource
let mut world = World::default();
world.insert_resource(stepping);
// start a new frame by running ihe begin_frame() system
let mut system_state: SystemState<Option<ResMut<Stepping>>> =
SystemState::new(&mut world);
let res = system_state.get_mut(&mut world);
Stepping::begin_frame(res);
// now run the schedule; this will panic if the executor doesn't
// handle stepping
schedule.run(&mut world);
};
}
/// verify the [`SimpleExecutor`] supports stepping
#[test]
fn simple_executor() {
assert_executor_supports_stepping!(ExecutorKind::Simple);
}
/// verify the [`SingleThreadedExecutor`] supports stepping
#[test]
fn single_threaded_executor() {
assert_executor_supports_stepping!(ExecutorKind::SingleThreaded);
}
/// verify the [`MultiThreadedExecutor`] supports stepping
#[test]
fn multi_threaded_executor() {
assert_executor_supports_stepping!(ExecutorKind::MultiThreaded);
}
}
}