bones_lib/
reset.rs

1//! Extensions of bones_ecs for utility functions in resetting a World
2use crate::prelude::*;
3
4/// Resource that allows user to trigger a reset of world, clearing entities, components, and resources.
5/// This is supported in bone's default session runners - but if implementing custom runner, must call `world.handle_world_reset` inside step.
6///
7/// `reset_world: ResMutInit<ResetWorld>` and setting `reset_world.reset = true;` may be used to trigger a reset from system execution.
8#[derive(HasSchema, Clone, Default)]
9pub struct ResetWorld {
10    /// Set to true to trigger reset of [`World`].
11    pub reset: bool,
12
13    /// List of resources that will be inserted into [`World`] after the resources are reset,
14    /// before startup systems are executed.
15    /// These override any `startup resources` captured during session build.
16    /// If want to preserve a resource instead of having it reset, insert it here.
17    pub reset_resources: UntypedResourceSet,
18}
19
20impl ResetWorld {
21    /// Insert a resource that will be applied after reset. If resource was created
22    /// on session iniialization, this will overwrite it using reset resource instead.
23    pub fn insert_reset_resource<T: HasSchema>(&mut self, resource: T) {
24        self.reset_resources.insert_resource(resource);
25    }
26
27    /// Get a mutable reference to a reset resource if found.
28    pub fn reset_resource_mut<T: HasSchema>(&self) -> Option<RefMut<'_, T>> {
29        self.reset_resources.resource_mut::<T>()
30    }
31
32    /// Insert resource in "empty" state - If resource was created
33    /// on session iniialization, instead of being reset to that state,
34    /// after reset this resource will not be on [`World`].
35    pub fn insert_empty_reset_resource<T: HasSchema>(&mut self) {
36        self.reset_resources.insert_empty::<T>();
37    }
38}
39
40/// Extension of [`World`]
41pub trait WorldExt {
42    /// May be called by [`SessionRunner`] before or after [`SystemStages::run`] to allow triggering reset
43    /// of world with the [`ResetWorld`] resource.
44    ///
45    /// `stages` is required, so that after reset, will immediatelly run startup tasks, instead of waiting until next step of [`SystemStages`].
46    /// This avoids edge cases where other sessions may read expected resources from this session before its next step.
47    fn handle_world_reset(&mut self, stages: &mut SystemStages);
48
49    /// Check if reset has been triggered by [`ResetWorld`] resource. If [`SessionRunner`] needs to do anything special
50    /// for a world reset (preserve managed resources, etc) - can check this before calling `handle_world_reset` to see
51    /// if a rese will occur.
52    fn reset_triggered(&self) -> bool;
53
54    /// Provides a low-level interface for resetting entities, components, and resources.
55    ///
56    /// `stages` is required, so that after reset, will immediatelly run startup tasks, instead of waiting until next step of [`SystemStages`].
57    /// This avoids edge cases where other sessions may read expected resources from this session before its next step.
58    ///
59    /// Waring: Calling this function on World in a [`Session`] using network session runner is likely to introduce non-determinism.
60    /// It is strongly recommended to use the [`ResetWorld`] Resource to trigger this in manner compatible with net rollback.
61    fn reset_internals(&mut self, stages: &mut SystemStages);
62}
63
64impl WorldExt for World {
65    fn handle_world_reset(&mut self, stages: &mut SystemStages) {
66        if self.reset_triggered() {
67            self.reset_internals(stages);
68        }
69    }
70
71    fn reset_triggered(&self) -> bool {
72        self.get_resource::<ResetWorld>().is_some_and(|r| r.reset)
73    }
74
75    fn reset_internals(&mut self, stages: &mut SystemStages) {
76        // Copy resources to be inserted after the reset.
77        let post_reset_resources = self
78            .get_resource::<ResetWorld>()
79            .map(|x| x.reset_resources.clone());
80
81        // Clear all component stores
82        self.components = ComponentStores::default();
83
84        // save copy of special resources that should always remain in session / managed by bones
85        let time = self.get_resource::<Time>().map(|t| *t);
86        let session_opts = self.get_resource::<SessionOptions>().map(|x| *x);
87
88        // Remove any owned resources (preserves shared resources)
89        // This allows world resources to be reset inside session run safely, as shared resources are only injected
90        // outside of session run, and a runner may take another step after the reset.
91        self.resources.clear_owned_resources();
92
93        // Entities were cleared with resources - ensure is present.
94        self.init_resource::<Entities>();
95
96        // Re-insert preserved resources
97        if let Some(time) = time {
98            self.insert_resource(time);
99        }
100        if let Some(session_opts) = session_opts {
101            self.insert_resource(session_opts);
102        }
103
104        // Insert startup resources, but do not yet run startup systems. (We do this after applying additional preserved "reset resources", as
105        // startup systems may want to access these).
106        stages.handle_startup_resources(self);
107
108        // Apply any reset resources to world, overwriting startup resources.
109        if let Some(resources) = post_reset_resources {
110            let remove_empty = true;
111            resources.insert_on_world(self, remove_empty);
112        }
113
114        // Now run the startup systems after updating resources.
115        stages.handle_startup_systems(self);
116    }
117}