Skip to main content

bevy_ecs/system/
combinator.rs

1use alloc::{format, vec::Vec};
2use bevy_utils::prelude::DebugName;
3use core::marker::PhantomData;
4
5use crate::{
6    change_detection::{CheckChangeTicks, Tick},
7    error::ErrorContext,
8    prelude::World,
9    query::FilteredAccess,
10    schedule::InternedSystemSet,
11    system::{input::SystemInput, SystemAccess, SystemIn},
12    world::unsafe_world_cell::UnsafeWorldCell,
13};
14
15use super::{IntoSystem, ReadOnlySystem, RunSystemError, System};
16
17/// Customizes the behavior of a [`CombinatorSystem`].
18///
19/// # Examples
20///
21/// ```
22/// use bevy_ecs::prelude::*;
23/// use bevy_ecs::system::{CombinatorSystem, Combine, RunSystemError};
24///
25/// // A system combinator that performs an exclusive-or (XOR)
26/// // operation on the output of two systems.
27/// pub type Xor<A, B> = CombinatorSystem<XorMarker, A, B>;
28///
29/// // This struct is used to customize the behavior of our combinator.
30/// pub struct XorMarker;
31///
32/// impl<A, B> Combine<A, B> for XorMarker
33/// where
34///     A: System<In = (), Out = bool>,
35///     B: System<In = (), Out = bool>,
36/// {
37///     type In = ();
38///     type Out = bool;
39///
40///     fn combine<T>(
41///         _input: Self::In,
42///         data: &mut T,
43///         a: impl FnOnce(A::In, &mut T) -> Result<A::Out, RunSystemError>,
44///         b: impl FnOnce(B::In, &mut T) -> Result<B::Out, RunSystemError>,
45///     ) -> Result<Self::Out, RunSystemError> {
46///         Ok(a((), data).unwrap_or(false) ^ b((), data).unwrap_or(false))
47///     }
48/// }
49///
50/// # #[derive(Resource, PartialEq, Eq)] struct A(u32);
51/// # #[derive(Resource, PartialEq, Eq)] struct B(u32);
52/// # #[derive(Resource, Default)] struct RanFlag(bool);
53/// # let mut world = World::new();
54/// # world.init_resource::<RanFlag>();
55/// #
56/// # let mut app = Schedule::default();
57/// app.add_systems(my_system.run_if(Xor::new(
58///     IntoSystem::into_system(resource_equals(A(1))),
59///     IntoSystem::into_system(resource_equals(B(1))),
60///     // The name of the combined system.
61///     "a ^ b".into(),
62/// )));
63/// # fn my_system(mut flag: ResMut<RanFlag>) { flag.0 = true; }
64/// #
65/// # world.insert_resource(A(0));
66/// # world.insert_resource(B(0));
67/// # app.run(&mut world);
68/// # // Neither condition passes, so the system does not run.
69/// # assert!(!world.resource::<RanFlag>().0);
70/// #
71/// # world.insert_resource(A(1));
72/// # app.run(&mut world);
73/// # // Only the first condition passes, so the system runs.
74/// # assert!(world.resource::<RanFlag>().0);
75/// # world.resource_mut::<RanFlag>().0 = false;
76/// #
77/// # world.insert_resource(B(1));
78/// # app.run(&mut world);
79/// # // Both conditions pass, so the system does not run.
80/// # assert!(!world.resource::<RanFlag>().0);
81/// #
82/// # world.insert_resource(A(0));
83/// # app.run(&mut world);
84/// # // Only the second condition passes, so the system runs.
85/// # assert!(world.resource::<RanFlag>().0);
86/// # world.resource_mut::<RanFlag>().0 = false;
87/// ```
88#[diagnostic::on_unimplemented(
89    message = "`{Self}` can not combine systems `{A}` and `{B}`",
90    label = "invalid system combination",
91    note = "the inputs and outputs of `{A}` and `{B}` are not compatible with this combiner"
92)]
93pub trait Combine<A: System, B: System> {
94    /// The [input](System::In) type for a [`CombinatorSystem`].
95    type In: SystemInput;
96
97    /// The [output](System::Out) type for a [`CombinatorSystem`].
98    type Out;
99
100    /// When used in a [`CombinatorSystem`], this function customizes how
101    /// the two composite systems are invoked and their outputs are combined.
102    ///
103    /// See the trait-level docs for [`Combine`] for an example implementation.
104    fn combine<T>(
105        input: <Self::In as SystemInput>::Inner<'_>,
106        data: &mut T,
107        a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,
108        b: impl FnOnce(SystemIn<'_, B>, &mut T) -> Result<B::Out, RunSystemError>,
109    ) -> Result<Self::Out, RunSystemError>;
110}
111
112/// A [`System`] defined by combining two other systems.
113/// The behavior of this combinator is specified by implementing the [`Combine`] trait.
114/// For a full usage example, see the docs for [`Combine`].
115pub struct CombinatorSystem<Func, A, B> {
116    _marker: PhantomData<fn() -> Func>,
117    a: A,
118    b: B,
119    name: DebugName,
120}
121
122impl<Func, A, B> CombinatorSystem<Func, A, B> {
123    /// Creates a new system that combines two inner systems.
124    ///
125    /// The returned system will only be usable if `Func` implements [`Combine<A, B>`].
126    pub fn new(a: A, b: B, name: DebugName) -> Self {
127        Self {
128            _marker: PhantomData,
129            a,
130            b,
131            name,
132        }
133    }
134}
135
136impl<A, B, Func> System for CombinatorSystem<Func, A, B>
137where
138    Func: Combine<A, B> + 'static,
139    A: System,
140    B: System,
141{
142    type In = Func::In;
143    type Out = Func::Out;
144
145    fn name(&self) -> DebugName {
146        self.name.clone()
147    }
148
149    #[inline]
150    fn flags(&self) -> super::SystemStateFlags {
151        self.a.flags() | self.b.flags()
152    }
153
154    unsafe fn run_unsafe(
155        &mut self,
156        input: SystemIn<'_, Self>,
157        world: UnsafeWorldCell,
158    ) -> Result<Self::Out, RunSystemError> {
159        struct PrivateUnsafeWorldCell<'w>(UnsafeWorldCell<'w>);
160
161        // Since control over handling system run errors is passed on to the
162        // implementation of `Func::combine`, which may run the two closures
163        // however it wants, errors must be intercepted here if they should be
164        // handled by the world's error handler.
165        unsafe fn run_system<S: System>(
166            system: &mut S,
167            input: SystemIn<S>,
168            world: &mut PrivateUnsafeWorldCell,
169        ) -> Result<S::Out, RunSystemError> {
170            // SAFETY: see comment on `Func::combine` call
171            match unsafe { system.run_unsafe(input, world.0) } {
172                // let the world's fallback error handler handle the error if `Failed(_)`
173                Err(RunSystemError::Failed(err)) => {
174                    // SAFETY: We registered access to FallbackErrorHandler in `initialize`.
175                    (unsafe { world.0.fallback_error_handler() })(
176                        err,
177                        ErrorContext::System {
178                            name: system.name(),
179                            last_run: system.get_last_run(),
180                        },
181                    );
182
183                    // Since the error handler takes the error by value, create a new error:
184                    // The original error has already been handled, including
185                    // the reason for the failure here isn't important.
186                    Err(format!("System `{}` failed", system.name()).into())
187                }
188                // `Skipped(_)` and `Ok(_)` are passed through:
189                // system skipping is not an error, and isn't passed to the
190                // world's error handler by the executors.
191                result @ (Ok(_) | Err(RunSystemError::Skipped(_))) => result,
192            }
193        }
194
195        Func::combine(
196            input,
197            &mut PrivateUnsafeWorldCell(world),
198            // SAFETY: The world accesses for both underlying systems have been registered,
199            // so the caller will guarantee that no other systems will conflict with (`a` or `b`) and the `FallbackErrorHandler` resource.
200            // If either system has `is_exclusive()`, then the combined system also has `is_exclusive`.
201            // Since we require a `combine` to pass in a mutable reference to `world` and that's a private type
202            // passed to a function as an unbound non-'static generic argument, they can never be called in parallel
203            // or re-entrantly because that would require forging another instance of `PrivateUnsafeWorldCell`.
204            // This means that the world accesses in the two closures will not conflict with each other.
205            // The closure's access to the FallbackErrorHandler does not
206            // conflict with any potential access to the FallbackErrorHandler by
207            // the systems since the closures are not run in parallel.
208            |input, world| unsafe { run_system(&mut self.a, input, world) },
209            // SAFETY: See the comment above.
210            |input, world| unsafe { run_system(&mut self.b, input, world) },
211        )
212    }
213
214    #[cfg(feature = "hotpatching")]
215    #[inline]
216    fn refresh_hotpatch(&mut self) {
217        self.a.refresh_hotpatch();
218        self.b.refresh_hotpatch();
219    }
220
221    #[inline]
222    fn apply_deferred(&mut self, world: &mut World) {
223        self.a.apply_deferred(world);
224        self.b.apply_deferred(world);
225    }
226
227    #[inline]
228    fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {
229        self.a.queue_deferred(world.reborrow());
230        self.b.queue_deferred(world);
231    }
232
233    fn initialize(&mut self, world: &mut World) -> SystemAccess {
234        let mut a_access = self.a.initialize(world);
235        let b_access = self.b.initialize(world);
236        a_access.extend(b_access);
237
238        // We might need to read the fallback error handler after the component
239        // systems have run to report failures.
240        let error_resource = world.register_component::<crate::error::FallbackErrorHandler>();
241        let mut error_resource_access = FilteredAccess::default();
242        error_resource_access.add_read(error_resource);
243        a_access.ensure_filtered_access(error_resource_access);
244
245        a_access
246    }
247
248    fn check_change_tick(&mut self, check: CheckChangeTicks) {
249        self.a.check_change_tick(check);
250        self.b.check_change_tick(check);
251    }
252
253    fn default_system_sets(&self) -> Vec<InternedSystemSet> {
254        let mut default_sets = self.a.default_system_sets();
255        default_sets.append(&mut self.b.default_system_sets());
256        default_sets
257    }
258
259    fn get_last_run(&self) -> Tick {
260        self.a.get_last_run()
261    }
262
263    fn set_last_run(&mut self, last_run: Tick) {
264        self.a.set_last_run(last_run);
265        self.b.set_last_run(last_run);
266    }
267}
268
269// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world.
270unsafe impl<Func, A, B> ReadOnlySystem for CombinatorSystem<Func, A, B>
271where
272    Func: Combine<A, B> + 'static,
273    A: ReadOnlySystem,
274    B: ReadOnlySystem,
275{
276}
277
278impl<Func, A, B> Clone for CombinatorSystem<Func, A, B>
279where
280    A: Clone,
281    B: Clone,
282{
283    /// Clone the combined system. The cloned instance must be `.initialize()`d before it can run.
284    fn clone(&self) -> Self {
285        CombinatorSystem::new(self.a.clone(), self.b.clone(), self.name.clone())
286    }
287}
288
289/// An [`IntoSystem`] creating an instance of [`PipeSystem`].
290///
291/// This `struct` is created by [`IntoSystem::pipe()`].
292/// See its documentation for more.
293#[derive(Clone)]
294pub struct IntoPipeSystem<A, B, N: PipeSystemName = ()> {
295    a: A,
296    b: B,
297    /// A function for determining the name of the [`PipeSystem`].
298    ///
299    /// The default value of `()` implements [`PipeSystemName`]
300    /// by combining the names of both systems.
301    name: N,
302}
303
304impl<A, B> IntoPipeSystem<A, B> {
305    /// Creates a new [`IntoSystem`] that pipes two inner systems.
306    ///
307    /// Unless changed, the name of the system will be
308    /// set to a combination of the names of the inner systems.
309    pub const fn new(a: A, b: B) -> Self {
310        Self { a, b, name: () }
311    }
312
313    /// Set the name of the output [`PipeSystem`] to the output of a function.
314    ///
315    /// The parameters to the function are the names of the two systems.
316    /// The first system is the one passed as `self` to [`IntoSystem::pipe`],
317    /// and the second system is the one passed as a parameter.
318    ///
319    /// Note that when piping multiple systems, they may themselves be [`PipeSystem`]s!
320    pub fn with_name_fn(
321        self,
322        name: impl FnOnce(DebugName, DebugName) -> DebugName,
323    ) -> IntoPipeSystem<A, B, impl PipeSystemName> {
324        IntoPipeSystem {
325            a: self.a,
326            b: self.b,
327            name,
328        }
329    }
330
331    /// Set the name of the output [`PipeSystem`] to the given string.
332    pub fn with_name(
333        self,
334        name: impl Into<DebugName>,
335    ) -> IntoPipeSystem<A, B, impl PipeSystemName> {
336        self.with_name_fn(|_, _| name.into())
337    }
338
339    /// Set the name of the output [`PipeSystem`] to the name of the first system.
340    ///
341    /// Note that the "first" system is the one passed as `self` to [`IntoSystem::pipe`].
342    /// When piping multiple systems, that may itself by another [`PipeSystem`]!
343    ///
344    /// ```
345    /// # use bevy_ecs::prelude::*;
346    /// # let a = IntoSystem::into_system(|| {}).with_name("a");
347    /// # let b = IntoSystem::into_system(|| {}).with_name("b");
348    /// # let c = IntoSystem::into_system(|| {}).with_name("c");
349    /// let system = a.pipe(b).pipe(c).with_first_name();
350    /// assert_eq!("Pipe(a, b)", &*IntoSystem::into_system(system).name());
351    /// # let a = IntoSystem::into_system(|| {}).with_name("a");
352    /// # let b = IntoSystem::into_system(|| {}).with_name("b");
353    /// # let c = IntoSystem::into_system(|| {}).with_name("c");
354    /// let system = a.pipe(b.pipe(c)).with_first_name();
355    /// assert_eq!("a", &*IntoSystem::into_system(system).name());
356    /// ```
357    pub fn with_first_name(self) -> IntoPipeSystem<A, B, impl PipeSystemName> {
358        self.with_name_fn(|name_1, _name_2| name_1)
359    }
360
361    /// Set the name of the output [`PipeSystem`] to the name of the second system.
362    ///   
363    /// Note that the "second" system is the one passed as a parameter to [`IntoSystem::pipe`].
364    /// When piping multiple systems, that may itself by another [`PipeSystem`]!
365    ///
366    /// ```
367    /// # use bevy_ecs::prelude::*;
368    /// # let a = IntoSystem::into_system(|| {}).with_name("a");
369    /// # let b = IntoSystem::into_system(|| {}).with_name("b");
370    /// # let c = IntoSystem::into_system(|| {}).with_name("c");
371    /// let system = a.pipe(b).pipe(c).with_second_name();
372    /// assert_eq!("c", &*IntoSystem::into_system(system).name());
373    /// # let a = IntoSystem::into_system(|| {}).with_name("a");
374    /// # let b = IntoSystem::into_system(|| {}).with_name("b");
375    /// # let c = IntoSystem::into_system(|| {}).with_name("c");
376    /// let system = a.pipe(b.pipe(c)).with_second_name();
377    /// assert_eq!("Pipe(b, c)", &*IntoSystem::into_system(system).name());
378    /// ```
379    pub fn with_second_name(self) -> IntoPipeSystem<A, B, impl PipeSystemName> {
380        self.with_name_fn(|_name_1, name_2| name_2)
381    }
382}
383
384/// A function for determining the name of a [`PipeSystem`]
385/// from the names of its inner systems.
386///
387/// This is a trait so that a [`IntoPipeSystem`] with a name function
388/// can still be a ZST for use in [`World::run_system_cached`].
389pub trait PipeSystemName {
390    /// Determines the name of the [`PipeSystem`].
391    fn name(self, name1: DebugName, name2: DebugName) -> DebugName;
392}
393
394impl<F: FnOnce(DebugName, DebugName) -> DebugName> PipeSystemName for F {
395    fn name(self, name1: DebugName, name2: DebugName) -> DebugName {
396        self(name1, name2)
397    }
398}
399
400impl PipeSystemName for () {
401    fn name(self, name1: DebugName, name2: DebugName) -> DebugName {
402        DebugName::owned(format!("Pipe({name1}, {name2})"))
403    }
404}
405
406#[doc(hidden)]
407pub struct IsPipeSystemMarker;
408
409impl<A, B, N, IA, OA, IB, OB, MA, MB> IntoSystem<IA, OB, (IsPipeSystemMarker, OA, IB, MA, MB)>
410    for IntoPipeSystem<A, B, N>
411where
412    IA: SystemInput,
413    A: IntoSystem<IA, OA, MA>,
414    B: IntoSystem<IB, OB, MB>,
415    N: PipeSystemName,
416    for<'a> IB: SystemInput<Inner<'a> = OA>,
417{
418    type System = PipeSystem<A::System, B::System>;
419
420    fn into_system(this: Self) -> Self::System {
421        let system_a = IntoSystem::into_system(this.a);
422        let system_b = IntoSystem::into_system(this.b);
423        let name = this.name.name(system_a.name(), system_b.name());
424        PipeSystem::new(system_a, system_b, name)
425    }
426}
427
428/// A [`System`] created by piping the output of the first system into the input of the second.
429///
430/// This `struct` is created by [`IntoSystem::pipe()`].
431/// See its documentation for more.
432///
433/// This can be repeated indefinitely, but system pipes cannot branch: the output is consumed by the receiving system.
434///
435/// Given two systems `A` and `B`, A may be piped into `B` as `A.pipe(B)` if the output type of `A` is
436/// equal to the input type of `B`.
437///
438/// Note that for [`FunctionSystem`](crate::system::FunctionSystem)s the output is the return value
439/// of the function and the input is the first [`SystemParam`](crate::system::SystemParam) if it is
440/// tagged with [`In`](crate::system::In) or `()` if the function has no designated input parameter.
441///
442/// # Examples
443///
444/// ```
445/// use std::num::ParseIntError;
446///
447/// use bevy_ecs::prelude::*;
448///
449/// fn main() {
450///     let mut world = World::default();
451///     world.insert_resource(Message("42".to_string()));
452///
453///     // pipe the `parse_message_system`'s output into the `filter_system`s input
454///     let mut piped_system = IntoSystem::into_system(parse_message_system.pipe(filter_system));
455///     piped_system.initialize(&mut world);
456///     assert_eq!(piped_system.run((), &mut world).unwrap(), Some(42));
457/// }
458///
459/// #[derive(Resource)]
460/// struct Message(String);
461///
462/// fn parse_message_system(message: Res<Message>) -> Result<usize, ParseIntError> {
463///     message.0.parse::<usize>()
464/// }
465///
466/// fn filter_system(In(result): In<Result<usize, ParseIntError>>) -> Option<usize> {
467///     result.ok().filter(|&n| n < 100)
468/// }
469/// ```
470pub struct PipeSystem<A, B> {
471    a: A,
472    b: B,
473    name: DebugName,
474}
475
476impl<A, B> PipeSystem<A, B>
477where
478    A: System,
479    B: System,
480    for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
481{
482    /// Creates a new system that pipes two inner systems.
483    pub fn new(a: A, b: B, name: DebugName) -> Self {
484        Self { a, b, name }
485    }
486}
487
488impl<A, B> System for PipeSystem<A, B>
489where
490    A: System,
491    B: System,
492    for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
493{
494    type In = A::In;
495    type Out = B::Out;
496
497    fn name(&self) -> DebugName {
498        self.name.clone()
499    }
500
501    #[inline]
502    fn flags(&self) -> super::SystemStateFlags {
503        self.a.flags() | self.b.flags()
504    }
505
506    unsafe fn run_unsafe(
507        &mut self,
508        input: SystemIn<'_, Self>,
509        world: UnsafeWorldCell,
510    ) -> Result<Self::Out, RunSystemError> {
511        // SAFETY: Upheld by caller
512        unsafe {
513            let value = self.a.run_unsafe(input, world)?;
514            self.b.run_unsafe(value, world)
515        }
516    }
517
518    #[cfg(feature = "hotpatching")]
519    #[inline]
520    fn refresh_hotpatch(&mut self) {
521        self.a.refresh_hotpatch();
522        self.b.refresh_hotpatch();
523    }
524
525    fn apply_deferred(&mut self, world: &mut World) {
526        self.a.apply_deferred(world);
527        self.b.apply_deferred(world);
528    }
529
530    fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {
531        self.a.queue_deferred(world.reborrow());
532        self.b.queue_deferred(world);
533    }
534
535    fn initialize(&mut self, world: &mut World) -> SystemAccess {
536        let mut a_access = self.a.initialize(world);
537        let b_access = self.b.initialize(world);
538        a_access.extend(b_access);
539        a_access
540    }
541
542    fn check_change_tick(&mut self, check: CheckChangeTicks) {
543        self.a.check_change_tick(check);
544        self.b.check_change_tick(check);
545    }
546
547    fn default_system_sets(&self) -> Vec<InternedSystemSet> {
548        let mut default_sets = self.a.default_system_sets();
549        default_sets.append(&mut self.b.default_system_sets());
550        default_sets
551    }
552
553    fn get_last_run(&self) -> Tick {
554        self.a.get_last_run()
555    }
556
557    fn set_last_run(&mut self, last_run: Tick) {
558        self.a.set_last_run(last_run);
559        self.b.set_last_run(last_run);
560    }
561}
562
563// SAFETY: Both systems are read-only, so any system created by piping them will only read from the world.
564unsafe impl<A, B> ReadOnlySystem for PipeSystem<A, B>
565where
566    A: ReadOnlySystem,
567    B: ReadOnlySystem,
568    for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
569{
570}
571
572#[cfg(test)]
573mod tests {
574    use crate::error::FallbackErrorHandler;
575    use crate::prelude::*;
576    use bevy_utils::prelude::DebugName;
577
578    use crate::{
579        schedule::OrElseMarker,
580        system::{assert_system_does_not_conflict, CombinatorSystem},
581    };
582
583    #[test]
584    fn combinator_with_error_handler_access() {
585        fn my_system(_: ResMut<FallbackErrorHandler>) {}
586        fn a() -> bool {
587            true
588        }
589        fn b(_: ResMut<FallbackErrorHandler>) -> bool {
590            true
591        }
592        fn asdf(_: In<bool>) {}
593
594        let mut world = World::new();
595        world.insert_resource(FallbackErrorHandler::default());
596
597        let system = CombinatorSystem::<OrElseMarker, _, _>::new(
598            IntoSystem::into_system(a),
599            IntoSystem::into_system(b),
600            DebugName::borrowed("a OR b"),
601        );
602
603        // `system` should not conflict with itself by mutably accessing the error handler resource.
604        assert_system_does_not_conflict(system.clone());
605
606        let mut schedule = Schedule::default();
607        schedule.add_systems((my_system, system.pipe(asdf)));
608        schedule.initialize(&mut world).unwrap();
609
610        // `my_system` should conflict with the combinator system because the combinator reads the error handler resource.
611        assert!(!schedule.graph().conflicting_systems().is_empty());
612
613        schedule.run(&mut world);
614    }
615
616    #[test]
617    fn exclusive_system_piping_is_possible() {
618        fn my_exclusive_system(_world: &mut World) -> u32 {
619            1
620        }
621
622        fn out_pipe(input: In<u32>) {
623            assert!(input.0 == 1);
624        }
625
626        let mut world = World::new();
627
628        let mut schedule = Schedule::default();
629        schedule.add_systems(my_exclusive_system.pipe(out_pipe));
630
631        schedule.run(&mut world);
632    }
633
634    #[test]
635    fn pipe_system_names() {
636        let make_system = || {
637            let system1 = IntoSystem::into_system(|| {}).with_name(DebugName::borrowed("system1"));
638            let system2 = IntoSystem::into_system(|| {}).with_name(DebugName::borrowed("system2"));
639            system1.pipe(system2)
640        };
641
642        let system = IntoSystem::into_system(make_system());
643        assert_eq!(
644            DebugName::owned("Pipe(system1, system2)".into()),
645            system.name()
646        );
647
648        let system = IntoSystem::into_system(make_system().with_name("custom name"));
649        assert_eq!(DebugName::borrowed("custom name"), system.name());
650
651        let system = IntoSystem::into_system(make_system().with_first_name());
652        assert_eq!(DebugName::borrowed("system1"), system.name());
653
654        let system = IntoSystem::into_system(make_system().with_second_name());
655        assert_eq!(DebugName::borrowed("system2"), system.name());
656    }
657
658    #[test]
659    fn pipe_system_zst() {
660        let mut world = World::new();
661
662        fn system1() {}
663        fn system2() {}
664
665        // Ensure `IntoPipeSystem` is a ZST that can be used with `run_system_cached`
666        world.run_system_cached(system1.pipe(system2)).unwrap();
667        world
668            .run_system_cached(system1.pipe(system2).with_first_name())
669            .unwrap();
670        world
671            .run_system_cached(system1.pipe(system2).with_second_name())
672            .unwrap();
673    }
674}