Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/schedule/schedule.rs
6849 views
1
#![expect(
2
clippy::module_inception,
3
reason = "This instance of module inception is being discussed; see #17344."
4
)]
5
use alloc::{
6
boxed::Box,
7
collections::{BTreeMap, BTreeSet},
8
format,
9
string::{String, ToString},
10
vec,
11
vec::Vec,
12
};
13
use bevy_platform::collections::{HashMap, HashSet};
14
use bevy_utils::{default, prelude::DebugName, TypeIdMap};
15
use core::{
16
any::{Any, TypeId},
17
fmt::{Debug, Write},
18
};
19
use fixedbitset::FixedBitSet;
20
use log::{error, info, warn};
21
use pass::ScheduleBuildPassObj;
22
use thiserror::Error;
23
#[cfg(feature = "trace")]
24
use tracing::info_span;
25
26
use crate::{component::CheckChangeTicks, system::System};
27
use crate::{
28
component::{ComponentId, Components},
29
prelude::Component,
30
resource::Resource,
31
schedule::*,
32
system::ScheduleSystem,
33
world::World,
34
};
35
36
use crate::{query::AccessConflicts, storage::SparseSetIndex};
37
pub use stepping::Stepping;
38
use Direction::{Incoming, Outgoing};
39
40
/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s excluding the current running [`Schedule`].
41
#[derive(Default, Resource)]
42
pub struct Schedules {
43
inner: HashMap<InternedScheduleLabel, Schedule>,
44
/// List of [`ComponentId`]s to ignore when reporting system order ambiguity conflicts
45
pub ignored_scheduling_ambiguities: BTreeSet<ComponentId>,
46
}
47
48
impl Schedules {
49
/// Constructs an empty `Schedules` with zero initial capacity.
50
pub fn new() -> Self {
51
Self::default()
52
}
53
54
/// Inserts a labeled schedule into the map.
55
///
56
/// If the map already had an entry for `label`, `schedule` is inserted,
57
/// and the old schedule is returned. Otherwise, `None` is returned.
58
pub fn insert(&mut self, schedule: Schedule) -> Option<Schedule> {
59
self.inner.insert(schedule.label, schedule)
60
}
61
62
/// Removes the schedule corresponding to the `label` from the map, returning it if it existed.
63
pub fn remove(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {
64
self.inner.remove(&label.intern())
65
}
66
67
/// Removes the (schedule, label) pair corresponding to the `label` from the map, returning it if it existed.
68
pub fn remove_entry(
69
&mut self,
70
label: impl ScheduleLabel,
71
) -> Option<(InternedScheduleLabel, Schedule)> {
72
self.inner.remove_entry(&label.intern())
73
}
74
75
/// Does a schedule with the provided label already exist?
76
pub fn contains(&self, label: impl ScheduleLabel) -> bool {
77
self.inner.contains_key(&label.intern())
78
}
79
80
/// Returns a reference to the schedule associated with `label`, if it exists.
81
pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
82
self.inner.get(&label.intern())
83
}
84
85
/// Returns a mutable reference to the schedule associated with `label`, if it exists.
86
pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
87
self.inner.get_mut(&label.intern())
88
}
89
90
/// Returns a mutable reference to the schedules associated with `label`, creating one if it doesn't already exist.
91
pub fn entry(&mut self, label: impl ScheduleLabel) -> &mut Schedule {
92
self.inner
93
.entry(label.intern())
94
.or_insert_with(|| Schedule::new(label))
95
}
96
97
/// Returns an iterator over all schedules. Iteration order is undefined.
98
pub fn iter(&self) -> impl Iterator<Item = (&dyn ScheduleLabel, &Schedule)> {
99
self.inner
100
.iter()
101
.map(|(label, schedule)| (&**label, schedule))
102
}
103
/// Returns an iterator over mutable references to all schedules. Iteration order is undefined.
104
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&dyn ScheduleLabel, &mut Schedule)> {
105
self.inner
106
.iter_mut()
107
.map(|(label, schedule)| (&**label, schedule))
108
}
109
110
/// Iterates the change ticks of all systems in all stored schedules and clamps any older than
111
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
112
/// This prevents overflow and thus prevents false positives.
113
pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {
114
#[cfg(feature = "trace")]
115
let _all_span = info_span!("check stored schedule ticks").entered();
116
#[cfg_attr(
117
not(feature = "trace"),
118
expect(
119
unused_variables,
120
reason = "The `label` variable goes unused if the `trace` feature isn't active"
121
)
122
)]
123
for (label, schedule) in &mut self.inner {
124
#[cfg(feature = "trace")]
125
let name = format!("{label:?}");
126
#[cfg(feature = "trace")]
127
let _one_span = info_span!("check schedule ticks", name = &name).entered();
128
schedule.check_change_ticks(check);
129
}
130
}
131
132
/// Applies the provided [`ScheduleBuildSettings`] to all schedules.
133
pub fn configure_schedules(&mut self, schedule_build_settings: ScheduleBuildSettings) {
134
for (_, schedule) in &mut self.inner {
135
schedule.set_build_settings(schedule_build_settings.clone());
136
}
137
}
138
139
/// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`.
140
pub fn allow_ambiguous_component<T: Component>(&mut self, world: &mut World) {
141
self.ignored_scheduling_ambiguities
142
.insert(world.register_component::<T>());
143
}
144
145
/// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`.
146
pub fn allow_ambiguous_resource<T: Resource>(&mut self, world: &mut World) {
147
self.ignored_scheduling_ambiguities
148
.insert(world.components_registrator().register_resource::<T>());
149
}
150
151
/// Iterate through the [`ComponentId`]'s that will be ignored.
152
pub fn iter_ignored_ambiguities(&self) -> impl Iterator<Item = &ComponentId> + '_ {
153
self.ignored_scheduling_ambiguities.iter()
154
}
155
156
/// Prints the names of the components and resources with [`info`]
157
///
158
/// May panic or retrieve incorrect names if [`Components`] is not from the same
159
/// world
160
pub fn print_ignored_ambiguities(&self, components: &Components) {
161
let mut message =
162
"System order ambiguities caused by conflicts on the following types are ignored:\n"
163
.to_string();
164
for id in self.iter_ignored_ambiguities() {
165
writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
166
}
167
168
info!("{message}");
169
}
170
171
/// Adds one or more systems to the [`Schedule`] matching the provided [`ScheduleLabel`].
172
pub fn add_systems<M>(
173
&mut self,
174
schedule: impl ScheduleLabel,
175
systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
176
) -> &mut Self {
177
self.entry(schedule).add_systems(systems);
178
179
self
180
}
181
182
/// Configures a collection of system sets in the provided schedule, adding any sets that do not exist.
183
#[track_caller]
184
pub fn configure_sets<M>(
185
&mut self,
186
schedule: impl ScheduleLabel,
187
sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
188
) -> &mut Self {
189
self.entry(schedule).configure_sets(sets);
190
191
self
192
}
193
194
/// Suppress warnings and errors that would result from systems in these sets having ambiguities
195
/// (conflicting access but indeterminate order) with systems in `set`.
196
///
197
/// When possible, do this directly in the `.add_systems(Update, a.ambiguous_with(b))` call.
198
/// However, sometimes two independent plugins `A` and `B` are reported as ambiguous, which you
199
/// can only suppress as the consumer of both.
200
#[track_caller]
201
pub fn ignore_ambiguity<M1, M2, S1, S2>(
202
&mut self,
203
schedule: impl ScheduleLabel,
204
a: S1,
205
b: S2,
206
) -> &mut Self
207
where
208
S1: IntoSystemSet<M1>,
209
S2: IntoSystemSet<M2>,
210
{
211
self.entry(schedule).ignore_ambiguity(a, b);
212
213
self
214
}
215
}
216
217
fn make_executor(kind: ExecutorKind) -> Box<dyn SystemExecutor> {
218
match kind {
219
ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()),
220
#[cfg(feature = "std")]
221
ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()),
222
}
223
}
224
225
/// Chain systems into dependencies
226
#[derive(Default)]
227
pub enum Chain {
228
/// Systems are independent. Nodes are allowed to run in any order.
229
#[default]
230
Unchained,
231
/// Systems are chained. `before -> after` ordering constraints
232
/// will be added between the successive elements.
233
Chained(TypeIdMap<Box<dyn Any>>),
234
}
235
236
impl Chain {
237
/// Specify that the systems must be chained.
238
pub fn set_chained(&mut self) {
239
if matches!(self, Chain::Unchained) {
240
*self = Self::Chained(Default::default());
241
};
242
}
243
/// Specify that the systems must be chained, and add the specified configuration for
244
/// all dependencies created between these systems.
245
pub fn set_chained_with_config<T: 'static>(&mut self, config: T) {
246
self.set_chained();
247
if let Chain::Chained(config_map) = self {
248
config_map.insert(TypeId::of::<T>(), Box::new(config));
249
} else {
250
unreachable!()
251
};
252
}
253
}
254
255
/// A collection of systems, and the metadata and executor needed to run them
256
/// in a certain order under certain conditions.
257
///
258
/// # Schedule labels
259
///
260
/// Each schedule has a [`ScheduleLabel`] value. This value is used to uniquely identify the
261
/// schedule when added to a [`World`]’s [`Schedules`], and may be used to specify which schedule
262
/// a system should be added to.
263
///
264
/// # Example
265
///
266
/// Here is an example of a `Schedule` running a "Hello world" system:
267
///
268
/// ```
269
/// # use bevy_ecs::prelude::*;
270
/// fn hello_world() { println!("Hello world!") }
271
///
272
/// fn main() {
273
/// let mut world = World::new();
274
/// let mut schedule = Schedule::default();
275
/// schedule.add_systems(hello_world);
276
///
277
/// schedule.run(&mut world);
278
/// }
279
/// ```
280
///
281
/// A schedule can also run several systems in an ordered way:
282
///
283
/// ```
284
/// # use bevy_ecs::prelude::*;
285
/// fn system_one() { println!("System 1 works!") }
286
/// fn system_two() { println!("System 2 works!") }
287
/// fn system_three() { println!("System 3 works!") }
288
///
289
/// fn main() {
290
/// let mut world = World::new();
291
/// let mut schedule = Schedule::default();
292
/// schedule.add_systems((
293
/// system_two,
294
/// system_one.before(system_two),
295
/// system_three.after(system_two),
296
/// ));
297
///
298
/// schedule.run(&mut world);
299
/// }
300
/// ```
301
///
302
/// Schedules are often inserted into a [`World`] and identified by their [`ScheduleLabel`] only:
303
///
304
/// ```
305
/// # use bevy_ecs::prelude::*;
306
/// use bevy_ecs::schedule::ScheduleLabel;
307
///
308
/// // Declare a new schedule label.
309
/// #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
310
/// struct Update;
311
///
312
/// // This system shall be part of the schedule.
313
/// fn an_update_system() {
314
/// println!("Hello world!");
315
/// }
316
///
317
/// fn main() {
318
/// let mut world = World::new();
319
///
320
/// // Add a system to the schedule with that label (creating it automatically).
321
/// world.get_resource_or_init::<Schedules>().add_systems(Update, an_update_system);
322
///
323
/// // Run the schedule, and therefore run the system.
324
/// world.run_schedule(Update);
325
/// }
326
/// ```
327
pub struct Schedule {
328
label: InternedScheduleLabel,
329
graph: ScheduleGraph,
330
executable: SystemSchedule,
331
executor: Box<dyn SystemExecutor>,
332
executor_initialized: bool,
333
warnings: Vec<ScheduleBuildWarning>,
334
}
335
336
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
337
struct DefaultSchedule;
338
339
impl Default for Schedule {
340
/// Creates a schedule with a default label. Only use in situations where
341
/// you don't care about the [`ScheduleLabel`]. Inserting a default schedule
342
/// into the world risks overwriting another schedule. For most situations
343
/// you should use [`Schedule::new`].
344
fn default() -> Self {
345
Self::new(DefaultSchedule)
346
}
347
}
348
349
impl Schedule {
350
/// Constructs an empty `Schedule`.
351
pub fn new(label: impl ScheduleLabel) -> Self {
352
let mut this = Self {
353
label: label.intern(),
354
graph: ScheduleGraph::new(),
355
executable: SystemSchedule::new(),
356
executor: make_executor(ExecutorKind::default()),
357
executor_initialized: false,
358
warnings: Vec::new(),
359
};
360
// Call `set_build_settings` to add any default build passes
361
this.set_build_settings(Default::default());
362
this
363
}
364
365
/// Returns the [`InternedScheduleLabel`] for this `Schedule`,
366
/// corresponding to the [`ScheduleLabel`] this schedule was created with.
367
pub fn label(&self) -> InternedScheduleLabel {
368
self.label
369
}
370
371
/// Add a collection of systems to the schedule.
372
pub fn add_systems<M>(
373
&mut self,
374
systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
375
) -> &mut Self {
376
self.graph.process_configs(systems.into_configs(), false);
377
self
378
}
379
380
/// Suppress warnings and errors that would result from systems in these sets having ambiguities
381
/// (conflicting access but indeterminate order) with systems in `set`.
382
#[track_caller]
383
pub fn ignore_ambiguity<M1, M2, S1, S2>(&mut self, a: S1, b: S2) -> &mut Self
384
where
385
S1: IntoSystemSet<M1>,
386
S2: IntoSystemSet<M2>,
387
{
388
let a = a.into_system_set();
389
let b = b.into_system_set();
390
391
let a_id = self.graph.system_sets.get_key_or_insert(a.intern());
392
let b_id = self.graph.system_sets.get_key_or_insert(b.intern());
393
394
self.graph
395
.ambiguous_with
396
.add_edge(NodeId::Set(a_id), NodeId::Set(b_id));
397
398
self
399
}
400
401
/// Configures a collection of system sets in this schedule, adding them if they does not exist.
402
#[track_caller]
403
pub fn configure_sets<M>(
404
&mut self,
405
sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
406
) -> &mut Self {
407
self.graph.configure_sets(sets);
408
self
409
}
410
411
/// Add a custom build pass to the schedule.
412
pub fn add_build_pass<T: ScheduleBuildPass>(&mut self, pass: T) -> &mut Self {
413
self.graph.passes.insert(TypeId::of::<T>(), Box::new(pass));
414
self
415
}
416
417
/// Remove a custom build pass.
418
pub fn remove_build_pass<T: ScheduleBuildPass>(&mut self) {
419
self.graph.passes.remove(&TypeId::of::<T>());
420
}
421
422
/// Changes miscellaneous build settings.
423
///
424
/// If [`settings.auto_insert_apply_deferred`][ScheduleBuildSettings::auto_insert_apply_deferred]
425
/// is `false`, this clears `*_ignore_deferred` edge settings configured so far.
426
///
427
/// Generally this method should be used before adding systems or set configurations to the schedule,
428
/// not after.
429
pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self {
430
if settings.auto_insert_apply_deferred {
431
if !self
432
.graph
433
.passes
434
.contains_key(&TypeId::of::<passes::AutoInsertApplyDeferredPass>())
435
{
436
self.add_build_pass(passes::AutoInsertApplyDeferredPass::default());
437
}
438
} else {
439
self.remove_build_pass::<passes::AutoInsertApplyDeferredPass>();
440
}
441
self.graph.settings = settings;
442
self
443
}
444
445
/// Returns the schedule's current `ScheduleBuildSettings`.
446
pub fn get_build_settings(&self) -> ScheduleBuildSettings {
447
self.graph.settings.clone()
448
}
449
450
/// Returns the schedule's current execution strategy.
451
pub fn get_executor_kind(&self) -> ExecutorKind {
452
self.executor.kind()
453
}
454
455
/// Sets the schedule's execution strategy.
456
pub fn set_executor_kind(&mut self, executor: ExecutorKind) -> &mut Self {
457
if executor != self.executor.kind() {
458
self.executor = make_executor(executor);
459
self.executor_initialized = false;
460
}
461
self
462
}
463
464
/// Set whether the schedule applies deferred system buffers on final time or not. This is a catch-all
465
/// in case a system uses commands but was not explicitly ordered before an instance of
466
/// [`ApplyDeferred`]. By default this
467
/// setting is true, but may be disabled if needed.
468
pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {
469
self.executor.set_apply_final_deferred(apply_final_deferred);
470
self
471
}
472
473
/// Runs all systems in this schedule on the `world`, using its current execution strategy.
474
pub fn run(&mut self, world: &mut World) {
475
#[cfg(feature = "trace")]
476
let _span = info_span!("schedule", name = ?self.label).entered();
477
478
world.check_change_ticks();
479
self.initialize(world).unwrap_or_else(|e| {
480
panic!(
481
"Error when initializing schedule {:?}: {}",
482
self.label,
483
e.to_string(self.graph(), world)
484
)
485
});
486
487
let error_handler = world.default_error_handler();
488
489
#[cfg(not(feature = "bevy_debug_stepping"))]
490
self.executor
491
.run(&mut self.executable, world, None, error_handler);
492
493
#[cfg(feature = "bevy_debug_stepping")]
494
{
495
let skip_systems = match world.get_resource_mut::<Stepping>() {
496
None => None,
497
Some(mut stepping) => stepping.skipped_systems(self),
498
};
499
500
self.executor.run(
501
&mut self.executable,
502
world,
503
skip_systems.as_ref(),
504
error_handler,
505
);
506
}
507
}
508
509
/// Initializes any newly-added systems and conditions, rebuilds the executable schedule,
510
/// and re-initializes the executor.
511
///
512
/// Moves all systems and run conditions out of the [`ScheduleGraph`].
513
pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> {
514
if self.graph.changed {
515
self.graph.initialize(world);
516
let ignored_ambiguities = world
517
.get_resource_or_init::<Schedules>()
518
.ignored_scheduling_ambiguities
519
.clone();
520
self.warnings = self.graph.update_schedule(
521
world,
522
&mut self.executable,
523
&ignored_ambiguities,
524
self.label,
525
)?;
526
self.graph.changed = false;
527
self.executor_initialized = false;
528
}
529
530
if !self.executor_initialized {
531
self.executor.init(&self.executable);
532
self.executor_initialized = true;
533
}
534
535
Ok(())
536
}
537
538
/// Returns the [`ScheduleGraph`].
539
pub fn graph(&self) -> &ScheduleGraph {
540
&self.graph
541
}
542
543
/// Returns a mutable reference to the [`ScheduleGraph`].
544
pub fn graph_mut(&mut self) -> &mut ScheduleGraph {
545
&mut self.graph
546
}
547
548
/// Returns the [`SystemSchedule`].
549
pub(crate) fn executable(&self) -> &SystemSchedule {
550
&self.executable
551
}
552
553
/// Iterates the change ticks of all systems in the schedule and clamps any older than
554
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
555
/// This prevents overflow and thus prevents false positives.
556
pub fn check_change_ticks(&mut self, check: CheckChangeTicks) {
557
for system in &mut self.executable.systems {
558
if !is_apply_deferred(system) {
559
system.check_change_tick(check);
560
}
561
}
562
563
for conditions in &mut self.executable.system_conditions {
564
for condition in conditions {
565
condition.check_change_tick(check);
566
}
567
}
568
569
for conditions in &mut self.executable.set_conditions {
570
for condition in conditions {
571
condition.check_change_tick(check);
572
}
573
}
574
}
575
576
/// Directly applies any accumulated [`Deferred`](crate::system::Deferred) system parameters (like [`Commands`](crate::prelude::Commands)) to the `world`.
577
///
578
/// Like always, deferred system parameters are applied in the "topological sort order" of the schedule graph.
579
/// As a result, buffers from one system are only guaranteed to be applied before those of other systems
580
/// if there is an explicit system ordering between the two systems.
581
///
582
/// This is used in rendering to extract data from the main world, storing the data in system buffers,
583
/// before applying their buffers in a different world.
584
pub fn apply_deferred(&mut self, world: &mut World) {
585
for SystemWithAccess { system, .. } in &mut self.executable.systems {
586
system.apply_deferred(world);
587
}
588
}
589
590
/// Returns an iterator over all systems in this schedule.
591
///
592
/// Note: this method will return [`ScheduleNotInitialized`] if the
593
/// schedule has never been initialized or run.
594
pub fn systems(
595
&self,
596
) -> Result<impl Iterator<Item = (SystemKey, &ScheduleSystem)> + Sized, ScheduleNotInitialized>
597
{
598
if !self.executor_initialized {
599
return Err(ScheduleNotInitialized);
600
}
601
602
let iter = self
603
.executable
604
.system_ids
605
.iter()
606
.zip(&self.executable.systems)
607
.map(|(&node_id, system)| (node_id, &system.system));
608
609
Ok(iter)
610
}
611
612
/// Returns the number of systems in this schedule.
613
pub fn systems_len(&self) -> usize {
614
if !self.executor_initialized {
615
self.graph.systems.len()
616
} else {
617
self.executable.systems.len()
618
}
619
}
620
621
/// Returns warnings that were generated during the last call to
622
/// [`Schedule::initialize`].
623
pub fn warnings(&self) -> &[ScheduleBuildWarning] {
624
&self.warnings
625
}
626
}
627
628
/// A directed acyclic graph structure.
629
pub struct Dag<N: GraphNodeId> {
630
/// A directed graph.
631
graph: DiGraph<N>,
632
/// A cached topological ordering of the graph.
633
topsort: Vec<N>,
634
}
635
636
impl<N: GraphNodeId> Dag<N> {
637
fn new() -> Self {
638
Self {
639
graph: DiGraph::default(),
640
topsort: Vec::new(),
641
}
642
}
643
644
/// The directed graph of the stored systems, connected by their ordering dependencies.
645
pub fn graph(&self) -> &DiGraph<N> {
646
&self.graph
647
}
648
649
/// A cached topological ordering of the graph.
650
///
651
/// The order is determined by the ordering dependencies between systems.
652
pub fn cached_topsort(&self) -> &[N] {
653
&self.topsort
654
}
655
}
656
657
impl<N: GraphNodeId> Default for Dag<N> {
658
fn default() -> Self {
659
Self {
660
graph: Default::default(),
661
topsort: Default::default(),
662
}
663
}
664
}
665
666
/// Metadata for a [`Schedule`].
667
///
668
/// The order isn't optimized; calling `ScheduleGraph::build_schedule` will return a
669
/// `SystemSchedule` where the order is optimized for execution.
670
#[derive(Default)]
671
pub struct ScheduleGraph {
672
/// Container of systems in the schedule.
673
pub systems: Systems,
674
/// Container of system sets in the schedule.
675
pub system_sets: SystemSets,
676
/// Directed acyclic graph of the hierarchy (which systems/sets are children of which sets)
677
hierarchy: Dag<NodeId>,
678
/// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets)
679
dependency: Dag<NodeId>,
680
ambiguous_with: UnGraph<NodeId>,
681
/// Nodes that are allowed to have ambiguous ordering relationship with any other systems.
682
pub ambiguous_with_all: HashSet<NodeId>,
683
conflicting_systems: Vec<(SystemKey, SystemKey, Vec<ComponentId>)>,
684
anonymous_sets: usize,
685
changed: bool,
686
settings: ScheduleBuildSettings,
687
passes: BTreeMap<TypeId, Box<dyn ScheduleBuildPassObj>>,
688
}
689
690
impl ScheduleGraph {
691
/// Creates an empty [`ScheduleGraph`] with default settings.
692
pub fn new() -> Self {
693
Self {
694
systems: Systems::default(),
695
system_sets: SystemSets::default(),
696
hierarchy: Dag::new(),
697
dependency: Dag::new(),
698
ambiguous_with: UnGraph::default(),
699
ambiguous_with_all: HashSet::default(),
700
conflicting_systems: Vec::new(),
701
anonymous_sets: 0,
702
changed: false,
703
settings: default(),
704
passes: default(),
705
}
706
}
707
708
/// Returns the [`Dag`] of the hierarchy.
709
///
710
/// The hierarchy is a directed acyclic graph of the systems and sets,
711
/// where an edge denotes that a system or set is the child of another set.
712
pub fn hierarchy(&self) -> &Dag<NodeId> {
713
&self.hierarchy
714
}
715
716
/// Returns the [`Dag`] of the dependencies in the schedule.
717
///
718
/// Nodes in this graph are systems and sets, and edges denote that
719
/// a system or set has to run before another system or set.
720
pub fn dependency(&self) -> &Dag<NodeId> {
721
&self.dependency
722
}
723
724
/// Returns the list of systems that conflict with each other, i.e. have ambiguities in their access.
725
///
726
/// If the `Vec<ComponentId>` is empty, the systems conflict on [`World`] access.
727
/// Must be called after [`ScheduleGraph::build_schedule`] to be non-empty.
728
pub fn conflicting_systems(&self) -> &[(SystemKey, SystemKey, Vec<ComponentId>)] {
729
&self.conflicting_systems
730
}
731
732
fn process_config<T: ProcessScheduleConfig + Schedulable>(
733
&mut self,
734
config: ScheduleConfig<T>,
735
collect_nodes: bool,
736
) -> ProcessConfigsResult {
737
ProcessConfigsResult {
738
densely_chained: true,
739
nodes: collect_nodes
740
.then_some(T::process_config(self, config))
741
.into_iter()
742
.collect(),
743
}
744
}
745
746
fn apply_collective_conditions<
747
T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
748
>(
749
&mut self,
750
configs: &mut [ScheduleConfigs<T>],
751
collective_conditions: Vec<BoxedCondition>,
752
) {
753
if !collective_conditions.is_empty() {
754
if let [config] = configs {
755
for condition in collective_conditions {
756
config.run_if_dyn(condition);
757
}
758
} else {
759
let set = self.create_anonymous_set();
760
for config in configs.iter_mut() {
761
config.in_set_inner(set.intern());
762
}
763
let mut set_config = InternedSystemSet::into_config(set.intern());
764
set_config.conditions.extend(collective_conditions);
765
self.configure_set_inner(set_config);
766
}
767
}
768
}
769
770
/// Adds the config nodes to the graph.
771
///
772
/// `collect_nodes` controls whether the `NodeId`s of the processed config nodes are stored in the returned [`ProcessConfigsResult`].
773
/// `process_config` is the function which processes each individual config node and returns a corresponding `NodeId`.
774
///
775
/// The fields on the returned [`ProcessConfigsResult`] are:
776
/// - `nodes`: a vector of all node ids contained in the nested `ScheduleConfigs`
777
/// - `densely_chained`: a boolean that is true if all nested nodes are linearly chained (with successive `after` orderings) in the order they are defined
778
#[track_caller]
779
fn process_configs<
780
T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
781
>(
782
&mut self,
783
configs: ScheduleConfigs<T>,
784
collect_nodes: bool,
785
) -> ProcessConfigsResult {
786
match configs {
787
ScheduleConfigs::ScheduleConfig(config) => self.process_config(config, collect_nodes),
788
ScheduleConfigs::Configs {
789
metadata,
790
mut configs,
791
collective_conditions,
792
} => {
793
self.apply_collective_conditions(&mut configs, collective_conditions);
794
795
let is_chained = matches!(metadata, Chain::Chained(_));
796
797
// Densely chained if
798
// * chained and all configs in the chain are densely chained, or
799
// * unchained with a single densely chained config
800
let mut densely_chained = is_chained || configs.len() == 1;
801
let mut configs = configs.into_iter();
802
let mut nodes = Vec::new();
803
804
let Some(first) = configs.next() else {
805
return ProcessConfigsResult {
806
nodes: Vec::new(),
807
densely_chained,
808
};
809
};
810
let mut previous_result = self.process_configs(first, collect_nodes || is_chained);
811
densely_chained &= previous_result.densely_chained;
812
813
for current in configs {
814
let current_result = self.process_configs(current, collect_nodes || is_chained);
815
densely_chained &= current_result.densely_chained;
816
817
if let Chain::Chained(chain_options) = &metadata {
818
// if the current result is densely chained, we only need to chain the first node
819
let current_nodes = if current_result.densely_chained {
820
&current_result.nodes[..1]
821
} else {
822
&current_result.nodes
823
};
824
// if the previous result was densely chained, we only need to chain the last node
825
let previous_nodes = if previous_result.densely_chained {
826
&previous_result.nodes[previous_result.nodes.len() - 1..]
827
} else {
828
&previous_result.nodes
829
};
830
831
for previous_node in previous_nodes {
832
for current_node in current_nodes {
833
self.dependency
834
.graph
835
.add_edge(*previous_node, *current_node);
836
837
for pass in self.passes.values_mut() {
838
pass.add_dependency(
839
*previous_node,
840
*current_node,
841
chain_options,
842
);
843
}
844
}
845
}
846
}
847
if collect_nodes {
848
nodes.append(&mut previous_result.nodes);
849
}
850
851
previous_result = current_result;
852
}
853
if collect_nodes {
854
nodes.append(&mut previous_result.nodes);
855
}
856
857
ProcessConfigsResult {
858
nodes,
859
densely_chained,
860
}
861
}
862
}
863
}
864
865
/// Add a [`ScheduleConfig`] to the graph, including its dependencies and conditions.
866
fn add_system_inner(&mut self, config: ScheduleConfig<ScheduleSystem>) -> SystemKey {
867
let key = self.systems.insert(config.node, config.conditions);
868
869
// graph updates are immediate
870
self.update_graphs(NodeId::System(key), config.metadata);
871
872
key
873
}
874
875
#[track_caller]
876
fn configure_sets<M>(&mut self, sets: impl IntoScheduleConfigs<InternedSystemSet, M>) {
877
self.process_configs(sets.into_configs(), false);
878
}
879
880
/// Add a single `ScheduleConfig` to the graph, including its dependencies and conditions.
881
fn configure_set_inner(&mut self, config: ScheduleConfig<InternedSystemSet>) -> SystemSetKey {
882
let key = self.system_sets.insert(config.node, config.conditions);
883
884
// graph updates are immediate
885
self.update_graphs(NodeId::Set(key), config.metadata);
886
887
key
888
}
889
890
fn create_anonymous_set(&mut self) -> AnonymousSet {
891
let id = self.anonymous_sets;
892
self.anonymous_sets += 1;
893
AnonymousSet::new(id)
894
}
895
896
/// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`]
897
fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) {
898
self.changed = true;
899
900
let GraphInfo {
901
hierarchy: sets,
902
dependencies,
903
ambiguous_with,
904
..
905
} = graph_info;
906
907
self.hierarchy.graph.add_node(id);
908
self.dependency.graph.add_node(id);
909
910
for key in sets
911
.into_iter()
912
.map(|set| self.system_sets.get_key_or_insert(set))
913
{
914
self.hierarchy.graph.add_edge(NodeId::Set(key), id);
915
916
// ensure set also appears in dependency graph
917
self.dependency.graph.add_node(NodeId::Set(key));
918
}
919
920
for (kind, key, options) in
921
dependencies
922
.into_iter()
923
.map(|Dependency { kind, set, options }| {
924
(kind, self.system_sets.get_key_or_insert(set), options)
925
})
926
{
927
let (lhs, rhs) = match kind {
928
DependencyKind::Before => (id, NodeId::Set(key)),
929
DependencyKind::After => (NodeId::Set(key), id),
930
};
931
self.dependency.graph.add_edge(lhs, rhs);
932
for pass in self.passes.values_mut() {
933
pass.add_dependency(lhs, rhs, &options);
934
}
935
936
// ensure set also appears in hierarchy graph
937
self.hierarchy.graph.add_node(NodeId::Set(key));
938
}
939
940
match ambiguous_with {
941
Ambiguity::Check => (),
942
Ambiguity::IgnoreWithSet(ambiguous_with) => {
943
for key in ambiguous_with
944
.into_iter()
945
.map(|set| self.system_sets.get_key_or_insert(set))
946
{
947
self.ambiguous_with.add_edge(id, NodeId::Set(key));
948
}
949
}
950
Ambiguity::IgnoreAll => {
951
self.ambiguous_with_all.insert(id);
952
}
953
}
954
}
955
956
/// Initializes any newly-added systems and conditions by calling
957
/// [`System::initialize`](crate::system::System).
958
pub fn initialize(&mut self, world: &mut World) {
959
self.systems.initialize(world);
960
self.system_sets.initialize(world);
961
}
962
963
/// Builds an execution-optimized [`SystemSchedule`] from the current state
964
/// of the graph. Also returns any warnings that were generated during the
965
/// build process.
966
///
967
/// This method also
968
/// - checks for dependency or hierarchy cycles
969
/// - checks for system access conflicts and reports ambiguities
970
pub fn build_schedule(
971
&mut self,
972
world: &mut World,
973
ignored_ambiguities: &BTreeSet<ComponentId>,
974
) -> Result<(SystemSchedule, Vec<ScheduleBuildWarning>), ScheduleBuildError> {
975
let mut warnings = Vec::new();
976
977
// check hierarchy for cycles
978
self.hierarchy.topsort =
979
self.topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy)?;
980
981
let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort);
982
if let Some(warning) =
983
self.optionally_check_hierarchy_conflicts(&hier_results.transitive_edges)?
984
{
985
warnings.push(warning);
986
}
987
988
// remove redundant edges
989
self.hierarchy.graph = hier_results.transitive_reduction;
990
991
// check dependencies for cycles
992
self.dependency.topsort =
993
self.topsort_graph(&self.dependency.graph, ReportCycles::Dependency)?;
994
995
// check for systems or system sets depending on sets they belong to
996
let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort);
997
self.check_for_cross_dependencies(&dep_results, &hier_results.connected)?;
998
999
// map all system sets to their systems
1000
// go in reverse topological order (bottom-up) for efficiency
1001
let (set_systems, set_system_bitsets) =
1002
self.map_sets_to_systems(&self.hierarchy.topsort, &self.hierarchy.graph);
1003
self.check_order_but_intersect(&dep_results.connected, &set_system_bitsets)?;
1004
1005
// check that there are no edges to system-type sets that have multiple instances
1006
self.check_system_type_set_ambiguity(&set_systems)?;
1007
1008
let mut dependency_flattened = self.get_dependency_flattened(&set_systems);
1009
1010
// modify graph with build passes
1011
let mut passes = core::mem::take(&mut self.passes);
1012
for pass in passes.values_mut() {
1013
pass.build(world, self, &mut dependency_flattened)?;
1014
}
1015
self.passes = passes;
1016
1017
// topsort
1018
let mut dependency_flattened_dag = Dag {
1019
topsort: self.topsort_graph(&dependency_flattened, ReportCycles::Dependency)?,
1020
graph: dependency_flattened,
1021
};
1022
1023
let flat_results = check_graph(
1024
&dependency_flattened_dag.graph,
1025
&dependency_flattened_dag.topsort,
1026
);
1027
1028
// remove redundant edges
1029
dependency_flattened_dag.graph = flat_results.transitive_reduction;
1030
1031
// flatten: combine `in_set` with `ambiguous_with` information
1032
let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems);
1033
1034
// check for conflicts
1035
let conflicting_systems = self.get_conflicting_systems(
1036
&flat_results.disconnected,
1037
&ambiguous_with_flattened,
1038
ignored_ambiguities,
1039
);
1040
if let Some(warning) = self.optionally_check_conflicts(&conflicting_systems)? {
1041
warnings.push(warning);
1042
}
1043
self.conflicting_systems = conflicting_systems;
1044
1045
// build the schedule
1046
Ok((
1047
self.build_schedule_inner(dependency_flattened_dag, hier_results.reachable),
1048
warnings,
1049
))
1050
}
1051
1052
/// Return a map from system set `NodeId` to a list of system `NodeId`s that are included in the set.
1053
/// Also return a map from system set `NodeId` to a `FixedBitSet` of system `NodeId`s that are included in the set,
1054
/// where the bitset order is the same as `self.systems`
1055
fn map_sets_to_systems(
1056
&self,
1057
hierarchy_topsort: &[NodeId],
1058
hierarchy_graph: &DiGraph<NodeId>,
1059
) -> (
1060
HashMap<SystemSetKey, Vec<SystemKey>>,
1061
HashMap<SystemSetKey, HashSet<SystemKey>>,
1062
) {
1063
let mut set_systems: HashMap<SystemSetKey, Vec<SystemKey>> =
1064
HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
1065
let mut set_system_sets: HashMap<SystemSetKey, HashSet<SystemKey>> =
1066
HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
1067
for &id in hierarchy_topsort.iter().rev() {
1068
let NodeId::Set(set_key) = id else {
1069
continue;
1070
};
1071
1072
let mut systems = Vec::new();
1073
let mut system_set = HashSet::with_capacity(self.systems.len());
1074
1075
for child in hierarchy_graph.neighbors_directed(id, Outgoing) {
1076
match child {
1077
NodeId::System(key) => {
1078
systems.push(key);
1079
system_set.insert(key);
1080
}
1081
NodeId::Set(key) => {
1082
let child_systems = set_systems.get(&key).unwrap();
1083
let child_system_set = set_system_sets.get(&key).unwrap();
1084
systems.extend_from_slice(child_systems);
1085
system_set.extend(child_system_set.iter());
1086
}
1087
}
1088
}
1089
1090
set_systems.insert(set_key, systems);
1091
set_system_sets.insert(set_key, system_set);
1092
}
1093
(set_systems, set_system_sets)
1094
}
1095
1096
fn get_dependency_flattened(
1097
&mut self,
1098
set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,
1099
) -> DiGraph<SystemKey> {
1100
// flatten: combine `in_set` with `before` and `after` information
1101
// have to do it like this to preserve transitivity
1102
let mut dependency_flattening = self.dependency.graph.clone();
1103
let mut temp = Vec::new();
1104
for (&set, systems) in set_systems {
1105
for pass in self.passes.values_mut() {
1106
pass.collapse_set(set, systems, &dependency_flattening, &mut temp);
1107
}
1108
if systems.is_empty() {
1109
// collapse dependencies for empty sets
1110
for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Incoming) {
1111
for b in dependency_flattening.neighbors_directed(NodeId::Set(set), Outgoing) {
1112
temp.push((a, b));
1113
}
1114
}
1115
} else {
1116
for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Incoming) {
1117
for &sys in systems {
1118
temp.push((a, NodeId::System(sys)));
1119
}
1120
}
1121
1122
for b in dependency_flattening.neighbors_directed(NodeId::Set(set), Outgoing) {
1123
for &sys in systems {
1124
temp.push((NodeId::System(sys), b));
1125
}
1126
}
1127
}
1128
1129
dependency_flattening.remove_node(NodeId::Set(set));
1130
for (a, b) in temp.drain(..) {
1131
dependency_flattening.add_edge(a, b);
1132
}
1133
}
1134
1135
// By this point, we should have removed all system sets from the graph,
1136
// so this conversion should never fail.
1137
dependency_flattening
1138
.try_into::<SystemKey>()
1139
.unwrap_or_else(|n| {
1140
unreachable!(
1141
"Flattened dependency graph has a leftover system set {}",
1142
self.get_node_name(&NodeId::Set(n))
1143
)
1144
})
1145
}
1146
1147
fn get_ambiguous_with_flattened(
1148
&self,
1149
set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,
1150
) -> UnGraph<NodeId> {
1151
let mut ambiguous_with_flattened = UnGraph::default();
1152
for (lhs, rhs) in self.ambiguous_with.all_edges() {
1153
match (lhs, rhs) {
1154
(NodeId::System(_), NodeId::System(_)) => {
1155
ambiguous_with_flattened.add_edge(lhs, rhs);
1156
}
1157
(NodeId::Set(lhs), NodeId::System(_)) => {
1158
for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1159
ambiguous_with_flattened.add_edge(NodeId::System(lhs_), rhs);
1160
}
1161
}
1162
(NodeId::System(_), NodeId::Set(rhs)) => {
1163
for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) {
1164
ambiguous_with_flattened.add_edge(lhs, NodeId::System(rhs_));
1165
}
1166
}
1167
(NodeId::Set(lhs), NodeId::Set(rhs)) => {
1168
for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1169
for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) {
1170
ambiguous_with_flattened
1171
.add_edge(NodeId::System(lhs_), NodeId::System(rhs_));
1172
}
1173
}
1174
}
1175
}
1176
}
1177
1178
ambiguous_with_flattened
1179
}
1180
1181
fn get_conflicting_systems(
1182
&self,
1183
flat_results_disconnected: &Vec<(SystemKey, SystemKey)>,
1184
ambiguous_with_flattened: &UnGraph<NodeId>,
1185
ignored_ambiguities: &BTreeSet<ComponentId>,
1186
) -> Vec<(SystemKey, SystemKey, Vec<ComponentId>)> {
1187
let mut conflicting_systems = Vec::new();
1188
for &(a, b) in flat_results_disconnected {
1189
if ambiguous_with_flattened.contains_edge(NodeId::System(a), NodeId::System(b))
1190
|| self.ambiguous_with_all.contains(&NodeId::System(a))
1191
|| self.ambiguous_with_all.contains(&NodeId::System(b))
1192
{
1193
continue;
1194
}
1195
1196
let system_a = &self.systems[a];
1197
let system_b = &self.systems[b];
1198
if system_a.is_exclusive() || system_b.is_exclusive() {
1199
conflicting_systems.push((a, b, Vec::new()));
1200
} else {
1201
let access_a = &system_a.access;
1202
let access_b = &system_b.access;
1203
if !access_a.is_compatible(access_b) {
1204
match access_a.get_conflicts(access_b) {
1205
AccessConflicts::Individual(conflicts) => {
1206
let conflicts: Vec<_> = conflicts
1207
.ones()
1208
.map(ComponentId::get_sparse_set_index)
1209
.filter(|id| !ignored_ambiguities.contains(id))
1210
.collect();
1211
if !conflicts.is_empty() {
1212
conflicting_systems.push((a, b, conflicts));
1213
}
1214
}
1215
AccessConflicts::All => {
1216
// there is no specific component conflicting, but the systems are overall incompatible
1217
// for example 2 systems with `Query<EntityMut>`
1218
conflicting_systems.push((a, b, Vec::new()));
1219
}
1220
}
1221
}
1222
}
1223
}
1224
1225
conflicting_systems
1226
}
1227
1228
fn build_schedule_inner(
1229
&self,
1230
dependency_flattened_dag: Dag<SystemKey>,
1231
hier_results_reachable: FixedBitSet,
1232
) -> SystemSchedule {
1233
let dg_system_ids = dependency_flattened_dag.topsort;
1234
let dg_system_idx_map = dg_system_ids
1235
.iter()
1236
.cloned()
1237
.enumerate()
1238
.map(|(i, id)| (id, i))
1239
.collect::<HashMap<_, _>>();
1240
1241
let hg_systems = self
1242
.hierarchy
1243
.topsort
1244
.iter()
1245
.cloned()
1246
.enumerate()
1247
.filter_map(|(i, id)| Some((i, id.as_system()?)))
1248
.collect::<Vec<_>>();
1249
1250
let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self
1251
.hierarchy
1252
.topsort
1253
.iter()
1254
.cloned()
1255
.enumerate()
1256
.filter_map(|(i, id)| {
1257
// ignore system sets that have no conditions
1258
// ignore system type sets (already covered, they don't have conditions)
1259
let key = id.as_set()?;
1260
self.system_sets.has_conditions(key).then_some((i, key))
1261
})
1262
.unzip();
1263
1264
let sys_count = self.systems.len();
1265
let set_with_conditions_count = hg_set_ids.len();
1266
let hg_node_count = self.hierarchy.graph.node_count();
1267
1268
// get the number of dependencies and the immediate dependents of each system
1269
// (needed by multi_threaded executor to run systems in the correct order)
1270
let mut system_dependencies = Vec::with_capacity(sys_count);
1271
let mut system_dependents = Vec::with_capacity(sys_count);
1272
for &sys_key in &dg_system_ids {
1273
let num_dependencies = dependency_flattened_dag
1274
.graph
1275
.neighbors_directed(sys_key, Incoming)
1276
.count();
1277
1278
let dependents = dependency_flattened_dag
1279
.graph
1280
.neighbors_directed(sys_key, Outgoing)
1281
.map(|dep_id| dg_system_idx_map[&dep_id])
1282
.collect::<Vec<_>>();
1283
1284
system_dependencies.push(num_dependencies);
1285
system_dependents.push(dependents);
1286
}
1287
1288
// get the rows and columns of the hierarchy graph's reachability matrix
1289
// (needed to we can evaluate conditions in the correct order)
1290
let mut systems_in_sets_with_conditions =
1291
vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count];
1292
for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() {
1293
let bitset = &mut systems_in_sets_with_conditions[i];
1294
for &(col, sys_key) in &hg_systems {
1295
let idx = dg_system_idx_map[&sys_key];
1296
let is_descendant = hier_results_reachable[index(row, col, hg_node_count)];
1297
bitset.set(idx, is_descendant);
1298
}
1299
}
1300
1301
let mut sets_with_conditions_of_systems =
1302
vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count];
1303
for &(col, sys_key) in &hg_systems {
1304
let i = dg_system_idx_map[&sys_key];
1305
let bitset = &mut sets_with_conditions_of_systems[i];
1306
for (idx, &row) in hg_set_with_conditions_idxs
1307
.iter()
1308
.enumerate()
1309
.take_while(|&(_idx, &row)| row < col)
1310
{
1311
let is_ancestor = hier_results_reachable[index(row, col, hg_node_count)];
1312
bitset.set(idx, is_ancestor);
1313
}
1314
}
1315
1316
SystemSchedule {
1317
systems: Vec::with_capacity(sys_count),
1318
system_conditions: Vec::with_capacity(sys_count),
1319
set_conditions: Vec::with_capacity(set_with_conditions_count),
1320
system_ids: dg_system_ids,
1321
set_ids: hg_set_ids,
1322
system_dependencies,
1323
system_dependents,
1324
sets_with_conditions_of_systems,
1325
systems_in_sets_with_conditions,
1326
}
1327
}
1328
1329
/// Updates the `SystemSchedule` from the `ScheduleGraph`.
1330
fn update_schedule(
1331
&mut self,
1332
world: &mut World,
1333
schedule: &mut SystemSchedule,
1334
ignored_ambiguities: &BTreeSet<ComponentId>,
1335
schedule_label: InternedScheduleLabel,
1336
) -> Result<Vec<ScheduleBuildWarning>, ScheduleBuildError> {
1337
if !self.systems.is_initialized() || !self.system_sets.is_initialized() {
1338
return Err(ScheduleBuildError::Uninitialized);
1339
}
1340
1341
// move systems out of old schedule
1342
for ((key, system), conditions) in schedule
1343
.system_ids
1344
.drain(..)
1345
.zip(schedule.systems.drain(..))
1346
.zip(schedule.system_conditions.drain(..))
1347
{
1348
self.systems.node_mut(key).inner = Some(system);
1349
*self.systems.get_conditions_mut(key).unwrap() = conditions;
1350
}
1351
1352
for (key, conditions) in schedule
1353
.set_ids
1354
.drain(..)
1355
.zip(schedule.set_conditions.drain(..))
1356
{
1357
*self.system_sets.get_conditions_mut(key).unwrap() = conditions;
1358
}
1359
1360
let (new_schedule, warnings) = self.build_schedule(world, ignored_ambiguities)?;
1361
*schedule = new_schedule;
1362
1363
for warning in &warnings {
1364
warn!(
1365
"{:?} schedule built successfully, however: {}",
1366
schedule_label,
1367
warning.to_string(self, world)
1368
);
1369
}
1370
1371
// move systems into new schedule
1372
for &key in &schedule.system_ids {
1373
let system = self.systems.node_mut(key).inner.take().unwrap();
1374
let conditions = core::mem::take(self.systems.get_conditions_mut(key).unwrap());
1375
schedule.systems.push(system);
1376
schedule.system_conditions.push(conditions);
1377
}
1378
1379
for &key in &schedule.set_ids {
1380
let conditions = core::mem::take(self.system_sets.get_conditions_mut(key).unwrap());
1381
schedule.set_conditions.push(conditions);
1382
}
1383
1384
Ok(warnings)
1385
}
1386
}
1387
1388
/// Values returned by [`ScheduleGraph::process_configs`]
1389
struct ProcessConfigsResult {
1390
/// All nodes contained inside this `process_configs` call's [`ScheduleConfigs`] hierarchy,
1391
/// if `ancestor_chained` is true
1392
nodes: Vec<NodeId>,
1393
/// True if and only if all nodes are "densely chained", meaning that all nested nodes
1394
/// are linearly chained (as if `after` system ordering had been applied between each node)
1395
/// in the order they are defined
1396
densely_chained: bool,
1397
}
1398
1399
/// Trait used by [`ScheduleGraph::process_configs`] to process a single [`ScheduleConfig`].
1400
trait ProcessScheduleConfig: Schedulable + Sized {
1401
/// Process a single [`ScheduleConfig`].
1402
fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId;
1403
}
1404
1405
impl ProcessScheduleConfig for ScheduleSystem {
1406
fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1407
NodeId::System(schedule_graph.add_system_inner(config))
1408
}
1409
}
1410
1411
impl ProcessScheduleConfig for InternedSystemSet {
1412
fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1413
NodeId::Set(schedule_graph.configure_set_inner(config))
1414
}
1415
}
1416
1417
/// Used to select the appropriate reporting function.
1418
pub enum ReportCycles {
1419
/// When sets contain themselves
1420
Hierarchy,
1421
/// When the graph is no longer a DAG
1422
Dependency,
1423
}
1424
1425
// methods for reporting errors
1426
impl ScheduleGraph {
1427
/// Returns the name of the node with the given [`NodeId`]. Resolves
1428
/// anonymous sets to a string that describes their contents.
1429
///
1430
/// Also displays the set(s) the node is contained in if
1431
/// [`ScheduleBuildSettings::report_sets`] is true, and shortens system names
1432
/// if [`ScheduleBuildSettings::use_shortnames`] is true.
1433
pub fn get_node_name(&self, id: &NodeId) -> String {
1434
self.get_node_name_inner(id, self.settings.report_sets)
1435
}
1436
1437
#[inline]
1438
fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String {
1439
match *id {
1440
NodeId::System(key) => {
1441
let name = self.systems[key].name();
1442
let name = if self.settings.use_shortnames {
1443
name.shortname().to_string()
1444
} else {
1445
name.to_string()
1446
};
1447
if report_sets {
1448
let sets = self.names_of_sets_containing_node(id);
1449
if sets.is_empty() {
1450
name
1451
} else if sets.len() == 1 {
1452
format!("{name} (in set {})", sets[0])
1453
} else {
1454
format!("{name} (in sets {})", sets.join(", "))
1455
}
1456
} else {
1457
name
1458
}
1459
}
1460
NodeId::Set(key) => {
1461
let set = &self.system_sets[key];
1462
if set.is_anonymous() {
1463
self.anonymous_set_name(id)
1464
} else {
1465
format!("{set:?}")
1466
}
1467
}
1468
}
1469
}
1470
1471
fn anonymous_set_name(&self, id: &NodeId) -> String {
1472
format!(
1473
"({})",
1474
self.hierarchy
1475
.graph
1476
.edges_directed(*id, Outgoing)
1477
// never get the sets of the members or this will infinite recurse when the report_sets setting is on.
1478
.map(|(_, member_id)| self.get_node_name_inner(&member_id, false))
1479
.reduce(|a, b| format!("{a}, {b}"))
1480
.unwrap_or_default()
1481
)
1482
}
1483
1484
/// If [`ScheduleBuildSettings::hierarchy_detection`] is [`LogLevel::Ignore`] this check
1485
/// is skipped.
1486
fn optionally_check_hierarchy_conflicts(
1487
&self,
1488
transitive_edges: &[(NodeId, NodeId)],
1489
) -> Result<Option<ScheduleBuildWarning>, ScheduleBuildError> {
1490
match (
1491
self.settings.hierarchy_detection,
1492
!transitive_edges.is_empty(),
1493
) {
1494
(LogLevel::Warn, true) => Ok(Some(ScheduleBuildWarning::HierarchyRedundancy(
1495
transitive_edges.to_vec(),
1496
))),
1497
(LogLevel::Error, true) => {
1498
Err(ScheduleBuildWarning::HierarchyRedundancy(transitive_edges.to_vec()).into())
1499
}
1500
_ => Ok(None),
1501
}
1502
}
1503
1504
/// Tries to topologically sort `graph`.
1505
///
1506
/// If the graph is acyclic, returns [`Ok`] with the list of [`NodeId`] in a valid
1507
/// topological order. If the graph contains cycles, returns [`Err`] with the list of
1508
/// strongly-connected components that contain cycles (also in a valid topological order).
1509
///
1510
/// # Errors
1511
///
1512
/// If the graph contain cycles, then an error is returned.
1513
pub fn topsort_graph<N: GraphNodeId + Into<NodeId>>(
1514
&self,
1515
graph: &DiGraph<N>,
1516
report: ReportCycles,
1517
) -> Result<Vec<N>, ScheduleBuildError> {
1518
// Check explicitly for self-edges.
1519
// `iter_sccs` won't report them as cycles because they still form components of one node.
1520
if let Some((node, _)) = graph.all_edges().find(|(left, right)| left == right) {
1521
let error = match report {
1522
ReportCycles::Hierarchy => ScheduleBuildError::HierarchyLoop(node.into()),
1523
ReportCycles::Dependency => ScheduleBuildError::DependencyLoop(node.into()),
1524
};
1525
return Err(error);
1526
}
1527
1528
// Tarjan's SCC algorithm returns elements in *reverse* topological order.
1529
let mut top_sorted_nodes = Vec::with_capacity(graph.node_count());
1530
let mut sccs_with_cycles = Vec::new();
1531
1532
for scc in graph.iter_sccs() {
1533
// A strongly-connected component is a group of nodes who can all reach each other
1534
// through one or more paths. If an SCC contains more than one node, there must be
1535
// at least one cycle within them.
1536
top_sorted_nodes.extend_from_slice(&scc);
1537
if scc.len() > 1 {
1538
sccs_with_cycles.push(scc);
1539
}
1540
}
1541
1542
if sccs_with_cycles.is_empty() {
1543
// reverse to get topological order
1544
top_sorted_nodes.reverse();
1545
Ok(top_sorted_nodes)
1546
} else {
1547
let mut cycles = Vec::new();
1548
for scc in &sccs_with_cycles {
1549
cycles.append(&mut simple_cycles_in_component(graph, scc));
1550
}
1551
1552
let error = match report {
1553
ReportCycles::Hierarchy => ScheduleBuildError::HierarchyCycle(
1554
cycles
1555
.into_iter()
1556
.map(|c| c.into_iter().map(Into::into).collect())
1557
.collect(),
1558
),
1559
ReportCycles::Dependency => ScheduleBuildError::DependencyCycle(
1560
cycles
1561
.into_iter()
1562
.map(|c| c.into_iter().map(Into::into).collect())
1563
.collect(),
1564
),
1565
};
1566
1567
Err(error)
1568
}
1569
}
1570
1571
fn check_for_cross_dependencies(
1572
&self,
1573
dep_results: &CheckGraphResults<NodeId>,
1574
hier_results_connected: &HashSet<(NodeId, NodeId)>,
1575
) -> Result<(), ScheduleBuildError> {
1576
for &(a, b) in &dep_results.connected {
1577
if hier_results_connected.contains(&(a, b)) || hier_results_connected.contains(&(b, a))
1578
{
1579
return Err(ScheduleBuildError::CrossDependency(a, b));
1580
}
1581
}
1582
1583
Ok(())
1584
}
1585
1586
fn check_order_but_intersect(
1587
&self,
1588
dep_results_connected: &HashSet<(NodeId, NodeId)>,
1589
set_system_sets: &HashMap<SystemSetKey, HashSet<SystemKey>>,
1590
) -> Result<(), ScheduleBuildError> {
1591
// check that there is no ordering between system sets that intersect
1592
for &(a, b) in dep_results_connected {
1593
let (NodeId::Set(a_key), NodeId::Set(b_key)) = (a, b) else {
1594
continue;
1595
};
1596
1597
let a_systems = set_system_sets.get(&a_key).unwrap();
1598
let b_systems = set_system_sets.get(&b_key).unwrap();
1599
1600
if !a_systems.is_disjoint(b_systems) {
1601
return Err(ScheduleBuildError::SetsHaveOrderButIntersect(a_key, b_key));
1602
}
1603
}
1604
1605
Ok(())
1606
}
1607
1608
fn check_system_type_set_ambiguity(
1609
&self,
1610
set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,
1611
) -> Result<(), ScheduleBuildError> {
1612
for (&key, systems) in set_systems {
1613
let set = &self.system_sets[key];
1614
if set.system_type().is_some() {
1615
let instances = systems.len();
1616
let ambiguous_with = self.ambiguous_with.edges(NodeId::Set(key));
1617
let before = self
1618
.dependency
1619
.graph
1620
.edges_directed(NodeId::Set(key), Incoming);
1621
let after = self
1622
.dependency
1623
.graph
1624
.edges_directed(NodeId::Set(key), Outgoing);
1625
let relations = before.count() + after.count() + ambiguous_with.count();
1626
if instances > 1 && relations > 0 {
1627
return Err(ScheduleBuildError::SystemTypeSetAmbiguity(key));
1628
}
1629
}
1630
}
1631
Ok(())
1632
}
1633
1634
/// if [`ScheduleBuildSettings::ambiguity_detection`] is [`LogLevel::Ignore`], this check is skipped
1635
fn optionally_check_conflicts(
1636
&self,
1637
conflicts: &[(SystemKey, SystemKey, Vec<ComponentId>)],
1638
) -> Result<Option<ScheduleBuildWarning>, ScheduleBuildError> {
1639
match (self.settings.ambiguity_detection, !conflicts.is_empty()) {
1640
(LogLevel::Warn, true) => Ok(Some(ScheduleBuildWarning::Ambiguity(conflicts.to_vec()))),
1641
(LogLevel::Error, true) => {
1642
Err(ScheduleBuildWarning::Ambiguity(conflicts.to_vec()).into())
1643
}
1644
_ => Ok(None),
1645
}
1646
}
1647
1648
/// convert conflicts to human readable format
1649
pub fn conflicts_to_string<'a>(
1650
&'a self,
1651
ambiguities: &'a [(SystemKey, SystemKey, Vec<ComponentId>)],
1652
components: &'a Components,
1653
) -> impl Iterator<Item = (String, String, Vec<DebugName>)> + 'a {
1654
ambiguities
1655
.iter()
1656
.map(move |(system_a, system_b, conflicts)| {
1657
let name_a = self.get_node_name(&NodeId::System(*system_a));
1658
let name_b = self.get_node_name(&NodeId::System(*system_b));
1659
1660
let conflict_names: Vec<_> = conflicts
1661
.iter()
1662
.map(|id| components.get_name(*id).unwrap())
1663
.collect();
1664
1665
(name_a, name_b, conflict_names)
1666
})
1667
}
1668
1669
fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(SystemSetKey) -> bool) {
1670
for (set_id, _) in self.hierarchy.graph.edges_directed(id, Incoming) {
1671
let NodeId::Set(set_key) = set_id else {
1672
continue;
1673
};
1674
if f(set_key) {
1675
self.traverse_sets_containing_node(NodeId::Set(set_key), f);
1676
}
1677
}
1678
}
1679
1680
fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec<String> {
1681
let mut sets = <HashSet<_>>::default();
1682
self.traverse_sets_containing_node(*id, &mut |key| {
1683
self.system_sets[key].system_type().is_none() && sets.insert(key)
1684
});
1685
let mut sets: Vec<_> = sets
1686
.into_iter()
1687
.map(|key| self.get_node_name(&NodeId::Set(key)))
1688
.collect();
1689
sets.sort();
1690
sets
1691
}
1692
}
1693
1694
/// Specifies how schedule construction should respond to detecting a certain kind of issue.
1695
#[derive(Debug, Clone, Copy, PartialEq)]
1696
pub enum LogLevel {
1697
/// Occurrences are completely ignored.
1698
Ignore,
1699
/// Occurrences are logged only.
1700
Warn,
1701
/// Occurrences are logged and result in errors.
1702
Error,
1703
}
1704
1705
/// Specifies miscellaneous settings for schedule construction.
1706
#[derive(Clone, Debug)]
1707
pub struct ScheduleBuildSettings {
1708
/// Determines whether the presence of ambiguities (systems with conflicting access but indeterminate order)
1709
/// is only logged or also results in an [`Ambiguity`](ScheduleBuildWarning::Ambiguity)
1710
/// warning or error.
1711
///
1712
/// Defaults to [`LogLevel::Ignore`].
1713
pub ambiguity_detection: LogLevel,
1714
/// Determines whether the presence of redundant edges in the hierarchy of system sets is only
1715
/// logged or also results in a [`HierarchyRedundancy`](ScheduleBuildWarning::HierarchyRedundancy)
1716
/// warning or error.
1717
///
1718
/// Defaults to [`LogLevel::Warn`].
1719
pub hierarchy_detection: LogLevel,
1720
/// Auto insert [`ApplyDeferred`] systems into the schedule,
1721
/// when there are [`Deferred`](crate::prelude::Deferred)
1722
/// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one
1723
/// such deferred buffer.
1724
///
1725
/// You may want to disable this if you only want to sync deferred params at the end of the schedule,
1726
/// or want to manually insert all your sync points.
1727
///
1728
/// Defaults to `true`
1729
pub auto_insert_apply_deferred: bool,
1730
/// If set to true, node names will be shortened instead of the fully qualified type path.
1731
///
1732
/// Defaults to `true`.
1733
pub use_shortnames: bool,
1734
/// If set to true, report all system sets the conflicting systems are part of.
1735
///
1736
/// Defaults to `true`.
1737
pub report_sets: bool,
1738
}
1739
1740
impl Default for ScheduleBuildSettings {
1741
fn default() -> Self {
1742
Self::new()
1743
}
1744
}
1745
1746
impl ScheduleBuildSettings {
1747
/// Default build settings.
1748
/// See the field-level documentation for the default value of each field.
1749
pub const fn new() -> Self {
1750
Self {
1751
ambiguity_detection: LogLevel::Ignore,
1752
hierarchy_detection: LogLevel::Warn,
1753
auto_insert_apply_deferred: true,
1754
use_shortnames: true,
1755
report_sets: true,
1756
}
1757
}
1758
}
1759
1760
/// Error to denote that [`Schedule::initialize`] or [`Schedule::run`] has not yet been called for
1761
/// this schedule.
1762
#[derive(Error, Debug)]
1763
#[error("executable schedule has not been built")]
1764
pub struct ScheduleNotInitialized;
1765
1766
#[cfg(test)]
1767
mod tests {
1768
use bevy_ecs_macros::ScheduleLabel;
1769
1770
use crate::{
1771
error::{ignore, panic, DefaultErrorHandler, Result},
1772
prelude::{ApplyDeferred, Res, Resource},
1773
schedule::{
1774
tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, SystemSet,
1775
},
1776
system::Commands,
1777
world::World,
1778
};
1779
1780
use super::Schedules;
1781
1782
#[derive(Resource)]
1783
struct Resource1;
1784
1785
#[derive(Resource)]
1786
struct Resource2;
1787
1788
#[test]
1789
fn unchanged_auto_insert_apply_deferred_has_no_effect() {
1790
use alloc::{vec, vec::Vec};
1791
1792
#[derive(PartialEq, Debug)]
1793
enum Entry {
1794
System(usize),
1795
SyncPoint(usize),
1796
}
1797
1798
#[derive(Resource, Default)]
1799
struct Log(Vec<Entry>);
1800
1801
fn system<const N: usize>(mut res: ResMut<Log>, mut commands: Commands) {
1802
res.0.push(Entry::System(N));
1803
commands
1804
.queue(|world: &mut World| world.resource_mut::<Log>().0.push(Entry::SyncPoint(N)));
1805
}
1806
1807
let mut world = World::default();
1808
world.init_resource::<Log>();
1809
let mut schedule = Schedule::default();
1810
schedule.add_systems((system::<1>, system::<2>).chain_ignore_deferred());
1811
schedule.set_build_settings(ScheduleBuildSettings {
1812
auto_insert_apply_deferred: true,
1813
..Default::default()
1814
});
1815
schedule.run(&mut world);
1816
let actual = world.remove_resource::<Log>().unwrap().0;
1817
1818
let expected = vec![
1819
Entry::System(1),
1820
Entry::System(2),
1821
Entry::SyncPoint(1),
1822
Entry::SyncPoint(2),
1823
];
1824
1825
assert_eq!(actual, expected);
1826
}
1827
1828
// regression test for https://github.com/bevyengine/bevy/issues/9114
1829
#[test]
1830
fn ambiguous_with_not_breaking_run_conditions() {
1831
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
1832
struct Set;
1833
1834
let mut world = World::new();
1835
let mut schedule = Schedule::default();
1836
1837
let system: fn() = || {
1838
panic!("This system must not run");
1839
};
1840
1841
schedule.configure_sets(Set.run_if(|| false));
1842
schedule.add_systems(system.ambiguous_with(|| ()).in_set(Set));
1843
schedule.run(&mut world);
1844
}
1845
1846
#[test]
1847
fn inserts_a_sync_point() {
1848
let mut schedule = Schedule::default();
1849
let mut world = World::default();
1850
schedule.add_systems(
1851
(
1852
|mut commands: Commands| commands.insert_resource(Resource1),
1853
|_: Res<Resource1>| {},
1854
)
1855
.chain(),
1856
);
1857
schedule.run(&mut world);
1858
1859
// inserted a sync point
1860
assert_eq!(schedule.executable.systems.len(), 3);
1861
}
1862
1863
#[test]
1864
fn explicit_sync_point_used_as_auto_sync_point() {
1865
let mut schedule = Schedule::default();
1866
let mut world = World::default();
1867
schedule.add_systems(
1868
(
1869
|mut commands: Commands| commands.insert_resource(Resource1),
1870
|_: Res<Resource1>| {},
1871
)
1872
.chain(),
1873
);
1874
schedule.add_systems((|| {}, ApplyDeferred, || {}).chain());
1875
schedule.run(&mut world);
1876
1877
// No sync point was inserted, since we can reuse the explicit sync point.
1878
assert_eq!(schedule.executable.systems.len(), 5);
1879
}
1880
1881
#[test]
1882
fn conditional_explicit_sync_point_not_used_as_auto_sync_point() {
1883
let mut schedule = Schedule::default();
1884
let mut world = World::default();
1885
schedule.add_systems(
1886
(
1887
|mut commands: Commands| commands.insert_resource(Resource1),
1888
|_: Res<Resource1>| {},
1889
)
1890
.chain(),
1891
);
1892
schedule.add_systems((|| {}, ApplyDeferred.run_if(|| false), || {}).chain());
1893
schedule.run(&mut world);
1894
1895
// A sync point was inserted, since the explicit sync point is not always run.
1896
assert_eq!(schedule.executable.systems.len(), 6);
1897
}
1898
1899
#[test]
1900
fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_chain() {
1901
let mut schedule = Schedule::default();
1902
let mut world = World::default();
1903
schedule.add_systems(
1904
(
1905
|mut commands: Commands| commands.insert_resource(Resource1),
1906
|_: Res<Resource1>| {},
1907
)
1908
.chain(),
1909
);
1910
schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().run_if(|| false));
1911
schedule.run(&mut world);
1912
1913
// A sync point was inserted, since the explicit sync point is not always run.
1914
assert_eq!(schedule.executable.systems.len(), 6);
1915
}
1916
1917
#[test]
1918
fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_system_set() {
1919
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
1920
struct Set;
1921
1922
let mut schedule = Schedule::default();
1923
let mut world = World::default();
1924
schedule.configure_sets(Set.run_if(|| false));
1925
schedule.add_systems(
1926
(
1927
|mut commands: Commands| commands.insert_resource(Resource1),
1928
|_: Res<Resource1>| {},
1929
)
1930
.chain(),
1931
);
1932
schedule.add_systems((|| {}, ApplyDeferred.in_set(Set), || {}).chain());
1933
schedule.run(&mut world);
1934
1935
// A sync point was inserted, since the explicit sync point is not always run.
1936
assert_eq!(schedule.executable.systems.len(), 6);
1937
}
1938
1939
#[test]
1940
fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_nested_system_set()
1941
{
1942
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
1943
struct Set1;
1944
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
1945
struct Set2;
1946
1947
let mut schedule = Schedule::default();
1948
let mut world = World::default();
1949
schedule.configure_sets(Set2.run_if(|| false));
1950
schedule.configure_sets(Set1.in_set(Set2));
1951
schedule.add_systems(
1952
(
1953
|mut commands: Commands| commands.insert_resource(Resource1),
1954
|_: Res<Resource1>| {},
1955
)
1956
.chain(),
1957
);
1958
schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().in_set(Set1));
1959
schedule.run(&mut world);
1960
1961
// A sync point was inserted, since the explicit sync point is not always run.
1962
assert_eq!(schedule.executable.systems.len(), 6);
1963
}
1964
1965
#[test]
1966
fn merges_sync_points_into_one() {
1967
let mut schedule = Schedule::default();
1968
let mut world = World::default();
1969
// insert two parallel command systems, it should only create one sync point
1970
schedule.add_systems(
1971
(
1972
(
1973
|mut commands: Commands| commands.insert_resource(Resource1),
1974
|mut commands: Commands| commands.insert_resource(Resource2),
1975
),
1976
|_: Res<Resource1>, _: Res<Resource2>| {},
1977
)
1978
.chain(),
1979
);
1980
schedule.run(&mut world);
1981
1982
// inserted sync points
1983
assert_eq!(schedule.executable.systems.len(), 4);
1984
1985
// merges sync points on rebuild
1986
schedule.add_systems(((
1987
(
1988
|mut commands: Commands| commands.insert_resource(Resource1),
1989
|mut commands: Commands| commands.insert_resource(Resource2),
1990
),
1991
|_: Res<Resource1>, _: Res<Resource2>| {},
1992
)
1993
.chain(),));
1994
schedule.run(&mut world);
1995
1996
assert_eq!(schedule.executable.systems.len(), 7);
1997
}
1998
1999
#[test]
2000
fn adds_multiple_consecutive_syncs() {
2001
let mut schedule = Schedule::default();
2002
let mut world = World::default();
2003
// insert two consecutive command systems, it should create two sync points
2004
schedule.add_systems(
2005
(
2006
|mut commands: Commands| commands.insert_resource(Resource1),
2007
|mut commands: Commands| commands.insert_resource(Resource2),
2008
|_: Res<Resource1>, _: Res<Resource2>| {},
2009
)
2010
.chain(),
2011
);
2012
schedule.run(&mut world);
2013
2014
assert_eq!(schedule.executable.systems.len(), 5);
2015
}
2016
2017
#[test]
2018
fn do_not_consider_ignore_deferred_before_exclusive_system() {
2019
let mut schedule = Schedule::default();
2020
let mut world = World::default();
2021
// chain_ignore_deferred adds no sync points usually but an exception is made for exclusive systems
2022
schedule.add_systems(
2023
(
2024
|_: Commands| {},
2025
// <- no sync point is added here because the following system is not exclusive
2026
|mut commands: Commands| commands.insert_resource(Resource1),
2027
// <- sync point is added here because the following system is exclusive which expects to see all commands to that point
2028
|world: &mut World| assert!(world.contains_resource::<Resource1>()),
2029
// <- no sync point is added here because the previous system has no deferred parameters
2030
|_: &mut World| {},
2031
// <- no sync point is added here because the following system is not exclusive
2032
|_: Commands| {},
2033
)
2034
.chain_ignore_deferred(),
2035
);
2036
schedule.run(&mut world);
2037
2038
assert_eq!(schedule.executable.systems.len(), 6); // 5 systems + 1 sync point
2039
}
2040
2041
#[test]
2042
fn bubble_sync_point_through_ignore_deferred_node() {
2043
let mut schedule = Schedule::default();
2044
let mut world = World::default();
2045
2046
let insert_resource_config = (
2047
// the first system has deferred commands
2048
|mut commands: Commands| commands.insert_resource(Resource1),
2049
// the second system has no deferred commands
2050
|| {},
2051
)
2052
// the first two systems are chained without a sync point in between
2053
.chain_ignore_deferred();
2054
2055
schedule.add_systems(
2056
(
2057
insert_resource_config,
2058
// the third system would panic if the command of the first system was not applied
2059
|_: Res<Resource1>| {},
2060
)
2061
// the third system is chained after the first two, possibly with a sync point in between
2062
.chain(),
2063
);
2064
2065
// To add a sync point between the second and third system despite the second having no commands,
2066
// the first system has to signal the second system that there are unapplied commands.
2067
// With that the second system will add a sync point after it so the third system will find the resource.
2068
2069
schedule.run(&mut world);
2070
2071
assert_eq!(schedule.executable.systems.len(), 4); // 3 systems + 1 sync point
2072
}
2073
2074
#[test]
2075
fn disable_auto_sync_points() {
2076
let mut schedule = Schedule::default();
2077
schedule.set_build_settings(ScheduleBuildSettings {
2078
auto_insert_apply_deferred: false,
2079
..Default::default()
2080
});
2081
let mut world = World::default();
2082
schedule.add_systems(
2083
(
2084
|mut commands: Commands| commands.insert_resource(Resource1),
2085
|res: Option<Res<Resource1>>| assert!(res.is_none()),
2086
)
2087
.chain(),
2088
);
2089
schedule.run(&mut world);
2090
2091
assert_eq!(schedule.executable.systems.len(), 2);
2092
}
2093
2094
mod no_sync_edges {
2095
use super::*;
2096
2097
fn insert_resource(mut commands: Commands) {
2098
commands.insert_resource(Resource1);
2099
}
2100
2101
fn resource_does_not_exist(res: Option<Res<Resource1>>) {
2102
assert!(res.is_none());
2103
}
2104
2105
#[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
2106
enum Sets {
2107
A,
2108
B,
2109
}
2110
2111
fn check_no_sync_edges(add_systems: impl FnOnce(&mut Schedule)) {
2112
let mut schedule = Schedule::default();
2113
let mut world = World::default();
2114
add_systems(&mut schedule);
2115
2116
schedule.run(&mut world);
2117
2118
assert_eq!(schedule.executable.systems.len(), 2);
2119
}
2120
2121
#[test]
2122
fn system_to_system_after() {
2123
check_no_sync_edges(|schedule| {
2124
schedule.add_systems((
2125
insert_resource,
2126
resource_does_not_exist.after_ignore_deferred(insert_resource),
2127
));
2128
});
2129
}
2130
2131
#[test]
2132
fn system_to_system_before() {
2133
check_no_sync_edges(|schedule| {
2134
schedule.add_systems((
2135
insert_resource.before_ignore_deferred(resource_does_not_exist),
2136
resource_does_not_exist,
2137
));
2138
});
2139
}
2140
2141
#[test]
2142
fn set_to_system_after() {
2143
check_no_sync_edges(|schedule| {
2144
schedule
2145
.add_systems((insert_resource, resource_does_not_exist.in_set(Sets::A)))
2146
.configure_sets(Sets::A.after_ignore_deferred(insert_resource));
2147
});
2148
}
2149
2150
#[test]
2151
fn set_to_system_before() {
2152
check_no_sync_edges(|schedule| {
2153
schedule
2154
.add_systems((insert_resource.in_set(Sets::A), resource_does_not_exist))
2155
.configure_sets(Sets::A.before_ignore_deferred(resource_does_not_exist));
2156
});
2157
}
2158
2159
#[test]
2160
fn set_to_set_after() {
2161
check_no_sync_edges(|schedule| {
2162
schedule
2163
.add_systems((
2164
insert_resource.in_set(Sets::A),
2165
resource_does_not_exist.in_set(Sets::B),
2166
))
2167
.configure_sets(Sets::B.after_ignore_deferred(Sets::A));
2168
});
2169
}
2170
2171
#[test]
2172
fn set_to_set_before() {
2173
check_no_sync_edges(|schedule| {
2174
schedule
2175
.add_systems((
2176
insert_resource.in_set(Sets::A),
2177
resource_does_not_exist.in_set(Sets::B),
2178
))
2179
.configure_sets(Sets::A.before_ignore_deferred(Sets::B));
2180
});
2181
}
2182
}
2183
2184
mod no_sync_chain {
2185
use super::*;
2186
2187
#[derive(Resource)]
2188
struct Ra;
2189
2190
#[derive(Resource)]
2191
struct Rb;
2192
2193
#[derive(Resource)]
2194
struct Rc;
2195
2196
fn run_schedule(expected_num_systems: usize, add_systems: impl FnOnce(&mut Schedule)) {
2197
let mut schedule = Schedule::default();
2198
let mut world = World::default();
2199
add_systems(&mut schedule);
2200
2201
schedule.run(&mut world);
2202
2203
assert_eq!(schedule.executable.systems.len(), expected_num_systems);
2204
}
2205
2206
#[test]
2207
fn only_chain_outside() {
2208
run_schedule(5, |schedule: &mut Schedule| {
2209
schedule.add_systems(
2210
(
2211
(
2212
|mut commands: Commands| commands.insert_resource(Ra),
2213
|mut commands: Commands| commands.insert_resource(Rb),
2214
),
2215
(
2216
|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2217
assert!(res_a.is_some());
2218
assert!(res_b.is_some());
2219
},
2220
|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2221
assert!(res_a.is_some());
2222
assert!(res_b.is_some());
2223
},
2224
),
2225
)
2226
.chain(),
2227
);
2228
});
2229
2230
run_schedule(4, |schedule: &mut Schedule| {
2231
schedule.add_systems(
2232
(
2233
(
2234
|mut commands: Commands| commands.insert_resource(Ra),
2235
|mut commands: Commands| commands.insert_resource(Rb),
2236
),
2237
(
2238
|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2239
assert!(res_a.is_none());
2240
assert!(res_b.is_none());
2241
},
2242
|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2243
assert!(res_a.is_none());
2244
assert!(res_b.is_none());
2245
},
2246
),
2247
)
2248
.chain_ignore_deferred(),
2249
);
2250
});
2251
}
2252
2253
#[test]
2254
fn chain_first() {
2255
run_schedule(6, |schedule: &mut Schedule| {
2256
schedule.add_systems(
2257
(
2258
(
2259
|mut commands: Commands| commands.insert_resource(Ra),
2260
|mut commands: Commands, res_a: Option<Res<Ra>>| {
2261
commands.insert_resource(Rb);
2262
assert!(res_a.is_some());
2263
},
2264
)
2265
.chain(),
2266
(
2267
|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2268
assert!(res_a.is_some());
2269
assert!(res_b.is_some());
2270
},
2271
|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2272
assert!(res_a.is_some());
2273
assert!(res_b.is_some());
2274
},
2275
),
2276
)
2277
.chain(),
2278
);
2279
});
2280
2281
run_schedule(5, |schedule: &mut Schedule| {
2282
schedule.add_systems(
2283
(
2284
(
2285
|mut commands: Commands| commands.insert_resource(Ra),
2286
|mut commands: Commands, res_a: Option<Res<Ra>>| {
2287
commands.insert_resource(Rb);
2288
assert!(res_a.is_some());
2289
},
2290
)
2291
.chain(),
2292
(
2293
|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2294
assert!(res_a.is_some());
2295
assert!(res_b.is_none());
2296
},
2297
|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2298
assert!(res_a.is_some());
2299
assert!(res_b.is_none());
2300
},
2301
),
2302
)
2303
.chain_ignore_deferred(),
2304
);
2305
});
2306
}
2307
2308
#[test]
2309
fn chain_second() {
2310
run_schedule(6, |schedule: &mut Schedule| {
2311
schedule.add_systems(
2312
(
2313
(
2314
|mut commands: Commands| commands.insert_resource(Ra),
2315
|mut commands: Commands| commands.insert_resource(Rb),
2316
),
2317
(
2318
|mut commands: Commands,
2319
res_a: Option<Res<Ra>>,
2320
res_b: Option<Res<Rb>>| {
2321
commands.insert_resource(Rc);
2322
assert!(res_a.is_some());
2323
assert!(res_b.is_some());
2324
},
2325
|res_a: Option<Res<Ra>>,
2326
res_b: Option<Res<Rb>>,
2327
res_c: Option<Res<Rc>>| {
2328
assert!(res_a.is_some());
2329
assert!(res_b.is_some());
2330
assert!(res_c.is_some());
2331
},
2332
)
2333
.chain(),
2334
)
2335
.chain(),
2336
);
2337
});
2338
2339
run_schedule(5, |schedule: &mut Schedule| {
2340
schedule.add_systems(
2341
(
2342
(
2343
|mut commands: Commands| commands.insert_resource(Ra),
2344
|mut commands: Commands| commands.insert_resource(Rb),
2345
),
2346
(
2347
|mut commands: Commands,
2348
res_a: Option<Res<Ra>>,
2349
res_b: Option<Res<Rb>>| {
2350
commands.insert_resource(Rc);
2351
assert!(res_a.is_none());
2352
assert!(res_b.is_none());
2353
},
2354
|res_a: Option<Res<Ra>>,
2355
res_b: Option<Res<Rb>>,
2356
res_c: Option<Res<Rc>>| {
2357
assert!(res_a.is_some());
2358
assert!(res_b.is_some());
2359
assert!(res_c.is_some());
2360
},
2361
)
2362
.chain(),
2363
)
2364
.chain_ignore_deferred(),
2365
);
2366
});
2367
}
2368
2369
#[test]
2370
fn chain_all() {
2371
run_schedule(7, |schedule: &mut Schedule| {
2372
schedule.add_systems(
2373
(
2374
(
2375
|mut commands: Commands| commands.insert_resource(Ra),
2376
|mut commands: Commands, res_a: Option<Res<Ra>>| {
2377
commands.insert_resource(Rb);
2378
assert!(res_a.is_some());
2379
},
2380
)
2381
.chain(),
2382
(
2383
|mut commands: Commands,
2384
res_a: Option<Res<Ra>>,
2385
res_b: Option<Res<Rb>>| {
2386
commands.insert_resource(Rc);
2387
assert!(res_a.is_some());
2388
assert!(res_b.is_some());
2389
},
2390
|res_a: Option<Res<Ra>>,
2391
res_b: Option<Res<Rb>>,
2392
res_c: Option<Res<Rc>>| {
2393
assert!(res_a.is_some());
2394
assert!(res_b.is_some());
2395
assert!(res_c.is_some());
2396
},
2397
)
2398
.chain(),
2399
)
2400
.chain(),
2401
);
2402
});
2403
2404
run_schedule(6, |schedule: &mut Schedule| {
2405
schedule.add_systems(
2406
(
2407
(
2408
|mut commands: Commands| commands.insert_resource(Ra),
2409
|mut commands: Commands, res_a: Option<Res<Ra>>| {
2410
commands.insert_resource(Rb);
2411
assert!(res_a.is_some());
2412
},
2413
)
2414
.chain(),
2415
(
2416
|mut commands: Commands,
2417
res_a: Option<Res<Ra>>,
2418
res_b: Option<Res<Rb>>| {
2419
commands.insert_resource(Rc);
2420
assert!(res_a.is_some());
2421
assert!(res_b.is_none());
2422
},
2423
|res_a: Option<Res<Ra>>,
2424
res_b: Option<Res<Rb>>,
2425
res_c: Option<Res<Rc>>| {
2426
assert!(res_a.is_some());
2427
assert!(res_b.is_some());
2428
assert!(res_c.is_some());
2429
},
2430
)
2431
.chain(),
2432
)
2433
.chain_ignore_deferred(),
2434
);
2435
});
2436
}
2437
}
2438
2439
#[derive(ScheduleLabel, Hash, Debug, Clone, PartialEq, Eq)]
2440
struct TestSchedule;
2441
2442
#[derive(Resource)]
2443
struct CheckSystemRan(usize);
2444
2445
#[test]
2446
fn add_systems_to_existing_schedule() {
2447
let mut schedules = Schedules::default();
2448
let schedule = Schedule::new(TestSchedule);
2449
2450
schedules.insert(schedule);
2451
schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2452
2453
let mut world = World::new();
2454
2455
world.insert_resource(CheckSystemRan(0));
2456
world.insert_resource(schedules);
2457
world.run_schedule(TestSchedule);
2458
2459
let value = world
2460
.get_resource::<CheckSystemRan>()
2461
.expect("CheckSystemRan Resource Should Exist");
2462
assert_eq!(value.0, 1);
2463
}
2464
2465
#[test]
2466
fn add_systems_to_non_existing_schedule() {
2467
let mut schedules = Schedules::default();
2468
2469
schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2470
2471
let mut world = World::new();
2472
2473
world.insert_resource(CheckSystemRan(0));
2474
world.insert_resource(schedules);
2475
world.run_schedule(TestSchedule);
2476
2477
let value = world
2478
.get_resource::<CheckSystemRan>()
2479
.expect("CheckSystemRan Resource Should Exist");
2480
assert_eq!(value.0, 1);
2481
}
2482
2483
#[derive(SystemSet, Debug, Hash, Clone, PartialEq, Eq)]
2484
enum TestSet {
2485
First,
2486
Second,
2487
}
2488
2489
#[test]
2490
fn configure_set_on_existing_schedule() {
2491
let mut schedules = Schedules::default();
2492
let schedule = Schedule::new(TestSchedule);
2493
2494
schedules.insert(schedule);
2495
2496
schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2497
schedules.add_systems(
2498
TestSchedule,
2499
(|mut ran: ResMut<CheckSystemRan>| {
2500
assert_eq!(ran.0, 0);
2501
ran.0 += 1;
2502
})
2503
.in_set(TestSet::First),
2504
);
2505
2506
schedules.add_systems(
2507
TestSchedule,
2508
(|mut ran: ResMut<CheckSystemRan>| {
2509
assert_eq!(ran.0, 1);
2510
ran.0 += 1;
2511
})
2512
.in_set(TestSet::Second),
2513
);
2514
2515
let mut world = World::new();
2516
2517
world.insert_resource(CheckSystemRan(0));
2518
world.insert_resource(schedules);
2519
world.run_schedule(TestSchedule);
2520
2521
let value = world
2522
.get_resource::<CheckSystemRan>()
2523
.expect("CheckSystemRan Resource Should Exist");
2524
assert_eq!(value.0, 2);
2525
}
2526
2527
#[test]
2528
fn configure_set_on_new_schedule() {
2529
let mut schedules = Schedules::default();
2530
2531
schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2532
schedules.add_systems(
2533
TestSchedule,
2534
(|mut ran: ResMut<CheckSystemRan>| {
2535
assert_eq!(ran.0, 0);
2536
ran.0 += 1;
2537
})
2538
.in_set(TestSet::First),
2539
);
2540
2541
schedules.add_systems(
2542
TestSchedule,
2543
(|mut ran: ResMut<CheckSystemRan>| {
2544
assert_eq!(ran.0, 1);
2545
ran.0 += 1;
2546
})
2547
.in_set(TestSet::Second),
2548
);
2549
2550
let mut world = World::new();
2551
2552
world.insert_resource(CheckSystemRan(0));
2553
world.insert_resource(schedules);
2554
world.run_schedule(TestSchedule);
2555
2556
let value = world
2557
.get_resource::<CheckSystemRan>()
2558
.expect("CheckSystemRan Resource Should Exist");
2559
assert_eq!(value.0, 2);
2560
}
2561
2562
#[test]
2563
fn test_default_error_handler() {
2564
#[derive(Resource, Default)]
2565
struct Ran(bool);
2566
2567
fn system(mut ran: ResMut<Ran>) -> Result {
2568
ran.0 = true;
2569
Err("I failed!".into())
2570
}
2571
2572
// Test that the default error handler is used
2573
let mut world = World::default();
2574
world.init_resource::<Ran>();
2575
world.insert_resource(DefaultErrorHandler(ignore));
2576
let mut schedule = Schedule::default();
2577
schedule.add_systems(system).run(&mut world);
2578
assert!(world.resource::<Ran>().0);
2579
2580
// Test that the handler doesn't change within the schedule
2581
schedule.add_systems(
2582
(|world: &mut World| {
2583
world.insert_resource(DefaultErrorHandler(panic));
2584
})
2585
.before(system),
2586
);
2587
schedule.run(&mut world);
2588
}
2589
}
2590
2591