bones_ecs/
stage.rs

1//! Implementation of stage abstraction for running collections of systems over a [`World`].
2
3use std::collections::VecDeque;
4
5use crate::prelude::*;
6
7/// Resource that is automatically added to the world while a system stage is being run
8/// that specifies the unique ID of the stage that being run.
9///
10/// If the stage is `Ulid(0)`, the default ID, then that means the startup stage is being run.
11#[derive(Deref, DerefMut, Clone, Copy, HasSchema, Default)]
12pub struct CurrentSystemStage(pub Ulid);
13
14/// Builder for [`SystemStages`]. It is immutable once created,
15pub struct SystemStagesBuilder {
16    /// The stages in the collection, in the order that they will be run.
17    stages: Vec<Box<dyn SystemStage>>,
18    /// The systems that should run at startup.
19    /// They will be executed next step based on if [`SessionStarted`] resource in world says session has not started, or if resource does not exist.
20    startup_systems: Vec<StaticSystem<(), ()>>,
21
22    /// Resources installed during session plugin installs. Copied to world as first step on startup of stages' execution.
23    startup_resources: UntypedResourceSet,
24
25    /// Systems that are continously run until they succeed(return Some). These run before all stages. Uses Option to allow for easy usage of `?`.
26    single_success_systems: Vec<StaticSystem<(), Option<()>>>,
27}
28
29impl Default for SystemStagesBuilder {
30    fn default() -> Self {
31        Self::with_core_stages()
32    }
33}
34
35impl SystemStagesBuilder {
36    /// Create a [`SystemStagesBuilder`] for [`SystemStages`] collection, initialized with a stage for each [`CoreStage`].
37    pub fn with_core_stages() -> Self {
38        Self {
39            stages: vec![
40                Box::new(SimpleSystemStage::new(CoreStage::First)),
41                Box::new(SimpleSystemStage::new(CoreStage::PreUpdate)),
42                Box::new(SimpleSystemStage::new(CoreStage::Update)),
43                Box::new(SimpleSystemStage::new(CoreStage::PostUpdate)),
44                Box::new(SimpleSystemStage::new(CoreStage::Last)),
45            ],
46            startup_resources: default(),
47            startup_systems: default(),
48            single_success_systems: Vec::new(),
49        }
50    }
51
52    /// Finish building and convert to [`SystemStages`]
53    pub fn finish(self) -> SystemStages {
54        SystemStages {
55            stages: self.stages,
56            startup_systems: self.startup_systems,
57            startup_resources: self.startup_resources,
58            single_success_systems: self.single_success_systems,
59        }
60    }
61
62    /// Create [`SystemStagesBuilder`] by taking existing [`SystemStages`].
63    pub fn from_stages(stages: SystemStages) -> Self {
64        Self {
65            stages: stages.stages,
66            startup_systems: stages.startup_systems,
67            startup_resources: stages.startup_resources,
68            single_success_systems: stages.single_success_systems,
69        }
70    }
71
72    /// Add a system that will run only once, before all of the other non-startup systems.
73    /// If wish to reset session and run again, can modify [`SessionStarted`] resource in world.
74    pub fn add_startup_system<Args, S>(&mut self, system: S) -> &mut Self
75    where
76        S: IntoSystem<Args, (), (), Sys = StaticSystem<(), ()>>,
77    {
78        self.startup_systems.push(system.system());
79        self
80    }
81
82    /// Add a system that will run each frame until it succeeds (returns Some). Runs before all stages. Uses Option to allow for easy usage of `?`.
83    pub fn add_single_success_system<Args, S>(&mut self, system: S) -> &mut Self
84    where
85        S: IntoSystem<Args, (), Option<()>, Sys = StaticSystem<(), Option<()>>>,
86    {
87        self.single_success_systems.push(system.system());
88        self
89    }
90
91    /// Add a [`System`] to the stage with the given label.
92    pub fn add_system_to_stage<Args, S>(&mut self, label: impl StageLabel, system: S) -> &mut Self
93    where
94        S: IntoSystem<Args, (), (), Sys = StaticSystem<(), ()>>,
95    {
96        let name = label.name();
97        let id = label.id();
98        let mut stage = None;
99
100        for st in &mut self.stages {
101            if st.id() == id {
102                stage = Some(st);
103            }
104        }
105
106        let Some(stage) = stage else {
107            panic!("Stage with label `{}` ( {} ) doesn't exist.", name, id);
108        };
109
110        stage.add_system(system.system());
111
112        self
113    }
114
115    /// Insert a new stage, before another existing stage
116    #[track_caller]
117    pub fn insert_stage_before<L: StageLabel, S: SystemStage + 'static>(
118        &mut self,
119        label: L,
120        stage: S,
121    ) -> &mut Self {
122        let stage_idx = self
123            .stages
124            .iter()
125            .position(|x| x.id() == label.id())
126            .unwrap_or_else(|| panic!("Could not find stage with label `{}`", label.name()));
127        self.stages.insert(stage_idx, Box::new(stage));
128
129        self
130    }
131
132    /// Insert a new stage, after another existing stage
133    #[track_caller]
134    pub fn insert_stage_after<L: StageLabel, S: SystemStage + 'static>(
135        &mut self,
136        label: L,
137        stage: S,
138    ) -> &mut Self {
139        let stage_idx = self
140            .stages
141            .iter()
142            .position(|x| x.id() == label.id())
143            .unwrap_or_else(|| panic!("Could not find stage with label `{}`", label.name()));
144        self.stages.insert(stage_idx + 1, Box::new(stage));
145
146        self
147    }
148
149    /// Insert a startup resource. On stage / session startup (first step), will be inserted into [`World`].
150    ///
151    /// If already exists, will be overwritten.
152    pub fn insert_startup_resource<T: HasSchema>(&mut self, resource: T) {
153        self.startup_resources.insert_resource(resource);
154    }
155
156    /// Init startup resource with default, and return mutable ref for modification.
157    /// If already exists, returns mutable ref to existing resource.
158    pub fn init_startup_resource<T: HasSchema + Default>(&mut self) -> RefMut<'_, T> {
159        self.startup_resources.init_resource::<T>()
160    }
161
162    /// Get mutable reference to startup resource if found.
163    #[track_caller]
164    pub fn startup_resource_mut<T: HasSchema>(&self) -> Option<RefMut<'_, T>> {
165        self.startup_resources.resource_mut()
166    }
167}
168
169/// An ordered collection of [`SystemStage`]s.
170pub struct SystemStages {
171    /// The stages in the collection, in the order that they will be run.
172    stages: Vec<Box<dyn SystemStage>>,
173
174    /// The systems that should run at startup.
175    /// They will be executed next step based on if [`SessionStarted`] resource in world says session has not started, or if resource does not exist.
176    startup_systems: Vec<StaticSystem<(), ()>>,
177
178    /// Resources installed during session plugin installs. Copied to world as first step on startup of stages' execution.
179    startup_resources: UntypedResourceSet,
180
181    /// Systems that are continously run until they succeed(return Some). These run before all stages. Uses Option to allow for easy usage of `?`.
182    single_success_systems: Vec<StaticSystem<(), Option<()>>>,
183}
184
185impl std::fmt::Debug for SystemStages {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        f.debug_struct("SystemStages")
188            // TODO: Add list of stages to the debug render for `SystemStages`.
189            // We can at least list the names of each stage for `SystemStages` debug
190            // implementation.
191            .finish()
192    }
193}
194
195impl Default for SystemStages {
196    fn default() -> Self {
197        SystemStagesBuilder::default().finish()
198    }
199}
200
201impl SystemStages {
202    /// Create builder for construction of [`SystemStages`].
203    pub fn builder() -> SystemStagesBuilder {
204        SystemStagesBuilder::default()
205    }
206
207    /// Execute the systems on the given `world`.
208    pub fn run(&mut self, world: &mut World) {
209        // If we haven't run startup systems and setup resources yet, do so
210        self.handle_startup(world);
211
212        // Run single success systems
213        for (index, system) in self.single_success_systems.iter_mut().enumerate() {
214            let should_run = !Self::has_single_success_system_succeeded(index, world);
215
216            if should_run && system.run(world, ()).is_some() {
217                Self::mark_single_success_system_succeeded(index, world);
218            }
219        }
220
221        // Run each stage
222        for stage in &mut self.stages {
223            // Set the current stage resource
224            world.insert_resource(CurrentSystemStage(stage.id()));
225
226            // Run the stage
227            stage.run(world);
228        }
229
230        // Cleanup killed entities
231        world.maintain();
232
233        // Remove the current system stage resource
234        world.resources.remove::<CurrentSystemStage>();
235    }
236
237    /// If [`SessionStarted`] resource indicates have not yet started,
238    /// perform startup tasks (insert startup resources, run startup systems).
239    ///
240    /// For advanced use cases in which want to only insert startup resources, or run startup systems and split this
241    /// behavior, see [`SystemStages::handle_startup_systems`] and `[SystemStages::handle_startup_resources`].
242    ///
243    ///
244    /// While this is used internally by [`SystemStages::run`], this is also used
245    /// for resetting world. This allows world to immediately startup and re-initialize after reset.
246    ///
247    /// # Panics
248    ///
249    /// May panic if resources are borrowed, should not borrow resources when calling.
250    pub fn handle_startup(&mut self, world: &mut World) {
251        self.handle_startup_resources(world);
252        self.handle_startup_systems(world);
253    }
254
255    /// If [`SessionStarted`] resource indicates startup resources have not yet been inserted, will do so and update `SessionStarted`.
256    ///
257    /// This function contains only half of stage's startup behavior, see [`SystemStages::handle_startup`] if not intending to split
258    /// resource insertion from startup systems (Splitting these is more for advanced special cases).
259    ///
260    /// # Panics
261    ///
262    /// May panic if resources are borrowed, should not borrow resources when calling.
263    pub fn handle_startup_resources(&mut self, world: &mut World) {
264        let (mut resources_inserted, systems_run) = match world.get_resource_mut::<SessionStarted>()
265        {
266            Some(session_started) => (
267                session_started.startup_resources_inserted,
268                session_started.startup_systems_executed,
269            ),
270            None => (false, false),
271        };
272
273        if !resources_inserted {
274            self.insert_startup_resources(world);
275            resources_inserted = true;
276
277            world.insert_resource(SessionStarted {
278                startup_systems_executed: systems_run,
279                startup_resources_inserted: resources_inserted,
280            });
281        }
282    }
283
284    /// If [`SessionStarted`] resource indicates startup systems have not yet been executed, will do so and update `SessionStarted`.
285    ///
286    /// This function contains only half of stage's startup behavior, see [`SystemStages::handle_startup`] if not intending to split
287    /// startup system execution from startup resource insertion (Splitting these is more for advanced special cases).
288    ///
289    /// # Panics
290    ///
291    /// May panic if resources are borrowed, should not borrow resources when calling.
292    pub fn handle_startup_systems(&mut self, world: &mut World) {
293        let (resources_inserted, mut systems_run) = match world.get_resource_mut::<SessionStarted>()
294        {
295            Some(session_started) => (
296                session_started.startup_resources_inserted,
297                session_started.startup_systems_executed,
298            ),
299            None => (false, false),
300        };
301
302        if !systems_run {
303            // Set the current stage resource
304            world.insert_resource(CurrentSystemStage(Ulid(0)));
305
306            // For each startup system
307            for system in &mut self.startup_systems {
308                // Run the system
309                system.run(world, ());
310            }
311            systems_run = true;
312
313            world.resources.remove::<CurrentSystemStage>();
314            world.insert_resource(SessionStarted {
315                startup_systems_executed: systems_run,
316                startup_resources_inserted: resources_inserted,
317            });
318        }
319    }
320
321    /// Check if single success system is marked as succeeded in [`SingleSuccessSystems`] [`Resource`].
322    fn has_single_success_system_succeeded(system_index: usize, world: &World) -> bool {
323        if let Some(system_success) = world.get_resource::<SingleSuccessSystems>() {
324            return system_success.has_system_succeeded(system_index);
325        }
326
327        false
328    }
329
330    /// Mark a single success system as succeeded in [`SingleSuccessSystems`] [`Resource`].
331    fn mark_single_success_system_succeeded(system_index: usize, world: &mut World) {
332        if let Some(mut system_succes) = world.get_resource_mut::<SingleSuccessSystems>() {
333            system_succes.set_system_completed(system_index);
334            return;
335        }
336
337        // Resource does not exist - must initialize it
338        world
339            .init_resource::<SingleSuccessSystems>()
340            .set_system_completed(system_index);
341    }
342
343    /// Insert the startup resources that [`SystemStages`] and session were built with into [`World`].
344    ///
345    /// This will update [`SessionStarted`] resource to flag that startup resources are inserted.
346    fn insert_startup_resources(&self, world: &mut World) {
347        for resource in self.startup_resources.resources().iter() {
348            // Deep copy startup resource and insert into world.
349            let resource_copy = resource.clone_data().unwrap();
350            let resource_cell = world.resources.untyped().get_cell(resource.schema());
351            let prev_val = resource_cell.insert(resource_copy).unwrap();
352
353            // Warn on already existing resource
354            if prev_val.is_some() {
355                let schema_name = resource.schema().full_name;
356                tracing::warn!("SystemStages` attempted to inserted resource {schema_name} on startup that already exists in world - startup resource not inserted.
357                    When building new session, startup resources should be initialized on `SessionBuilder`.");
358            }
359        }
360    }
361}
362
363/// Trait for system stages. A stage is a
364pub trait SystemStage: Sync + Send {
365    /// The unique identifier for the stage.
366    fn id(&self) -> Ulid;
367    /// The human-readable name for the stage, used for error messages when something goes wrong.
368    fn name(&self) -> String;
369    /// Execute the systems on the given `world`.
370    fn run(&mut self, world: &World);
371
372    /// Add a system to this stage.
373    fn add_system(&mut self, system: StaticSystem<(), ()>);
374    /// Remove all systems from this stage.
375    fn remove_all_systems(&mut self);
376}
377
378/// A collection of systems that will be run in order.
379pub struct SimpleSystemStage {
380    /// The unique identifier for the stage.
381    pub id: Ulid,
382    /// The human-readable name for the stage, used for error messages when something goes wrong.
383    pub name: String,
384    /// The list of systems in the stage.
385    ///
386    /// Each system will be run in the order that they are in in this list.
387    pub systems: Vec<StaticSystem<(), ()>>,
388}
389
390impl SimpleSystemStage {
391    /// Create a new, empty stage, for the given label.
392    pub fn new<L: StageLabel>(label: L) -> Self {
393        Self {
394            id: label.id(),
395            name: label.name(),
396            systems: Default::default(),
397        }
398    }
399}
400
401impl SystemStage for SimpleSystemStage {
402    fn id(&self) -> Ulid {
403        self.id
404    }
405
406    fn name(&self) -> String {
407        self.name.clone()
408    }
409
410    fn run(&mut self, world: &World) {
411        // Run the systems
412        for system in &mut self.systems {
413            system.run(world, ());
414        }
415
416        // Drain the command queue
417        let queue = world.resources.get_mut::<CommandQueue>();
418        if let Some(mut command_queue) = queue {
419            for mut system in command_queue.queue.drain(..) {
420                system.run(world, ());
421            }
422        }
423    }
424
425    fn add_system(&mut self, system: StaticSystem<(), ()>) {
426        self.systems.push(system);
427    }
428
429    fn remove_all_systems(&mut self) {
430        self.systems.clear();
431    }
432}
433
434/// Trait for things that may be used to identify a system stage.
435pub trait StageLabel {
436    /// Returns the human-readable name of the label, used in error messages.
437    fn name(&self) -> String;
438    /// Returns a unique identifier for the stage.
439    fn id(&self) -> Ulid;
440}
441
442/// A [`StageLabel`] for the 5 core stages.
443#[derive(Copy, Clone, Debug, PartialEq, Eq)]
444pub enum CoreStage {
445    /// The first stage
446    First,
447    /// The second stage
448    PreUpdate,
449    /// The third stage
450    Update,
451    /// The fourth stage
452    PostUpdate,
453    /// The fifth stage
454    Last,
455}
456
457impl StageLabel for CoreStage {
458    fn name(&self) -> String {
459        format!("{:?}", self)
460    }
461
462    fn id(&self) -> Ulid {
463        match self {
464            CoreStage::First => Ulid(2021715391084198804812356024998495966),
465            CoreStage::PreUpdate => Ulid(2021715401330719559452824437611089988),
466            CoreStage::Update => Ulid(2021715410160177201728645950400543948),
467            CoreStage::PostUpdate => Ulid(2021715423103233646561968734173322317),
468            CoreStage::Last => Ulid(2021715433398666914977687392909851554),
469        }
470    }
471}
472
473/// A resource containing the [`Commands`] command queue.
474///
475/// You can use [`Commands`] as a [`SystemParam`] as a shortcut to [`ResMut<CommandQueue>`].
476#[derive(HasSchema, Default)]
477pub struct CommandQueue {
478    /// The system queue that will be run at the end of the stage
479    pub queue: VecDeque<StaticSystem<(), ()>>,
480}
481
482impl Clone for CommandQueue {
483    fn clone(&self) -> Self {
484        if self.queue.is_empty() {
485            Self {
486                queue: VecDeque::with_capacity(self.queue.capacity()),
487            }
488        } else {
489            panic!(
490                "Cannot clone CommandQueue. This probably happened because you are \
491                trying to clone a World while a system stage is still executing."
492            )
493        }
494    }
495}
496
497impl CommandQueue {
498    /// Add a system to be run at the end of the stage.
499    pub fn add<Args, S>(&mut self, system: S)
500    where
501        S: IntoSystem<Args, (), (), Sys = StaticSystem<(), ()>>,
502    {
503        self.queue.push_back(system.system());
504    }
505}
506
507/// A [`SystemParam`] that can be used to schedule systems that will be run at the end of the
508/// current [`SystemStage`].
509///
510/// This is a shortcut for [`ResMut<CommandQueue>`].
511#[derive(Deref, DerefMut)]
512pub struct Commands<'a>(RefMut<'a, CommandQueue>);
513
514impl<'a> SystemParam for Commands<'a> {
515    type State = AtomicResource<CommandQueue>;
516    type Param<'s> = Commands<'s>;
517
518    fn get_state(world: &World) -> Self::State {
519        let cell = world.resources.get_cell::<CommandQueue>();
520        cell.init(world);
521        cell
522    }
523
524    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
525        Commands(state.borrow_mut().unwrap())
526    }
527}
528
529/// Resource tracking if Session has started (startup systems executed and resources inserted).
530/// If field is set to false, that operation needs to be handled on next stage run.
531/// If resource is not present, assumed to have not started (and will be initialized upon next stage execution).
532#[derive(Copy, Clone, HasSchema, Default)]
533pub struct SessionStarted {
534    /// Have startup systems executed?
535    pub startup_systems_executed: bool,
536
537    /// Have startup resources beeen inserted into [`World`]?
538    pub startup_resources_inserted: bool,
539}
540
541/// Resource tracking which of single success systems in `Session`'s [`SystemStages`] have completed.
542/// Success is tracked to
543#[derive(HasSchema, Clone, Default)]
544pub struct SingleSuccessSystems {
545    /// Set of indices of [`SystemStages`]'s single success systems that have succeeded.
546    pub systems_succeeded: HashSet<usize>,
547}
548
549impl SingleSuccessSystems {
550    /// Reset single success systems completion status. so they run again until success.
551    #[allow(dead_code)]
552    pub fn reset(&mut self) {
553        self.systems_succeeded.clear();
554    }
555
556    /// Check if system has completed
557    pub fn has_system_succeeded(&self, index: usize) -> bool {
558        self.systems_succeeded.contains(&index)
559    }
560
561    /// Mark system as completed.
562    pub fn set_system_completed(&mut self, index: usize) {
563        self.systems_succeeded.insert(index);
564    }
565}