Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/system/combinator.rs
6604 views
1
use alloc::{format, vec::Vec};
2
use bevy_utils::prelude::DebugName;
3
use core::marker::PhantomData;
4
5
use crate::{
6
component::{CheckChangeTicks, Tick},
7
prelude::World,
8
query::FilteredAccessSet,
9
schedule::InternedSystemSet,
10
system::{input::SystemInput, SystemIn, SystemParamValidationError},
11
world::unsafe_world_cell::UnsafeWorldCell,
12
};
13
14
use super::{IntoSystem, ReadOnlySystem, RunSystemError, 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, RunSystemError};
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<T>(
40
/// _input: Self::In,
41
/// data: &mut T,
42
/// a: impl FnOnce(A::In, &mut T) -> Result<A::Out, RunSystemError>,
43
/// b: impl FnOnce(B::In, &mut T) -> Result<B::Out, RunSystemError>,
44
/// ) -> Result<Self::Out, RunSystemError> {
45
/// Ok(a((), data)? ^ b((), data)?)
46
/// }
47
/// }
48
///
49
/// # #[derive(Resource, PartialEq, Eq)] struct A(u32);
50
/// # #[derive(Resource, PartialEq, Eq)] struct B(u32);
51
/// # #[derive(Resource, Default)] struct RanFlag(bool);
52
/// # let mut world = World::new();
53
/// # world.init_resource::<RanFlag>();
54
/// #
55
/// # let mut app = Schedule::default();
56
/// app.add_systems(my_system.run_if(Xor::new(
57
/// IntoSystem::into_system(resource_equals(A(1))),
58
/// IntoSystem::into_system(resource_equals(B(1))),
59
/// // The name of the combined system.
60
/// "a ^ b".into(),
61
/// )));
62
/// # fn my_system(mut flag: ResMut<RanFlag>) { flag.0 = true; }
63
/// #
64
/// # world.insert_resource(A(0));
65
/// # world.insert_resource(B(0));
66
/// # app.run(&mut world);
67
/// # // Neither condition passes, so the system does not run.
68
/// # assert!(!world.resource::<RanFlag>().0);
69
/// #
70
/// # world.insert_resource(A(1));
71
/// # app.run(&mut world);
72
/// # // Only the first condition passes, so the system runs.
73
/// # assert!(world.resource::<RanFlag>().0);
74
/// # world.resource_mut::<RanFlag>().0 = false;
75
/// #
76
/// # world.insert_resource(B(1));
77
/// # app.run(&mut world);
78
/// # // Both conditions pass, so the system does not run.
79
/// # assert!(!world.resource::<RanFlag>().0);
80
/// #
81
/// # world.insert_resource(A(0));
82
/// # app.run(&mut world);
83
/// # // Only the second condition passes, so the system runs.
84
/// # assert!(world.resource::<RanFlag>().0);
85
/// # world.resource_mut::<RanFlag>().0 = false;
86
/// ```
87
#[diagnostic::on_unimplemented(
88
message = "`{Self}` can not combine systems `{A}` and `{B}`",
89
label = "invalid system combination",
90
note = "the inputs and outputs of `{A}` and `{B}` are not compatible with this combiner"
91
)]
92
pub trait Combine<A: System, B: System> {
93
/// The [input](System::In) type for a [`CombinatorSystem`].
94
type In: SystemInput;
95
96
/// The [output](System::Out) type for a [`CombinatorSystem`].
97
type Out;
98
99
/// When used in a [`CombinatorSystem`], this function customizes how
100
/// the two composite systems are invoked and their outputs are combined.
101
///
102
/// See the trait-level docs for [`Combine`] for an example implementation.
103
fn combine<T>(
104
input: <Self::In as SystemInput>::Inner<'_>,
105
data: &mut T,
106
a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,
107
b: impl FnOnce(SystemIn<'_, B>, &mut T) -> Result<B::Out, RunSystemError>,
108
) -> Result<Self::Out, RunSystemError>;
109
}
110
111
/// A [`System`] defined by combining two other systems.
112
/// The behavior of this combinator is specified by implementing the [`Combine`] trait.
113
/// For a full usage example, see the docs for [`Combine`].
114
pub struct CombinatorSystem<Func, A, B> {
115
_marker: PhantomData<fn() -> Func>,
116
a: A,
117
b: B,
118
name: DebugName,
119
}
120
121
impl<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 fn new(a: A, b: B, name: DebugName) -> Self {
126
Self {
127
_marker: PhantomData,
128
a,
129
b,
130
name,
131
}
132
}
133
}
134
135
impl<A, B, Func> System for CombinatorSystem<Func, A, B>
136
where
137
Func: Combine<A, B> + 'static,
138
A: System,
139
B: System,
140
{
141
type In = Func::In;
142
type Out = Func::Out;
143
144
fn name(&self) -> DebugName {
145
self.name.clone()
146
}
147
148
#[inline]
149
fn flags(&self) -> super::SystemStateFlags {
150
self.a.flags() | self.b.flags()
151
}
152
153
unsafe fn run_unsafe(
154
&mut self,
155
input: SystemIn<'_, Self>,
156
world: UnsafeWorldCell,
157
) -> Result<Self::Out, RunSystemError> {
158
struct PrivateUnsafeWorldCell<'w>(UnsafeWorldCell<'w>);
159
160
Func::combine(
161
input,
162
&mut PrivateUnsafeWorldCell(world),
163
// SAFETY: The world accesses for both underlying systems have been registered,
164
// so the caller will guarantee that no other systems will conflict with `a` or `b`.
165
// If either system has `is_exclusive()`, then the combined system also has `is_exclusive`.
166
// Since we require a `combine` to pass in a mutable reference to `world` and that's a private type
167
// passed to a function as an unbound non-'static generic argument, they can never be called in parallel
168
// or re-entrantly because that would require forging another instance of `PrivateUnsafeWorldCell`.
169
// This means that the world accesses in the two closures will not conflict with each other.
170
|input, world| unsafe { self.a.run_unsafe(input, world.0) },
171
// `Self::validate_param_unsafe` already validated the first system,
172
// but we still need to validate the second system once the first one runs.
173
// SAFETY: See the comment above.
174
|input, world| unsafe {
175
self.b.validate_param_unsafe(world.0)?;
176
self.b.run_unsafe(input, world.0)
177
},
178
)
179
}
180
181
#[cfg(feature = "hotpatching")]
182
#[inline]
183
fn refresh_hotpatch(&mut self) {
184
self.a.refresh_hotpatch();
185
self.b.refresh_hotpatch();
186
}
187
188
#[inline]
189
fn apply_deferred(&mut self, world: &mut World) {
190
self.a.apply_deferred(world);
191
self.b.apply_deferred(world);
192
}
193
194
#[inline]
195
fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {
196
self.a.queue_deferred(world.reborrow());
197
self.b.queue_deferred(world);
198
}
199
200
#[inline]
201
unsafe fn validate_param_unsafe(
202
&mut self,
203
world: UnsafeWorldCell,
204
) -> Result<(), SystemParamValidationError> {
205
// We only validate parameters for the first system,
206
// since it may make changes to the world that affect
207
// whether the second system has valid parameters.
208
// The second system will be validated in `Self::run_unsafe`.
209
// SAFETY: Delegate to other `System` implementations.
210
unsafe { self.a.validate_param_unsafe(world) }
211
}
212
213
fn initialize(&mut self, world: &mut World) -> FilteredAccessSet {
214
let mut a_access = self.a.initialize(world);
215
let b_access = self.b.initialize(world);
216
a_access.extend(b_access);
217
a_access
218
}
219
220
fn check_change_tick(&mut self, check: CheckChangeTicks) {
221
self.a.check_change_tick(check);
222
self.b.check_change_tick(check);
223
}
224
225
fn default_system_sets(&self) -> Vec<InternedSystemSet> {
226
let mut default_sets = self.a.default_system_sets();
227
default_sets.append(&mut self.b.default_system_sets());
228
default_sets
229
}
230
231
fn get_last_run(&self) -> Tick {
232
self.a.get_last_run()
233
}
234
235
fn set_last_run(&mut self, last_run: Tick) {
236
self.a.set_last_run(last_run);
237
self.b.set_last_run(last_run);
238
}
239
}
240
241
/// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world.
242
unsafe impl<Func, A, B> ReadOnlySystem for CombinatorSystem<Func, A, B>
243
where
244
Func: Combine<A, B> + 'static,
245
A: ReadOnlySystem,
246
B: ReadOnlySystem,
247
{
248
}
249
250
impl<Func, A, B> Clone for CombinatorSystem<Func, A, B>
251
where
252
A: Clone,
253
B: Clone,
254
{
255
/// Clone the combined system. The cloned instance must be `.initialize()`d before it can run.
256
fn clone(&self) -> Self {
257
CombinatorSystem::new(self.a.clone(), self.b.clone(), self.name.clone())
258
}
259
}
260
261
/// An [`IntoSystem`] creating an instance of [`PipeSystem`].
262
#[derive(Clone)]
263
pub struct IntoPipeSystem<A, B> {
264
a: A,
265
b: B,
266
}
267
268
impl<A, B> IntoPipeSystem<A, B> {
269
/// Creates a new [`IntoSystem`] that pipes two inner systems.
270
pub const fn new(a: A, b: B) -> Self {
271
Self { a, b }
272
}
273
}
274
275
#[doc(hidden)]
276
pub struct IsPipeSystemMarker;
277
278
impl<A, B, IA, OA, IB, OB, MA, MB> IntoSystem<IA, OB, (IsPipeSystemMarker, OA, IB, MA, MB)>
279
for IntoPipeSystem<A, B>
280
where
281
IA: SystemInput,
282
A: IntoSystem<IA, OA, MA>,
283
B: IntoSystem<IB, OB, MB>,
284
for<'a> IB: SystemInput<Inner<'a> = OA>,
285
{
286
type System = PipeSystem<A::System, B::System>;
287
288
fn into_system(this: Self) -> Self::System {
289
let system_a = IntoSystem::into_system(this.a);
290
let system_b = IntoSystem::into_system(this.b);
291
let name = format!("Pipe({}, {})", system_a.name(), system_b.name());
292
PipeSystem::new(system_a, system_b, DebugName::owned(name))
293
}
294
}
295
296
/// A [`System`] created by piping the output of the first system into the input of the second.
297
///
298
/// This can be repeated indefinitely, but system pipes cannot branch: the output is consumed by the receiving system.
299
///
300
/// Given two systems `A` and `B`, A may be piped into `B` as `A.pipe(B)` if the output type of `A` is
301
/// equal to the input type of `B`.
302
///
303
/// Note that for [`FunctionSystem`](crate::system::FunctionSystem)s the output is the return value
304
/// of the function and the input is the first [`SystemParam`](crate::system::SystemParam) if it is
305
/// tagged with [`In`](crate::system::In) or `()` if the function has no designated input parameter.
306
///
307
/// # Examples
308
///
309
/// ```
310
/// use std::num::ParseIntError;
311
///
312
/// use bevy_ecs::prelude::*;
313
///
314
/// fn main() {
315
/// let mut world = World::default();
316
/// world.insert_resource(Message("42".to_string()));
317
///
318
/// // pipe the `parse_message_system`'s output into the `filter_system`s input
319
/// let mut piped_system = IntoSystem::into_system(parse_message_system.pipe(filter_system));
320
/// piped_system.initialize(&mut world);
321
/// assert_eq!(piped_system.run((), &mut world).unwrap(), Some(42));
322
/// }
323
///
324
/// #[derive(Resource)]
325
/// struct Message(String);
326
///
327
/// fn parse_message_system(message: Res<Message>) -> Result<usize, ParseIntError> {
328
/// message.0.parse::<usize>()
329
/// }
330
///
331
/// fn filter_system(In(result): In<Result<usize, ParseIntError>>) -> Option<usize> {
332
/// result.ok().filter(|&n| n < 100)
333
/// }
334
/// ```
335
pub struct PipeSystem<A, B> {
336
a: A,
337
b: B,
338
name: DebugName,
339
}
340
341
impl<A, B> PipeSystem<A, B>
342
where
343
A: System,
344
B: System,
345
for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
346
{
347
/// Creates a new system that pipes two inner systems.
348
pub fn new(a: A, b: B, name: DebugName) -> Self {
349
Self { a, b, name }
350
}
351
}
352
353
impl<A, B> System for PipeSystem<A, B>
354
where
355
A: System,
356
B: System,
357
for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
358
{
359
type In = A::In;
360
type Out = B::Out;
361
362
fn name(&self) -> DebugName {
363
self.name.clone()
364
}
365
366
#[inline]
367
fn flags(&self) -> super::SystemStateFlags {
368
self.a.flags() | self.b.flags()
369
}
370
371
unsafe fn run_unsafe(
372
&mut self,
373
input: SystemIn<'_, Self>,
374
world: UnsafeWorldCell,
375
) -> Result<Self::Out, RunSystemError> {
376
let value = self.a.run_unsafe(input, world)?;
377
// `Self::validate_param_unsafe` already validated the first system,
378
// but we still need to validate the second system once the first one runs.
379
self.b.validate_param_unsafe(world)?;
380
self.b.run_unsafe(value, world)
381
}
382
383
#[cfg(feature = "hotpatching")]
384
#[inline]
385
fn refresh_hotpatch(&mut self) {
386
self.a.refresh_hotpatch();
387
self.b.refresh_hotpatch();
388
}
389
390
fn apply_deferred(&mut self, world: &mut World) {
391
self.a.apply_deferred(world);
392
self.b.apply_deferred(world);
393
}
394
395
fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {
396
self.a.queue_deferred(world.reborrow());
397
self.b.queue_deferred(world);
398
}
399
400
unsafe fn validate_param_unsafe(
401
&mut self,
402
world: UnsafeWorldCell,
403
) -> Result<(), SystemParamValidationError> {
404
// We only validate parameters for the first system,
405
// since it may make changes to the world that affect
406
// whether the second system has valid parameters.
407
// The second system will be validated in `Self::run_unsafe`.
408
// SAFETY: Delegate to the `System` implementation for `a`.
409
unsafe { self.a.validate_param_unsafe(world) }
410
}
411
412
fn initialize(&mut self, world: &mut World) -> FilteredAccessSet {
413
let mut a_access = self.a.initialize(world);
414
let b_access = self.b.initialize(world);
415
a_access.extend(b_access);
416
a_access
417
}
418
419
fn check_change_tick(&mut self, check: CheckChangeTicks) {
420
self.a.check_change_tick(check);
421
self.b.check_change_tick(check);
422
}
423
424
fn default_system_sets(&self) -> Vec<InternedSystemSet> {
425
let mut default_sets = self.a.default_system_sets();
426
default_sets.append(&mut self.b.default_system_sets());
427
default_sets
428
}
429
430
fn get_last_run(&self) -> Tick {
431
self.a.get_last_run()
432
}
433
434
fn set_last_run(&mut self, last_run: Tick) {
435
self.a.set_last_run(last_run);
436
self.b.set_last_run(last_run);
437
}
438
}
439
440
/// SAFETY: Both systems are read-only, so any system created by piping them will only read from the world.
441
unsafe impl<A, B> ReadOnlySystem for PipeSystem<A, B>
442
where
443
A: ReadOnlySystem,
444
B: ReadOnlySystem,
445
for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
446
{
447
}
448
449
#[cfg(test)]
450
mod tests {
451
452
#[test]
453
fn exclusive_system_piping_is_possible() {
454
use crate::prelude::*;
455
456
fn my_exclusive_system(_world: &mut World) -> u32 {
457
1
458
}
459
460
fn out_pipe(input: In<u32>) {
461
assert!(input.0 == 1);
462
}
463
464
let mut world = World::new();
465
466
let mut schedule = Schedule::default();
467
schedule.add_systems(my_exclusive_system.pipe(out_pipe));
468
469
schedule.run(&mut world);
470
}
471
}
472
473