Rename system chaining to system piping (#6230)

# Objective

> System chaining is a confusing name: it implies the ability to construct non-linear graphs, and suggests a sense of system ordering that is only incidentally true. Instead, it actually works by passing data from one system to the next, much like the pipe operator.

> In the accepted [stageless RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/45-stageless.md), this concept is renamed to piping, and "system chaining" is used to construct groups of systems with ordering dependencies between them.

Fixes #6225.

## Changelog

System chaining has been renamed to system piping to improve clarity (and free up the name for new ordering APIs). 

## Migration Guide

The `.chain(handler_system)` method on systems is now `.pipe(handler_system)`.
The `IntoChainSystem` trait is now `IntoPipeSystem`, and the `ChainSystem` struct is now `PipeSystem`.
This commit is contained in:
Alice Cecile 2022-10-11 15:21:12 +00:00
parent 6ce7ce208e
commit c0a93aa7a4
8 changed files with 62 additions and 60 deletions

View file

@ -872,12 +872,12 @@ category = "ECS (Entity Component System)"
wasm = false wasm = false
[[example]] [[example]]
name = "system_chaining" name = "system_piping"
path = "examples/ecs/system_chaining.rs" path = "examples/ecs/system_piping.rs"
[package.metadata.example.system_chaining] [package.metadata.example.system_piping]
name = "System Chaining" name = "System Piping"
description = "Chain two systems together, specifying a return type in a system (such as `Result`)" description = "Pipe the output of one system into a second, allowing you to handle any errors gracefully"
category = "ECS (Entity Component System)" category = "ECS (Entity Component System)"
wasm = false wasm = false

View file

@ -38,7 +38,7 @@ pub mod prelude {
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage, Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
}, },
system::{ system::{
adapter as system_adapter, Commands, In, IntoChainSystem, IntoSystem, Local, NonSend, adapter as system_adapter, Commands, In, IntoPipeSystem, IntoSystem, Local, NonSend,
NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut, NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut,
Resource, System, SystemParamFunction, Resource, System, SystemParamFunction,
}, },

View file

@ -3,7 +3,7 @@ use crate::{
RunCriteriaDescriptor, RunCriteriaDescriptorCoercion, RunCriteriaLabel, ShouldRun, RunCriteriaDescriptor, RunCriteriaDescriptorCoercion, RunCriteriaLabel, ShouldRun,
SystemSet, SystemSet,
}, },
system::{In, IntoChainSystem, Local, Res, ResMut, Resource}, system::{In, IntoPipeSystem, Local, Res, ResMut, Resource},
}; };
use std::{ use std::{
any::TypeId, any::TypeId,
@ -79,7 +79,7 @@ where
(move |state: Res<State<T>>| { (move |state: Res<State<T>>| {
state.stack.last().unwrap() == &pred && state.transition.is_none() state.stack.last().unwrap() == &pred && state.transition.is_none()
}) })
.chain(should_run_adapter::<T>) .pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>()) .after(DriverLabel::of::<T>())
} }
@ -95,7 +95,7 @@ where
Some(_) => false, Some(_) => false,
None => *is_inactive, None => *is_inactive,
}) })
.chain(should_run_adapter::<T>) .pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>()) .after(DriverLabel::of::<T>())
} }
@ -118,7 +118,7 @@ where
Some(_) => false, Some(_) => false,
None => *is_in_stack, None => *is_in_stack,
}) })
.chain(should_run_adapter::<T>) .pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>()) .after(DriverLabel::of::<T>())
} }
@ -133,7 +133,7 @@ where
_ => false, _ => false,
}) })
}) })
.chain(should_run_adapter::<T>) .pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>()) .after(DriverLabel::of::<T>())
} }
@ -148,7 +148,7 @@ where
_ => false, _ => false,
}) })
}) })
.chain(should_run_adapter::<T>) .pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>()) .after(DriverLabel::of::<T>())
} }
@ -162,7 +162,7 @@ where
_ => false, _ => false,
}) })
}) })
.chain(should_run_adapter::<T>) .pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>()) .after(DriverLabel::of::<T>())
} }
@ -176,7 +176,7 @@ where
_ => false, _ => false,
}) })
}) })
.chain(should_run_adapter::<T>) .pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>()) .after(DriverLabel::of::<T>())
} }

View file

@ -499,7 +499,7 @@ impl<T> Copy for SystemTypeIdLabel<T> {}
/// ///
/// # Example /// # Example
/// ///
/// To create something like [`ChainSystem`], but in entirely safe code. /// To create something like [`PipeSystem`], but in entirely safe code.
/// ///
/// ```rust /// ```rust
/// use std::num::ParseIntError; /// use std::num::ParseIntError;
@ -510,8 +510,8 @@ impl<T> Copy for SystemTypeIdLabel<T> {}
/// // Unfortunately, we need all of these generics. `A` is the first system, with its /// // Unfortunately, we need all of these generics. `A` is the first system, with its
/// // parameters and marker type required for coherence. `B` is the second system, and /// // parameters and marker type required for coherence. `B` is the second system, and
/// // the other generics are for the input/output types of `A` and `B`. /// // the other generics are for the input/output types of `A` and `B`.
/// /// Chain creates a new system which calls `a`, then calls `b` with the output of `a` /// /// Pipe creates a new system which calls `a`, then calls `b` with the output of `a`
/// pub fn chain<AIn, Shared, BOut, A, AParam, AMarker, B, BParam, BMarker>( /// pub fn pipe<AIn, Shared, BOut, A, AParam, AMarker, B, BParam, BMarker>(
/// mut a: A, /// mut a: A,
/// mut b: B, /// mut b: B,
/// ) -> impl FnMut(In<AIn>, ParamSet<(SystemParamItem<AParam>, SystemParamItem<BParam>)>) -> BOut /// ) -> impl FnMut(In<AIn>, ParamSet<(SystemParamItem<AParam>, SystemParamItem<BParam>)>) -> BOut
@ -529,15 +529,15 @@ impl<T> Copy for SystemTypeIdLabel<T> {}
/// } /// }
/// } /// }
/// ///
/// // Usage example for `chain`: /// // Usage example for `pipe`:
/// fn main() { /// fn main() {
/// let mut world = World::default(); /// let mut world = World::default();
/// world.insert_resource(Message("42".to_string())); /// world.insert_resource(Message("42".to_string()));
/// ///
/// // chain the `parse_message_system`'s output into the `filter_system`s input /// // pipe the `parse_message_system`'s output into the `filter_system`s input
/// let mut chained_system = IntoSystem::into_system(chain(parse_message, filter)); /// let mut piped_system = IntoSystem::into_system(pipe(parse_message, filter));
/// chained_system.initialize(&mut world); /// piped_system.initialize(&mut world);
/// assert_eq!(chained_system.run((), &mut world), Some(42)); /// assert_eq!(piped_system.run((), &mut world), Some(42));
/// } /// }
/// ///
/// #[derive(Resource)] /// #[derive(Resource)]
@ -551,7 +551,7 @@ impl<T> Copy for SystemTypeIdLabel<T> {}
/// result.ok().filter(|&n| n < 100) /// result.ok().filter(|&n| n < 100)
/// } /// }
/// ``` /// ```
/// [`ChainSystem`]: crate::system::ChainSystem /// [`PipeSystem`]: crate::system::PipeSystem
/// [`ParamSet`]: crate::system::ParamSet /// [`ParamSet`]: crate::system::ParamSet
pub trait SystemParamFunction<In, Out, Param: SystemParam, Marker>: Send + Sync + 'static { pub trait SystemParamFunction<In, Out, Param: SystemParam, Marker>: Send + Sync + 'static {
fn run(&mut self, input: In, param_value: SystemParamItem<Param>) -> Out; fn run(&mut self, input: In, param_value: SystemParamItem<Param>) -> Out;

View file

@ -75,8 +75,8 @@ mod function_system;
mod query; mod query;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
mod system; mod system;
mod system_chaining;
mod system_param; mod system_param;
mod system_piping;
pub use commands::*; pub use commands::*;
pub use exclusive_function_system::*; pub use exclusive_function_system::*;
@ -84,8 +84,8 @@ pub use exclusive_system_param::*;
pub use function_system::*; pub use function_system::*;
pub use query::*; pub use query::*;
pub use system::*; pub use system::*;
pub use system_chaining::*;
pub use system_param::*; pub use system_param::*;
pub use system_piping::*;
/// Ensure that a given function is a system /// Ensure that a given function is a system
/// ///

View file

@ -7,10 +7,11 @@ use crate::{
}; };
use std::borrow::Cow; use std::borrow::Cow;
/// A [`System`] that chains two systems together, creating a new system that routes the output of /// A [`System`] created by piping the output of the first system into the input of the second.
/// the first system into the input of the second system, yielding the output of the second system.
/// ///
/// Given two systems `A` and `B`, A may be chained with `B` as `A.chain(B)` if the output type of `A` is /// This can be repeated indefinitely, but system pipes cannot branch: the output is consumed by the receiving system.
///
/// Given two systems `A` and `B`, A may be piped into `B` as `A.pipe(B)` if the output type of `A` is
/// equal to the input type of `B`. /// equal to the input type of `B`.
/// ///
/// Note that for [`FunctionSystem`](crate::system::FunctionSystem)s the output is the return value /// Note that for [`FunctionSystem`](crate::system::FunctionSystem)s the output is the return value
@ -28,10 +29,10 @@ use std::borrow::Cow;
/// let mut world = World::default(); /// let mut world = World::default();
/// world.insert_resource(Message("42".to_string())); /// world.insert_resource(Message("42".to_string()));
/// ///
/// // chain the `parse_message_system`'s output into the `filter_system`s input /// // pipe the `parse_message_system`'s output into the `filter_system`s input
/// let mut chained_system = parse_message_system.chain(filter_system); /// let mut piped_system = parse_message_system.pipe(filter_system);
/// chained_system.initialize(&mut world); /// piped_system.initialize(&mut world);
/// assert_eq!(chained_system.run((), &mut world), Some(42)); /// assert_eq!(piped_system.run((), &mut world), Some(42));
/// } /// }
/// ///
/// #[derive(Resource)] /// #[derive(Resource)]
@ -45,7 +46,7 @@ use std::borrow::Cow;
/// result.ok().filter(|&n| n < 100) /// result.ok().filter(|&n| n < 100)
/// } /// }
/// ``` /// ```
pub struct ChainSystem<SystemA, SystemB> { pub struct PipeSystem<SystemA, SystemB> {
system_a: SystemA, system_a: SystemA,
system_b: SystemB, system_b: SystemB,
name: Cow<'static, str>, name: Cow<'static, str>,
@ -53,7 +54,7 @@ pub struct ChainSystem<SystemA, SystemB> {
archetype_component_access: Access<ArchetypeComponentId>, archetype_component_access: Access<ArchetypeComponentId>,
} }
impl<SystemA: System, SystemB: System<In = SystemA::Out>> System for ChainSystem<SystemA, SystemB> { impl<SystemA: System, SystemB: System<In = SystemA::Out>> System for PipeSystem<SystemA, SystemB> {
type In = SystemA::In; type In = SystemA::In;
type Out = SystemB::Out; type Out = SystemB::Out;
@ -121,33 +122,34 @@ impl<SystemA: System, SystemB: System<In = SystemA::Out>> System for ChainSystem
} }
} }
/// An extension trait providing the [`IntoChainSystem::chain`] method for convenient [`System`] /// An extension trait providing the [`IntoPipeSystem::pipe`] method to pass input from one system into the next.
/// chaining.
/// ///
/// This trait is blanket implemented for all system pairs that fulfill the chaining requirement. /// The first system must have return type `T`
/// and the second system must have [`In<T>`](crate::system::In) as its first system parameter.
/// ///
/// See [`ChainSystem`]. /// This trait is blanket implemented for all system pairs that fulfill the type requirements.
pub trait IntoChainSystem<ParamA, Payload, SystemB, ParamB, Out>: ///
/// See [`PipeSystem`].
pub trait IntoPipeSystem<ParamA, Payload, SystemB, ParamB, Out>:
IntoSystem<(), Payload, ParamA> + Sized IntoSystem<(), Payload, ParamA> + Sized
where where
SystemB: IntoSystem<Payload, Out, ParamB>, SystemB: IntoSystem<Payload, Out, ParamB>,
{ {
/// Chain this system `A` with another system `B` creating a new system that feeds system A's /// Pass the output of this system `A` into a second system `B`, creating a new compound system.
/// output into system `B`, returning the output of system `B`. fn pipe(self, system: SystemB) -> PipeSystem<Self::System, SystemB::System>;
fn chain(self, system: SystemB) -> ChainSystem<Self::System, SystemB::System>;
} }
impl<SystemA, ParamA, Payload, SystemB, ParamB, Out> impl<SystemA, ParamA, Payload, SystemB, ParamB, Out>
IntoChainSystem<ParamA, Payload, SystemB, ParamB, Out> for SystemA IntoPipeSystem<ParamA, Payload, SystemB, ParamB, Out> for SystemA
where where
SystemA: IntoSystem<(), Payload, ParamA>, SystemA: IntoSystem<(), Payload, ParamA>,
SystemB: IntoSystem<Payload, Out, ParamB>, SystemB: IntoSystem<Payload, Out, ParamB>,
{ {
fn chain(self, system: SystemB) -> ChainSystem<SystemA::System, SystemB::System> { fn pipe(self, system: SystemB) -> PipeSystem<SystemA::System, SystemB::System> {
let system_a = IntoSystem::into_system(self); let system_a = IntoSystem::into_system(self);
let system_b = IntoSystem::into_system(system); let system_b = IntoSystem::into_system(system);
ChainSystem { PipeSystem {
name: Cow::Owned(format!("Chain({}, {})", system_a.name(), system_b.name())), name: Cow::Owned(format!("Pipe({}, {})", system_a.name(), system_b.name())),
system_a, system_a,
system_b, system_b,
archetype_component_access: Default::default(), archetype_component_access: Default::default(),
@ -156,7 +158,7 @@ where
} }
} }
/// A collection of common adapters for [chaining](super::ChainSystem) the result of a system. /// A collection of common adapters for [piping](super::PipeSystem) the result of a system.
pub mod adapter { pub mod adapter {
use crate::system::In; use crate::system::In;
use std::fmt::Debug; use std::fmt::Debug;
@ -168,9 +170,9 @@ pub mod adapter {
/// use bevy_ecs::prelude::*; /// use bevy_ecs::prelude::*;
/// ///
/// return1 /// return1
/// .chain(system_adapter::new(u32::try_from)) /// .pipe(system_adapter::new(u32::try_from))
/// .chain(system_adapter::unwrap) /// .pipe(system_adapter::unwrap)
/// .chain(print); /// .pipe(print);
/// ///
/// fn return1() -> u64 { 1 } /// fn return1() -> u64 { 1 }
/// fn print(In(x): In<impl std::fmt::Debug>) { /// fn print(In(x): In<impl std::fmt::Debug>) {
@ -204,7 +206,7 @@ pub mod adapter {
/// .add_system_to_stage( /// .add_system_to_stage(
/// CoreStage::Update, /// CoreStage::Update,
/// // Panic if the load system returns an error. /// // Panic if the load system returns an error.
/// load_save_system.chain(system_adapter::unwrap) /// load_save_system.pipe(system_adapter::unwrap)
/// ) /// )
/// // ... /// // ...
/// # ; /// # ;
@ -224,7 +226,7 @@ pub mod adapter {
res.unwrap() res.unwrap()
} }
/// System adapter that ignores the output of the previous system in a chain. /// System adapter that ignores the output of the previous system in a pipe.
/// This is useful for fallible systems that should simply return early in case of an `Err`/`None`. /// This is useful for fallible systems that should simply return early in case of an `Err`/`None`.
/// ///
/// # Examples /// # Examples
@ -248,7 +250,7 @@ pub mod adapter {
/// .add_system_to_stage( /// .add_system_to_stage(
/// CoreStage::Update, /// CoreStage::Update,
/// // If the system fails, just move on and try again next frame. /// // If the system fails, just move on and try again next frame.
/// fallible_system.chain(system_adapter::ignore) /// fallible_system.pipe(system_adapter::ignore)
/// ) /// )
/// // ... /// // ...
/// # ; /// # ;
@ -278,8 +280,8 @@ pub mod adapter {
unimplemented!() unimplemented!()
} }
assert_is_system(returning::<Result<u32, std::io::Error>>.chain(unwrap)); assert_is_system(returning::<Result<u32, std::io::Error>>.pipe(unwrap));
assert_is_system(returning::<Option<()>>.chain(ignore)); assert_is_system(returning::<Option<()>>.pipe(ignore));
assert_is_system(returning::<&str>.chain(new(u64::from_str)).chain(unwrap)); assert_is_system(returning::<&str>.pipe(new(u64::from_str)).pipe(unwrap));
} }
} }

View file

@ -199,9 +199,9 @@ Example | Description
[Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed in a previous stage during the current frame [Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed in a previous stage during the current frame
[Startup System](../examples/ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up) [Startup System](../examples/ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up)
[State](../examples/ecs/state.rs) | Illustrates how to use States to control transitioning from a Menu state to an InGame state [State](../examples/ecs/state.rs) | Illustrates how to use States to control transitioning from a Menu state to an InGame state
[System Chaining](../examples/ecs/system_chaining.rs) | Chain two systems together, specifying a return type in a system (such as `Result`)
[System Closure](../examples/ecs/system_closure.rs) | Show how to use closures as systems, and how to configure `Local` variables by capturing external state [System Closure](../examples/ecs/system_closure.rs) | Show how to use closures as systems, and how to configure `Local` variables by capturing external state
[System Parameter](../examples/ecs/system_param.rs) | Illustrates creating custom system parameters with `SystemParam` [System Parameter](../examples/ecs/system_param.rs) | Illustrates creating custom system parameters with `SystemParam`
[System Piping](../examples/ecs/system_piping.rs) | Pipe the output of one system into a second, allowing you to handle any errors gracefully
[System Sets](../examples/ecs/system_sets.rs) | Shows `SystemSet` use along with run criterion [System Sets](../examples/ecs/system_sets.rs) | Shows `SystemSet` use along with run criterion
[Timers](../examples/ecs/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state [Timers](../examples/ecs/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state

View file

@ -1,5 +1,5 @@
//! Illustrates how to make a single system from multiple functions running in sequence and sharing //! Illustrates how to make a single system from multiple functions running in sequence,
//! their inputs and outputs. //! passing the output of the first into the input of the next.
use anyhow::Result; use anyhow::Result;
use bevy::prelude::*; use bevy::prelude::*;
@ -7,7 +7,7 @@ use bevy::prelude::*;
fn main() { fn main() {
App::new() App::new()
.insert_resource(Message("42".to_string())) .insert_resource(Message("42".to_string()))
.add_system(parse_message_system.chain(handler_system)) .add_system(parse_message_system.pipe(handler_system))
.run(); .run();
} }