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);
    }
}