bones_lib/
lib.rs

1//! The core bones library.
2
3#![warn(missing_docs)]
4// This cfg_attr is needed because `rustdoc::all` includes lints not supported on stable
5#![cfg_attr(doc, allow(unknown_lints))]
6#![deny(rustdoc::all)]
7
8#[doc(inline)]
9pub use bones_ecs as ecs;
10
11/// Bones lib prelude
12pub mod prelude {
13    pub use crate::{
14        ecs::prelude::*, instant::Instant, reset::*, time::*, Game, GamePlugin, Session,
15        SessionBuilder, SessionCommand, SessionOptions, SessionPlugin, SessionRunner, Sessions,
16    };
17    pub use ustr::{ustr, Ustr, UstrMap, UstrSet};
18}
19
20pub use instant;
21pub mod reset;
22pub mod time;
23
24use std::{collections::VecDeque, fmt::Debug, sync::Arc};
25use tracing::warn;
26
27use crate::prelude::*;
28
29/// Builder type used to create [`Session`]. If using this directly (as opposed to [`Sessions::create_with`]),
30/// it is important to rember to finish session and add to [`Sessions`] with [`SessionBuilder::finish_and_add`].
31pub struct SessionBuilder {
32    /// Name of session
33    pub name: Ustr,
34    /// System stage builder
35    pub stages: SystemStagesBuilder,
36    /// Whether or not this session should have it's systems run.
37    pub active: bool,
38    /// Whether or not this session should be rendered.
39    pub visible: bool,
40    /// The priority of this session relative to other sessions in the [`Game`].
41    pub priority: i32,
42    /// The session runner to use for this session.
43    pub runner: Box<dyn SessionRunner>,
44
45    /// Tracks if builder has been finished, and warn on drop if not finished.
46    finish_guard: FinishGuard,
47}
48
49impl SessionBuilder {
50    /// Create a new [`SessionBuilder`]. Be sure to add it to [`Sessions`] when finished, with [`SessionBuilder::finish_and_add`], or [`Sessions::create`].
51    ///
52    /// # Panics
53    ///
54    /// Panics if the `name.try_into()` cannot convert into [`Ustr`].
55    pub fn new<N: TryInto<Ustr>>(name: N) -> Self
56    where
57        <N as TryInto<Ustr>>::Error: Debug,
58    {
59        let name = name
60            .try_into()
61            .expect("Session name could not be converted into Ustr.");
62        Self {
63            name,
64            stages: default(),
65            active: true,
66            visible: true,
67            priority: 0,
68            runner: Box::new(DefaultSessionRunner),
69            finish_guard: FinishGuard { finished: false },
70        }
71    }
72
73    /// Create a [`SessionBuilder`] to match existing [`Session`], consuming it. This is useful if a [`GamePlugin`] wishes to modify a session created by another plugin.
74    /// The builder is initialized with settings from session, can be further modified, and then added to session to replace existing.
75    pub fn from_existing_session<N: TryInto<Ustr>>(name: N, session: Session) -> Self
76    where
77        <N as TryInto<Ustr>>::Error: Debug,
78    {
79        let name = name
80            .try_into()
81            .expect("Session name could not be converted into Ustr.");
82
83        Self {
84            name,
85            stages: SystemStagesBuilder::from_stages(session.stages),
86            active: session.active,
87            visible: session.visible,
88            priority: session.priority,
89            runner: session.runner,
90            finish_guard: FinishGuard { finished: false },
91        }
92    }
93
94    /// Get the [`SystemStagesBuilder`] (though the stage build functions are also on [`SessionBuilder`] for convenience).
95    pub fn stages(&mut self) -> &mut SystemStagesBuilder {
96        &mut self.stages
97    }
98
99    /// Whether or not session should run systems.
100    pub fn set_active(&mut self, active: bool) -> &mut Self {
101        self.active = active;
102        self
103    }
104
105    /// Whether or not session should be rendered.
106    pub fn set_visible(&mut self, visible: bool) -> &mut Self {
107        self.visible = visible;
108        self
109    }
110
111    /// The priority of this session relative to other sessions in the [`Game`].
112    pub fn set_priority(&mut self, priority: i32) -> &mut Self {
113        self.priority = priority;
114        self
115    }
116
117    /// Insert a resource.
118    ///
119    /// Note: The resource is not actually initialized in World until first step of [`SystemStages`].
120    /// To mutate or inspect a resource inserted by another [`SessionPlugin`] during session build, use [`SessionBuilder::resource_mut`].
121    pub fn insert_resource<T: HasSchema>(&mut self, resource: T) -> &mut Self {
122        self.stages.insert_startup_resource(resource);
123        self
124    }
125
126    /// Insert a resource using default value (if not found). Returns a mutable ref for modification.
127    ///
128    /// Note: The resource is not actually initialized in World until first step of [`SystemStages`].
129    /// To mutate or inspect a resource inserted by another [`SessionPlugin`] during session build, use [`SessionBuilder::resource_mut`].
130    pub fn init_resource<T: HasSchema + Default>(&mut self) -> RefMut<'_, T> {
131        self.stages.init_startup_resource::<T>()
132    }
133
134    /// Get mutable reference to a resource if it exists.
135    pub fn resource_mut<T: HasSchema>(&self) -> Option<RefMut<'_, T>> {
136        self.stages.startup_resource_mut::<T>()
137    }
138
139    /// Add a system that will run only once, before all of the other non-startup systems.
140    /// If wish to reset startup systems during gameplay and run again, can modify [`SessionStarted`] resource in world.
141    pub fn add_startup_system<Args, S>(&mut self, system: S) -> &mut Self
142    where
143        S: IntoSystem<Args, (), (), Sys = StaticSystem<(), ()>>,
144    {
145        self.stages.add_startup_system(system.system());
146        self
147    }
148
149    /// 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 `?`.
150    pub fn add_single_success_system<Args, S>(&mut self, system: S) -> &mut Self
151    where
152        S: IntoSystem<Args, (), Option<()>, Sys = StaticSystem<(), Option<()>>>,
153    {
154        self.stages.add_single_success_system(system.system());
155        self
156    }
157
158    /// Add a [`System`] to the stage with the given label.
159    pub fn add_system_to_stage<Args, S>(&mut self, label: impl StageLabel, system: S) -> &mut Self
160    where
161        S: IntoSystem<Args, (), (), Sys = StaticSystem<(), ()>>,
162    {
163        self.stages.add_system_to_stage(label, system);
164        self
165    }
166
167    /// Insert a new stage, before another existing stage
168    pub fn insert_stage_before<L: StageLabel, S: SystemStage + 'static>(
169        &mut self,
170        label: L,
171        stage: S,
172    ) -> &mut SessionBuilder {
173        self.stages.insert_stage_before(label, stage);
174        self
175    }
176
177    /// Insert a new stage, after another existing stage
178    pub fn insert_stage_after<L: StageLabel, S: SystemStage + 'static>(
179        &mut self,
180        label: L,
181        stage: S,
182    ) -> &mut SessionBuilder {
183        self.stages.insert_stage_after(label, stage);
184
185        self
186    }
187
188    /// Set the session runner for this session.
189    pub fn set_session_runner(&mut self, runner: Box<dyn SessionRunner>) {
190        self.runner = runner;
191    }
192
193    /// Install a plugin.
194    pub fn install_plugin(&mut self, plugin: impl SessionPlugin) -> &mut Self {
195        plugin.install(self);
196        self
197    }
198
199    /// Finalize and add to [`Sessions`].
200    ///
201    /// Alternatively, you may directly pass a [`SessionBuilder`] to [`Sessions::create`] to add and finalize.
202    pub fn finish_and_add(mut self, sessions: &mut Sessions) -> &mut Session {
203        let mut session = Session {
204            world: {
205                let mut w = World::default();
206                w.init_resource::<Time>();
207                w
208            },
209            stages: self.stages.finish(),
210            active: self.active,
211            visible: self.visible,
212            priority: self.priority,
213            runner: self.runner,
214        };
215
216        // mark guard as finished to avoid warning on drop of SessionBuilder.
217        self.finish_guard.finished = true;
218
219        // Insert startup resources so if other sessions look for them, they are present.
220        // Startup systems are deferred until first step of stages.
221        session.stages.handle_startup_resources(&mut session.world);
222
223        sessions.add(self.name, session)
224    }
225}
226
227/// Guard for [`SessionBuilder` ensuring we warn if it goes out of scope without being finished and added to [`Sessions`].
228struct FinishGuard {
229    pub finished: bool,
230}
231
232impl Drop for FinishGuard {
233    fn drop(&mut self) {
234        if !self.finished {
235            warn!("`SessionBuilder` went out of scope. This may have been an acccident in building Session.
236                        `finish` the session builder, or directly add to Game's `Sessions` to ensure is created and saved.")
237        }
238    }
239}
240
241/// A bones game. This includes all of the game worlds, and systems.
242///
243/// [`Session`] is not allowed to be constructed directly.
244/// See [`Sessions::create`] or [`SessionBuilder`] for creating a new `Session`.
245#[non_exhaustive]
246#[derive(Deref, DerefMut)]
247pub struct Session {
248    /// The ECS world for the core.
249    pub world: World,
250    /// The system stages.
251    #[deref]
252    pub stages: SystemStages,
253    /// Whether or not this session should have it's systems run.
254    pub active: bool,
255    /// Whether or not this session should be rendered.
256    pub visible: bool,
257    /// The priority of this session relative to other sessions in the [`Game`].
258    pub priority: i32,
259    /// The session runner to use for this session.
260    pub runner: Box<dyn SessionRunner>,
261}
262
263impl std::fmt::Debug for Session {
264    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265        f.debug_struct("Session")
266            .field("world", &self.world)
267            .field("stages", &self.stages)
268            .field("active", &self.active)
269            .field("visible", &self.visible)
270            .field("priority", &self.priority)
271            .field("runner", &"SessionRunner")
272            .finish()
273    }
274}
275
276impl Session {
277    /// Snapshot the world state.
278    ///
279    /// This is the same as `core.world.clone()`, but it is more explicit.
280    pub fn snapshot(&self) -> World {
281        self.world.clone()
282    }
283
284    /// Restore the world state.
285    ///
286    /// Re-sets the world state to that of the provided `world`, which may or may not have been
287    /// created with [`snapshot()`][Self::snapshot].
288    ///
289    /// This is the same as doing an [`std::mem::swap`] on `self.world`, but it is more explicit.
290    pub fn restore(&mut self, world: &mut World) {
291        std::mem::swap(&mut self.world, world)
292    }
293
294    /// Set the session runner for this session.
295    pub fn set_session_runner(&mut self, runner: Box<dyn SessionRunner>) {
296        self.runner = runner;
297    }
298}
299
300/// Trait for plugins that can be installed into a [`Session`].
301pub trait SessionPlugin {
302    /// Install the plugin into the [`Session`].
303    fn install(self, session: &mut SessionBuilder);
304}
305impl<F: FnOnce(&mut SessionBuilder)> SessionPlugin for F {
306    fn install(self, session: &mut SessionBuilder) {
307        (self)(session)
308    }
309}
310
311/// Trait for plugins that can be installed into a [`Game`].
312pub trait GamePlugin {
313    /// Install the plugin into the [`Game`].
314    fn install(self, game: &mut Game);
315}
316impl<F: FnOnce(&mut Game)> GamePlugin for F {
317    fn install(self, game: &mut Game) {
318        (self)(game)
319    }
320}
321
322/// A session runner is in charge of advancing a [`Session`] simulation.
323pub trait SessionRunner: Sync + Send + 'static {
324    /// Step the simulation once.
325    ///
326    /// It is the responsibility of the session runner to update the [`Time`] resource if necessary.
327    ///
328    /// If no special behavior is desired, the simplest session runner, and the one that is
329    /// implemented by [`DefaultSessionRunner`] is as follows:
330    ///
331    /// ```
332    /// # use bones_lib::prelude::*;
333    /// # struct Example;
334    /// # impl SessionRunner for Example {
335    /// fn step(&mut self, now: Instant, world: &mut World, stages: &mut SystemStages) {
336    ///     world.resource_mut::<Time>().update_with_instant(now);
337    ///     stages.run(world);
338    ///
339    ///     // Not required for runner - but calling this allows [`ResetWorld`] resource to
340    ///     // reset world state from gameplay.
341    ///     world.handle_world_reset(stages);
342    ///
343    /// }
344    /// fn restart_session(&mut self) {}
345    /// fn disable_local_input(&mut self, disable_input: bool) {}
346    /// # }
347    /// ```
348    fn step(&mut self, now: Instant, world: &mut World, stages: &mut SystemStages);
349
350    /// Restart Session Runner. This should reset accumulated time, inputs, etc.
351    ///
352    /// The expectation is that current players using it may continue to, so something like a network
353    /// socket or player info should persist.
354    fn restart_session(&mut self);
355
356    /// Disable the capture of local input by this session.
357    fn disable_local_input(&mut self, input_disabled: bool);
358}
359
360/// The default [`SessionRunner`], which just runs the systems once every time it is run.
361#[derive(Default)]
362pub struct DefaultSessionRunner;
363impl SessionRunner for DefaultSessionRunner {
364    fn step(&mut self, now: instant::Instant, world: &mut World, stages: &mut SystemStages) {
365        world.resource_mut::<Time>().update_with_instant(now);
366        stages.run(world);
367
368        // Checks if reset of world has been triggered by [`ResetWorld`] and handles a reset.
369        world.handle_world_reset(stages);
370    }
371
372    // This is a no-op as no state, but implemented this way in case that changes later.
373    #[allow(clippy::default_constructed_unit_structs)]
374    fn restart_session(&mut self) {
375        *self = DefaultSessionRunner::default();
376    }
377
378    // `DefaultSessionRunner` does not collect input so this impl is not relevant.
379    fn disable_local_input(&mut self, _input_disabled: bool) {}
380}
381
382/// The [`Game`] encompasses a complete bones game's logic, independent of the renderer and IO
383/// implementations.
384///
385/// Games are made up of one or more [`Session`]s, each of which contains it's own [`World`] and
386/// [`SystemStages`]. These different sessions can be used for parts of the game with independent
387/// states, such as the main menu and the gameplay.
388pub struct Game {
389    /// The sessions that make up the game.
390    pub sessions: Sessions,
391    /// The collection of systems that are associated to the game itself, and not a specific
392    /// session.
393    pub systems: GameSystems,
394    /// List of sorted session keys.
395    ///
396    /// These are only guaranteed to be sorted and up-to-date immediately after calling
397    /// [`Game::step()`].
398    pub sorted_session_keys: Vec<Ustr>,
399    /// Collection of resources that will have a shared instance of each be inserted into each
400    /// session automatically.
401    pub shared_resources: Vec<AtomicUntypedResource>,
402}
403
404impl Default for Game {
405    fn default() -> Self {
406        let mut game = Self {
407            sessions: default(),
408            systems: default(),
409            sorted_session_keys: default(),
410            shared_resources: default(),
411        };
412
413        // Init Sessions shared resource so it exists for game step.
414        // (Game's sessions temporarily moved to this resource during execution)
415        game.init_shared_resource::<Sessions>();
416        game
417    }
418}
419
420impl Game {
421    /// Create an empty game with an asset server.
422    pub fn new() -> Self {
423        Self::default()
424    }
425
426    /// Install a [`GamePlugin`].
427    pub fn install_plugin<P: GamePlugin>(&mut self, plugin: P) -> &mut Self {
428        plugin.install(self);
429        self
430    }
431    #[track_caller]
432    /// Get the shared resource of a given type out of this [`Game`]s shared
433    /// resources if it exists.
434    pub fn get_shared_resource<T: HasSchema>(&self) -> Option<Ref<'_, T>> {
435        let res = self
436            .shared_resources
437            .iter()
438            .find(|x| x.schema() == T::schema())?;
439        let borrow = res.borrow();
440
441        if borrow.is_some() {
442            // SOUND: We know the type matches T
443            Some(Ref::map(borrow, |b| unsafe {
444                b.as_ref().unwrap().as_ref().cast_into_unchecked()
445            }))
446        } else {
447            None
448        }
449    }
450
451    #[track_caller]
452    /// Get the mutable shared resource of a given type out of this [`Game`]s
453    /// shared resources if it exists.
454    pub fn get_shared_resource_mut<T: HasSchema>(&self) -> Option<RefMut<'_, T>> {
455        let res = self
456            .shared_resources
457            .iter()
458            .find(|x| x.schema() == T::schema())?;
459        let borrow = res.borrow_mut();
460
461        if borrow.is_some() {
462            // SOUND: We know the type matches T
463            Some(RefMut::map(borrow, |b| unsafe {
464                b.as_mut().unwrap().as_mut().cast_into_mut_unchecked()
465            }))
466        } else {
467            None
468        }
469    }
470
471    #[track_caller]
472    /// Get the shared resource of a given type out of this [`Game`]s shared
473    /// resources. Panics if the resource doesn't exist.
474    pub fn shared_resource<T: HasSchema>(&self) -> Ref<'_, T> {
475        self.get_shared_resource()
476            .expect("shared resource not found")
477    }
478
479    #[track_caller]
480    /// Get the mutable shared resource of a given type out of this [`Game`]s
481    /// shared resources. Panics if it doesn't exist.
482    pub fn shared_resource_mut<T: HasSchema>(&self) -> RefMut<'_, T> {
483        self.get_shared_resource_mut()
484            .expect("shared resource not found")
485    }
486
487    /// Get the shared resource cell of a given type out of this [`Game`]s shared resources.
488    pub fn shared_resource_cell<T: HasSchema>(&self) -> Option<AtomicResource<T>> {
489        let res = self
490            .shared_resources
491            .iter()
492            .find(|x| x.schema() == T::schema())?;
493        Some(AtomicResource::from_untyped(res.clone()).unwrap())
494    }
495
496    /// Initialize a resource that will be shared across game sessions using it's [`Default`] value
497    /// if it is not already initialized, and borrow it for modification.
498    pub fn init_shared_resource<T: HasSchema + Default>(&mut self) -> RefMut<'_, T> {
499        if !self
500            .shared_resources
501            .iter()
502            .any(|x| x.schema() == T::schema())
503        {
504            self.insert_shared_resource(T::default());
505        }
506        self.shared_resource_mut::<T>()
507    }
508
509    /// Insert a resource that will be shared across all game sessions.
510    ///
511    /// > **Note:** This resource will only be visible in sessions that have not already
512    /// > initialized or access a resource of the same type locally.
513    pub fn insert_shared_resource<T: HasSchema>(&mut self, resource: T) {
514        // Update an existing resource of the same type.
515        for r in &mut self.shared_resources {
516            if r.schema() == T::schema() {
517                let mut borrow = r.borrow_mut();
518
519                if let Some(b) = borrow.as_mut().as_mut() {
520                    *b.cast_mut() = resource;
521                } else {
522                    *borrow = Some(SchemaBox::new(resource))
523                }
524                return;
525            }
526        }
527
528        // Or insert a new resource if we couldn't find one
529        self.shared_resources
530            .push(Arc::new(UntypedResource::new(SchemaBox::new(resource))));
531    }
532
533    // /// Remove a shared resource, if it is present in the world.
534    // /// # Panics
535    // /// Panics if the resource is set and it's cell has another handle to it and cannot be
536    // /// unwrapped.
537    // pub fn remove_shared_resource<T: HasSchema>(&mut self) -> Option<T> {
538    //     self.shared_resources
539    //         .iter()
540    //         .position(|x| x.schema() == T::schema())
541    //         .map(|idx| {
542    //             self.shared_resources
543    //                 .remove(idx)
544    //                 .try_into_inner()
545    //                 .unwrap()
546    //                 .into_inner()
547    //         })
548    // }
549
550    /// Step the game simulation.
551    pub fn step(&mut self, now: instant::Instant) {
552        // Pull out the game systems so that we can run them on the game
553        let mut game_systems = std::mem::take(&mut self.systems);
554
555        // Run game startup systems
556        if !game_systems.has_run_startup {
557            for system in &mut game_systems.startup {
558                system(self);
559            }
560            game_systems.has_run_startup = true;
561        }
562
563        // Run the before systems
564        for system in &mut game_systems.before {
565            system(self)
566        }
567
568        // Sort session keys by priority
569        self.sorted_session_keys.clear();
570        self.sorted_session_keys.extend(self.sessions.map.keys());
571        self.sorted_session_keys
572            .sort_by_key(|name| self.sessions.map.get(name).unwrap().priority);
573
574        // For every session
575        for session_name in self.sorted_session_keys.clone() {
576            // Extract the current session
577            let Some(mut current_session) = self.sessions.map.remove(&session_name) else {
578                // This may happen if the session was deleted by another session.
579                continue;
580            };
581
582            // If this session is active
583            let options = if current_session.active {
584                // Run any before session game systems
585                if let Some(systems) = game_systems.before_session.get_mut(&session_name) {
586                    for system in systems {
587                        system(self)
588                    }
589                }
590
591                // Make sure session contains all of the shared resources
592                for r in &self.shared_resources {
593                    if !current_session
594                        .world
595                        .resources
596                        .untyped()
597                        .contains_cell(r.schema().id())
598                    {
599                        current_session
600                            .world
601                            .resources
602                            .untyped()
603                            .insert_cell(r.clone())
604                            .unwrap();
605                    }
606                }
607
608                // Insert the session options
609                current_session.world.resources.insert(SessionOptions {
610                    active: true,
611                    delete: false,
612                    visible: current_session.visible,
613                });
614
615                // Insert the other sessions into the current session's world
616                {
617                    let mut sessions = current_session.world.resource_mut::<Sessions>();
618                    std::mem::swap(&mut *sessions, &mut self.sessions);
619                }
620
621                // Step the current session's simulation using it's session runner
622                current_session.runner.step(
623                    now,
624                    &mut current_session.world,
625                    &mut current_session.stages,
626                );
627
628                // Pull the sessions back out of the world
629                {
630                    let mut sessions = current_session.world.resource_mut::<Sessions>();
631                    std::mem::swap(&mut *sessions, &mut self.sessions);
632                }
633
634                // Pull the current session options back out of the world.
635                *current_session.world.resource::<SessionOptions>()
636            } else {
637                SessionOptions {
638                    active: false,
639                    visible: current_session.visible,
640                    delete: false,
641                }
642            };
643
644            // Delete the session
645            if options.delete {
646                let session_idx = self
647                    .sorted_session_keys
648                    .iter()
649                    .position(|x| x == &session_name)
650                    .unwrap();
651                self.sorted_session_keys.remove(session_idx);
652
653            // Update session options
654            } else {
655                current_session.active = options.active;
656                current_session.visible = options.visible;
657
658                // Insert the current session back into the session list
659                self.sessions.map.insert(session_name, current_session);
660            }
661
662            // Run any after session game systems
663            if let Some(systems) = game_systems.after_session.get_mut(&session_name) {
664                for system in systems {
665                    system(self)
666                }
667            }
668        }
669
670        // Execute Session Commands
671        {
672            let mut session_commands: VecDeque<Box<SessionCommand>> = default();
673            std::mem::swap(&mut session_commands, &mut self.sessions.commands);
674            for command in session_commands.drain(..) {
675                command(&mut self.sessions);
676            }
677        }
678
679        // Run after systems
680        for system in &mut game_systems.after {
681            system(self)
682        }
683
684        // Replace the game systems
685        self.systems = game_systems;
686
687        // Make sure sorted session keys does not include sessions that were deleted during
688        // execution by other sessions.
689        self.sorted_session_keys
690            .retain(|x| self.sessions.iter().any(|(id, _)| id == x));
691    }
692}
693
694/// A system that runs directly on a [`Game`] instead of in a specific [`Session`].
695pub type GameSystem = Box<dyn FnMut(&mut Game) + Sync + Send>;
696
697/// A collection of systems associated directly to a [`Game`] as opposed to a [`Session`].
698#[derive(Default)]
699pub struct GameSystems {
700    /// Flag which indicates whether or not the startup systems have been run yet.
701    pub has_run_startup: bool,
702    /// Startup systems.
703    pub startup: Vec<GameSystem>,
704    /// Game systems that are run before sessions are run.
705    pub before: Vec<GameSystem>,
706    /// Game systems that are run after sessions are run.
707    pub after: Vec<GameSystem>,
708    /// Game systems that are run after a specific session is run.
709    pub after_session: HashMap<Ustr, Vec<GameSystem>>,
710    /// Game systems that are run before a specific session is run.
711    pub before_session: HashMap<Ustr, Vec<GameSystem>>,
712}
713
714impl GameSystems {
715    /// Add a system that will run only once, before all of the other non-startup systems.
716    pub fn add_startup_system<F>(&mut self, system: F) -> &mut Self
717    where
718        F: FnMut(&mut Game) + Sync + Send + 'static,
719    {
720        self.startup.push(Box::new(system));
721        self
722    }
723
724    /// Add a system that will run on every step, before all of the sessions are run.
725    pub fn add_before_system<F>(&mut self, system: F) -> &mut Self
726    where
727        F: FnMut(&mut Game) + Sync + Send + 'static,
728    {
729        self.before.push(Box::new(system));
730        self
731    }
732
733    /// Add a system that will run on every step, after all of the sessions are run.
734    pub fn add_after_system<F>(&mut self, system: F) -> &mut Self
735    where
736        F: FnMut(&mut Game) + Sync + Send + 'static,
737    {
738        self.after.push(Box::new(system));
739        self
740    }
741
742    /// Add a system that will run every time the named session is run, before the session is run.
743    pub fn add_before_session_system<F>(&mut self, session: &str, system: F) -> &mut Self
744    where
745        F: FnMut(&mut Game) + Sync + Send + 'static,
746    {
747        self.before_session
748            .entry(session.into())
749            .or_default()
750            .push(Box::new(system));
751        self
752    }
753
754    /// Add a system that will run every time the named session is run, after the session is run.
755    pub fn add_after_session_system<F>(&mut self, session: &str, system: F) -> &mut Self
756    where
757        F: FnMut(&mut Game) + Sync + Send + 'static,
758    {
759        self.after_session
760            .entry(session.into())
761            .or_default()
762            .push(Box::new(system));
763        self
764    }
765}
766
767/// Type of session command
768pub type SessionCommand = dyn FnOnce(&mut Sessions) + Sync + Send;
769
770/// Container for multiple game sessions.
771///
772/// Each session shares the same [`Entities`].
773#[derive(HasSchema, Default)]
774#[schema(no_clone)]
775pub struct Sessions {
776    map: UstrMap<Session>,
777
778    /// Commands that operate on [`Sessions`], called after all sessions update.
779    /// These may be used to add/delete/modify sessions.
780    ///
781    /// Commands are useful in a situation where you want to remove / recreate
782    /// a session from within it's own system. You cannot do this while the `Session` is running.
783    ///
784    /// Commands added inside a session command will not be executed until next frame.
785    commands: VecDeque<Box<SessionCommand>>,
786}
787
788/// Resource that allows you to configure the current session.
789#[derive(HasSchema, Default, Debug, Clone, Copy)]
790#[repr(C)]
791pub struct SessionOptions {
792    /// Whether or not this session should be active after this frame.
793    pub active: bool,
794    /// Whether or not this session should be visible.
795    pub visible: bool,
796    /// Whether or not this session should be deleted.
797    pub delete: bool,
798}
799
800impl Sessions {
801    /// Create a new session from [`SessionBuilder`], insert into [`Sessions`], and borrow it mutably so it can be modified.
802    /// If session with same name already exists, it will be replaced.
803    pub fn create(&mut self, builder: SessionBuilder) -> &mut Session {
804        builder.finish_and_add(self)
805    }
806
807    /// Create a new session from default [`SessionBuilder`], and modify in closure before it is added to [`Sessions`]. Then borrow it mutably so it can be modified.
808    ///
809    /// If session with same name already exists, it will be replaced.
810    pub fn create_with<N: TryInto<Ustr>>(
811        &mut self,
812        name: N,
813        plugin: impl SessionPlugin,
814    ) -> &mut Session
815    where
816        <N as TryInto<Ustr>>::Error: Debug,
817    {
818        let mut builder = SessionBuilder::new(name);
819        builder.install_plugin(plugin);
820        builder.finish_and_add(self)
821    }
822
823    /// Replaces an existing session after converting it into a [`SessionBuilder`] with same systems, stages, etc. It then is given to `build_function` to extend or modify it.
824    ///
825    /// WARNING - this is intended to allow a [`GamePlugin`] to extend a session created by another plugin during setup. It destroys and re-creates session, and if used during gameplay,
826    /// could break assumptions around determinism and cause desyncs in network play. The sessions [`World`] is not preserved. This is a utility for constructing session, not for runtime mutation.
827    ///
828    /// `None` is returned if the session with name was not found.
829    pub fn modify_and_replace_existing_session<N: TryInto<Ustr>>(
830        &mut self,
831        name: N,
832        build_function: impl FnOnce(&mut SessionBuilder),
833    ) -> Option<&mut Session>
834    where
835        <N as TryInto<Ustr>>::Error: Debug,
836    {
837        let name = name
838            .try_into()
839            .expect("Session name could not be converted into Ustr.");
840
841        let session = self.map.remove(&name)?;
842        let mut builder = SessionBuilder::from_existing_session(name, session);
843        build_function(&mut builder);
844        Some(builder.finish_and_add(self))
845    }
846
847    /// Delete a session.
848    #[track_caller]
849    pub fn delete<K: TryInto<Ustr>>(&mut self, name: K)
850    where
851        <K as TryInto<Ustr>>::Error: Debug,
852    {
853        self.map.remove(&name.try_into().unwrap());
854    }
855
856    /// Borrow a session from the sessions list.
857    #[track_caller]
858    pub fn get<K: TryInto<Ustr>>(&self, name: K) -> Option<&Session>
859    where
860        <K as TryInto<Ustr>>::Error: Debug,
861    {
862        self.map.get(&name.try_into().unwrap())
863    }
864
865    /// Borrow a session from the sessions list.
866    #[track_caller]
867    pub fn get_mut<K: TryInto<Ustr>>(&mut self, name: K) -> Option<&mut Session>
868    where
869        <K as TryInto<Ustr>>::Error: Debug,
870    {
871        self.map.get_mut(&name.try_into().unwrap())
872    }
873
874    /// Mutably iterate over sessions.
875    pub fn iter_mut(&mut self) -> std::collections::hash_map::IterMut<'_, Ustr, Session> {
876        self.map.iter_mut()
877    }
878
879    /// Iterate over sessions.
880    pub fn iter(&self) -> std::collections::hash_map::Iter<'_, Ustr, Session> {
881        self.map.iter()
882    }
883
884    /// Add a [`SessionCommand`] to queue.
885    pub fn add_command(&mut self, command: Box<SessionCommand>) {
886        self.commands.push_back(command);
887    }
888
889    /// Add a [`Session`] to [`Sessions`]. This function is private, used by [`SessionBuilder`] to save the finished `Session`,
890    /// and is not directly useful to user as a `Session` may not be directly constructed.
891    ///
892    /// To build a new session, see [`Sessions::create`] or [`Sessions::create_with`].
893    fn add(&mut self, name: Ustr, session: Session) -> &mut Session {
894        // Insert it into the map
895        self.map.insert(name, session);
896
897        // And borrow it for the modification
898        self.map.get_mut(&name).unwrap()
899    }
900}