Exclusive Systems Now Implement System. Flexible Exclusive System Params (#6083)

# Objective

The [Stageless RFC](https://github.com/bevyengine/rfcs/pull/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) {
  }
}
```
This commit is contained in:
Carter Anderson 2022-09-26 23:57:07 +00:00
parent 92e78a4bc5
commit dc3f801239
37 changed files with 756 additions and 856 deletions

View file

@ -1,10 +1,4 @@
use bevy_ecs::{
component::Component,
prelude::{ParallelSystemDescriptorCoercion, Res, Resource, RunCriteriaDescriptorCoercion},
schedule::{RunCriteriaLabel, ShouldRun, Stage, SystemStage},
system::Query,
world::World,
};
use bevy_ecs::{prelude::*, schedule::ShouldRun};
use criterion::Criterion;
fn run_stage(stage: &mut SystemStage, world: &mut World) {

View file

@ -12,7 +12,7 @@ use bevy_ecs::{
entity::Entity,
prelude::Component,
reflect::ReflectComponent,
schedule::ParallelSystemDescriptorCoercion,
schedule::IntoSystemDescriptor,
system::{Query, Res},
};
use bevy_hierarchy::Children;

View file

@ -3,7 +3,7 @@ pub use bevy_derive::AppLabel;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
event::{Event, Events},
prelude::{FromWorld, IntoExclusiveSystem},
prelude::FromWorld,
schedule::{
IntoSystemDescriptor, Schedule, ShouldRun, Stage, StageLabel, State, StateData, SystemSet,
SystemStage,
@ -84,7 +84,7 @@ impl Default for App {
app.add_default_stages()
.add_event::<AppExit>()
.add_system_to_stage(CoreStage::Last, World::clear_trackers.exclusive_system());
.add_system_to_stage(CoreStage::Last, World::clear_trackers);
#[cfg(feature = "bevy_ci_testing")]
{

View file

@ -79,7 +79,7 @@ type IdCursor = isize;
/// }
/// #
/// # bevy_ecs::system::assert_is_system(setup);
/// # bevy_ecs::system::IntoExclusiveSystem::exclusive_system(exclusive_system);
/// # bevy_ecs::system::assert_is_system(exclusive_system);
/// ```
///
/// 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.

View file

@ -34,14 +34,13 @@ pub mod prelude {
event::{EventReader, EventWriter, Events},
query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without},
schedule::{
ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion, RunCriteria,
RunCriteriaDescriptorCoercion, RunCriteriaLabel, Schedule, Stage, StageLabel, State,
SystemLabel, SystemSet, SystemStage,
IntoSystemDescriptor, RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel,
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
},
system::{
adapter as system_adapter, Commands, In, IntoChainSystem, IntoExclusiveSystem,
IntoSystem, Local, NonSend, NonSendMut, ParallelCommands, ParamSet, Query,
RemovedComponents, Res, ResMut, Resource, System, SystemParamFunction,
adapter as system_adapter, Commands, In, IntoChainSystem, IntoSystem, Local, NonSend,
NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut,
Resource, System, SystemParamFunction,
},
world::{FromWorld, Mut, World},
};

View file

@ -45,7 +45,6 @@ impl SystemOrderAmbiguity {
stage: &SystemStage,
world: &World,
) -> Self {
use crate::schedule::graph_utils::GraphNode;
use SystemStageSegment::*;
// TODO: blocked on https://github.com/bevyengine/bevy/pull/4166
@ -220,7 +219,7 @@ impl SystemStage {
/// Returns vector containing all pairs of indices of systems with ambiguous execution order,
/// along with specific components that have triggered the warning.
/// Systems must be topologically sorted beforehand.
fn find_ambiguities(systems: &[impl SystemContainer]) -> Vec<(usize, usize, Vec<ComponentId>)> {
fn find_ambiguities(systems: &[SystemContainer]) -> Vec<(usize, usize, Vec<ComponentId>)> {
let mut all_dependencies = Vec::<FixedBitSet>::with_capacity(systems.len());
let mut all_dependants = Vec::<FixedBitSet>::with_capacity(systems.len());
for (index, container) in systems.iter().enumerate() {
@ -263,15 +262,17 @@ fn find_ambiguities(systems: &[impl SystemContainer]) -> Vec<(usize, usize, Vec<
// .take(index_a)
{
if !processed.contains(index_b) {
let a_access = systems[index_a].component_access();
let b_access = systems[index_b].component_access();
if let (Some(a), Some(b)) = (a_access, b_access) {
let conflicts = a.get_conflicts(b);
let system_a = &systems[index_a];
let system_b = &systems[index_b];
if system_a.is_exclusive() || system_b.is_exclusive() {
ambiguities.push((index_a, index_b, Vec::new()));
} else {
let a_access = systems[index_a].component_access();
let b_access = systems[index_b].component_access();
let conflicts = a_access.get_conflicts(b_access);
if !conflicts.is_empty() {
ambiguities.push((index_a, index_b, conflicts));
}
} else {
ambiguities.push((index_a, index_b, Vec::new()));
}
}
}
@ -467,12 +468,12 @@ mod tests {
let mut test_stage = SystemStage::parallel();
test_stage
// All 3 of these conflict with each other
.add_system(write_world_system.exclusive_system())
.add_system(write_world_system.exclusive_system().at_end())
.add_system(res_system.exclusive_system())
.add_system(write_world_system)
.add_system(write_world_system.at_end())
.add_system(res_system.at_start())
// These do not, as they're in different segments of the stage
.add_system(write_world_system.exclusive_system().at_start())
.add_system(write_world_system.exclusive_system().before_commands());
.add_system(write_world_system.at_start())
.add_system(write_world_system.before_commands());
test_stage.run(&mut world);

View file

@ -1,11 +1,11 @@
use crate::{schedule::ParallelSystemContainer, world::World};
use crate::{schedule::SystemContainer, world::World};
use downcast_rs::{impl_downcast, Downcast};
pub trait ParallelSystemExecutor: Downcast + Send + Sync {
/// Called by `SystemStage` whenever `systems` have been changed.
fn rebuild_cached_data(&mut self, systems: &[ParallelSystemContainer]);
fn rebuild_cached_data(&mut self, systems: &[SystemContainer]);
fn run_systems(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World);
fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World);
}
impl_downcast!(ParallelSystemExecutor);
@ -14,9 +14,9 @@ impl_downcast!(ParallelSystemExecutor);
pub struct SingleThreadedExecutor;
impl ParallelSystemExecutor for SingleThreadedExecutor {
fn rebuild_cached_data(&mut self, _: &[ParallelSystemContainer]) {}
fn rebuild_cached_data(&mut self, _: &[SystemContainer]) {}
fn run_systems(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World) {
fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World) {
for system in systems {
if system.should_run() {
#[cfg(feature = "trace")]

View file

@ -1,7 +1,7 @@
use crate::{
archetype::ArchetypeComponentId,
query::Access,
schedule::{ParallelSystemContainer, ParallelSystemExecutor},
schedule::{ParallelSystemExecutor, SystemContainer},
world::World,
};
use async_channel::{Receiver, Sender};
@ -77,7 +77,7 @@ impl Default for ParallelExecutor {
}
impl ParallelSystemExecutor for ParallelExecutor {
fn rebuild_cached_data(&mut self, systems: &[ParallelSystemContainer]) {
fn rebuild_cached_data(&mut self, systems: &[SystemContainer]) {
self.system_metadata.clear();
self.queued.grow(systems.len());
self.running.grow(systems.len());
@ -104,7 +104,7 @@ impl ParallelSystemExecutor for ParallelExecutor {
}
}
fn run_systems(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World) {
fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World) {
#[cfg(test)]
if self.events_sender.is_none() {
let (sender, receiver) = async_channel::unbounded::<SchedulingEvent>();
@ -167,7 +167,7 @@ impl ParallelExecutor {
fn prepare_systems<'scope>(
&mut self,
scope: &mut Scope<'scope, ()>,
systems: &'scope mut [ParallelSystemContainer],
systems: &'scope mut [SystemContainer],
world: &'scope World,
) {
// These are used as a part of a unit test.

View file

@ -5,11 +5,10 @@ use crate::{
prelude::IntoSystem,
schedule::{
graph_utils::{self, DependencyGraphError},
BoxedRunCriteria, DuplicateLabelStrategy, ExclusiveSystemContainer, GraphNode,
InsertionPoint, ParallelExecutor, ParallelSystemContainer, ParallelSystemExecutor,
RunCriteriaContainer, RunCriteriaDescriptor, RunCriteriaDescriptorOrLabel,
RunCriteriaInner, RunCriteriaLabelId, ShouldRun, SingleThreadedExecutor, SystemContainer,
SystemDescriptor, SystemLabelId, SystemSet,
BoxedRunCriteria, DuplicateLabelStrategy, ExclusiveInsertionPoint, GraphNode,
ParallelExecutor, ParallelSystemExecutor, RunCriteriaContainer, RunCriteriaDescriptor,
RunCriteriaDescriptorOrLabel, RunCriteriaInner, RunCriteriaLabelId, ShouldRun,
SingleThreadedExecutor, SystemContainer, SystemDescriptor, SystemLabelId, SystemSet,
},
world::{World, WorldId},
};
@ -62,14 +61,14 @@ pub struct SystemStage {
/// Topologically sorted run criteria of systems.
run_criteria: Vec<RunCriteriaContainer>,
/// Topologically sorted exclusive systems that want to be run at the start of the stage.
pub(super) exclusive_at_start: Vec<ExclusiveSystemContainer>,
pub(super) exclusive_at_start: Vec<SystemContainer>,
/// Topologically sorted exclusive systems that want to be run after parallel systems but
/// before the application of their command buffers.
pub(super) exclusive_before_commands: Vec<ExclusiveSystemContainer>,
pub(super) exclusive_before_commands: Vec<SystemContainer>,
/// Topologically sorted exclusive systems that want to be run at the end of the stage.
pub(super) exclusive_at_end: Vec<ExclusiveSystemContainer>,
pub(super) exclusive_at_end: Vec<SystemContainer>,
/// Topologically sorted parallel systems.
pub(super) parallel: Vec<ParallelSystemContainer>,
pub(super) parallel: Vec<SystemContainer>,
/// Determines if the stage was modified and needs to rebuild its graphs and orders.
pub(super) systems_modified: bool,
/// Determines if the stage's executor was changed.
@ -156,63 +155,63 @@ impl SystemStage {
self
}
fn add_system_inner(&mut self, system: SystemDescriptor, default_run_criteria: Option<usize>) {
fn add_system_inner(
&mut self,
mut descriptor: SystemDescriptor,
default_run_criteria: Option<usize>,
) {
self.systems_modified = true;
match system {
SystemDescriptor::Exclusive(mut descriptor) => {
let insertion_point = descriptor.insertion_point;
let criteria = descriptor.run_criteria.take();
let mut container = ExclusiveSystemContainer::from_descriptor(descriptor);
match criteria {
Some(RunCriteriaDescriptorOrLabel::Label(label)) => {
container.run_criteria_label = Some(label);
}
Some(RunCriteriaDescriptorOrLabel::Descriptor(criteria_descriptor)) => {
container.run_criteria_label = criteria_descriptor.label;
container.run_criteria_index =
Some(self.add_run_criteria_internal(criteria_descriptor));
}
None => {
container.run_criteria_index = default_run_criteria;
}
if let Some(insertion_point) = descriptor.exclusive_insertion_point {
let criteria = descriptor.run_criteria.take();
let mut container = SystemContainer::from_descriptor(descriptor);
match criteria {
Some(RunCriteriaDescriptorOrLabel::Label(label)) => {
container.run_criteria_label = Some(label);
}
match insertion_point {
InsertionPoint::AtStart => {
let index = self.exclusive_at_start.len();
self.uninitialized_at_start.push(index);
self.exclusive_at_start.push(container);
}
InsertionPoint::BeforeCommands => {
let index = self.exclusive_before_commands.len();
self.uninitialized_before_commands.push(index);
self.exclusive_before_commands.push(container);
}
InsertionPoint::AtEnd => {
let index = self.exclusive_at_end.len();
self.uninitialized_at_end.push(index);
self.exclusive_at_end.push(container);
}
Some(RunCriteriaDescriptorOrLabel::Descriptor(criteria_descriptor)) => {
container.run_criteria_label = criteria_descriptor.label;
container.run_criteria_index =
Some(self.add_run_criteria_internal(criteria_descriptor));
}
None => {
container.run_criteria_index = default_run_criteria;
}
}
SystemDescriptor::Parallel(mut descriptor) => {
let criteria = descriptor.run_criteria.take();
let mut container = ParallelSystemContainer::from_descriptor(descriptor);
match criteria {
Some(RunCriteriaDescriptorOrLabel::Label(label)) => {
container.run_criteria_label = Some(label);
}
Some(RunCriteriaDescriptorOrLabel::Descriptor(criteria_descriptor)) => {
container.run_criteria_label = criteria_descriptor.label;
container.run_criteria_index =
Some(self.add_run_criteria_internal(criteria_descriptor));
}
None => {
container.run_criteria_index = default_run_criteria;
}
match insertion_point {
ExclusiveInsertionPoint::AtStart => {
let index = self.exclusive_at_start.len();
self.uninitialized_at_start.push(index);
self.exclusive_at_start.push(container);
}
ExclusiveInsertionPoint::BeforeCommands => {
let index = self.exclusive_before_commands.len();
self.uninitialized_before_commands.push(index);
self.exclusive_before_commands.push(container);
}
ExclusiveInsertionPoint::AtEnd => {
let index = self.exclusive_at_end.len();
self.uninitialized_at_end.push(index);
self.exclusive_at_end.push(container);
}
self.uninitialized_parallel.push(self.parallel.len());
self.parallel.push(container);
}
} else {
let criteria = descriptor.run_criteria.take();
let mut container = SystemContainer::from_descriptor(descriptor);
match criteria {
Some(RunCriteriaDescriptorOrLabel::Label(label)) => {
container.run_criteria_label = Some(label);
}
Some(RunCriteriaDescriptorOrLabel::Descriptor(criteria_descriptor)) => {
container.run_criteria_label = criteria_descriptor.label;
container.run_criteria_index =
Some(self.add_run_criteria_internal(criteria_descriptor));
}
None => {
container.run_criteria_index = default_run_criteria;
}
}
self.uninitialized_parallel.push(self.parallel.len());
self.parallel.push(container);
}
}
@ -233,21 +232,21 @@ impl SystemStage {
/// Topologically sorted parallel systems.
///
/// Note that systems won't be fully-formed until the stage has been run at least once.
pub fn parallel_systems(&self) -> &[impl SystemContainer] {
pub fn parallel_systems(&self) -> &[SystemContainer] {
&self.parallel
}
/// Topologically sorted exclusive systems that want to be run at the start of the stage.
///
/// Note that systems won't be fully-formed until the stage has been run at least once.
pub fn exclusive_at_start_systems(&self) -> &[impl SystemContainer] {
pub fn exclusive_at_start_systems(&self) -> &[SystemContainer] {
&self.exclusive_at_start
}
/// Topologically sorted exclusive systems that want to be run at the end of the stage.
///
/// Note that systems won't be fully-formed until the stage has been run at least once.
pub fn exclusive_at_end_systems(&self) -> &[impl SystemContainer] {
pub fn exclusive_at_end_systems(&self) -> &[SystemContainer] {
&self.exclusive_at_end
}
@ -255,7 +254,7 @@ impl SystemStage {
/// before the application of their command buffers.
///
/// Note that systems won't be fully-formed until the stage has been run at least once.
pub fn exclusive_before_commands_systems(&self) -> &[impl SystemContainer] {
pub fn exclusive_before_commands_systems(&self) -> &[SystemContainer] {
&self.exclusive_before_commands
}
@ -270,17 +269,12 @@ impl SystemStage {
let (run_criteria, mut systems) = system_set.bake();
let set_run_criteria_index = run_criteria.and_then(|criteria| {
// validate that no systems have criteria
for system in &mut systems {
if let Some(name) = match system {
SystemDescriptor::Exclusive(descriptor) => descriptor
.run_criteria
.is_some()
.then(|| descriptor.system.name()),
SystemDescriptor::Parallel(descriptor) => descriptor
.run_criteria
.is_some()
.then(|| descriptor.system.name()),
} {
for descriptor in &mut systems {
if let Some(name) = descriptor
.run_criteria
.is_some()
.then(|| descriptor.system.name())
{
panic!(
"The system {} has a run criteria, but its `SystemSet` also has a run \
criteria. This is not supported. Consider moving the system into a \
@ -295,16 +289,7 @@ impl SystemStage {
}
RunCriteriaDescriptorOrLabel::Label(label) => {
for system in &mut systems {
match system {
SystemDescriptor::Exclusive(descriptor) => {
descriptor.run_criteria =
Some(RunCriteriaDescriptorOrLabel::Label(label));
}
SystemDescriptor::Parallel(descriptor) => {
descriptor.run_criteria =
Some(RunCriteriaDescriptorOrLabel::Label(label));
}
}
system.run_criteria = Some(RunCriteriaDescriptorOrLabel::Label(label));
}
None
@ -503,8 +488,7 @@ impl SystemStage {
fn check_uses_resource(&self, resource_id: ComponentId, world: &World) {
debug_assert!(!self.systems_modified);
for system in &self.parallel {
let access = system.component_access().unwrap();
if !access.has_read(resource_id) {
if !system.component_access().has_read(resource_id) {
let component_name = world.components().get_info(resource_id).unwrap().name();
warn!(
"System {} doesn't access resource {component_name}, despite being required to",
@ -578,8 +562,8 @@ impl SystemStage {
}
}
fn update_run_criteria_indices<T: SystemContainer>(
systems: &mut [T],
fn update_run_criteria_indices(
systems: &mut [SystemContainer],
order_inverted: &[(usize, &usize)],
) {
for system in systems {
@ -605,7 +589,7 @@ impl SystemStage {
/// Sorts given system containers topologically, populates their resolved dependencies
/// and run criteria.
fn process_systems(
systems: &mut Vec<impl SystemContainer>,
systems: &mut Vec<SystemContainer>,
run_criteria_labels: &HashMap<RunCriteriaLabelId, usize>,
) -> Result<(), DependencyGraphError<HashSet<SystemLabelId>>> {
let mut graph = graph_utils::build_dependency_graph(systems);
@ -701,7 +685,7 @@ impl Stage for SystemStage {
run_system_loop = false;
fn should_run(
container: &impl SystemContainer,
container: &SystemContainer,
run_criteria: &[RunCriteriaContainer],
default: ShouldRun,
) -> bool {
@ -717,13 +701,24 @@ impl Stage for SystemStage {
// Run systems that want to be at the start of stage.
for container in &mut self.exclusive_at_start {
if should_run(container, &self.run_criteria, default_should_run) {
#[cfg(feature = "trace")]
let _system_span = bevy_utils::tracing::info_span!(
"exclusive_system",
name = &*container.name()
)
.entered();
container.system_mut().run(world);
{
#[cfg(feature = "trace")]
let _system_span = bevy_utils::tracing::info_span!(
"exclusive_system",
name = &*container.name()
)
.entered();
container.system_mut().run((), world);
}
{
#[cfg(feature = "trace")]
let _system_span = bevy_utils::tracing::info_span!(
"system_commands",
name = &*container.name()
)
.entered();
container.system_mut().apply_buffers(world);
}
}
}
@ -738,13 +733,24 @@ impl Stage for SystemStage {
// Run systems that want to be between parallel systems and their command buffers.
for container in &mut self.exclusive_before_commands {
if should_run(container, &self.run_criteria, default_should_run) {
#[cfg(feature = "trace")]
let _system_span = bevy_utils::tracing::info_span!(
"exclusive_system",
name = &*container.name()
)
.entered();
container.system_mut().run(world);
{
#[cfg(feature = "trace")]
let _system_span = bevy_utils::tracing::info_span!(
"exclusive_system",
name = &*container.name()
)
.entered();
container.system_mut().run((), world);
}
{
#[cfg(feature = "trace")]
let _system_span = bevy_utils::tracing::info_span!(
"system_commands",
name = &*container.name()
)
.entered();
container.system_mut().apply_buffers(world);
}
}
}
@ -766,13 +772,24 @@ impl Stage for SystemStage {
// Run systems that want to be at the end of stage.
for container in &mut self.exclusive_at_end {
if should_run(container, &self.run_criteria, default_should_run) {
#[cfg(feature = "trace")]
let _system_span = bevy_utils::tracing::info_span!(
"exclusive_system",
name = &*container.name()
)
.entered();
container.system_mut().run(world);
{
#[cfg(feature = "trace")]
let _system_span = bevy_utils::tracing::info_span!(
"exclusive_system",
name = &*container.name()
)
.entered();
container.system_mut().run((), world);
}
{
#[cfg(feature = "trace")]
let _system_span = bevy_utils::tracing::info_span!(
"system_commands",
name = &*container.name()
)
.entered();
container.system_mut().apply_buffers(world);
}
}
}
@ -826,11 +843,10 @@ mod tests {
use crate::{
schedule::{
ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion, RunCriteria,
RunCriteriaDescriptorCoercion, ShouldRun, SingleThreadedExecutor, Stage, SystemLabel,
SystemSet, SystemStage,
IntoSystemDescriptor, RunCriteria, RunCriteriaDescriptorCoercion, ShouldRun,
SingleThreadedExecutor, Stage, SystemLabel, SystemSet, SystemStage,
},
system::{In, IntoExclusiveSystem, Local, Query, ResMut},
system::{In, Local, Query, ResMut},
world::World,
};
@ -868,10 +884,10 @@ mod tests {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(0).exclusive_system().at_start())
.with_system(make_exclusive(0).at_start())
.with_system(make_parallel(1))
.with_system(make_exclusive(2).exclusive_system().before_commands())
.with_system(make_exclusive(3).exclusive_system().at_end());
.with_system(make_exclusive(2).before_commands())
.with_system(make_exclusive(3).at_end());
stage.run(&mut world);
assert_eq!(world.resource_mut::<EntityCount>().0, vec![0, 1, 2, 3]);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
@ -883,10 +899,10 @@ mod tests {
world.resource_mut::<EntityCount>().0.clear();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(2).exclusive_system().before_commands())
.with_system(make_exclusive(3).exclusive_system().at_end())
.with_system(make_exclusive(2).before_commands())
.with_system(make_exclusive(3).at_end())
.with_system(make_parallel(1))
.with_system(make_exclusive(0).exclusive_system().at_start());
.with_system(make_exclusive(0).at_start());
stage.run(&mut world);
assert_eq!(world.resource::<EntityCount>().0, vec![0, 1, 2, 3]);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
@ -898,10 +914,10 @@ mod tests {
world.resource_mut::<EntityCount>().0.clear();
let mut stage = SystemStage::parallel()
.with_system(make_parallel(2).exclusive_system().before_commands())
.with_system(make_parallel(3).exclusive_system().at_end())
.with_system(make_parallel(2).before_commands())
.with_system(make_parallel(3).at_end())
.with_system(make_parallel(1))
.with_system(make_parallel(0).exclusive_system().at_start());
.with_system(make_parallel(0).at_start());
stage.run(&mut world);
assert_eq!(world.resource::<EntityCount>().0, vec![0, 1, 2, 3]);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
@ -930,9 +946,9 @@ mod tests {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(1).exclusive_system().label(L1).after(L0))
.with_system(make_exclusive(2).exclusive_system().after(L1))
.with_system(make_exclusive(0).exclusive_system().label(L0));
.with_system(make_exclusive(1).label(L1).after(L0))
.with_system(make_exclusive(2).after(L1))
.with_system(make_exclusive(0).label(L0));
stage.run(&mut world);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
stage.run(&mut world);
@ -944,9 +960,9 @@ mod tests {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(1).exclusive_system().label(L1).before(L2))
.with_system(make_exclusive(2).exclusive_system().label(L2))
.with_system(make_exclusive(0).exclusive_system().before(L1));
.with_system(make_exclusive(1).label(L1).before(L2))
.with_system(make_exclusive(2).label(L2))
.with_system(make_exclusive(0).before(L1));
stage.run(&mut world);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
stage.run(&mut world);
@ -958,11 +974,11 @@ mod tests {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(2).exclusive_system().label(L2))
.with_system(make_exclusive(1).exclusive_system().after(L0).before(L2))
.with_system(make_exclusive(0).exclusive_system().label(L0))
.with_system(make_exclusive(4).exclusive_system().label(L4))
.with_system(make_exclusive(3).exclusive_system().after(L2).before(L4));
.with_system(make_exclusive(2).label(L2))
.with_system(make_exclusive(1).after(L0).before(L2))
.with_system(make_exclusive(0).label(L0))
.with_system(make_exclusive(4).label(L4))
.with_system(make_exclusive(3).after(L2).before(L4));
stage.run(&mut world);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
stage.run(&mut world);
@ -977,9 +993,9 @@ mod tests {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(1).exclusive_system().label(First).after(L0))
.with_system(make_exclusive(2).exclusive_system().after(First))
.with_system(make_exclusive(0).exclusive_system().label(First).label(L0));
.with_system(make_exclusive(1).label(First).after(L0))
.with_system(make_exclusive(2).after(First))
.with_system(make_exclusive(0).label(First).label(L0));
stage.run(&mut world);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
stage.run(&mut world);
@ -987,11 +1003,11 @@ mod tests {
world.resource_mut::<EntityCount>().0.clear();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(2).exclusive_system().after(L01).label(L2))
.with_system(make_exclusive(1).exclusive_system().label(L01).after(L0))
.with_system(make_exclusive(0).exclusive_system().label(L01).label(L0))
.with_system(make_exclusive(4).exclusive_system().label(L4))
.with_system(make_exclusive(3).exclusive_system().after(L2).before(L4));
.with_system(make_exclusive(2).after(L01).label(L2))
.with_system(make_exclusive(1).label(L01).after(L0))
.with_system(make_exclusive(0).label(L01).label(L0))
.with_system(make_exclusive(4).label(L4))
.with_system(make_exclusive(3).after(L2).before(L4));
stage.run(&mut world);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
stage.run(&mut world);
@ -1002,17 +1018,11 @@ mod tests {
world.resource_mut::<EntityCount>().0.clear();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(2).exclusive_system().label(L234).label(L2))
.with_system(make_exclusive(1).exclusive_system().before(L234).after(L0))
.with_system(make_exclusive(0).exclusive_system().label(L0))
.with_system(make_exclusive(4).exclusive_system().label(L234).label(L4))
.with_system(
make_exclusive(3)
.exclusive_system()
.label(L234)
.after(L2)
.before(L4),
);
.with_system(make_exclusive(2).label(L234).label(L2))
.with_system(make_exclusive(1).before(L234).after(L0))
.with_system(make_exclusive(0).label(L0))
.with_system(make_exclusive(4).label(L234).label(L4))
.with_system(make_exclusive(3).label(L234).after(L2).before(L4));
stage.run(&mut world);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
stage.run(&mut world);
@ -1027,31 +1037,11 @@ mod tests {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(
make_exclusive(2)
.exclusive_system()
.label(L2)
.after(L1)
.before(L3)
.before(L3),
)
.with_system(
make_exclusive(1)
.exclusive_system()
.label(L1)
.after(L0)
.after(L0)
.before(L2),
)
.with_system(make_exclusive(0).exclusive_system().label(L0).before(L1))
.with_system(make_exclusive(4).exclusive_system().label(L4).after(L3))
.with_system(
make_exclusive(3)
.exclusive_system()
.label(L3)
.after(L2)
.before(L4),
);
.with_system(make_exclusive(2).label(L2).after(L1).before(L3).before(L3))
.with_system(make_exclusive(1).label(L1).after(L0).after(L0).before(L2))
.with_system(make_exclusive(0).label(L0).before(L1))
.with_system(make_exclusive(4).label(L4).after(L3))
.with_system(make_exclusive(3).label(L3).after(L2).before(L4));
stage.run(&mut world);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
stage.run(&mut world);
@ -1066,14 +1056,14 @@ mod tests {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(2).exclusive_system().label(L2))
.with_system(make_exclusive(2).label(L2))
.with_system_set(
SystemSet::new()
.with_system(make_exclusive(0).exclusive_system().label(L0))
.with_system(make_exclusive(4).exclusive_system().label(L4))
.with_system(make_exclusive(3).exclusive_system().after(L2).before(L4)),
.with_system(make_exclusive(0).label(L0))
.with_system(make_exclusive(4).label(L4))
.with_system(make_exclusive(3).after(L2).before(L4)),
)
.with_system(make_exclusive(1).exclusive_system().after(L0).before(L2));
.with_system(make_exclusive(1).after(L0).before(L2));
stage.run(&mut world);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
stage.run(&mut world);
@ -1088,13 +1078,13 @@ mod tests {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(0).exclusive_system().before(L1))
.with_system(make_exclusive(0).before(L1))
.with_system_set(
SystemSet::new()
.with_run_criteria(every_other_time)
.with_system(make_exclusive(1).exclusive_system().label(L1)),
.with_system(make_exclusive(1).label(L1)),
)
.with_system(make_exclusive(2).exclusive_system().after(L1));
.with_system(make_exclusive(2).after(L1));
stage.run(&mut world);
stage.run(&mut world);
stage.set_executor(Box::new(SingleThreadedExecutor::default()));
@ -1111,8 +1101,7 @@ mod tests {
fn exclusive_cycle_1() {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(0).exclusive_system().label(L0).after(L0));
let mut stage = SystemStage::parallel().with_system(make_exclusive(0).label(L0).after(L0));
stage.run(&mut world);
}
@ -1122,8 +1111,8 @@ mod tests {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(0).exclusive_system().label(L0).after(L1))
.with_system(make_exclusive(1).exclusive_system().label(L1).after(L0));
.with_system(make_exclusive(0).label(L0).after(L1))
.with_system(make_exclusive(1).label(L1).after(L0));
stage.run(&mut world);
}
@ -1133,9 +1122,9 @@ mod tests {
let mut world = World::new();
world.init_resource::<EntityCount>();
let mut stage = SystemStage::parallel()
.with_system(make_exclusive(0).exclusive_system().label(L0))
.with_system(make_exclusive(1).exclusive_system().after(L0).before(L2))
.with_system(make_exclusive(2).exclusive_system().label(L2).before(L0));
.with_system(make_exclusive(0).label(L0))
.with_system(make_exclusive(1).after(L0).before(L2))
.with_system(make_exclusive(2).label(L2).before(L0));
stage.run(&mut world);
}

View file

@ -1,117 +1,26 @@
use crate::{
component::ComponentId,
query::Access,
schedule::{
ExclusiveSystemDescriptor, GraphNode, ParallelSystemDescriptor, RunCriteriaLabelId,
SystemLabelId,
},
system::{ExclusiveSystem, System},
schedule::{GraphNode, RunCriteriaLabelId, SystemDescriptor, SystemLabelId},
system::System,
};
use std::borrow::Cow;
/// System metadata like its name, labels, order requirements and component access.
pub trait SystemContainer: GraphNode<Label = SystemLabelId> {
#[doc(hidden)]
fn dependencies(&self) -> &[usize];
#[doc(hidden)]
fn set_dependencies(&mut self, dependencies: impl IntoIterator<Item = usize>);
#[doc(hidden)]
fn run_criteria(&self) -> Option<usize>;
#[doc(hidden)]
fn set_run_criteria(&mut self, index: usize);
fn run_criteria_label(&self) -> Option<&RunCriteriaLabelId>;
fn component_access(&self) -> Option<&Access<ComponentId>>;
}
pub(super) struct ExclusiveSystemContainer {
system: Box<dyn ExclusiveSystem>,
pub(super) run_criteria_index: Option<usize>,
pub(super) run_criteria_label: Option<RunCriteriaLabelId>,
dependencies: Vec<usize>,
labels: Vec<SystemLabelId>,
before: Vec<SystemLabelId>,
after: Vec<SystemLabelId>,
}
impl ExclusiveSystemContainer {
pub(super) fn from_descriptor(descriptor: ExclusiveSystemDescriptor) -> Self {
ExclusiveSystemContainer {
system: descriptor.system,
run_criteria_index: None,
run_criteria_label: None,
dependencies: Vec::new(),
labels: descriptor.labels,
before: descriptor.before,
after: descriptor.after,
}
}
pub(super) fn system_mut(&mut self) -> &mut Box<dyn ExclusiveSystem> {
&mut self.system
}
}
impl GraphNode for ExclusiveSystemContainer {
type Label = SystemLabelId;
fn name(&self) -> Cow<'static, str> {
self.system.name()
}
fn labels(&self) -> &[SystemLabelId] {
&self.labels
}
fn before(&self) -> &[SystemLabelId] {
&self.before
}
fn after(&self) -> &[SystemLabelId] {
&self.after
}
}
impl SystemContainer for ExclusiveSystemContainer {
fn dependencies(&self) -> &[usize] {
&self.dependencies
}
fn set_dependencies(&mut self, dependencies: impl IntoIterator<Item = usize>) {
self.dependencies.clear();
self.dependencies.extend(dependencies);
}
fn run_criteria(&self) -> Option<usize> {
self.run_criteria_index
}
fn set_run_criteria(&mut self, index: usize) {
self.run_criteria_index = Some(index);
}
fn run_criteria_label(&self) -> Option<&RunCriteriaLabelId> {
self.run_criteria_label.as_ref()
}
fn component_access(&self) -> Option<&Access<ComponentId>> {
None
}
}
pub struct ParallelSystemContainer {
pub struct SystemContainer {
system: Box<dyn System<In = (), Out = ()>>,
pub(crate) run_criteria_index: Option<usize>,
pub(crate) run_criteria_label: Option<RunCriteriaLabelId>,
pub(crate) should_run: bool,
is_exclusive: bool,
dependencies: Vec<usize>,
labels: Vec<SystemLabelId>,
before: Vec<SystemLabelId>,
after: Vec<SystemLabelId>,
}
impl ParallelSystemContainer {
pub(crate) fn from_descriptor(descriptor: ParallelSystemDescriptor) -> Self {
ParallelSystemContainer {
impl SystemContainer {
pub(crate) fn from_descriptor(descriptor: SystemDescriptor) -> Self {
SystemContainer {
system: descriptor.system,
should_run: false,
run_criteria_index: None,
@ -120,6 +29,7 @@ impl ParallelSystemContainer {
labels: descriptor.labels,
before: descriptor.before,
after: descriptor.after,
is_exclusive: descriptor.exclusive_insertion_point.is_some(),
}
}
@ -142,9 +52,34 @@ impl ParallelSystemContainer {
pub fn dependencies(&self) -> &[usize] {
&self.dependencies
}
pub fn set_dependencies(&mut self, dependencies: impl IntoIterator<Item = usize>) {
self.dependencies.clear();
self.dependencies.extend(dependencies);
}
pub fn run_criteria(&self) -> Option<usize> {
self.run_criteria_index
}
pub fn set_run_criteria(&mut self, index: usize) {
self.run_criteria_index = Some(index);
}
pub fn run_criteria_label(&self) -> Option<&RunCriteriaLabelId> {
self.run_criteria_label.as_ref()
}
pub fn component_access(&self) -> &Access<ComponentId> {
self.system().component_access()
}
pub fn is_exclusive(&self) -> bool {
self.is_exclusive
}
}
impl GraphNode for ParallelSystemContainer {
impl GraphNode for SystemContainer {
type Label = SystemLabelId;
fn name(&self) -> Cow<'static, str> {
@ -163,30 +98,3 @@ impl GraphNode for ParallelSystemContainer {
&self.after
}
}
impl SystemContainer for ParallelSystemContainer {
fn dependencies(&self) -> &[usize] {
&self.dependencies
}
fn set_dependencies(&mut self, dependencies: impl IntoIterator<Item = usize>) {
self.dependencies.clear();
self.dependencies.extend(dependencies);
}
fn run_criteria(&self) -> Option<usize> {
self.run_criteria_index
}
fn set_run_criteria(&mut self, index: usize) {
self.run_criteria_index = Some(index);
}
fn run_criteria_label(&self) -> Option<&RunCriteriaLabelId> {
self.run_criteria_label.as_ref()
}
fn component_access(&self) -> Option<&Access<ComponentId>> {
Some(self.system().component_access())
}
}

View file

@ -1,9 +1,6 @@
use crate::{
schedule::{IntoRunCriteria, RunCriteriaDescriptorOrLabel, SystemLabel, SystemLabelId},
system::{
AsSystemLabel, BoxedSystem, ExclusiveSystem, ExclusiveSystemCoerced, ExclusiveSystemFn,
IntoSystem,
},
system::{AsSystemLabel, BoxedSystem, IntoSystem},
};
/// Encapsulates a system and information on when it run in a `SystemStage`.
@ -32,301 +29,188 @@ use crate::{
/// SystemStage::parallel()
/// .with_system(do_something.label(Something))
/// .with_system(do_the_other_thing.after(Something))
/// .with_system(do_something_else.exclusive_system().at_end());
/// .with_system(do_something_else.at_end());
/// ```
pub enum SystemDescriptor {
Parallel(ParallelSystemDescriptor),
Exclusive(ExclusiveSystemDescriptor),
pub struct SystemDescriptor {
pub(crate) system: BoxedSystem<(), ()>,
pub(crate) exclusive_insertion_point: Option<ExclusiveInsertionPoint>,
pub(crate) run_criteria: Option<RunCriteriaDescriptorOrLabel>,
pub(crate) labels: Vec<SystemLabelId>,
pub(crate) before: Vec<SystemLabelId>,
pub(crate) after: Vec<SystemLabelId>,
}
impl SystemDescriptor {
fn new(system: BoxedSystem<(), ()>) -> SystemDescriptor {
SystemDescriptor {
labels: system.default_labels(),
exclusive_insertion_point: if system.is_exclusive() {
Some(ExclusiveInsertionPoint::AtStart)
} else {
None
},
system,
run_criteria: None,
before: Vec::new(),
after: Vec::new(),
}
}
}
pub trait IntoSystemDescriptor<Params> {
fn into_descriptor(self) -> SystemDescriptor;
}
impl IntoSystemDescriptor<()> for ParallelSystemDescriptor {
fn into_descriptor(self) -> SystemDescriptor {
SystemDescriptor::Parallel(self)
}
}
impl<Params, S> IntoSystemDescriptor<Params> for S
where
S: IntoSystem<(), (), Params>,
{
fn into_descriptor(self) -> SystemDescriptor {
new_parallel_descriptor(Box::new(IntoSystem::into_system(self))).into_descriptor()
}
}
impl IntoSystemDescriptor<()> for SystemDescriptor {
fn into_descriptor(self) -> SystemDescriptor {
self
}
}
impl IntoSystemDescriptor<()> for BoxedSystem<(), ()> {
fn into_descriptor(self) -> SystemDescriptor {
new_parallel_descriptor(self).into_descriptor()
}
}
impl IntoSystemDescriptor<()> for ExclusiveSystemDescriptor {
fn into_descriptor(self) -> SystemDescriptor {
SystemDescriptor::Exclusive(self)
}
}
impl<F> IntoSystemDescriptor<()> for ExclusiveSystemFn<F>
where
F: FnMut(&mut crate::prelude::World) + Send + Sync + 'static,
{
fn into_descriptor(self) -> SystemDescriptor {
new_exclusive_descriptor(Box::new(self)).into_descriptor()
}
}
impl IntoSystemDescriptor<()> for ExclusiveSystemCoerced {
fn into_descriptor(self) -> SystemDescriptor {
new_exclusive_descriptor(Box::new(self)).into_descriptor()
}
}
/// Encapsulates a parallel system and information on when it runs in a `SystemStage`.
pub struct ParallelSystemDescriptor {
pub(crate) system: BoxedSystem<(), ()>,
pub(crate) run_criteria: Option<RunCriteriaDescriptorOrLabel>,
pub(crate) labels: Vec<SystemLabelId>,
pub(crate) before: Vec<SystemLabelId>,
pub(crate) after: Vec<SystemLabelId>,
}
fn new_parallel_descriptor(system: BoxedSystem<(), ()>) -> ParallelSystemDescriptor {
ParallelSystemDescriptor {
labels: system.default_labels(),
system,
run_criteria: None,
before: Vec::new(),
after: Vec::new(),
}
}
pub trait ParallelSystemDescriptorCoercion<Params> {
/// Assigns a run criteria to the system. Can be a new descriptor or a label of a
/// run criteria defined elsewhere.
fn with_run_criteria<Marker>(
self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> ParallelSystemDescriptor;
) -> SystemDescriptor;
/// Assigns a label to the system; there can be more than one, and it doesn't have to be unique.
fn label(self, label: impl SystemLabel) -> ParallelSystemDescriptor;
fn label(self, label: impl SystemLabel) -> SystemDescriptor;
/// Specifies that the system should run before systems with the given label.
fn before<Marker>(self, label: impl AsSystemLabel<Marker>) -> ParallelSystemDescriptor;
fn before<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor;
/// Specifies that the system should run after systems with the given label.
fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> ParallelSystemDescriptor;
fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor;
/// Specifies that the system should run with other exclusive systems at the start of stage.
fn at_start(self) -> SystemDescriptor;
/// Specifies that the system should run with other exclusive systems after the parallel
/// systems and before command buffer application.
fn before_commands(self) -> SystemDescriptor;
/// Specifies that the system should run with other exclusive systems at the end of stage.
fn at_end(self) -> SystemDescriptor;
}
impl ParallelSystemDescriptorCoercion<()> for ParallelSystemDescriptor {
impl IntoSystemDescriptor<()> for SystemDescriptor {
fn with_run_criteria<Marker>(
mut self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> ParallelSystemDescriptor {
) -> SystemDescriptor {
self.run_criteria = Some(run_criteria.into());
self
}
fn label(mut self, label: impl SystemLabel) -> ParallelSystemDescriptor {
fn label(mut self, label: impl SystemLabel) -> SystemDescriptor {
self.labels.push(label.as_label());
self
}
fn before<Marker>(mut self, label: impl AsSystemLabel<Marker>) -> ParallelSystemDescriptor {
fn before<Marker>(mut self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
self.before.push(label.as_system_label().as_label());
self
}
fn after<Marker>(mut self, label: impl AsSystemLabel<Marker>) -> ParallelSystemDescriptor {
fn after<Marker>(mut self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
self.after.push(label.as_system_label().as_label());
self
}
fn at_start(mut self) -> SystemDescriptor {
self.exclusive_insertion_point = Some(ExclusiveInsertionPoint::AtStart);
self
}
fn before_commands(mut self) -> SystemDescriptor {
self.exclusive_insertion_point = Some(ExclusiveInsertionPoint::BeforeCommands);
self
}
fn at_end(mut self) -> SystemDescriptor {
self.exclusive_insertion_point = Some(ExclusiveInsertionPoint::AtEnd);
self
}
fn into_descriptor(self) -> SystemDescriptor {
self
}
}
impl<S, Params> ParallelSystemDescriptorCoercion<Params> for S
impl<S, Params> IntoSystemDescriptor<Params> for S
where
S: IntoSystem<(), (), Params>,
{
fn with_run_criteria<Marker>(
self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> ParallelSystemDescriptor {
new_parallel_descriptor(Box::new(IntoSystem::into_system(self)))
) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self)))
.with_run_criteria(run_criteria)
}
fn label(self, label: impl SystemLabel) -> ParallelSystemDescriptor {
new_parallel_descriptor(Box::new(IntoSystem::into_system(self))).label(label)
fn label(self, label: impl SystemLabel) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).label(label)
}
fn before<Marker>(self, label: impl AsSystemLabel<Marker>) -> ParallelSystemDescriptor {
new_parallel_descriptor(Box::new(IntoSystem::into_system(self))).before(label)
fn before<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).before(label)
}
fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> ParallelSystemDescriptor {
new_parallel_descriptor(Box::new(IntoSystem::into_system(self))).after(label)
fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).after(label)
}
fn at_start(self) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).at_start()
}
fn before_commands(self) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).before_commands()
}
fn at_end(self) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).at_end()
}
fn into_descriptor(self) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self)))
}
}
impl ParallelSystemDescriptorCoercion<()> for BoxedSystem<(), ()> {
impl IntoSystemDescriptor<()> for BoxedSystem<(), ()> {
fn with_run_criteria<Marker>(
self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> ParallelSystemDescriptor {
new_parallel_descriptor(self).with_run_criteria(run_criteria)
) -> SystemDescriptor {
SystemDescriptor::new(self).with_run_criteria(run_criteria)
}
fn label(self, label: impl SystemLabel) -> ParallelSystemDescriptor {
new_parallel_descriptor(self).label(label)
fn label(self, label: impl SystemLabel) -> SystemDescriptor {
SystemDescriptor::new(self).label(label)
}
fn before<Marker>(self, label: impl AsSystemLabel<Marker>) -> ParallelSystemDescriptor {
new_parallel_descriptor(self).before(label)
fn before<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
SystemDescriptor::new(self).before(label)
}
fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> ParallelSystemDescriptor {
new_parallel_descriptor(self).after(label)
fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
SystemDescriptor::new(self).after(label)
}
fn at_start(self) -> SystemDescriptor {
SystemDescriptor::new(self).at_start()
}
fn before_commands(self) -> SystemDescriptor {
SystemDescriptor::new(self).before_commands()
}
fn at_end(self) -> SystemDescriptor {
SystemDescriptor::new(self).at_end()
}
fn into_descriptor(self) -> SystemDescriptor {
SystemDescriptor::new(self)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum InsertionPoint {
pub(crate) enum ExclusiveInsertionPoint {
AtStart,
BeforeCommands,
AtEnd,
}
/// Encapsulates an exclusive system and information on when it runs in a `SystemStage`.
pub struct ExclusiveSystemDescriptor {
pub(crate) system: Box<dyn ExclusiveSystem>,
pub(crate) run_criteria: Option<RunCriteriaDescriptorOrLabel>,
pub(crate) labels: Vec<SystemLabelId>,
pub(crate) before: Vec<SystemLabelId>,
pub(crate) after: Vec<SystemLabelId>,
pub(crate) insertion_point: InsertionPoint,
}
fn new_exclusive_descriptor(system: Box<dyn ExclusiveSystem>) -> ExclusiveSystemDescriptor {
ExclusiveSystemDescriptor {
system,
run_criteria: None,
labels: Vec::new(),
before: Vec::new(),
after: Vec::new(),
insertion_point: InsertionPoint::AtStart,
}
}
pub trait ExclusiveSystemDescriptorCoercion {
/// Assigns a run criteria to the system. Can be a new descriptor or a label of a
/// run criteria defined elsewhere.
fn with_run_criteria<Marker>(
self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> ExclusiveSystemDescriptor;
/// Assigns a label to the system; there can be more than one, and it doesn't have to be unique.
fn label(self, label: impl SystemLabel) -> ExclusiveSystemDescriptor;
/// Specifies that the system should run before systems with the given label.
fn before(self, label: impl SystemLabel) -> ExclusiveSystemDescriptor;
/// Specifies that the system should run after systems with the given label.
fn after(self, label: impl SystemLabel) -> ExclusiveSystemDescriptor;
/// Specifies that the system should run with other exclusive systems at the start of stage.
fn at_start(self) -> ExclusiveSystemDescriptor;
/// Specifies that the system should run with other exclusive systems after the parallel
/// systems and before command buffer application.
fn before_commands(self) -> ExclusiveSystemDescriptor;
/// Specifies that the system should run with other exclusive systems at the end of stage.
fn at_end(self) -> ExclusiveSystemDescriptor;
}
impl ExclusiveSystemDescriptorCoercion for ExclusiveSystemDescriptor {
fn with_run_criteria<Marker>(
mut self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> ExclusiveSystemDescriptor {
self.run_criteria = Some(run_criteria.into());
self
}
fn label(mut self, label: impl SystemLabel) -> ExclusiveSystemDescriptor {
self.labels.push(label.as_label());
self
}
fn before(mut self, label: impl SystemLabel) -> ExclusiveSystemDescriptor {
self.before.push(label.as_label());
self
}
fn after(mut self, label: impl SystemLabel) -> ExclusiveSystemDescriptor {
self.after.push(label.as_label());
self
}
fn at_start(mut self) -> ExclusiveSystemDescriptor {
self.insertion_point = InsertionPoint::AtStart;
self
}
fn before_commands(mut self) -> ExclusiveSystemDescriptor {
self.insertion_point = InsertionPoint::BeforeCommands;
self
}
fn at_end(mut self) -> ExclusiveSystemDescriptor {
self.insertion_point = InsertionPoint::AtEnd;
self
}
}
impl<T> ExclusiveSystemDescriptorCoercion for T
where
T: ExclusiveSystem + 'static,
{
fn with_run_criteria<Marker>(
self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> ExclusiveSystemDescriptor {
new_exclusive_descriptor(Box::new(self)).with_run_criteria(run_criteria)
}
fn label(self, label: impl SystemLabel) -> ExclusiveSystemDescriptor {
new_exclusive_descriptor(Box::new(self)).label(label)
}
fn before(self, label: impl SystemLabel) -> ExclusiveSystemDescriptor {
new_exclusive_descriptor(Box::new(self)).before(label)
}
fn after(self, label: impl SystemLabel) -> ExclusiveSystemDescriptor {
new_exclusive_descriptor(Box::new(self)).after(label)
}
fn at_start(self) -> ExclusiveSystemDescriptor {
new_exclusive_descriptor(Box::new(self)).at_start()
}
fn before_commands(self) -> ExclusiveSystemDescriptor {
new_exclusive_descriptor(Box::new(self)).before_commands()
}
fn at_end(self) -> ExclusiveSystemDescriptor {
new_exclusive_descriptor(Box::new(self)).at_end()
}
}

View file

@ -107,18 +107,9 @@ impl SystemSet {
after,
} = self;
for descriptor in &mut systems {
match descriptor {
SystemDescriptor::Parallel(descriptor) => {
descriptor.labels.extend(labels.iter().cloned());
descriptor.before.extend(before.iter().cloned());
descriptor.after.extend(after.iter().cloned());
}
SystemDescriptor::Exclusive(descriptor) => {
descriptor.labels.extend(labels.iter().cloned());
descriptor.before.extend(before.iter().cloned());
descriptor.after.extend(after.iter().cloned());
}
}
descriptor.labels.extend(labels.iter().cloned());
descriptor.before.extend(before.iter().cloned());
descriptor.after.extend(after.iter().cloned());
}
(run_criteria, systems)
}

View file

@ -0,0 +1,203 @@
use crate::{
archetype::ArchetypeComponentId,
change_detection::MAX_CHANGE_AGE,
component::ComponentId,
query::Access,
schedule::{SystemLabel, SystemLabelId},
system::{
check_system_change_tick, AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamFetch,
ExclusiveSystemParamItem, ExclusiveSystemParamState, IntoSystem, System, SystemMeta,
SystemTypeIdLabel,
},
world::{World, WorldId},
};
use bevy_ecs_macros::all_tuples;
use std::{borrow::Cow, marker::PhantomData};
/// A function system that runs with exclusive [`World`] access.
///
/// You get this by calling [`IntoSystem::into_system`] on a function that only accepts
/// [`ExclusiveSystemParam`]s.
///
/// [`ExclusiveFunctionSystem`] must be `.initialized` before they can be run.
pub struct ExclusiveFunctionSystem<Param, Marker, F>
where
Param: ExclusiveSystemParam,
{
func: F,
param_state: Option<Param::Fetch>,
system_meta: SystemMeta,
world_id: Option<WorldId>,
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
marker: PhantomData<fn() -> Marker>,
}
pub struct IsExclusiveFunctionSystem;
impl<Param, Marker, F> IntoSystem<(), (), (IsExclusiveFunctionSystem, Param, Marker)> for F
where
Param: ExclusiveSystemParam + 'static,
Marker: 'static,
F: ExclusiveSystemParamFunction<Param, Marker> + Send + Sync + 'static,
{
type System = ExclusiveFunctionSystem<Param, Marker, F>;
fn into_system(func: Self) -> Self::System {
ExclusiveFunctionSystem {
func,
param_state: None,
system_meta: SystemMeta::new::<F>(),
world_id: None,
marker: PhantomData,
}
}
}
const PARAM_MESSAGE: &str = "System's param_state was not found. Did you forget to initialize this system before running it?";
impl<Param, Marker, F> System for ExclusiveFunctionSystem<Param, Marker, F>
where
Param: ExclusiveSystemParam + 'static,
Marker: 'static,
F: ExclusiveSystemParamFunction<Param, Marker> + Send + Sync + 'static,
{
type In = ();
type Out = ();
#[inline]
fn name(&self) -> Cow<'static, str> {
self.system_meta.name.clone()
}
#[inline]
fn component_access(&self) -> &Access<ComponentId> {
self.system_meta.component_access_set.combined_access()
}
#[inline]
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
&self.system_meta.archetype_component_access
}
#[inline]
fn is_send(&self) -> bool {
// exclusive systems should have access to non-send resources
// the executor runs exclusive systems on the main thread, so this
// field reflects that constraint
false
}
#[inline]
unsafe fn run_unsafe(&mut self, _input: Self::In, _world: &World) -> Self::Out {
panic!("Cannot run exclusive systems with a shared World reference");
}
fn run(&mut self, _input: Self::In, world: &mut World) -> Self::Out {
let saved_last_tick = world.last_change_tick;
world.last_change_tick = self.system_meta.last_change_tick;
let params = <Param as ExclusiveSystemParam>::Fetch::get_param(
self.param_state.as_mut().expect(PARAM_MESSAGE),
&self.system_meta,
);
self.func.run(world, params);
let change_tick = world.change_tick.get_mut();
self.system_meta.last_change_tick = *change_tick;
*change_tick += 1;
world.last_change_tick = saved_last_tick;
}
#[inline]
fn is_exclusive(&self) -> bool {
true
}
fn get_last_change_tick(&self) -> u32 {
self.system_meta.last_change_tick
}
fn set_last_change_tick(&mut self, last_change_tick: u32) {
self.system_meta.last_change_tick = last_change_tick;
}
#[inline]
fn apply_buffers(&mut self, world: &mut World) {
let param_state = self.param_state.as_mut().expect(PARAM_MESSAGE);
param_state.apply(world);
}
#[inline]
fn initialize(&mut self, world: &mut World) {
self.world_id = Some(world.id());
self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE);
self.param_state = Some(<Param::Fetch as ExclusiveSystemParamState>::init(
world,
&mut self.system_meta,
));
}
fn update_archetype_component_access(&mut self, _world: &World) {}
#[inline]
fn check_change_tick(&mut self, change_tick: u32) {
check_system_change_tick(
&mut self.system_meta.last_change_tick,
change_tick,
self.system_meta.name.as_ref(),
);
}
fn default_labels(&self) -> Vec<SystemLabelId> {
vec![self.func.as_system_label().as_label()]
}
}
impl<Param: ExclusiveSystemParam, Marker, T: ExclusiveSystemParamFunction<Param, Marker>>
AsSystemLabel<(Param, Marker, IsExclusiveFunctionSystem)> for T
{
#[inline]
fn as_system_label(&self) -> SystemLabelId {
SystemTypeIdLabel::<T>(PhantomData).as_label()
}
}
/// A trait implemented for all exclusive system functions that can be used as [`System`]s.
///
/// This trait can be useful for making your own systems which accept other systems,
/// sometimes called higher order systems.
pub trait ExclusiveSystemParamFunction<Param: ExclusiveSystemParam, Marker>:
Send + Sync + 'static
{
fn run(&mut self, world: &mut World, param_value: ExclusiveSystemParamItem<Param>);
}
macro_rules! impl_exclusive_system_function {
($($param: ident),*) => {
#[allow(non_snake_case)]
impl<Func: Send + Sync + 'static, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<($($param,)*), ()> for Func
where
for <'a> &'a mut Func:
FnMut(&mut World, $($param),*) +
FnMut(&mut World, $(ExclusiveSystemParamItem<$param>),*)
{
#[inline]
fn run(&mut self, world: &mut World, param_value: ExclusiveSystemParamItem< ($($param,)*)>) {
// Yes, this is strange, but `rustc` fails to compile this impl
// without using this function. It fails to recognise that `func`
// is a function, potentially because of the multiple impls of `FnMut`
#[allow(clippy::too_many_arguments)]
fn call_inner<$($param,)*>(
mut f: impl FnMut(&mut World, $($param,)*),
world: &mut World,
$($param: $param,)*
) {
f(world, $($param,)*)
}
let ($($param,)*) = param_value;
call_inner(self, world, $($param),*)
}
}
};
}
// Note that we rely on the highest impl to be <= the highest order of the tuple impls
// of `SystemParam` created.
all_tuples!(impl_exclusive_system_function, 0, 16, F);

View file

@ -1,177 +0,0 @@
use crate::{
change_detection::MAX_CHANGE_AGE,
system::{check_system_change_tick, BoxedSystem, IntoSystem},
world::World,
};
use std::borrow::Cow;
pub trait ExclusiveSystem: Send + Sync + 'static {
fn name(&self) -> Cow<'static, str>;
fn run(&mut self, world: &mut World);
fn initialize(&mut self, world: &mut World);
fn check_change_tick(&mut self, change_tick: u32);
}
pub struct ExclusiveSystemFn<F> {
func: F,
name: Cow<'static, str>,
last_change_tick: u32,
}
impl<F> ExclusiveSystem for ExclusiveSystemFn<F>
where
F: FnMut(&mut World) + Send + Sync + 'static,
{
fn name(&self) -> Cow<'static, str> {
self.name.clone()
}
fn run(&mut self, world: &mut World) {
// The previous value is saved in case this exclusive system is run by another exclusive
// system
let saved_last_tick = world.last_change_tick;
world.last_change_tick = self.last_change_tick;
(self.func)(world);
let change_tick = world.change_tick.get_mut();
self.last_change_tick = *change_tick;
*change_tick += 1;
world.last_change_tick = saved_last_tick;
}
fn initialize(&mut self, world: &mut World) {
self.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE);
}
fn check_change_tick(&mut self, change_tick: u32) {
check_system_change_tick(&mut self.last_change_tick, change_tick, self.name.as_ref());
}
}
pub trait IntoExclusiveSystem<Params, SystemType> {
fn exclusive_system(self) -> SystemType;
}
impl<F> IntoExclusiveSystem<&mut World, ExclusiveSystemFn<F>> for F
where
F: FnMut(&mut World) + Send + Sync + 'static,
{
fn exclusive_system(self) -> ExclusiveSystemFn<F> {
ExclusiveSystemFn {
func: self,
name: core::any::type_name::<F>().into(),
last_change_tick: 0,
}
}
}
pub struct ExclusiveSystemCoerced {
system: BoxedSystem<(), ()>,
}
impl ExclusiveSystem for ExclusiveSystemCoerced {
fn name(&self) -> Cow<'static, str> {
self.system.name()
}
fn run(&mut self, world: &mut World) {
self.system.run((), world);
self.system.apply_buffers(world);
}
fn initialize(&mut self, world: &mut World) {
self.system.initialize(world);
}
fn check_change_tick(&mut self, change_tick: u32) {
self.system.check_change_tick(change_tick);
}
}
impl<S, Params> IntoExclusiveSystem<Params, ExclusiveSystemCoerced> for S
where
S: IntoSystem<(), (), Params>,
{
fn exclusive_system(self) -> ExclusiveSystemCoerced {
ExclusiveSystemCoerced {
system: Box::new(IntoSystem::into_system(self)),
}
}
}
#[cfg(test)]
mod tests {
use crate::{
self as bevy_ecs,
component::Component,
entity::Entity,
query::With,
schedule::{Stage, SystemStage},
system::{Commands, IntoExclusiveSystem, Query, ResMut, Resource},
world::World,
};
#[derive(Component)]
struct Foo(f32);
#[test]
fn parallel_with_commands_as_exclusive() {
let mut world = World::new();
#[derive(Resource)]
struct Counter(usize);
fn removal(
mut commands: Commands,
query: Query<Entity, With<Foo>>,
mut counter: ResMut<Counter>,
) {
for entity in &query {
counter.0 += 1;
commands.entity(entity).remove::<Foo>();
}
}
let mut stage = SystemStage::parallel().with_system(removal);
world.spawn(Foo(0.0f32));
world.insert_resource(Counter(0));
stage.run(&mut world);
stage.run(&mut world);
assert_eq!(world.resource::<Counter>().0, 1);
let mut stage = SystemStage::parallel().with_system(removal.exclusive_system());
world.spawn(Foo(0.0f32));
world.insert_resource(Counter(0));
stage.run(&mut world);
stage.run(&mut world);
assert_eq!(world.resource::<Counter>().0, 1);
}
#[test]
fn update_archetype_for_exclusive_system_coerced() {
#[derive(Resource, Default)]
struct CountEntities(Vec<usize>);
fn spawn_entity(mut commands: crate::prelude::Commands) {
commands.spawn(Foo(0.0));
}
fn count_entities(query: Query<&Foo>, mut res: ResMut<CountEntities>) {
res.0.push(query.iter().len());
}
let mut world = World::new();
world.init_resource::<CountEntities>();
let mut stage = SystemStage::parallel()
.with_system(spawn_entity)
.with_system(count_entities.exclusive_system());
stage.run(&mut world);
stage.run(&mut world);
assert_eq!(world.resource::<CountEntities>().0, vec![0, 1]);
}
}

View file

@ -0,0 +1,130 @@
use crate::{
prelude::{FromWorld, QueryState},
query::{ReadOnlyWorldQuery, WorldQuery},
system::{Local, LocalState, SystemMeta, SystemParam, SystemState},
world::World,
};
use bevy_ecs_macros::all_tuples;
use bevy_utils::synccell::SyncCell;
pub trait ExclusiveSystemParam: Sized {
type Fetch: for<'s> ExclusiveSystemParamFetch<'s>;
}
pub type ExclusiveSystemParamItem<'s, P> =
<<P as ExclusiveSystemParam>::Fetch as ExclusiveSystemParamFetch<'s>>::Item;
/// The state of a [`SystemParam`].
pub trait ExclusiveSystemParamState: Send + Sync {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self;
#[inline]
fn apply(&mut self, _world: &mut World) {}
}
pub trait ExclusiveSystemParamFetch<'state>: ExclusiveSystemParamState {
type Item: ExclusiveSystemParam<Fetch = Self>;
fn get_param(state: &'state mut Self, system_meta: &SystemMeta) -> Self::Item;
}
impl<'a, Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> ExclusiveSystemParam
for &'a mut QueryState<Q, F>
{
type Fetch = QueryState<Q, F>;
}
impl<'s, Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> ExclusiveSystemParamFetch<'s>
for QueryState<Q, F>
{
type Item = &'s mut QueryState<Q, F>;
fn get_param(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item {
state
}
}
impl<Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> ExclusiveSystemParamState
for QueryState<Q, F>
{
fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self {
QueryState::new(world)
}
}
impl<'a, P: SystemParam + 'static> ExclusiveSystemParam for &'a mut SystemState<P> {
type Fetch = SystemState<P>;
}
impl<'s, P: SystemParam + 'static> ExclusiveSystemParamFetch<'s> for SystemState<P> {
type Item = &'s mut SystemState<P>;
fn get_param(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item {
state
}
}
impl<P: SystemParam> ExclusiveSystemParamState for SystemState<P> {
fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self {
SystemState::new(world)
}
}
impl<'s, T: FromWorld + Send + Sync + 'static> ExclusiveSystemParam for Local<'s, T> {
type Fetch = LocalState<T>;
}
impl<'s, T: FromWorld + Send + Sync + 'static> ExclusiveSystemParamFetch<'s> for LocalState<T> {
type Item = Local<'s, T>;
fn get_param(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item {
Local(state.0.get())
}
}
impl<T: FromWorld + Send + Sync> ExclusiveSystemParamState for LocalState<T> {
fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self {
Self(SyncCell::new(T::from_world(world)))
}
}
macro_rules! impl_exclusive_system_param_tuple {
($($param: ident),*) => {
impl<$($param: ExclusiveSystemParam),*> ExclusiveSystemParam for ($($param,)*) {
type Fetch = ($($param::Fetch,)*);
}
#[allow(unused_variables)]
#[allow(non_snake_case)]
impl<'s, $($param: ExclusiveSystemParamFetch<'s>),*> ExclusiveSystemParamFetch<'s> for ($($param,)*) {
type Item = ($($param::Item,)*);
#[inline]
#[allow(clippy::unused_unit)]
fn get_param(
state: &'s mut Self,
system_meta: &SystemMeta,
) -> Self::Item {
let ($($param,)*) = state;
($($param::get_param($param, system_meta),)*)
}
}
// SAFETY: implementors of each `ExclusiveSystemParamState` in the tuple have validated their impls
#[allow(clippy::undocumented_unsafe_blocks)] // false positive by clippy
#[allow(non_snake_case)]
impl<$($param: ExclusiveSystemParamState),*> ExclusiveSystemParamState for ($($param,)*) {
#[inline]
fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self {
(($($param::init(_world, _system_meta),)*))
}
#[inline]
fn apply(&mut self, _world: &mut World) {
let ($($param,)*) = self;
$($param.apply(_world);)*
}
}
};
}
all_tuples!(impl_exclusive_system_param_tuple, 0, 16, P);

View file

@ -27,7 +27,7 @@ pub struct SystemMeta {
}
impl SystemMeta {
fn new<T>() -> Self {
pub(crate) fn new<T>() -> Self {
Self {
name: std::any::type_name::<T>().into(),
archetype_component_access: Access::default(),
@ -112,8 +112,8 @@ impl SystemMeta {
///
/// struct MyEvent;
/// #[derive(Resource)]
/// struct CachedSystemState<'w, 's>{
/// event_state: SystemState<EventReader<'w, 's, MyEvent>>
/// struct CachedSystemState {
/// event_state: SystemState<EventReader<'static, 'static, MyEvent>>
/// }
///
/// // Create and store a system state once
@ -133,7 +133,7 @@ impl SystemMeta {
/// };
/// });
/// ```
pub struct SystemState<Param: SystemParam> {
pub struct SystemState<Param: SystemParam + 'static> {
meta: SystemMeta,
param_state: <Param as SystemParam>::Fetch,
world_id: WorldId,
@ -387,6 +387,11 @@ where
self.system_meta.is_send
}
#[inline]
fn is_exclusive(&self) -> bool {
false
}
#[inline]
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out {
let change_tick = world.increment_change_tick();
@ -459,7 +464,7 @@ where
}
/// A [`SystemLabel`] that was automatically generated for a system on the basis of its `TypeId`.
pub struct SystemTypeIdLabel<T: 'static>(PhantomData<fn() -> T>);
pub struct SystemTypeIdLabel<T: 'static>(pub(crate) PhantomData<fn() -> T>);
impl<T: 'static> SystemLabel for SystemTypeIdLabel<T> {
#[inline]

View file

@ -69,7 +69,8 @@
//! - [`()` (unit primitive type)](https://doc.rust-lang.org/stable/std/primitive.unit.html)
mod commands;
mod exclusive_system;
mod exclusive_function_system;
mod exclusive_system_param;
mod function_system;
mod query;
#[allow(clippy::module_inception)]
@ -78,7 +79,8 @@ mod system_chaining;
mod system_param;
pub use commands::*;
pub use exclusive_system::*;
pub use exclusive_function_system::*;
pub use exclusive_system_param::*;
pub use function_system::*;
pub use query::*;
pub use system::*;
@ -98,34 +100,6 @@ pub fn assert_is_system<In, Out, Params, S: IntoSystem<In, Out, Params>>(sys: S)
}
}
/// Ensure that a given function is an exclusive system
///
/// This should be used when writing doc examples,
/// to confirm that systems used in an example are
/// valid exclusive systems
///
/// Passing assert
/// ```
/// # use bevy_ecs::prelude::World;
/// # use bevy_ecs::system::assert_is_exclusive_system;
/// fn an_exclusive_system(_world: &mut World) {}
///
/// assert_is_exclusive_system(an_exclusive_system);
/// ```
///
/// Failing assert
/// ```compile_fail
/// # use bevy_ecs::prelude::World;
/// # use bevy_ecs::system::assert_is_exclusive_system;
/// fn not_an_exclusive_system(_world: &mut World, number: f32) {}
///
/// assert_is_exclusive_system(not_an_exclusive_system);
/// ```
pub fn assert_is_exclusive_system<Params, SystemType>(
_sys: impl IntoExclusiveSystem<Params, SystemType>,
) {
}
#[cfg(test)]
mod tests {
use std::any::TypeId;
@ -142,8 +116,8 @@ mod tests {
query::{Added, Changed, Or, With, Without},
schedule::{Schedule, Stage, SystemStage},
system::{
Commands, IntoExclusiveSystem, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query,
RemovedComponents, Res, ResMut, Resource, System, SystemState,
Commands, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, RemovedComponents,
Res, ResMut, Resource, System, SystemState,
},
world::{FromWorld, World},
};
@ -322,10 +296,7 @@ mod tests {
let mut update = SystemStage::parallel();
update.add_system(incr_e_on_flip);
schedule.add_stage(UpdateStage, update);
schedule.add_stage(
ClearTrackers,
SystemStage::single(World::clear_trackers.exclusive_system()),
);
schedule.add_stage(ClearTrackers, SystemStage::single(World::clear_trackers));
schedule.run(&mut world);
assert_eq!(world.resource::<Added>().0, 1);

View file

@ -32,6 +32,9 @@ pub trait System: Send + Sync + 'static {
/// Returns true if the system is [`Send`].
fn is_send(&self) -> bool;
/// Returns true if the system must be run exclusively.
fn is_exclusive(&self) -> bool;
/// Runs the system with the given input in the world. Unlike [`System::run`], this function
/// takes a shared reference to [`World`] and may therefore break Rust's aliasing rules, making
/// it unsafe to call.

View file

@ -73,6 +73,10 @@ impl<SystemA: System, SystemB: System<In = SystemA::Out>> System for ChainSystem
self.system_a.is_send() && self.system_b.is_send()
}
fn is_exclusive(&self) -> bool {
self.system_a.is_exclusive() || self.system_b.is_exclusive()
}
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out {
let out = self.system_a.run_unsafe(input, world);
self.system_b.run_unsafe(out, world)

View file

@ -680,7 +680,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldState {
/// // .add_system(reset_to_system(my_config))
/// # assert_is_system(reset_to_system(Config(10)));
/// ```
pub struct Local<'a, T: FromWorld + Send + 'static>(&'a mut T);
pub struct Local<'a, T: FromWorld + Send + 'static>(pub(crate) &'a mut T);
// SAFETY: Local only accesses internal state
unsafe impl<T: Send + 'static> ReadOnlySystemParamFetch for LocalState<T> {}
@ -712,7 +712,7 @@ impl<'a, T: FromWorld + Send + Sync + 'static> DerefMut for Local<'a, T> {
/// The [`SystemParamState`] of [`Local<T>`].
#[doc(hidden)]
pub struct LocalState<T: Send + 'static>(SyncCell<T>);
pub struct LocalState<T: Send + 'static>(pub(crate) SyncCell<T>);
impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> {
type Fetch = LocalState<T>;

View file

@ -2,7 +2,7 @@ mod converter;
mod gilrs_system;
use bevy_app::{App, CoreStage, Plugin, StartupStage};
use bevy_ecs::schedule::ParallelSystemDescriptorCoercion;
use bevy_ecs::schedule::IntoSystemDescriptor;
use bevy_input::InputSystem;
use bevy_utils::tracing::error;
use gilrs::GilrsBuilder;

View file

@ -6,7 +6,7 @@ pub mod mouse;
pub mod touch;
pub use axis::*;
use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel};
use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel};
pub use input::*;
pub mod prelude {

View file

@ -142,7 +142,7 @@ impl Plugin for PbrPlugin {
// NOTE: Clusters need to have been added before update_clusters is run so
// add as an exclusive system
add_clusters
.exclusive_system()
.at_start()
.label(SimulationLightSystems::AddClusters),
)
.add_system_to_stage(
@ -219,7 +219,7 @@ impl Plugin for PbrPlugin {
// this is added as an exclusive system because it contributes new views. it must run (and have Commands applied)
// _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out
render::prepare_lights
.exclusive_system()
.at_start()
.label(RenderLightSystems::PrepareLights),
)
.add_system_to_stage(

View file

@ -10,7 +10,7 @@ use bevy_ecs::{
entity::Entity,
event::EventReader,
prelude::World,
schedule::ParallelSystemDescriptorCoercion,
schedule::IntoSystemDescriptor,
system::{
lifetimeless::{Read, SQuery, SRes},
Commands, Local, Query, Res, ResMut, Resource, SystemParamItem,

View file

@ -57,7 +57,7 @@ where
}
#[doc(hidden)]
pub struct ExtractState<P: SystemParam> {
pub struct ExtractState<P: SystemParam + 'static> {
state: SystemState<P>,
main_world_state: ResState<MainWorld>,
}

View file

@ -196,7 +196,7 @@ impl Plugin for RenderPlugin {
RenderStage::Render,
SystemStage::parallel()
.with_system(PipelineCache::process_pipeline_queue_system)
.with_system(render_system.exclusive_system().at_end()),
.with_system(render_system.at_end()),
)
.add_stage(RenderStage::Cleanup, SystemStage::parallel())
.init_resource::<RenderGraph>()

View file

@ -151,7 +151,7 @@ impl<P: PhaseItem> DrawFunctions<P> {
pub trait RenderCommand<P: PhaseItem> {
/// Specifies all ECS data required by [`RenderCommand::render`].
/// All parameters have to be read only.
type Param: SystemParam;
type Param: SystemParam + 'static;
/// Renders the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`].
fn render<'w>(
@ -168,7 +168,7 @@ pub enum RenderCommandResult {
}
pub trait EntityRenderCommand {
type Param: SystemParam;
type Param: SystemParam + 'static;
fn render<'w>(
view: Entity,
item: Entity,
@ -231,7 +231,10 @@ pub enum BatchResult {
IncompatibleItems,
}
impl<P: EntityPhaseItem, E: EntityRenderCommand> RenderCommand<P> for E {
impl<P: EntityPhaseItem, E: EntityRenderCommand> RenderCommand<P> for E
where
E::Param: 'static,
{
type Param = E::Param;
#[inline]
@ -292,7 +295,7 @@ all_tuples!(render_command_tuple_impl, 0, 15, C);
/// Wraps a [`RenderCommand`] into a state so that it can be used as a [`Draw`] function.
/// Therefore the [`RenderCommand::Param`] is queried from the ECS and passed to the command.
pub struct RenderCommandState<P: PhaseItem, C: RenderCommand<P>> {
pub struct RenderCommandState<P: PhaseItem + 'static, C: RenderCommand<P>> {
state: SystemState<C::Param>,
}

View file

@ -18,7 +18,7 @@ pub mod prelude {
use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_ecs::{schedule::ExclusiveSystemDescriptorCoercion, system::IntoExclusiveSystem};
use bevy_ecs::prelude::*;
#[derive(Default)]
pub struct ScenePlugin;
@ -29,10 +29,7 @@ impl Plugin for ScenePlugin {
.add_asset::<Scene>()
.init_asset_loader::<SceneLoader>()
.init_resource::<SceneSpawner>()
.add_system_to_stage(
CoreStage::PreUpdate,
scene_spawner_system.exclusive_system().at_end(),
)
.add_system_to_stage(CoreStage::PreUpdate, scene_spawner_system.at_end())
// Systems `*_bundle_spawner` must run before `scene_spawner_system`
.add_system_to_stage(CoreStage::PreUpdate, scene_spawner);
}

View file

@ -29,7 +29,7 @@ pub use texture_atlas_builder::*;
use bevy_app::prelude::*;
use bevy_asset::{AddAsset, Assets, HandleUntyped};
use bevy_core_pipeline::core_2d::Transparent2d;
use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel};
use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel};
use bevy_reflect::TypeUuid;
use bevy_render::{
render_phase::AddRenderCommand,

View file

@ -6,7 +6,7 @@ use bevy_ecs::{
entity::Entity,
event::EventReader,
prelude::{Bundle, World},
schedule::ParallelSystemDescriptorCoercion,
schedule::IntoSystemDescriptor,
system::{
lifetimeless::{Read, SQuery, SRes},
Commands, Local, Query, Res, ResMut, Resource, SystemParamItem,

View file

@ -28,7 +28,7 @@ pub mod prelude {
use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_ecs::{schedule::ParallelSystemDescriptorCoercion, system::Resource};
use bevy_ecs::{schedule::IntoSystemDescriptor, system::Resource};
use bevy_render::{RenderApp, RenderStage};
use bevy_sprite::SpriteSystem;
use bevy_window::ModifiesWindows;

View file

@ -189,6 +189,10 @@ impl System for FixedTimestep {
self.internal_system.is_send()
}
fn is_exclusive(&self) -> bool {
false
}
unsafe fn run_unsafe(&mut self, _input: (), world: &World) -> ShouldRun {
// SAFETY: this system inherits the internal system's component access and archetype component
// access, which means the caller has ensured running the internal system is safe

View file

@ -40,10 +40,7 @@ impl Plugin for TimePlugin {
.register_type::<Stopwatch>()
// time system is added as an "exclusive system" to ensure it runs before other systems
// in CoreStage::First
.add_system_to_stage(
CoreStage::First,
time_system.exclusive_system().at_start().label(TimeSystem),
);
.add_system_to_stage(CoreStage::First, time_system.at_start().label(TimeSystem));
}
}

View file

@ -27,7 +27,7 @@ pub mod prelude {
use bevy_app::prelude::*;
use bevy_ecs::{
schedule::{ParallelSystemDescriptorCoercion, SystemLabel},
schedule::{IntoSystemDescriptor, SystemLabel},
system::Resource,
};
use bevy_input::InputSystem;

View file

@ -25,7 +25,7 @@ pub mod prelude {
use bevy_app::prelude::*;
use bevy_ecs::{
event::Events,
schedule::{ParallelSystemDescriptorCoercion, SystemLabel},
schedule::{IntoSystemDescriptor, SystemLabel},
system::Resource,
};

View file

@ -316,13 +316,7 @@ fn main() {
MyStage::BeforeRound,
new_player_system.after(new_round_system),
)
.add_system_to_stage(
MyStage::BeforeRound,
// Systems which take `&mut World` as an argument must call `.exclusive_system()`.
// The following will not compile.
//.add_system_to_stage(MyStage::BeforeRound, exclusive_player_system)
exclusive_player_system.exclusive_system(),
)
.add_system_to_stage(MyStage::BeforeRound, exclusive_player_system)
.add_system_to_stage(MyStage::AfterRound, score_check_system)
.add_system_to_stage(
// We can ensure that `game_over_system` runs after `score_check_system` using explicit ordering

View file

@ -16,7 +16,7 @@ fn main() {
.add_plugins(DefaultPlugins)
.register_type::<ComponentA>()
.register_type::<ComponentB>()
.add_startup_system(save_scene_system.exclusive_system())
.add_startup_system(save_scene_system)
.add_startup_system(load_scene_system)
.add_startup_system(infotext_system)
.add_system(log_system)