Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/system/combinator.rs
9421 views
1
use alloc::{format, vec::Vec};
2
use bevy_utils::prelude::DebugName;
3
use core::marker::PhantomData;
4
5
use crate::{
6
change_detection::{CheckChangeTicks, Tick},
7
error::ErrorContext,
8
prelude::World,
9
query::FilteredAccessSet,
10
schedule::InternedSystemSet,
11
system::{input::SystemInput, SystemIn, SystemParamValidationError},
12
world::unsafe_world_cell::UnsafeWorldCell,
13
};
14
15
use 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
)]
93
pub 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`].
115
pub struct CombinatorSystem<Func, A, B> {
116
_marker: PhantomData<fn() -> Func>,
117
a: A,
118
b: B,
119
name: DebugName,
120
}
121
122
impl<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
136
impl<A, B, Func> System for CombinatorSystem<Func, A, B>
137
where
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 {
172
system.validate_param_unsafe(world.0)?;
173
system.run_unsafe(input, world.0)
174
})() {
175
// let the world's default error handler handle the error if `Failed(_)`
176
Err(RunSystemError::Failed(err)) => {
177
// SAFETY: We registered access to DefaultErrorHandler in `initialize`.
178
(unsafe { world.0.default_error_handler() })(
179
err,
180
ErrorContext::System {
181
name: system.name(),
182
last_run: system.get_last_run(),
183
},
184
);
185
186
// Since the error handler takes the error by value, create a new error:
187
// The original error has already been handled, including
188
// the reason for the failure here isn't important.
189
Err(format!("System `{}` failed", system.name()).into())
190
}
191
// `Skipped(_)` and `Ok(_)` are passed through:
192
// system skipping is not an error, and isn't passed to the
193
// world's error handler by the executors.
194
result @ (Ok(_) | Err(RunSystemError::Skipped(_))) => result,
195
}
196
}
197
198
Func::combine(
199
input,
200
&mut PrivateUnsafeWorldCell(world),
201
// SAFETY: The world accesses for both underlying systems have been registered,
202
// so the caller will guarantee that no other systems will conflict with (`a` or `b`) and the `DefaultErrorHandler` resource.
203
// If either system has `is_exclusive()`, then the combined system also has `is_exclusive`.
204
// Since we require a `combine` to pass in a mutable reference to `world` and that's a private type
205
// passed to a function as an unbound non-'static generic argument, they can never be called in parallel
206
// or re-entrantly because that would require forging another instance of `PrivateUnsafeWorldCell`.
207
// This means that the world accesses in the two closures will not conflict with each other.
208
// The closure's access to the DefaultErrorHandler does not
209
// conflict with any potential access to the DefaultErrorHandler by
210
// the systems since the closures are not run in parallel.
211
|input, world| unsafe { run_system(&mut self.a, input, world) },
212
// SAFETY: See the comment above.
213
|input, world| unsafe { run_system(&mut self.b, input, world) },
214
)
215
}
216
217
#[cfg(feature = "hotpatching")]
218
#[inline]
219
fn refresh_hotpatch(&mut self) {
220
self.a.refresh_hotpatch();
221
self.b.refresh_hotpatch();
222
}
223
224
#[inline]
225
fn apply_deferred(&mut self, world: &mut World) {
226
self.a.apply_deferred(world);
227
self.b.apply_deferred(world);
228
}
229
230
#[inline]
231
fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {
232
self.a.queue_deferred(world.reborrow());
233
self.b.queue_deferred(world);
234
}
235
236
#[inline]
237
unsafe fn validate_param_unsafe(
238
&mut self,
239
_world: UnsafeWorldCell,
240
) -> Result<(), SystemParamValidationError> {
241
// Both systems are validated in `Self::run_unsafe`, so that we get the
242
// chance to run the second system even if the first one fails to
243
// validate.
244
Ok(())
245
}
246
247
fn initialize(&mut self, world: &mut World) -> FilteredAccessSet {
248
let mut a_access = self.a.initialize(world);
249
let b_access = self.b.initialize(world);
250
a_access.extend(b_access);
251
252
// We might need to read the default error handler after the component
253
// systems have run to report failures.
254
let error_resource = world.register_resource::<crate::error::DefaultErrorHandler>();
255
a_access.add_unfiltered_resource_read(error_resource);
256
a_access
257
}
258
259
fn check_change_tick(&mut self, check: CheckChangeTicks) {
260
self.a.check_change_tick(check);
261
self.b.check_change_tick(check);
262
}
263
264
fn default_system_sets(&self) -> Vec<InternedSystemSet> {
265
let mut default_sets = self.a.default_system_sets();
266
default_sets.append(&mut self.b.default_system_sets());
267
default_sets
268
}
269
270
fn get_last_run(&self) -> Tick {
271
self.a.get_last_run()
272
}
273
274
fn set_last_run(&mut self, last_run: Tick) {
275
self.a.set_last_run(last_run);
276
self.b.set_last_run(last_run);
277
}
278
}
279
280
// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world.
281
unsafe impl<Func, A, B> ReadOnlySystem for CombinatorSystem<Func, A, B>
282
where
283
Func: Combine<A, B> + 'static,
284
A: ReadOnlySystem,
285
B: ReadOnlySystem,
286
{
287
}
288
289
impl<Func, A, B> Clone for CombinatorSystem<Func, A, B>
290
where
291
A: Clone,
292
B: Clone,
293
{
294
/// Clone the combined system. The cloned instance must be `.initialize()`d before it can run.
295
fn clone(&self) -> Self {
296
CombinatorSystem::new(self.a.clone(), self.b.clone(), self.name.clone())
297
}
298
}
299
300
/// An [`IntoSystem`] creating an instance of [`PipeSystem`].
301
#[derive(Clone)]
302
pub struct IntoPipeSystem<A, B> {
303
a: A,
304
b: B,
305
}
306
307
impl<A, B> IntoPipeSystem<A, B> {
308
/// Creates a new [`IntoSystem`] that pipes two inner systems.
309
pub const fn new(a: A, b: B) -> Self {
310
Self { a, b }
311
}
312
}
313
314
#[doc(hidden)]
315
pub struct IsPipeSystemMarker;
316
317
impl<A, B, IA, OA, IB, OB, MA, MB> IntoSystem<IA, OB, (IsPipeSystemMarker, OA, IB, MA, MB)>
318
for IntoPipeSystem<A, B>
319
where
320
IA: SystemInput,
321
A: IntoSystem<IA, OA, MA>,
322
B: IntoSystem<IB, OB, MB>,
323
for<'a> IB: SystemInput<Inner<'a> = OA>,
324
{
325
type System = PipeSystem<A::System, B::System>;
326
327
fn into_system(this: Self) -> Self::System {
328
let system_a = IntoSystem::into_system(this.a);
329
let system_b = IntoSystem::into_system(this.b);
330
let name = format!("Pipe({}, {})", system_a.name(), system_b.name());
331
PipeSystem::new(system_a, system_b, DebugName::owned(name))
332
}
333
}
334
335
/// A [`System`] created by piping the output of the first system into the input of the second.
336
///
337
/// This can be repeated indefinitely, but system pipes cannot branch: the output is consumed by the receiving system.
338
///
339
/// Given two systems `A` and `B`, A may be piped into `B` as `A.pipe(B)` if the output type of `A` is
340
/// equal to the input type of `B`.
341
///
342
/// Note that for [`FunctionSystem`](crate::system::FunctionSystem)s the output is the return value
343
/// of the function and the input is the first [`SystemParam`](crate::system::SystemParam) if it is
344
/// tagged with [`In`](crate::system::In) or `()` if the function has no designated input parameter.
345
///
346
/// # Examples
347
///
348
/// ```
349
/// use std::num::ParseIntError;
350
///
351
/// use bevy_ecs::prelude::*;
352
///
353
/// fn main() {
354
/// let mut world = World::default();
355
/// world.insert_resource(Message("42".to_string()));
356
///
357
/// // pipe the `parse_message_system`'s output into the `filter_system`s input
358
/// let mut piped_system = IntoSystem::into_system(parse_message_system.pipe(filter_system));
359
/// piped_system.initialize(&mut world);
360
/// assert_eq!(piped_system.run((), &mut world).unwrap(), Some(42));
361
/// }
362
///
363
/// #[derive(Resource)]
364
/// struct Message(String);
365
///
366
/// fn parse_message_system(message: Res<Message>) -> Result<usize, ParseIntError> {
367
/// message.0.parse::<usize>()
368
/// }
369
///
370
/// fn filter_system(In(result): In<Result<usize, ParseIntError>>) -> Option<usize> {
371
/// result.ok().filter(|&n| n < 100)
372
/// }
373
/// ```
374
pub struct PipeSystem<A, B> {
375
a: A,
376
b: B,
377
name: DebugName,
378
}
379
380
impl<A, B> PipeSystem<A, B>
381
where
382
A: System,
383
B: System,
384
for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
385
{
386
/// Creates a new system that pipes two inner systems.
387
pub fn new(a: A, b: B, name: DebugName) -> Self {
388
Self { a, b, name }
389
}
390
}
391
392
impl<A, B> System for PipeSystem<A, B>
393
where
394
A: System,
395
B: System,
396
for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
397
{
398
type In = A::In;
399
type Out = B::Out;
400
401
fn name(&self) -> DebugName {
402
self.name.clone()
403
}
404
405
#[inline]
406
fn flags(&self) -> super::SystemStateFlags {
407
self.a.flags() | self.b.flags()
408
}
409
410
unsafe fn run_unsafe(
411
&mut self,
412
input: SystemIn<'_, Self>,
413
world: UnsafeWorldCell,
414
) -> Result<Self::Out, RunSystemError> {
415
// SAFETY: Upheld by caller
416
unsafe {
417
let value = self.a.run_unsafe(input, world)?;
418
// `Self::validate_param_unsafe` already validated the first system,
419
// but we still need to validate the second system once the first one runs.
420
self.b.validate_param_unsafe(world)?;
421
self.b.run_unsafe(value, world)
422
}
423
}
424
425
#[cfg(feature = "hotpatching")]
426
#[inline]
427
fn refresh_hotpatch(&mut self) {
428
self.a.refresh_hotpatch();
429
self.b.refresh_hotpatch();
430
}
431
432
fn apply_deferred(&mut self, world: &mut World) {
433
self.a.apply_deferred(world);
434
self.b.apply_deferred(world);
435
}
436
437
fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {
438
self.a.queue_deferred(world.reborrow());
439
self.b.queue_deferred(world);
440
}
441
442
unsafe fn validate_param_unsafe(
443
&mut self,
444
world: UnsafeWorldCell,
445
) -> Result<(), SystemParamValidationError> {
446
// We only validate parameters for the first system,
447
// since it may make changes to the world that affect
448
// whether the second system has valid parameters.
449
// The second system will be validated in `Self::run_unsafe`.
450
// SAFETY: Delegate to the `System` implementation for `a`.
451
unsafe { self.a.validate_param_unsafe(world) }
452
}
453
454
fn initialize(&mut self, world: &mut World) -> FilteredAccessSet {
455
let mut a_access = self.a.initialize(world);
456
let b_access = self.b.initialize(world);
457
a_access.extend(b_access);
458
a_access
459
}
460
461
fn check_change_tick(&mut self, check: CheckChangeTicks) {
462
self.a.check_change_tick(check);
463
self.b.check_change_tick(check);
464
}
465
466
fn default_system_sets(&self) -> Vec<InternedSystemSet> {
467
let mut default_sets = self.a.default_system_sets();
468
default_sets.append(&mut self.b.default_system_sets());
469
default_sets
470
}
471
472
fn get_last_run(&self) -> Tick {
473
self.a.get_last_run()
474
}
475
476
fn set_last_run(&mut self, last_run: Tick) {
477
self.a.set_last_run(last_run);
478
self.b.set_last_run(last_run);
479
}
480
}
481
482
// SAFETY: Both systems are read-only, so any system created by piping them will only read from the world.
483
unsafe impl<A, B> ReadOnlySystem for PipeSystem<A, B>
484
where
485
A: ReadOnlySystem,
486
B: ReadOnlySystem,
487
for<'a> B::In: SystemInput<Inner<'a> = A::Out>,
488
{
489
}
490
491
#[cfg(test)]
492
mod tests {
493
use crate::error::DefaultErrorHandler;
494
use crate::prelude::*;
495
use bevy_utils::prelude::DebugName;
496
497
use crate::{
498
schedule::OrMarker,
499
system::{assert_system_does_not_conflict, CombinatorSystem},
500
};
501
502
#[test]
503
fn combinator_with_error_handler_access() {
504
fn my_system(_: ResMut<DefaultErrorHandler>) {}
505
fn a() -> bool {
506
true
507
}
508
fn b(_: ResMut<DefaultErrorHandler>) -> bool {
509
true
510
}
511
fn asdf(_: In<bool>) {}
512
513
let mut world = World::new();
514
world.insert_resource(DefaultErrorHandler::default());
515
516
let system = CombinatorSystem::<OrMarker, _, _>::new(
517
IntoSystem::into_system(a),
518
IntoSystem::into_system(b),
519
DebugName::borrowed("a OR b"),
520
);
521
522
// `system` should not conflict with itself by mutably accessing the error handler resource.
523
assert_system_does_not_conflict(system.clone());
524
525
let mut schedule = Schedule::default();
526
schedule.add_systems((my_system, system.pipe(asdf)));
527
schedule.initialize(&mut world).unwrap();
528
529
// `my_system` should conflict with the combinator system because the combinator reads the error handler resource.
530
assert!(!schedule.graph().conflicting_systems().is_empty());
531
532
schedule.run(&mut world);
533
}
534
535
#[test]
536
fn exclusive_system_piping_is_possible() {
537
fn my_exclusive_system(_world: &mut World) -> u32 {
538
1
539
}
540
541
fn out_pipe(input: In<u32>) {
542
assert!(input.0 == 1);
543
}
544
545
let mut world = World::new();
546
547
let mut schedule = Schedule::default();
548
schedule.add_systems(my_exclusive_system.pipe(out_pipe));
549
550
schedule.run(&mut world);
551
}
552
}
553
554