Skip to content

Commit

Permalink
Rename system chaining to system piping (bevyengine#6230)
Browse files Browse the repository at this point in the history
# 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 bevyengine#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`.
  • Loading branch information
alice-i-cecile authored and ItsDoot committed Feb 1, 2023
1 parent e3db74d commit c8a8fb7
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 60 deletions.
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -872,12 +872,12 @@ category = "ECS (Entity Component System)"
wasm = false

[[example]]
name = "system_chaining"
path = "examples/ecs/system_chaining.rs"
name = "system_piping"
path = "examples/ecs/system_piping.rs"

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

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub mod prelude {
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
},
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,
Resource, System, SystemParamFunction,
},
Expand Down
16 changes: 8 additions & 8 deletions crates/bevy_ecs/src/schedule/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
RunCriteriaDescriptor, RunCriteriaDescriptorCoercion, RunCriteriaLabel, ShouldRun,
SystemSet,
},
system::{In, IntoChainSystem, Local, Res, ResMut, Resource},
system::{In, IntoPipeSystem, Local, Res, ResMut, Resource},
};
use std::{
any::TypeId,
Expand Down Expand Up @@ -79,7 +79,7 @@ where
(move |state: Res<State<T>>| {
state.stack.last().unwrap() == &pred && state.transition.is_none()
})
.chain(should_run_adapter::<T>)
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}

Expand All @@ -95,7 +95,7 @@ where
Some(_) => false,
None => *is_inactive,
})
.chain(should_run_adapter::<T>)
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}

Expand All @@ -118,7 +118,7 @@ where
Some(_) => false,
None => *is_in_stack,
})
.chain(should_run_adapter::<T>)
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}

Expand All @@ -133,7 +133,7 @@ where
_ => false,
})
})
.chain(should_run_adapter::<T>)
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}

Expand All @@ -148,7 +148,7 @@ where
_ => false,
})
})
.chain(should_run_adapter::<T>)
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}

Expand All @@ -162,7 +162,7 @@ where
_ => false,
})
})
.chain(should_run_adapter::<T>)
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}

Expand All @@ -176,7 +176,7 @@ where
_ => false,
})
})
.chain(should_run_adapter::<T>)
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}

Expand Down
18 changes: 9 additions & 9 deletions crates/bevy_ecs/src/system/function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ impl<T> Copy for SystemTypeIdLabel<T> {}
///
/// # Example
///
/// To create something like [`ChainSystem`], but in entirely safe code.
/// To create something like [`PipeSystem`], but in entirely safe code.
///
/// ```rust
/// use std::num::ParseIntError;
Expand All @@ -510,8 +510,8 @@ impl<T> Copy for SystemTypeIdLabel<T> {}
/// // 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
/// // 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`
/// pub fn chain<AIn, Shared, BOut, A, AParam, AMarker, B, BParam, BMarker>(
/// /// Pipe creates a new system which calls `a`, then calls `b` with the output of `a`
/// pub fn pipe<AIn, Shared, BOut, A, AParam, AMarker, B, BParam, BMarker>(
/// mut a: A,
/// mut b: B,
/// ) -> impl FnMut(In<AIn>, ParamSet<(SystemParamItem<AParam>, SystemParamItem<BParam>)>) -> BOut
Expand All @@ -529,15 +529,15 @@ impl<T> Copy for SystemTypeIdLabel<T> {}
/// }
/// }
///
/// // Usage example for `chain`:
/// // Usage example for `pipe`:
/// fn main() {
/// let mut world = World::default();
/// world.insert_resource(Message("42".to_string()));
///
/// // chain the `parse_message_system`'s output into the `filter_system`s input
/// let mut chained_system = IntoSystem::into_system(chain(parse_message, filter));
/// chained_system.initialize(&mut world);
/// assert_eq!(chained_system.run((), &mut world), Some(42));
/// // pipe the `parse_message_system`'s output into the `filter_system`s input
/// let mut piped_system = IntoSystem::into_system(pipe(parse_message, filter));
/// piped_system.initialize(&mut world);
/// assert_eq!(piped_system.run((), &mut world), Some(42));
/// }
///
/// #[derive(Resource)]
Expand All @@ -551,7 +551,7 @@ impl<T> Copy for SystemTypeIdLabel<T> {}
/// result.ok().filter(|&n| n < 100)
/// }
/// ```
/// [`ChainSystem`]: crate::system::ChainSystem
/// [`PipeSystem`]: crate::system::PipeSystem
/// [`ParamSet`]: crate::system::ParamSet
pub trait SystemParamFunction<In, Out, Param: SystemParam, Marker>: Send + Sync + 'static {
fn run(&mut self, input: In, param_value: SystemParamItem<Param>) -> Out;
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/system/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,17 @@ mod function_system;
mod query;
#[allow(clippy::module_inception)]
mod system;
mod system_chaining;
mod system_param;
mod system_piping;

pub use commands::*;
pub use exclusive_function_system::*;
pub use exclusive_system_param::*;
pub use function_system::*;
pub use query::*;
pub use system::*;
pub use system_chaining::*;
pub use system_param::*;
pub use system_piping::*;

/// Ensure that a given function is a system
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ use crate::{
};
use std::borrow::Cow;

/// A [`System`] that chains two systems together, creating a new system that routes the output of
/// the first system into the input of the second system, yielding the output of the second system.
/// A [`System`] created by piping the output of the first system into the input of the second.
///
/// 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`.
///
/// Note that for [`FunctionSystem`](crate::system::FunctionSystem)s the output is the return value
Expand All @@ -28,10 +29,10 @@ use std::borrow::Cow;
/// let mut world = World::default();
/// world.insert_resource(Message("42".to_string()));
///
/// // chain the `parse_message_system`'s output into the `filter_system`s input
/// let mut chained_system = parse_message_system.chain(filter_system);
/// chained_system.initialize(&mut world);
/// assert_eq!(chained_system.run((), &mut world), Some(42));
/// // pipe the `parse_message_system`'s output into the `filter_system`s input
/// let mut piped_system = parse_message_system.pipe(filter_system);
/// piped_system.initialize(&mut world);
/// assert_eq!(piped_system.run((), &mut world), Some(42));
/// }
///
/// #[derive(Resource)]
Expand All @@ -45,15 +46,15 @@ use std::borrow::Cow;
/// result.ok().filter(|&n| n < 100)
/// }
/// ```
pub struct ChainSystem<SystemA, SystemB> {
pub struct PipeSystem<SystemA, SystemB> {
system_a: SystemA,
system_b: SystemB,
name: Cow<'static, str>,
component_access: Access<ComponentId>,
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 Out = SystemB::Out;

Expand Down Expand Up @@ -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`]
/// chaining.
/// An extension trait providing the [`IntoPipeSystem::pipe`] method to pass input from one system into the next.
///
/// The first system must have return type `T`
/// and the second system must have [`In<T>`](crate::system::In) as its first system parameter.
///
/// This trait is blanket implemented for all system pairs that fulfill the chaining requirement.
/// This trait is blanket implemented for all system pairs that fulfill the type requirements.
///
/// See [`ChainSystem`].
pub trait IntoChainSystem<ParamA, Payload, SystemB, ParamB, Out>:
/// See [`PipeSystem`].
pub trait IntoPipeSystem<ParamA, Payload, SystemB, ParamB, Out>:
IntoSystem<(), Payload, ParamA> + Sized
where
SystemB: IntoSystem<Payload, Out, ParamB>,
{
/// Chain this system `A` with another system `B` creating a new system that feeds system A's
/// output into system `B`, returning the output of system `B`.
fn chain(self, system: SystemB) -> ChainSystem<Self::System, SystemB::System>;
/// Pass the output of this system `A` into a second system `B`, creating a new compound system.
fn pipe(self, system: SystemB) -> PipeSystem<Self::System, SystemB::System>;
}

impl<SystemA, ParamA, Payload, SystemB, ParamB, Out>
IntoChainSystem<ParamA, Payload, SystemB, ParamB, Out> for SystemA
IntoPipeSystem<ParamA, Payload, SystemB, ParamB, Out> for SystemA
where
SystemA: IntoSystem<(), Payload, ParamA>,
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_b = IntoSystem::into_system(system);
ChainSystem {
name: Cow::Owned(format!("Chain({}, {})", system_a.name(), system_b.name())),
PipeSystem {
name: Cow::Owned(format!("Pipe({}, {})", system_a.name(), system_b.name())),
system_a,
system_b,
archetype_component_access: Default::default(),
Expand All @@ -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 {
use crate::system::In;
use std::fmt::Debug;
Expand All @@ -168,9 +170,9 @@ pub mod adapter {
/// use bevy_ecs::prelude::*;
///
/// return1
/// .chain(system_adapter::new(u32::try_from))
/// .chain(system_adapter::unwrap)
/// .chain(print);
/// .pipe(system_adapter::new(u32::try_from))
/// .pipe(system_adapter::unwrap)
/// .pipe(print);
///
/// fn return1() -> u64 { 1 }
/// fn print(In(x): In<impl std::fmt::Debug>) {
Expand Down Expand Up @@ -204,7 +206,7 @@ pub mod adapter {
/// .add_system_to_stage(
/// CoreStage::Update,
/// // Panic if the load system returns an error.
/// load_save_system.chain(system_adapter::unwrap)
/// load_save_system.pipe(system_adapter::unwrap)
/// )
/// // ...
/// # ;
Expand All @@ -224,7 +226,7 @@ pub mod adapter {
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`.
///
/// # Examples
Expand All @@ -248,7 +250,7 @@ pub mod adapter {
/// .add_system_to_stage(
/// CoreStage::Update,
/// // If the system fails, just move on and try again next frame.
/// fallible_system.chain(system_adapter::ignore)
/// fallible_system.pipe(system_adapter::ignore)
/// )
/// // ...
/// # ;
Expand Down Expand Up @@ -278,8 +280,8 @@ pub mod adapter {
unimplemented!()
}

assert_is_system(returning::<Result<u32, std::io::Error>>.chain(unwrap));
assert_is_system(returning::<Option<()>>.chain(ignore));
assert_is_system(returning::<&str>.chain(new(u64::from_str)).chain(unwrap));
assert_is_system(returning::<Result<u32, std::io::Error>>.pipe(unwrap));
assert_is_system(returning::<Option<()>>.pipe(ignore));
assert_is_system(returning::<&str>.pipe(new(u64::from_str)).pipe(unwrap));
}
}
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
[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
[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 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
[Timers](../examples/ecs/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//! Illustrates how to make a single system from multiple functions running in sequence and sharing
//! their inputs and outputs.
//! Illustrates how to make a single system from multiple functions running in sequence,
//! passing the output of the first into the input of the next.
use anyhow::Result;
use bevy::prelude::*;

fn main() {
App::new()
.insert_resource(Message("42".to_string()))
.add_system(parse_message_system.chain(handler_system))
.add_system(parse_message_system.pipe(handler_system))
.run();
}

Expand Down

0 comments on commit c8a8fb7

Please sign in to comment.