bevy_ecs/system/combinator.rs
1use std::{borrow::Cow, cell::UnsafeCell, marker::PhantomData};
2
3use bevy_ptr::UnsafeCellDeref;
4
5use crate::{
6 archetype::ArchetypeComponentId,
7 component::{ComponentId, Tick},
8 prelude::World,
9 query::Access,
10 schedule::InternedSystemSet,
11 world::unsafe_world_cell::UnsafeWorldCell,
12};
13
14use super::{ReadOnlySystem, System};
15
16/// Customizes the behavior of a [`CombinatorSystem`].
17///
18/// # Examples
19///
20/// ```
21/// use bevy_ecs::prelude::*;
22/// use bevy_ecs::system::{CombinatorSystem, Combine};
23///
24/// // A system combinator that performs an exclusive-or (XOR)
25/// // operation on the output of two systems.
26/// pub type Xor<A, B> = CombinatorSystem<XorMarker, A, B>;
27///
28/// // This struct is used to customize the behavior of our combinator.
29/// pub struct XorMarker;
30///
31/// impl<A, B> Combine<A, B> for XorMarker
32/// where
33/// A: System<In = (), Out = bool>,
34/// B: System<In = (), Out = bool>,
35/// {
36/// type In = ();
37/// type Out = bool;
38///
39/// fn combine(
40/// _input: Self::In,
41/// a: impl FnOnce(A::In) -> A::Out,
42/// b: impl FnOnce(B::In) -> B::Out,
43/// ) -> Self::Out {
44/// a(()) ^ b(())
45/// }
46/// }
47///
48/// # #[derive(Resource, PartialEq, Eq)] struct A(u32);
49/// # #[derive(Resource, PartialEq, Eq)] struct B(u32);
50/// # #[derive(Resource, Default)] struct RanFlag(bool);
51/// # let mut world = World::new();
52/// # world.init_resource::<RanFlag>();
53/// #
54/// # let mut app = Schedule::default();
55/// app.add_systems(my_system.run_if(Xor::new(
56/// IntoSystem::into_system(resource_equals(A(1))),
57/// IntoSystem::into_system(resource_equals(B(1))),
58/// // The name of the combined system.
59/// std::borrow::Cow::Borrowed("a ^ b"),
60/// )));
61/// # fn my_system(mut flag: ResMut<RanFlag>) { flag.0 = true; }
62/// #
63/// # world.insert_resource(A(0));
64/// # world.insert_resource(B(0));
65/// # app.run(&mut world);
66/// # // Neither condition passes, so the system does not run.
67/// # assert!(!world.resource::<RanFlag>().0);
68/// #
69/// # world.insert_resource(A(1));
70/// # app.run(&mut world);
71/// # // Only the first condition passes, so the system runs.
72/// # assert!(world.resource::<RanFlag>().0);
73/// # world.resource_mut::<RanFlag>().0 = false;
74/// #
75/// # world.insert_resource(B(1));
76/// # app.run(&mut world);
77/// # // Both conditions pass, so the system does not run.
78/// # assert!(!world.resource::<RanFlag>().0);
79/// #
80/// # world.insert_resource(A(0));
81/// # app.run(&mut world);
82/// # // Only the second condition passes, so the system runs.
83/// # assert!(world.resource::<RanFlag>().0);
84/// # world.resource_mut::<RanFlag>().0 = false;
85/// ```
86#[diagnostic::on_unimplemented(
87 message = "`{Self}` can not combine systems `{A}` and `{B}`",
88 label = "invalid system combination",
89 note = "the inputs and outputs of `{A}` and `{B}` are not compatible with this combiner"
90)]
91pub trait Combine<A: System, B: System> {
92 /// The [input](System::In) type for a [`CombinatorSystem`].
93 type In;
94
95 /// The [output](System::Out) type for a [`CombinatorSystem`].
96 type Out;
97
98 /// When used in a [`CombinatorSystem`], this function customizes how
99 /// the two composite systems are invoked and their outputs are combined.
100 ///
101 /// See the trait-level docs for [`Combine`] for an example implementation.
102 fn combine(
103 input: Self::In,
104 a: impl FnOnce(A::In) -> A::Out,
105 b: impl FnOnce(B::In) -> B::Out,
106 ) -> Self::Out;
107}
108
109/// A [`System`] defined by combining two other systems.
110/// The behavior of this combinator is specified by implementing the [`Combine`] trait.
111/// For a full usage example, see the docs for [`Combine`].
112pub struct CombinatorSystem<Func, A, B> {
113 _marker: PhantomData<fn() -> Func>,
114 a: A,
115 b: B,
116 name: Cow<'static, str>,
117 component_access: Access<ComponentId>,
118 archetype_component_access: Access<ArchetypeComponentId>,
119}
120
121impl<Func, A, B> CombinatorSystem<Func, A, B> {
122 /// Creates a new system that combines two inner systems.
123 ///
124 /// The returned system will only be usable if `Func` implements [`Combine<A, B>`].
125 pub const fn new(a: A, b: B, name: Cow<'static, str>) -> Self {
126 Self {
127 _marker: PhantomData,
128 a,
129 b,
130 name,
131 component_access: Access::new(),
132 archetype_component_access: Access::new(),
133 }
134 }
135}
136
137impl<A, B, Func> System for CombinatorSystem<Func, A, B>
138where
139 Func: Combine<A, B> + 'static,
140 A: System,
141 B: System,
142{
143 type In = Func::In;
144 type Out = Func::Out;
145
146 fn name(&self) -> Cow<'static, str> {
147 self.name.clone()
148 }
149
150 fn component_access(&self) -> &Access<ComponentId> {
151 &self.component_access
152 }
153
154 fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
155 &self.archetype_component_access
156 }
157
158 fn is_send(&self) -> bool {
159 self.a.is_send() && self.b.is_send()
160 }
161
162 fn is_exclusive(&self) -> bool {
163 self.a.is_exclusive() || self.b.is_exclusive()
164 }
165
166 fn has_deferred(&self) -> bool {
167 self.a.has_deferred() || self.b.has_deferred()
168 }
169
170 unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out {
171 Func::combine(
172 input,
173 // SAFETY: The world accesses for both underlying systems have been registered,
174 // so the caller will guarantee that no other systems will conflict with `a` or `b`.
175 // Since these closures are `!Send + !Sync + !'static`, they can never be called
176 // in parallel, so their world accesses will not conflict with each other.
177 // Additionally, `update_archetype_component_access` has been called,
178 // which forwards to the implementations for `self.a` and `self.b`.
179 |input| unsafe { self.a.run_unsafe(input, world) },
180 // SAFETY: See the comment above.
181 |input| unsafe { self.b.run_unsafe(input, world) },
182 )
183 }
184
185 fn run<'w>(&mut self, input: Self::In, world: &'w mut World) -> Self::Out {
186 // SAFETY: Converting `&mut T` -> `&UnsafeCell<T>`
187 // is explicitly allowed in the docs for `UnsafeCell`.
188 let world: &'w UnsafeCell<World> = unsafe { std::mem::transmute(world) };
189 Func::combine(
190 input,
191 // SAFETY: Since these closures are `!Send + !Sync + !'static`, they can never
192 // be called in parallel. Since mutable access to `world` only exists within
193 // the scope of either closure, we can be sure they will never alias one another.
194 |input| self.a.run(input, unsafe { world.deref_mut() }),
195 #[allow(clippy::undocumented_unsafe_blocks)]
196 |input| self.b.run(input, unsafe { world.deref_mut() }),
197 )
198 }
199
200 fn apply_deferred(&mut self, world: &mut World) {
201 self.a.apply_deferred(world);
202 self.b.apply_deferred(world);
203 }
204
205 #[inline]
206 fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {
207 self.a.queue_deferred(world.reborrow());
208 self.b.queue_deferred(world);
209 }
210
211 fn initialize(&mut self, world: &mut World) {
212 self.a.initialize(world);
213 self.b.initialize(world);
214 self.component_access.extend(self.a.component_access());
215 self.component_access.extend(self.b.component_access());
216 }
217
218 fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
219 self.a.update_archetype_component_access(world);
220 self.b.update_archetype_component_access(world);
221
222 self.archetype_component_access
223 .extend(self.a.archetype_component_access());
224 self.archetype_component_access
225 .extend(self.b.archetype_component_access());
226 }
227
228 fn check_change_tick(&mut self, change_tick: Tick) {
229 self.a.check_change_tick(change_tick);
230 self.b.check_change_tick(change_tick);
231 }
232
233 fn default_system_sets(&self) -> Vec<InternedSystemSet> {
234 let mut default_sets = self.a.default_system_sets();
235 default_sets.append(&mut self.b.default_system_sets());
236 default_sets
237 }
238
239 fn get_last_run(&self) -> Tick {
240 self.a.get_last_run()
241 }
242
243 fn set_last_run(&mut self, last_run: Tick) {
244 self.a.set_last_run(last_run);
245 self.b.set_last_run(last_run);
246 }
247}
248
249/// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world.
250unsafe impl<A, B, Func> ReadOnlySystem for CombinatorSystem<Func, A, B>
251where
252 Func: Combine<A, B> + 'static,
253 A: ReadOnlySystem,
254 B: ReadOnlySystem,
255{
256}
257
258impl<Func, A, B> Clone for CombinatorSystem<Func, A, B>
259where
260 A: Clone,
261 B: Clone,
262{
263 /// Clone the combined system. The cloned instance must be `.initialize()`d before it can run.
264 fn clone(&self) -> Self {
265 CombinatorSystem::new(self.a.clone(), self.b.clone(), self.name.clone())
266 }
267}
268
269/// A [`System`] created by piping the output of the first system into the input of the second.
270///
271/// This can be repeated indefinitely, but system pipes cannot branch: the output is consumed by the receiving system.
272///
273/// Given two systems `A` and `B`, A may be piped into `B` as `A.pipe(B)` if the output type of `A` is
274/// equal to the input type of `B`.
275///
276/// Note that for [`FunctionSystem`](crate::system::FunctionSystem)s the output is the return value
277/// of the function and the input is the first [`SystemParam`](crate::system::SystemParam) if it is
278/// tagged with [`In`](crate::system::In) or `()` if the function has no designated input parameter.
279///
280/// # Examples
281///
282/// ```
283/// use std::num::ParseIntError;
284///
285/// use bevy_ecs::prelude::*;
286///
287/// fn main() {
288/// let mut world = World::default();
289/// world.insert_resource(Message("42".to_string()));
290///
291/// // pipe the `parse_message_system`'s output into the `filter_system`s input
292/// let mut piped_system = parse_message_system.pipe(filter_system);
293/// piped_system.initialize(&mut world);
294/// assert_eq!(piped_system.run((), &mut world), Some(42));
295/// }
296///
297/// #[derive(Resource)]
298/// struct Message(String);
299///
300/// fn parse_message_system(message: Res<Message>) -> Result<usize, ParseIntError> {
301/// message.0.parse::<usize>()
302/// }
303///
304/// fn filter_system(In(result): In<Result<usize, ParseIntError>>) -> Option<usize> {
305/// result.ok().filter(|&n| n < 100)
306/// }
307/// ```
308pub type PipeSystem<SystemA, SystemB> = CombinatorSystem<Pipe, SystemA, SystemB>;
309
310#[doc(hidden)]
311pub struct Pipe;
312
313impl<A, B> Combine<A, B> for Pipe
314where
315 A: System,
316 B: System<In = A::Out>,
317{
318 type In = A::In;
319 type Out = B::Out;
320
321 fn combine(
322 input: Self::In,
323 a: impl FnOnce(A::In) -> A::Out,
324 b: impl FnOnce(B::In) -> B::Out,
325 ) -> Self::Out {
326 let value = a(input);
327 b(value)
328 }
329}