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