Skip to content

Commit dc3f801

Browse files
committed
Exclusive Systems Now Implement System. Flexible Exclusive System Params (#6083)
# Objective The [Stageless RFC](bevyengine/rfcs#45) involves allowing exclusive systems to be referenced and ordered relative to parallel systems. We've agreed that unifying systems under `System` is the right move. This is an alternative to #4166 (see rationale in the comments I left there). Note that this builds on the learnings established there (and borrows some patterns). ## Solution This unifies parallel and exclusive systems under the shared `System` trait, removing the old `ExclusiveSystem` trait / impls. This is accomplished by adding a new `ExclusiveFunctionSystem` impl similar to `FunctionSystem`. It is backed by `ExclusiveSystemParam`, which is similar to `SystemParam`. There is a new flattened out SystemContainer api (which cuts out a lot of trait and type complexity). This means you can remove all cases of `exclusive_system()`: ```rust // before commands.add_system(some_system.exclusive_system()); // after commands.add_system(some_system); ``` I've also implemented `ExclusiveSystemParam` for `&mut QueryState` and `&mut SystemState`, which makes this possible in exclusive systems: ```rust fn some_exclusive_system( world: &mut World, transforms: &mut QueryState<&Transform>, state: &mut SystemState<(Res<Time>, Query<&Player>)>, ) { for transform in transforms.iter(world) { println!("{transform:?}"); } let (time, players) = state.get(world); for player in players.iter() { println!("{player:?}"); } } ``` Note that "exclusive function systems" assume `&mut World` is present (and the first param). I think this is a fair assumption, given that the presence of `&mut World` is what defines the need for an exclusive system. I added some targeted SystemParam `static` constraints, which removed the need for this: ``` rust fn some_exclusive_system(state: &mut SystemState<(Res<'static, Time>, Query<&'static Player>)>) {} ``` ## Related - #2923 - #3001 - #3946 ## Changelog - `ExclusiveSystem` trait (and implementations) has been removed in favor of sharing the `System` trait. - `ExclusiveFunctionSystem` and `ExclusiveSystemParam` were added, enabling flexible exclusive function systems - `&mut SystemState` and `&mut QueryState` now implement `ExclusiveSystemParam` - Exclusive and parallel System configuration is now done via a unified `SystemDescriptor`, `IntoSystemDescriptor`, and `SystemContainer` api. ## Migration Guide Calling `.exclusive_system()` is no longer required (or supported) for converting exclusive system functions to exclusive systems: ```rust // Old (0.8) app.add_system(some_exclusive_system.exclusive_system()); // New (0.9) app.add_system(some_exclusive_system); ``` Converting "normal" parallel systems to exclusive systems is done by calling the exclusive ordering apis: ```rust // Old (0.8) app.add_system(some_system.exclusive_system().at_end()); // New (0.9) app.add_system(some_system.at_end()); ``` Query state in exclusive systems can now be cached via ExclusiveSystemParams, which should be preferred for clarity and performance reasons: ```rust // Old (0.8) fn some_system(world: &mut World) { let mut transforms = world.query::<&Transform>(); for transform in transforms.iter(world) { } } // New (0.9) fn some_system(world: &mut World, transforms: &mut QueryState<&Transform>) { for transform in transforms.iter(world) { } } ```
1 parent 92e78a4 commit dc3f801

File tree

37 files changed

+735
-835
lines changed

37 files changed

+735
-835
lines changed

benches/benches/bevy_ecs/scheduling/run_criteria.rs

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
use bevy_ecs::{
2-
component::Component,
3-
prelude::{ParallelSystemDescriptorCoercion, Res, Resource, RunCriteriaDescriptorCoercion},
4-
schedule::{RunCriteriaLabel, ShouldRun, Stage, SystemStage},
5-
system::Query,
6-
world::World,
7-
};
1+
use bevy_ecs::{prelude::*, schedule::ShouldRun};
82
use criterion::Criterion;
93

104
fn run_stage(stage: &mut SystemStage, world: &mut World) {

crates/bevy_animation/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use bevy_ecs::{
1212
entity::Entity,
1313
prelude::Component,
1414
reflect::ReflectComponent,
15-
schedule::ParallelSystemDescriptorCoercion,
15+
schedule::IntoSystemDescriptor,
1616
system::{Query, Res},
1717
};
1818
use bevy_hierarchy::Children;

crates/bevy_app/src/app.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pub use bevy_derive::AppLabel;
33
use bevy_derive::{Deref, DerefMut};
44
use bevy_ecs::{
55
event::{Event, Events},
6-
prelude::{FromWorld, IntoExclusiveSystem},
6+
prelude::FromWorld,
77
schedule::{
88
IntoSystemDescriptor, Schedule, ShouldRun, Stage, StageLabel, State, StateData, SystemSet,
99
SystemStage,
@@ -84,7 +84,7 @@ impl Default for App {
8484

8585
app.add_default_stages()
8686
.add_event::<AppExit>()
87-
.add_system_to_stage(CoreStage::Last, World::clear_trackers.exclusive_system());
87+
.add_system_to_stage(CoreStage::Last, World::clear_trackers);
8888

8989
#[cfg(feature = "bevy_ci_testing")]
9090
{

crates/bevy_ecs/src/entity/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ type IdCursor = isize;
7979
/// }
8080
/// #
8181
/// # bevy_ecs::system::assert_is_system(setup);
82-
/// # bevy_ecs::system::IntoExclusiveSystem::exclusive_system(exclusive_system);
82+
/// # bevy_ecs::system::assert_is_system(exclusive_system);
8383
/// ```
8484
///
8585
/// It can be used to refer to a specific entity to apply [`EntityCommands`], or to call [`Query::get`] (or similar methods) to access its components.

crates/bevy_ecs/src/lib.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,13 @@ pub mod prelude {
3434
event::{EventReader, EventWriter, Events},
3535
query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without},
3636
schedule::{
37-
ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion, RunCriteria,
38-
RunCriteriaDescriptorCoercion, RunCriteriaLabel, Schedule, Stage, StageLabel, State,
39-
SystemLabel, SystemSet, SystemStage,
37+
IntoSystemDescriptor, RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel,
38+
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
4039
},
4140
system::{
42-
adapter as system_adapter, Commands, In, IntoChainSystem, IntoExclusiveSystem,
43-
IntoSystem, Local, NonSend, NonSendMut, ParallelCommands, ParamSet, Query,
44-
RemovedComponents, Res, ResMut, Resource, System, SystemParamFunction,
41+
adapter as system_adapter, Commands, In, IntoChainSystem, IntoSystem, Local, NonSend,
42+
NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut,
43+
Resource, System, SystemParamFunction,
4544
},
4645
world::{FromWorld, Mut, World},
4746
};

crates/bevy_ecs/src/schedule/ambiguity_detection.rs

+14-13
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ impl SystemOrderAmbiguity {
4545
stage: &SystemStage,
4646
world: &World,
4747
) -> Self {
48-
use crate::schedule::graph_utils::GraphNode;
4948
use SystemStageSegment::*;
5049

5150
// TODO: blocked on https://github.com/bevyengine/bevy/pull/4166
@@ -220,7 +219,7 @@ impl SystemStage {
220219
/// Returns vector containing all pairs of indices of systems with ambiguous execution order,
221220
/// along with specific components that have triggered the warning.
222221
/// Systems must be topologically sorted beforehand.
223-
fn find_ambiguities(systems: &[impl SystemContainer]) -> Vec<(usize, usize, Vec<ComponentId>)> {
222+
fn find_ambiguities(systems: &[SystemContainer]) -> Vec<(usize, usize, Vec<ComponentId>)> {
224223
let mut all_dependencies = Vec::<FixedBitSet>::with_capacity(systems.len());
225224
let mut all_dependants = Vec::<FixedBitSet>::with_capacity(systems.len());
226225
for (index, container) in systems.iter().enumerate() {
@@ -263,15 +262,17 @@ fn find_ambiguities(systems: &[impl SystemContainer]) -> Vec<(usize, usize, Vec<
263262
// .take(index_a)
264263
{
265264
if !processed.contains(index_b) {
266-
let a_access = systems[index_a].component_access();
267-
let b_access = systems[index_b].component_access();
268-
if let (Some(a), Some(b)) = (a_access, b_access) {
269-
let conflicts = a.get_conflicts(b);
265+
let system_a = &systems[index_a];
266+
let system_b = &systems[index_b];
267+
if system_a.is_exclusive() || system_b.is_exclusive() {
268+
ambiguities.push((index_a, index_b, Vec::new()));
269+
} else {
270+
let a_access = systems[index_a].component_access();
271+
let b_access = systems[index_b].component_access();
272+
let conflicts = a_access.get_conflicts(b_access);
270273
if !conflicts.is_empty() {
271274
ambiguities.push((index_a, index_b, conflicts));
272275
}
273-
} else {
274-
ambiguities.push((index_a, index_b, Vec::new()));
275276
}
276277
}
277278
}
@@ -467,12 +468,12 @@ mod tests {
467468
let mut test_stage = SystemStage::parallel();
468469
test_stage
469470
// All 3 of these conflict with each other
470-
.add_system(write_world_system.exclusive_system())
471-
.add_system(write_world_system.exclusive_system().at_end())
472-
.add_system(res_system.exclusive_system())
471+
.add_system(write_world_system)
472+
.add_system(write_world_system.at_end())
473+
.add_system(res_system.at_start())
473474
// These do not, as they're in different segments of the stage
474-
.add_system(write_world_system.exclusive_system().at_start())
475-
.add_system(write_world_system.exclusive_system().before_commands());
475+
.add_system(write_world_system.at_start())
476+
.add_system(write_world_system.before_commands());
476477

477478
test_stage.run(&mut world);
478479

crates/bevy_ecs/src/schedule/executor.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use crate::{schedule::ParallelSystemContainer, world::World};
1+
use crate::{schedule::SystemContainer, world::World};
22
use downcast_rs::{impl_downcast, Downcast};
33

44
pub trait ParallelSystemExecutor: Downcast + Send + Sync {
55
/// Called by `SystemStage` whenever `systems` have been changed.
6-
fn rebuild_cached_data(&mut self, systems: &[ParallelSystemContainer]);
6+
fn rebuild_cached_data(&mut self, systems: &[SystemContainer]);
77

8-
fn run_systems(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World);
8+
fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World);
99
}
1010

1111
impl_downcast!(ParallelSystemExecutor);
@@ -14,9 +14,9 @@ impl_downcast!(ParallelSystemExecutor);
1414
pub struct SingleThreadedExecutor;
1515

1616
impl ParallelSystemExecutor for SingleThreadedExecutor {
17-
fn rebuild_cached_data(&mut self, _: &[ParallelSystemContainer]) {}
17+
fn rebuild_cached_data(&mut self, _: &[SystemContainer]) {}
1818

19-
fn run_systems(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World) {
19+
fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World) {
2020
for system in systems {
2121
if system.should_run() {
2222
#[cfg(feature = "trace")]

crates/bevy_ecs/src/schedule/executor_parallel.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
archetype::ArchetypeComponentId,
33
query::Access,
4-
schedule::{ParallelSystemContainer, ParallelSystemExecutor},
4+
schedule::{ParallelSystemExecutor, SystemContainer},
55
world::World,
66
};
77
use async_channel::{Receiver, Sender};
@@ -77,7 +77,7 @@ impl Default for ParallelExecutor {
7777
}
7878

7979
impl ParallelSystemExecutor for ParallelExecutor {
80-
fn rebuild_cached_data(&mut self, systems: &[ParallelSystemContainer]) {
80+
fn rebuild_cached_data(&mut self, systems: &[SystemContainer]) {
8181
self.system_metadata.clear();
8282
self.queued.grow(systems.len());
8383
self.running.grow(systems.len());
@@ -104,7 +104,7 @@ impl ParallelSystemExecutor for ParallelExecutor {
104104
}
105105
}
106106

107-
fn run_systems(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World) {
107+
fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World) {
108108
#[cfg(test)]
109109
if self.events_sender.is_none() {
110110
let (sender, receiver) = async_channel::unbounded::<SchedulingEvent>();
@@ -167,7 +167,7 @@ impl ParallelExecutor {
167167
fn prepare_systems<'scope>(
168168
&mut self,
169169
scope: &mut Scope<'scope, ()>,
170-
systems: &'scope mut [ParallelSystemContainer],
170+
systems: &'scope mut [SystemContainer],
171171
world: &'scope World,
172172
) {
173173
// These are used as a part of a unit test.

0 commit comments

Comments
 (0)