Skip to content

Commit bbc70c7

Browse files
committed
docs and example
1 parent c2d996a commit bbc70c7

File tree

5 files changed

+195
-9
lines changed

5 files changed

+195
-9
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,11 +721,83 @@ pub fn derive_from_world(input: TokenStream) -> TokenStream {
721721
})
722722
}
723723

724+
/// Automatically compose systems together with function syntax.
725+
///
726+
/// This macro provides some nice syntax on top of the `SystemRunner` `SystemParam`
727+
/// to allow running systems inside other systems. Overall, the macro accepts normal
728+
/// closure syntax:
729+
///
730+
/// ```ignore
731+
/// let system_a = |world: &mut World| { 10 };
732+
/// let system_b = |a: In<u32>, world: &mut World| { println!("{}", *a + 12) };
733+
/// compose! {
734+
/// || -> Result<(), RunSystemError> {
735+
/// let a = run!(system-a)?;
736+
/// run!(system_b);
737+
/// }
738+
/// }
739+
/// ```
740+
///
741+
/// What's special is that the macro will expand any invocations of `run!()` into
742+
/// calls to `SystemRunner::run` or `SystemRunner::run_with`. The `run!()` accepts
743+
/// two parameters: first, a system identifier (or a path to one), and second, an
744+
/// optional input to invoke the system with.
745+
///
746+
/// Notes:
747+
/// 1. All system runners are passed through a `ParamSet`, so invoked systems will
748+
/// not conflict with each other. However, invoked systems may still conflict
749+
/// with system params in the outer closure.
750+
///
751+
/// 2. `run!` will not accept expressions that evaluate to systems, only direct
752+
/// identifiers or paths. So, if you want to call something like:
753+
///
754+
/// ```ignore
755+
/// run!(|query: Query<(&A, &B, &mut C)>| { ... })`
756+
/// ```
757+
///
758+
/// Assign the expression to a variable first.
724759
#[proc_macro]
725760
pub fn compose(input: TokenStream) -> TokenStream {
726761
system_composition::compose(input, false)
727762
}
728763

764+
/// Automatically compose systems together with function syntax.
765+
///
766+
/// Unlike [`compose`], this macro allows generating systems that take input.
767+
///
768+
/// This macro provides some nice syntax on top of the `SystemRunner` `SystemParam`
769+
/// to allow running systems inside other systems. Overall, the macro accepts normal
770+
/// closure syntax:
771+
///
772+
/// ```ignore
773+
/// let system_a = |input: In<u32>, world: &mut World| { *input + 10 };
774+
/// let system_b = |a: In<u32>, world: &mut World| { println!("{}", *a + 12) };
775+
/// compose_with! {
776+
/// |input: In<u32>| -> Result<(), RunSystemError> {
777+
/// let a = run!(system_a, input)?;
778+
/// run!(system_b);
779+
/// }
780+
/// }
781+
/// ```
782+
///
783+
/// What's special is that the macro will expand any invocations of `run!()` into
784+
/// calls to `SystemRunner::run` or `SystemRunner::run_with`. The `run!()` accepts
785+
/// two parameters: first, a system identifier (or a path to one), and second, an
786+
/// optional input to invoke the system with.
787+
///
788+
/// Notes:
789+
/// 1. All system runners are passed through a `ParamSet`, so invoked systems will
790+
/// not conflict with each other. However, invoked systems may still conflict
791+
/// with system params in the outer closure.
792+
///
793+
/// 2. `run!` will not accept expressions that evaluate to systems, only direct
794+
/// identifiers or paths. So, if you want to call something like:
795+
///
796+
/// ```ignore
797+
/// run!(|query: Query<(&A, &B, &mut C)>| { ... })`
798+
/// ```
799+
///
800+
/// Assign the expression to a variable first.
729801
#[proc_macro]
730802
pub fn compose_with(input: TokenStream) -> TokenStream {
731803
system_composition::compose(input, true)

crates/bevy_ecs/src/system/system_param.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2765,6 +2765,47 @@ unsafe impl SystemParam for FilteredResourcesMut<'_, '_> {
27652765
}
27662766
}
27672767

2768+
/// A [`SystemParam`] that allows running other systems.
2769+
///
2770+
/// To be useful, this must be configured using a [`SystemRunnerBuilder`](crate::system::SystemRunnerBuilder)
2771+
/// to build the system using a [`SystemParamBuilder`](crate::prelude::SystemParamBuilder).
2772+
///
2773+
/// Also see the macros [`compose`](crate::system::compose) and [`compose_with`](crate::system::compose_with)
2774+
/// for some nice syntax on top of this API.
2775+
///
2776+
/// # Examples
2777+
///
2778+
/// ```
2779+
/// # use bevy_ecs::{prelude::*, system::*};
2780+
/// #
2781+
/// # #[derive(Component)]
2782+
/// # struct A;
2783+
/// #
2784+
/// # #[derive(Component)]
2785+
/// # struct B;
2786+
/// # let mut world = World::new();
2787+
/// #
2788+
/// fn count_a(a: Query<&A>) -> u32 {
2789+
/// a.len()
2790+
/// }
2791+
///
2792+
/// fn count_b(b: Query<&B>) -> u32 {
2793+
/// b.len()
2794+
/// }
2795+
///
2796+
/// let get_sum = (
2797+
/// ParamBuilder::system(count_a),
2798+
/// ParamBuilder::system(count_b)
2799+
/// )
2800+
/// .build_state(&mut world)
2801+
/// .build_system(
2802+
/// |mut run_a: SystemRunner<(), u32>, mut run_b: SystemRunner<(), u32>| -> Result<u32, RunSystemError> {
2803+
/// let a = run_a.run()?;
2804+
/// let b = run_b.run()?;
2805+
/// Ok(a + b)
2806+
/// }
2807+
/// );
2808+
/// ```
27682809
pub struct SystemRunner<'w, 's, In = (), Out = (), Sys = dyn System<In = In, Out = Out>>
27692810
where
27702811
In: SystemInput,
@@ -2779,6 +2820,7 @@ where
27792820
In: SystemInput,
27802821
Sys: System<In = In, Out = Out> + ?Sized,
27812822
{
2823+
/// Run the system with input.
27822824
#[inline]
27832825
pub fn run_with(&mut self, input: In::Inner<'_>) -> Result<Out, RunSystemError> {
27842826
// SAFETY:
@@ -2798,6 +2840,7 @@ impl<'w, 's, Out, Sys> SystemRunner<'w, 's, (), Out, Sys>
27982840
where
27992841
Sys: System<In = (), Out = Out> + ?Sized,
28002842
{
2843+
/// Run the system.
28012844
#[inline]
28022845
pub fn run(&mut self) -> Result<Out, RunSystemError> {
28032846
self.run_with(())

examples/ecs/system_piping.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! passing the output of the first into the input of the next.
33
44
use bevy::prelude::*;
5+
use bevy_ecs::system::{compose, RunSystemError};
56
use std::num::ParseIntError;
67

78
use bevy::log::{debug, error, info, Level, LogPlugin};
@@ -21,17 +22,29 @@ fn main() {
2122
parse_message_system.pipe(handler_system),
2223
data_pipe_system.map(|out| info!("{out}")),
2324
parse_message_system.map(|out| debug!("{out:?}")),
24-
warning_pipe_system.map(|out| {
25-
if let Err(err) = out {
26-
error!("{err}");
25+
parse_message_system.map(drop),
26+
// You can also use the `compose!` macro to pipe systems together!
27+
// This is even more powerful, but might be harder to use with
28+
// fully generic code. See the docs on `compose!` and `compose_with!`
29+
// for more info.
30+
compose! {
31+
|| -> Result<(), RunSystemError> {
32+
let out = run!(warning_pipe_system)?;
33+
if let Err(err) = out {
34+
error!("{err}");
35+
}
36+
Ok(())
2737
}
28-
}),
29-
parse_error_message_system.map(|out| {
30-
if let Err(err) = out {
31-
error!("{err}");
38+
},
39+
compose! {
40+
|| -> Result<(), RunSystemError> {
41+
let out = run!(parse_error_message_system)?;
42+
if let Err(err) = out {
43+
error!("{err}");
44+
}
45+
Ok(())
3246
}
33-
}),
34-
parse_message_system.map(drop),
47+
},
3548
),
3649
)
3750
.run();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: "New required method on SystemInput"
3+
pull_requests: [todo]
4+
---
5+
6+
Custom implementations of the `SystemInput` trait will need to implement a new
7+
required method: `unwrap`. Like `wrap`, it converts between the inner input item
8+
and the wrapper, but in the opposite direction. In most cases it should be
9+
trivial to add.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: System Composition
3+
authors: ["@ecoskey"]
4+
pull_requests: [todo]
5+
---
6+
7+
## `SystemRunner` SystemParam
8+
9+
We've been working on some new tools to make composing multiple ECS systems together
10+
even easier. Bevy 0.18 introduces the `SystemRunner` `SystemParam`, allowing running
11+
systems inside other systems!
12+
13+
```rust
14+
fn count_a(a: Query<&A>) -> u32 {
15+
a.len()
16+
}
17+
18+
fn count_b(b: Query<&B>) -> u32 {
19+
b.len()
20+
}
21+
22+
let get_sum = (
23+
ParamBuilder::system(count_a),
24+
ParamBuilder::system(count_b)
25+
)
26+
.build_system(
27+
|mut run_a: SystemRunner<(), u32>, mut run_b: SystemRunner<(), u32>| -> Result<u32, RunSystemError> {
28+
let a = run_a.run()?;
29+
let b = run_b.run()?;
30+
Ok(a + b)
31+
}
32+
);
33+
```
34+
35+
## `compose!` and `compose_with!`
36+
37+
With this new API we've also added some nice macro syntax to go on top. The `compose!`
38+
and `compose_with!` macros will automatically transform a provided closure, making
39+
the new `SystemRunner` params almost seamless to use.
40+
41+
```rust
42+
compose! {
43+
|| -> Result<u32, RunSystemError> {
44+
let a = run!(count_a)?;
45+
let b = run!(count_b)?;
46+
Ok(a + b)
47+
}
48+
}
49+
```

0 commit comments

Comments
 (0)