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}