diff --git a/framework_crates/bones_ecs/src/resources.rs b/framework_crates/bones_ecs/src/resources.rs index daa72ab3cb..372e2ab572 100644 --- a/framework_crates/bones_ecs/src/resources.rs +++ b/framework_crates/bones_ecs/src/resources.rs @@ -449,6 +449,121 @@ impl AtomicResource { } } +/// Utility container for storing set of [`UntypedResource`]. +/// Cloning +#[derive(Default)] +pub struct UntypedResourceSet { + resources: Vec, +} + +impl Clone for UntypedResourceSet { + fn clone(&self) -> Self { + Self { + resources: self + .resources + .iter() + .map(|res| { + if let Some(clone) = res.clone_data() { + UntypedResource::new(clone) + } else { + UntypedResource::empty(res.schema()) + } + }) + .collect(), + } + } +} + +impl UntypedResourceSet { + /// Insert a startup resource. On stage / session startup (first step), will be inserted into [`World`]. + /// + /// If already exists, will be overwritten. + pub fn insert_resource(&mut self, resource: T) { + // Update an existing resource of the same type. + for r in &mut self.resources { + if r.schema() == T::schema() { + let mut borrow = r.borrow_mut(); + + if let Some(b) = borrow.as_mut() { + *b.cast_mut() = resource; + } else { + *borrow = Some(SchemaBox::new(resource)) + } + return; + } + } + + // Or insert a new resource if we couldn't find one + self.resources + .push(UntypedResource::new(SchemaBox::new(resource))) + } + + /// Init resource with default, and return mutable ref for modification. + /// If already exists, returns mutable ref to existing resource. + pub fn init_resource(&mut self) -> RefMut { + if !self.resources.iter().any(|x| x.schema() == T::schema()) { + self.insert_resource(T::default()); + } + self.resource_mut::().unwrap() + } + + /// Get mutable reference to startup resource if found. + #[track_caller] + pub fn resource_mut(&self) -> Option> { + let res = self.resources.iter().find(|x| x.schema() == T::schema())?; + let borrow = res.borrow_mut(); + + if borrow.is_some() { + // SOUND: We know the type matches T + Some(RefMut::map(borrow, |b| unsafe { + b.as_mut().unwrap().as_mut().cast_into_mut_unchecked() + })) + } else { + None + } + } + + /// Insert an [`UntypedResource`] with empty cell for [`Schema`] of `T`. + /// If resource already exists for this schema, overwrite it with empty. + pub fn insert_empty(&mut self) { + for r in &mut self.resources { + if r.schema() == T::schema() { + let mut borrow = r.borrow_mut(); + *borrow = None; + return; + } + } + + // Or insert a new empty resource if we couldn't find one + self.resources.push(UntypedResource::empty(T::schema())); + } + + /// Get immutable ref to Vec of resources + pub fn resources(&self) -> &Vec { + &self.resources + } + + /// Insert resources in world. If resource already exists, overwrites it. + /// + /// If `remove_empty` is set, if resource cell is empty, it will remove the + /// resource from cell on world. + pub fn insert_on_world(&self, world: &mut World, remove_empty: bool) { + for resource in self.resources.iter() { + let resource_cell = world.resources.untyped().get_cell(resource.schema()); + + // Deep copy resource and insert into world. + if let Some(resource_copy) = resource.clone_data() { + resource_cell + .insert(resource_copy) + .expect("Schema mismatch error"); + } else if remove_empty { + // Remove the resource on world + resource_cell.remove(); + } + } + } +} + #[cfg(test)] mod test { use std::sync::Arc; diff --git a/framework_crates/bones_ecs/src/stage.rs b/framework_crates/bones_ecs/src/stage.rs index f7827ddf96..543fbdf14b 100644 --- a/framework_crates/bones_ecs/src/stage.rs +++ b/framework_crates/bones_ecs/src/stage.rs @@ -20,7 +20,7 @@ pub struct SystemStagesBuilder { startup_systems: Vec>, /// Resources installed during session plugin installs. Copied to world as first step on startup of stages' execution. - startup_resources: Vec, + startup_resources: UntypedResourceSet, /// Systems that are continously run until they succeed(return Some). These run before all stages. Uses Option to allow for easy usage of `?`. single_success_systems: Vec>>, @@ -140,55 +140,19 @@ impl SystemStagesBuilder { /// /// If already exists, will be overwritten. pub fn insert_startup_resource(&mut self, resource: T) { - // Update an existing resource of the same type. - for r in &mut self.startup_resources { - if r.schema() == T::schema() { - let mut borrow = r.borrow_mut(); - - if let Some(b) = borrow.as_mut() { - *b.cast_mut() = resource; - } else { - *borrow = Some(SchemaBox::new(resource)) - } - return; - } - } - - // Or insert a new resource if we couldn't find one - self.startup_resources - .push(UntypedResource::new(SchemaBox::new(resource))); + self.startup_resources.insert_resource(resource); } /// Init startup resource with default, and return mutable ref for modification. /// If already exists, returns mutable ref to existing resource. pub fn init_startup_resource(&mut self) -> RefMut { - if !self - .startup_resources - .iter() - .any(|x| x.schema() == T::schema()) - { - self.insert_startup_resource(T::default()); - } - self.startup_resource_mut::().unwrap() + self.startup_resources.init_resource::() } /// Get mutable reference to startup resource if found. #[track_caller] pub fn startup_resource_mut(&self) -> Option> { - let res = self - .startup_resources - .iter() - .find(|x| x.schema() == T::schema())?; - let borrow = res.borrow_mut(); - - if borrow.is_some() { - // SOUND: We know the type matches T - Some(RefMut::map(borrow, |b| unsafe { - b.as_mut().unwrap().as_mut().cast_into_mut_unchecked() - })) - } else { - None - } + self.startup_resources.resource_mut() } } @@ -202,7 +166,7 @@ pub struct SystemStages { startup_systems: Vec>, /// Resources installed during session plugin installs. Copied to world as first step on startup of stages' execution. - startup_resources: Vec, + startup_resources: UntypedResourceSet, /// Systems that are continously run until they succeed(return Some). These run before all stages. Uses Option to allow for easy usage of `?`. single_success_systems: Vec>>, @@ -322,7 +286,7 @@ impl SystemStages { /// Insert the startup resources that [`SystemStages`] and session were built with into [`World`]. fn insert_startup_resources(&self, world: &mut World) { - for resource in self.startup_resources.iter() { + for resource in self.startup_resources.resources().iter() { // Deep copy startup resource and insert into world. let resource_copy = resource.clone_data().unwrap(); let resource_cell = world.resources.untyped().get_cell(resource.schema()); diff --git a/framework_crates/bones_framework/tests/reset.rs b/framework_crates/bones_framework/tests/reset.rs index 624740c7c9..7c812021ee 100644 --- a/framework_crates/bones_framework/tests/reset.rs +++ b/framework_crates/bones_framework/tests/reset.rs @@ -120,3 +120,82 @@ pub fn single_success_system_reset() { assert_eq!(counter.0, 2); } } + +#[test] +pub fn reset_world_resource_override() { + let mut game = Game::new(); + + // insert counter resource + game.sessions.create_with("game", |builder| { + builder.init_resource::(); + }); + + game.step(Instant::now()); + { + let session = game.sessions.get("game").unwrap(); + let counter = session.world.get_resource::().unwrap(); + // asert in default state of 0 + assert_eq!(counter.0, 0); + } + + // Add command that will trigger reset on next step, + // and add reset resource of Counter with value 1 + { + let game_session = game.sessions.get_mut("game").unwrap(); + game_session.world.init_resource::().add( + |mut reset: ResMutInit| { + reset.reset = true; + reset.insert_reset_resource(Counter(1)); + }, + ); + } + + // Step to reset + game.step(Instant::now()); + { + let session = game.sessions.get("game").unwrap(); + let counter = session.world.get_resource::().unwrap(); + // Verify the reset resource of value 1 was applied, instead of resetting to default state + assert_eq!(counter.0, 1); + } +} + +#[test] +pub fn reset_world_emtpy_resource() { + let mut game = Game::new(); + + // insert counter resource + game.sessions.create_with("game", |builder| { + builder.init_resource::(); + }); + + game.step(Instant::now()); + { + let session = game.sessions.get("game").unwrap(); + let counter = session.world.get_resource::().unwrap(); + // asert in default state of 0 + assert_eq!(counter.0, 0); + } + + // Add command that will trigger reset on next step, + // and add reset resource of Counter with value 1 + { + let game_session = game.sessions.get_mut("game").unwrap(); + game_session.world.init_resource::().add( + |mut reset: ResMutInit| { + reset.reset = true; + reset.insert_empty_reset_resource::(); + }, + ); + } + + // Step to reset + game.step(Instant::now()); + { + let session = game.sessions.get("game").unwrap(); + let counter = session.world.get_resource::(); + + // Verify resource was removed instead of reseting to initial state of session build. + assert!(counter.is_none()) + } +} diff --git a/framework_crates/bones_lib/src/reset.rs b/framework_crates/bones_lib/src/reset.rs index 8cb5109002..c4eacd0cad 100644 --- a/framework_crates/bones_lib/src/reset.rs +++ b/framework_crates/bones_lib/src/reset.rs @@ -5,10 +5,35 @@ use crate::prelude::*; /// 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` and setting `reset_world.reset = true;` may be used to trigger a reset from system execution. -#[derive(Copy, Clone, HasSchema, Default)] +#[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 reset. + /// 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(&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(&self) -> Option> { + self.reset_resources.resource_mut::() + } + + /// 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(&mut self) { + self.reset_resources.insert_empty::(); + } } /// Extension of [`World`] @@ -47,6 +72,11 @@ impl WorldExt for World { } fn reset_internals(&mut self, stages: &mut SystemStages) { + // Copy resources to be inserted after the reset. + let post_reset_resources = self + .get_resource::() + .map(|x| x.reset_resources.clone()); + // Clear all component stores self.components = ComponentStores::default(); @@ -72,5 +102,11 @@ impl WorldExt for World { // Immediately run startup tasks, ensuring startup resources are present and run startup systems. stages.handle_startup(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); + } } }