From 019a5921f7a9f75f52cd91991384ad2ce4dd6213 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Fri, 1 Dec 2023 01:29:33 +0000 Subject: [PATCH] feat: make World only ever require &self. (#270) This is wide but relatively shallow update that makes `World` only ever require `&self`, allowing us to remove the system initialization stage that was necessary to have `&mut World` access for initializing resources and components. The main change was to allow `Resources` to have resources added to it with only a `&self` reference by using a `once_map` internally. This meant that all resource cells now become `Option` on the inside. This conceptually means that cells for reasources are lazily initialized and always considered to be present, but they may or may not actually contain a resource value. --- demos/features/src/main.rs | 6 +- .../bones_bevy_renderer/src/lib.rs | 6 +- .../bones_ecs/examples/pos_vel.rs | 3 - framework_crates/bones_ecs/src/lib.rs | 2 +- framework_crates/bones_ecs/src/resources.rs | 499 ++++++++++-------- framework_crates/bones_ecs/src/stage.rs | 55 +- framework_crates/bones_ecs/src/system.rs | 114 ++-- framework_crates/bones_ecs/src/world.rs | 81 +-- .../bones_framework/src/localization.rs | 12 +- .../bones_framework/src/params.rs | 1 - .../bones_framework/src/render/sprite.rs | 3 +- .../src/render/ui/widgets/bordered_button.rs | 1 + .../src/render/ui/widgets/bordered_frame.rs | 1 + framework_crates/bones_lib/src/lib.rs | 142 ++--- .../bones_schema/src/alloc/map.rs | 18 +- framework_crates/bones_schema/src/ptr.rs | 10 +- framework_crates/bones_schema/src/ser_de.rs | 2 +- framework_crates/bones_schema/tests/tests.rs | 2 +- .../src/lua/bindings/ecsref.rs | 20 +- .../src/lua/bindings/resources.rs | 21 +- 20 files changed, 467 insertions(+), 532 deletions(-) diff --git a/demos/features/src/main.rs b/demos/features/src/main.rs index 0afce4c9ee..41bc5ecdb9 100644 --- a/demos/features/src/main.rs +++ b/demos/features/src/main.rs @@ -274,11 +274,7 @@ fn menu_system( // // This makes it easier to compose widgets that have differing access to the // bones world. - // - // Note that all of the parameters of the system must have been initialized - // already, so if they are not initialized by another system, you have to use - // `world.init_param::()` in the session plugin to initialize them. - world.run_initialized_system(demo_widget, ui); + world.run_system(demo_widget, ui); ui.add_space(30.0); }); diff --git a/framework_crates/bones_bevy_renderer/src/lib.rs b/framework_crates/bones_bevy_renderer/src/lib.rs index 7f81d3c342..f1838ee1dc 100644 --- a/framework_crates/bones_bevy_renderer/src/lib.rs +++ b/framework_crates/bones_bevy_renderer/src/lib.rs @@ -621,7 +621,7 @@ fn load_egui_textures( // TODO: Avoid doing this every frame when there have been no assets loaded. // We should should be able to use the asset load progress event listener to detect newly // loaded assets that will need to be handled. - let mut bones_egui_textures = bones_egui_textures_cell.borrow_mut(); + let mut bones_egui_textures = bones_egui_textures_cell.borrow_mut().unwrap(); // Take all loaded image assets and conver them to external images that reference bevy handles bones_image_ids.load_bones_images( asset_server, @@ -665,7 +665,7 @@ fn egui_input_hook( mut data: ResMut, ) { if let Some(hook) = data.game.shared_resource_cell::() { - let hook = hook.borrow(); + let hook = hook.borrow().unwrap(); let mut egui_input = egui_query.get_single_mut().unwrap(); (hook.0)(&mut data.game, &mut egui_input); } @@ -782,7 +782,7 @@ fn step_bones_game(world: &mut World) { data.game.shared_resource_cell().unwrap() } }; - let bones_window = bones_window.borrow_mut(); + let bones_window = bones_window.borrow_mut().unwrap(); let is_fullscreen = matches!(&window.mode, WindowMode::Fullscreen); if is_fullscreen != bones_window.fullscreen { diff --git a/framework_crates/bones_ecs/examples/pos_vel.rs b/framework_crates/bones_ecs/examples/pos_vel.rs index e7c6cc0255..3e1389bd76 100644 --- a/framework_crates/bones_ecs/examples/pos_vel.rs +++ b/framework_crates/bones_ecs/examples/pos_vel.rs @@ -31,9 +31,6 @@ fn main() { .add_system_to_stage(CoreStage::Update, pos_vel_system) .add_system_to_stage(CoreStage::PostUpdate, print_system); - // Initialize our systems ( must be called once before calling stages.run() ) - stages.initialize_systems(&mut world); - // Run our game loop for 10 frames for _ in 0..10 { stages.run(&mut world); diff --git a/framework_crates/bones_ecs/src/lib.rs b/framework_crates/bones_ecs/src/lib.rs index 51ad16a7d1..c5ee3e8656 100644 --- a/framework_crates/bones_ecs/src/lib.rs +++ b/framework_crates/bones_ecs/src/lib.rs @@ -77,7 +77,7 @@ mod test { #[test] fn insert_comp_with_gap() { - let mut w = World::new(); + let w = World::new(); #[derive(HasSchema, Default, Clone)] #[repr(C)] diff --git a/framework_crates/bones_ecs/src/resources.rs b/framework_crates/bones_ecs/src/resources.rs index 195379d39c..141f9641d5 100644 --- a/framework_crates/bones_ecs/src/resources.rs +++ b/framework_crates/bones_ecs/src/resources.rs @@ -2,142 +2,92 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc}; -use atomicell::borrow::{AtomicBorrow, AtomicBorrowMut}; +use once_map::OnceMap; use crate::prelude::*; +/// An untyped, atomic resource cell. +pub type AtomicUntypedResource = Arc; + /// An untyped resource that may be inserted into [`UntypedResources`]. -#[derive(Clone)] -pub struct UntypedAtomicResource { - cell: Arc>, +/// +/// This is fundamentally a [`Arc>>`] and thus represents +/// a cell that may or may not contain a resource of it's schema. +pub struct UntypedResource { + cell: AtomicCell>, schema: &'static Schema, } -impl std::fmt::Debug for UntypedAtomicResource { +impl std::fmt::Debug for UntypedResource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("UntypedAtomicResource") - .finish_non_exhaustive() + f.debug_struct("UntypedResource").finish_non_exhaustive() } } -impl UntypedAtomicResource { - /// Creates a new [`UntypedAtomicResource`] storing the given data. +impl UntypedResource { + /// Initialize a new, empty [`UntypedResource`]. + pub fn empty(schema: &'static Schema) -> Self { + Self { + cell: AtomicCell::new(None), + schema, + } + } + + /// Creates a new [`UntypedResource`] storing the given data. pub fn new(resource: SchemaBox) -> Self { Self { schema: resource.schema(), - cell: Arc::new(AtomicCell::new(resource)), + cell: AtomicCell::new(Some(resource)), } } - /// Create a new [`UntypedAtomicResource`] for the given schema, initially populated with the default + /// Create a new [`UntypedResource`] for the given schema, initially populated with the default /// value for the schema. - pub fn from_schema(schema: &'static Schema) -> Self { + pub fn from_default(schema: &'static Schema) -> Self { Self { - cell: Arc::new(AtomicCell::new(SchemaBox::default(schema))), + cell: AtomicCell::new(Some(SchemaBox::default(schema))), schema, } } /// Clone the inner data, creating a new copy instead of returning another handle the the same /// data, as the normal `clone()` implementation does. - pub fn clone_data(&self) -> Self { - Self { - cell: Arc::new(AtomicCell::new((*self.cell.borrow()).clone())), - schema: self.schema, - } - } - - /// Borrow the resource. - pub fn borrow(&self) -> AtomicSchemaRef { - let (reference, borrow) = Ref::into_split(self.cell.borrow()); - // SOUND: we keep the borrow along with the reference so that the pointer remains valid. - let schema_ref = unsafe { reference.as_ref() }.as_ref(); - AtomicSchemaRef { schema_ref, borrow } + pub fn clone_data(&self) -> Option { + (*self.cell.borrow()).clone() } - /// Mutably borrow the resource. - pub fn borrow_mut(&self) -> AtomicSchemaRefMut { - let (mut reference, borrow) = RefMut::into_split(self.cell.borrow_mut()); - // SOUND: we keep the borrow along with the reference so that the pointer remains valid. - let schema_ref = unsafe { reference.as_mut() }.as_mut(); - AtomicSchemaRefMut { schema_ref, borrow } + /// Insert resource data into the cell, returning the previous data. + /// # Errors + /// Errors if the schema of the data does not match that of this cell. + pub fn insert(&self, data: SchemaBox) -> Result, SchemaMismatchError> { + self.schema.ensure_match(data.schema())?; + let mut data = Some(data); + std::mem::swap(&mut data, &mut *self.cell.borrow_mut()); + Ok(data) } - /// Try to extract the inner schema box, if this is the reference to atomic resource. - pub fn try_into_inner(self) -> Result { - let schema = self.schema; - let cell = - Arc::try_unwrap(self.cell).map_err(|cell| UntypedAtomicResource { cell, schema })?; - Ok(cell.into_inner()) + /// Remove the resource data, returning what was stored in it. + pub fn remove(&self) -> Option { + let mut data = None; + std::mem::swap(&mut data, &mut *self.cell.borrow_mut()); + data } - /// Get the schema of the resource. - pub fn schema(&self) -> &'static Schema { - self.schema - } -} - -/// An atomic borrow of a [`SchemaRef`]. -pub struct AtomicSchemaRef<'a> { - schema_ref: SchemaRef<'a>, - borrow: AtomicBorrow<'a>, -} - -impl<'a> AtomicSchemaRef<'a> { - /// Get a [`SchemaRef`] that points to the inner value. - /// - /// > **Note:** Ideally this method would be unnecessary, but it is impossible to properly - /// implement [`Deref`][std::ops::Deref] because deref must return a reference and we actually - /// need to return a [`SchemaRef`] with a shortened lifetime, binding it to this - /// [`AtomicSchemaRef`] borrow. - pub fn schema_ref(&self) -> SchemaRef<'_> { - self.schema_ref - } - - /// # Safety - /// You must know that T represents the data in the [`SchemaRef`]. - pub unsafe fn deref(self) -> Ref<'a, T> { - Ref::with_borrow(self.schema_ref.cast_into_unchecked(), self.borrow) - } - - /// Convert into typed [`Ref`]. This panics if the schema doesn't match. + /// Borrow the resource. #[track_caller] - pub fn typed(self) -> Ref<'a, T> { - assert_eq!(T::schema(), self.schema_ref.schema(), "Schema mismatch"); - // SOUND: we've checked for matching schema. - unsafe { self.deref() } + pub fn borrow(&self) -> Ref> { + self.cell.borrow() } -} -/// An atomic borrow of a [`SchemaRefMut`]. -pub struct AtomicSchemaRefMut<'a> { - schema_ref: SchemaRefMut<'a>, - borrow: AtomicBorrowMut<'a>, -} - -impl<'a> AtomicSchemaRefMut<'a> { - /// Get a [`SchemaRefMut`] that points to the inner value. - /// - /// > **Note:** Ideally this method would be unnecessary, but it is impossible to properly - /// implement [`DerefMut`][std::ops::Deref] because deref must return a reference and we actually - /// need to return a [`SchemaRefMut`] with a shortened lifetime, binding it to this - /// [`AtomicSchemaRefMut`] borrow. - pub fn schema_ref_mut(&mut self) -> SchemaRefMut<'_> { - self.schema_ref.reborrow() - } - - /// # Safety - /// You must know that T represents the data in the [`SchemaRefMut`]. - pub unsafe fn deref_mut(self) -> RefMut<'a, T> { - RefMut::with_borrow(self.schema_ref.cast_into_mut_unchecked(), self.borrow) + /// Mutably borrow the resource. + #[track_caller] + pub fn borrow_mut(&self) -> RefMut> { + self.cell.borrow_mut() } - /// Convert into typed [`RefMut`]. This panics if the schema doesn't match. - #[track_caller] - pub fn typed(self) -> RefMut<'a, T> { - assert_eq!(T::schema(), self.schema_ref.schema(), "Schema mismatch"); - // SOUND: we've checked for matching schema. - unsafe { self.deref_mut() } + /// Get the schema of the resource. + pub fn schema(&self) -> &'static Schema { + self.schema } } @@ -149,19 +99,41 @@ impl<'a> AtomicSchemaRefMut<'a> { /// should use [`Resources`] instead. #[derive(Default)] pub struct UntypedResources { - resources: HashMap, + resources: OnceMap, } impl Clone for UntypedResources { fn clone(&self) -> Self { - let resources = self - .resources - .iter() - .map(|(k, v)| (*k, v.clone_data())) - .collect(); - Self { resources } + let binding = self.resources.read_only_view(); + let resources = binding.iter().map(|(_, v)| (v.schema, v.clone_data())); + + let new_resources = OnceMap::default(); + for (schema, resource) in resources { + new_resources.map_insert( + schema.id(), + |_| Arc::new(UntypedResource::empty(schema)), + |_, cell| { + if let Some(resource) = resource { + cell.insert(resource).unwrap(); + } + }, + ); + } + Self { + resources: new_resources, + } + } +} + +/// Error thrown when a resource cell cannot be inserted because it already exists. +#[derive(Debug, Clone, Copy)] +pub struct CellAlreadyPresentError; +impl std::fmt::Display for CellAlreadyPresentError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Resource cell already present") } } +impl std::error::Error for CellAlreadyPresentError {} impl UntypedResources { /// Create an empty [`UntypedResources`]. @@ -169,57 +141,52 @@ impl UntypedResources { Self::default() } - /// Get the number of resources in the store. - #[must_use] - pub fn len(&self) -> usize { - self.resources.len() - } - - /// Returns whether the store is empty. - #[must_use] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Insert a resource. - pub fn insert(&mut self, resource: SchemaBox) -> Option { - let id = resource.schema().id(); - self.resources - .insert(id, UntypedAtomicResource::new(resource)) - } - - /// Check whether or not the resoruce with the given ID is present. - pub fn contains(&self, id: SchemaId) -> bool { + /// Check whether or not a cell for the given resource has been initialized yet. + pub fn contains_cell(&self, id: SchemaId) -> bool { self.resources.contains_key(&id) } - /// Insert a resource. - pub fn insert_cell( - &mut self, - resource: UntypedAtomicResource, - ) -> Option { - let id = resource.schema().id(); - self.resources.insert(id, resource) - } - - /// Get a cell containing the resource data pointer for the given ID. - pub fn get_cell(&self, schema_id: SchemaId) -> Option { - self.resources.get(&schema_id).cloned() + /// Check whether or not the resource with the given ID is present. + pub fn contains(&self, id: SchemaId) -> bool { + self.resources + .map_get(&id, |_, cell| cell.borrow().is_some()) + .unwrap_or_default() } - /// Get a reference to an untyped resource. - pub fn get(&self, schema_id: SchemaId) -> Option { - self.resources.get(&schema_id).map(|x| x.borrow()) + /// This is an advanced use-case function that allows you to insert a resource cell directly. + /// + /// Normally this is completely unnecessary, because cells are automatically inserted lazily as + /// requested. + /// + /// Inserting this manually is used internally for shared resources, by inserting the same + /// cell into multiple worlds. + /// + /// # Errors + /// This will error if there is already a cell for the resource present. You cannot add a new + /// cell once one has already been inserted. + pub fn insert_cell(&self, cell: AtomicUntypedResource) -> Result<(), CellAlreadyPresentError> { + let schema = cell.schema; + if self.resources.contains_key(&schema.id()) { + Err(CellAlreadyPresentError) + } else { + self.resources.insert(schema.id(), |_| cell); + Ok(()) + } } - /// Get a mutable reference to an untyped resource. - pub fn get_mut(&mut self, schema_id: SchemaId) -> Option { - self.resources.get(&schema_id).map(|x| x.borrow_mut()) + /// Borrow the resource for the given schema. + pub fn get(&self, schema: &'static Schema) -> &UntypedResource { + self.resources + .insert(schema.id(), |_| Arc::new(UntypedResource::empty(schema))) } - /// Remove a resource. - pub fn remove(&mut self, id: SchemaId) -> Option { - self.resources.remove(&id) + /// Get a cell for the resource with the given schema. + pub fn get_cell(&self, schema: &'static Schema) -> AtomicUntypedResource { + self.resources.map_insert( + schema.id(), + |_| Arc::new(UntypedResource::empty(schema)), + |_, cell| cell.clone(), + ) } } @@ -237,77 +204,69 @@ impl Resources { Self::default() } - /// Get the number of resources in the store. - #[must_use] - pub fn len(&self) -> usize { - self.untyped.len() - } - - /// Returns whether the store is empty. - #[must_use] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - /// Insert a resource. - pub fn insert(&mut self, resource: T) -> Option> { + pub fn insert(&self, resource: T) -> Option { self.untyped + .get(T::schema()) .insert(SchemaBox::new(resource)) - .map(|x| AtomicResource::from_untyped(x)) + .unwrap() + .map(|x| x.cast_into()) } - /// Insert a resource cell. - pub fn insert_cell(&mut self, resource: AtomicResource) { - self.untyped.insert_cell(resource.untyped); + /// Check whether or not a resource is in the store. + /// + /// See [get()][Self::get] + pub fn contains(&self) -> bool { + self.untyped.resources.contains_key(&T::schema().id()) } - /// Borrow a resource. - pub fn get(&self) -> Option> { - self.untyped.get(T::schema().id()).map(|x| { - // SOUND: untyped resources returns data matching the schema of T. - unsafe { x.deref() } - }) + /// Remove a resource from the store, if it is present. + pub fn remove(&self) -> Option { + self.untyped + .get(T::schema()) + .remove() + // SOUND: we know the type matches because we retrieve it by it's schema. + .map(|x| unsafe { x.cast_into_unchecked() }) } - /// Mutably borrow a resource. - pub fn get_mut(&mut self) -> Option> { - self.untyped.get_mut(T::schema().id()).map(|x| { - // SOUND: untyped resources returns data matching the schema of T. - unsafe { x.deref_mut() } - }) + /// Borrow a resource. + pub fn get(&self) -> Option> { + let b = self.untyped.get(T::schema()).borrow(); + if b.is_some() { + Some(Ref::map(b, |b| unsafe { + b.as_ref().unwrap().as_ref().cast_into_unchecked() + })) + } else { + None + } } - /// Check whether or not a resource is in the store. - /// - /// See [get()][Self::get] - pub fn contains(&self) -> bool { - self.untyped.resources.contains_key(&T::schema().id()) + /// Borrow a resource. + pub fn get_mut(&self) -> Option> { + let b = self.untyped.get(T::schema()).borrow_mut(); + if b.is_some() { + Some(RefMut::map(b, |b| unsafe { + b.as_mut().unwrap().as_mut().cast_into_mut_unchecked() + })) + } else { + None + } } /// Gets a clone of the resource cell for the resource of the given type. - pub fn get_cell(&self) -> Option> { - let untyped = self.untyped.get_cell(T::schema().id())?; - - Some(AtomicResource { + pub fn get_cell(&self) -> AtomicResource { + let untyped = self.untyped.get_cell(T::schema()).clone(); + AtomicResource { untyped, _phantom: PhantomData, - }) - } - - /// Remove a resource from the store. - pub fn remove_cell(&mut self) -> Option> { - let previous = self.untyped.remove(T::schema().id()); - previous.map(|x| AtomicResource::from_untyped(x)) + } } /// Borrow the underlying [`UntypedResources`] store. pub fn untyped(&self) -> &UntypedResources { &self.untyped } - /// Mutably borrow the underlying [`UntypedResources`] store. - pub fn untyped_mut(&mut self) -> &mut UntypedResources { - &mut self.untyped - } + /// Consume [`Resources`] and extract the underlying [`UntypedResources`]. pub fn into_untyped(self) -> UntypedResources { self.untyped @@ -322,13 +281,18 @@ impl Resources { /// [`borrow_mut()`][Self::borrow_mut]. #[derive(Clone)] pub struct AtomicResource { - untyped: UntypedAtomicResource, + untyped: AtomicUntypedResource, _phantom: PhantomData, } impl Debug for AtomicResource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("AtomicResource(")?; - T::fmt(self.untyped.cell.borrow().cast_ref(), f)?; + self.untyped + .cell + .borrow() + .as_ref() + .map(|x| x.cast_ref::()) + .fmt(f)?; f.write_str(")")?; Ok(()) } @@ -337,48 +301,122 @@ impl Debug for AtomicResource { impl Default for AtomicResource { fn default() -> Self { Self { - untyped: UntypedAtomicResource::new(SchemaBox::new(T::default())), + untyped: Arc::new(UntypedResource::new(SchemaBox::new(T::default()))), _phantom: Default::default(), } } } impl AtomicResource { - /// Create a new atomic resource. - /// - /// This can be inserted into a world by calling `world.resources.insert_cell`. + /// Create a new, empty resource cell. + pub fn empty() -> Self { + Self { + untyped: Arc::new(UntypedResource::empty(T::schema())), + _phantom: PhantomData, + } + } + + /// Create a new resource cell with the given data. pub fn new(data: T) -> Self { AtomicResource { - untyped: UntypedAtomicResource::new(SchemaBox::new(data)), + untyped: Arc::new(UntypedResource::new(SchemaBox::new(data))), _phantom: PhantomData, } } - /// Create from an [`UntypedAtomicResource`]. - pub fn from_untyped(untyped: UntypedAtomicResource) -> Self { - assert_eq!(T::schema(), untyped.schema); - AtomicResource { + /// Create from an [`UntypedResource`]. + pub fn from_untyped(untyped: AtomicUntypedResource) -> Result { + T::schema().ensure_match(untyped.schema)?; + Ok(AtomicResource { untyped, _phantom: PhantomData, - } + }) + } + + /// Remove the resource from the cell, leaving the cell empty. + pub fn remove(&self) -> Option { + self.untyped + .remove() + // SOUND: The untyped data of an atomic resource must always be `T`. + .map(|x| unsafe { x.cast_into_unchecked() }) } /// Lock the resource for reading. /// /// This returns a read guard, very similar to an [`RwLock`][std::sync::RwLock]. - pub fn borrow(&self) -> Ref { + pub fn borrow(&self) -> Option> { let borrow = self.untyped.borrow(); - // SOUND: We know that the data pointer is valid for type T. - unsafe { borrow.deref() } + if borrow.is_some() { + Some(Ref::map(borrow, |r| unsafe { + r.as_ref().unwrap().as_ref().cast_into_unchecked() + })) + } else { + None + } } /// Lock the resource for read-writing. /// /// This returns a write guard, very similar to an [`RwLock`][std::sync::RwLock]. - pub fn borrow_mut(&self) -> RefMut { + pub fn borrow_mut(&self) -> Option> { let borrow = self.untyped.borrow_mut(); - // SOUND: We know that the data pointer is valid for type T. - unsafe { borrow.deref_mut() } + if borrow.is_some() { + Some(RefMut::map(borrow, |r| unsafe { + r.as_mut().unwrap().as_mut().cast_into_mut_unchecked() + })) + } else { + None + } + } + + /// Convert into an untyped resource. + pub fn into_untyped(self) -> AtomicUntypedResource { + self.untyped + } +} + +impl AtomicResource { + /// Initialize the resource using it's [`FromWorld`] implementation, if it is not present. + pub fn init(&self, world: &World) { + let mut borrow = self.untyped.borrow_mut(); + if unlikely(borrow.is_none()) { + *borrow = Some(SchemaBox::new(T::from_world(world))) + } + } + + /// Borrow the resource, initializing it if it doesn't exist. + #[track_caller] + pub fn init_borrow(&self, world: &World) -> Ref { + let map_borrow = |borrow| { + // SOUND: we know the schema matches. + Ref::map(borrow, |b: &Option| unsafe { + b.as_ref().unwrap().as_ref().cast_into_unchecked() + }) + }; + let borrow = self.untyped.borrow(); + if unlikely(borrow.is_none()) { + drop(borrow); + { + let mut borrow_mut = self.untyped.borrow_mut(); + *borrow_mut = Some(SchemaBox::new(T::from_world(world))); + } + + map_borrow(self.untyped.borrow()) + } else { + map_borrow(borrow) + } + } + + /// Borrow the resource, initializing it if it doesn't exist. + #[track_caller] + pub fn init_borrow_mut(&self, world: &World) -> RefMut { + let mut borrow = self.untyped.borrow_mut(); + if unlikely(borrow.is_none()) { + *borrow = Some(SchemaBox::new(T::from_world(world))); + } + RefMut::map(borrow, |b| unsafe { + b.as_mut().unwrap().as_mut().cast_into_mut_unchecked() + }) } } @@ -396,23 +434,26 @@ mod test { #[repr(C)] struct B(u32); - let mut resources = Resources::new(); + let r1 = Resources::new(); - resources.insert(A(String::from("hi"))); - assert_eq!(resources.get::().unwrap().0, "hi"); + r1.insert(A(String::from("hi"))); + let r1a = r1.get_cell::(); + assert_eq!(r1a.borrow().unwrap().0, "hi"); - let r2 = resources.clone(); + let r2 = r1.clone(); - resources.insert(A(String::from("bye"))); - resources.insert(A(String::from("world"))); - assert_eq!(resources.get::().unwrap().0, "world"); + r1.insert(A(String::from("bye"))); + r1.insert(A(String::from("world"))); + assert_eq!(r1a.borrow().unwrap().0, "world"); - assert_eq!(r2.get::().unwrap().0, "hi"); + let r2a = r2.get_cell::(); + assert_eq!(r2a.borrow().unwrap().0, "hi"); - resources.insert(B(1)); - assert_eq!(resources.get::().unwrap().0, 1); - resources.insert(B(2)); - assert_eq!(resources.get::().unwrap().0, 2); - assert_eq!(resources.get::().unwrap().0, "world"); + r1.insert(B(1)); + let r1b = r1.get_cell::(); + assert_eq!(r1b.borrow().unwrap().0, 1); + r1.insert(B(2)); + assert_eq!(r1b.borrow().unwrap().0, 2); + assert_eq!(r1a.borrow().unwrap().0, "world"); } } diff --git a/framework_crates/bones_ecs/src/stage.rs b/framework_crates/bones_ecs/src/stage.rs index 6b7c39a052..17e73a57cd 100644 --- a/framework_crates/bones_ecs/src/stage.rs +++ b/framework_crates/bones_ecs/src/stage.rs @@ -37,19 +37,7 @@ impl Default for SystemStages { } impl SystemStages { - /// Initialize the systems in the stages agains the [`World`]. - /// - /// This must be called once before calling [`run()`][Self::run]. - pub fn initialize_systems(&mut self, world: &mut World) { - for stage in &mut self.stages { - stage.initialize(world); - } - } - /// Execute the systems on the given `world`. - /// - /// > **Note:** You must call [`initialize_systems()`][Self::initialize_systems] once before - /// > calling `run()` one or more times. pub fn run(&mut self, world: &mut World) { // If we haven't run our startup systems yet if !self.has_started { @@ -58,8 +46,7 @@ impl SystemStages { // For each startup system for system in &mut self.startup_systems { - // Initialize and run the system - system.initialize(world); + // Run the system system.run(world, ()); } @@ -80,7 +67,7 @@ impl SystemStages { world.maintain(); // Remove the current system stage resource - world.resources.remove_cell::(); + world.resources.remove::(); } /// Create a [`SystemStages`] collection, initialized with a stage for each [`CoreStage`]. @@ -173,14 +160,7 @@ pub trait SystemStage: Sync + Send { /// The human-readable name for the stage, used for error messages when something goes wrong. fn name(&self) -> String; /// Execute the systems on the given `world`. - /// - /// > **Note:** You must call [`initialize()`][Self::initialize] once before calling `run()` one - /// > or more times. - fn run(&mut self, world: &mut World); - /// Initialize the contained systems for the given `world`. - /// - /// Must be called once before calling [`run()`][Self::run]. - fn initialize(&mut self, world: &mut World); + fn run(&mut self, world: &World); /// Add a system to this stage. fn add_system(&mut self, system: StaticSystem<(), ()>); @@ -218,32 +198,21 @@ impl SystemStage for SimpleSystemStage { self.name.clone() } - fn run(&mut self, world: &mut World) { + fn run(&mut self, world: &World) { // Run the systems for system in &mut self.systems { system.run(world, ()); } // Drain the command queue - { - if let Some(command_queue) = world.resources.get_cell::() { - let mut command_queue = command_queue.borrow_mut(); - - for mut system in command_queue.queue.drain(..) { - system.initialize(world); - system.run(world, ()); - } + let queue = world.resources.get_mut::(); + if let Some(mut command_queue) = queue { + for mut system in command_queue.queue.drain(..) { + system.run(world, ()); } } } - fn initialize(&mut self, world: &mut World) { - world.init_resource::(); - for system in &mut self.systems { - system.initialize(world); - } - } - fn add_system(&mut self, system: StaticSystem<(), ()>) { self.systems.push(system); } @@ -333,13 +302,13 @@ impl<'a> SystemParam for Commands<'a> { type State = AtomicResource; type Param<'s> = Commands<'s>; - fn initialize(_world: &mut World) {} - fn get_state(world: &World) -> Self::State { - world.resources.get_cell::().unwrap() + let cell = world.resources.get_cell::(); + cell.init(world); + cell } fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { - Commands(state.borrow_mut()) + Commands(state.borrow_mut().unwrap()) } } diff --git a/framework_crates/bones_ecs/src/system.rs b/framework_crates/bones_ecs/src/system.rs index 20aa20ccc0..868b46a5f2 100644 --- a/framework_crates/bones_ecs/src/system.rs +++ b/framework_crates/bones_ecs/src/system.rs @@ -11,9 +11,6 @@ struct Test { /// Trait implemented by systems. pub trait System { - /// Initialize the system, creating any component or resource storages necessary for the system - /// to run in the world. - fn initialize(&self, world: &mut World); /// Run the system. fn run(&mut self, world: &World, input: In) -> Out; /// Get a best-effort name for the system, used in diagnostics. @@ -22,12 +19,6 @@ pub trait System { /// Struct containing a static system. pub struct StaticSystem { - /// This should be called once to initialize the system, allowing it to intialize any resources - /// or components in the world. - /// - /// Usually only called once, but this is not guaranteed so the implementation should be - /// idempotent. - pub initialize: fn(&mut World), /// This is run every time the system is executed pub run: Box Out + Send + Sync>, /// A best-effort name for the system, for diagnostic purposes. @@ -35,9 +26,6 @@ pub struct StaticSystem { } impl System for StaticSystem { - fn initialize(&self, world: &mut World) { - (self.initialize)(world) - } fn run(&mut self, world: &World, input: In) -> Out { (self.run)(world, input) } @@ -95,10 +83,6 @@ pub trait SystemParam: Sized { /// > If the type is not the same, then system functions will not be able to take it as an /// > argument. type Param<'s>; - /// This will be called to give the parameter a chance to initialize it's world storage. - /// - /// You can use this chance to init any resources or components you need in the world. - fn initialize(world: &mut World); /// This is called to produce the intermediate state of the system parameter. /// /// This state will be created immediately before the system is run, and will kept alive until @@ -113,7 +97,6 @@ pub trait SystemParam: Sized { impl SystemParam for &'_ World { type State = (); type Param<'s> = &'s World; - fn initialize(_world: &mut World) {} fn get_state(_world: &World) -> Self::State {} fn borrow<'s>(world: &'s World, _state: &'s mut Self::State) -> Self::Param<'s> { world @@ -184,10 +167,12 @@ impl<'a, T: HasSchema> SystemParam for Res<'a, T> { type State = AtomicResource; type Param<'p> = Res<'p, T>; - fn initialize(_world: &mut World) {} - fn get_state(world: &World) -> Self::State { - world.resources.get_cell::().unwrap_or_else(|| { + world.resources.get_cell::() + } + + fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { + Res(state.borrow().unwrap_or_else(|| { panic!( "Resource of type `{}` not in world. \ You may need to insert or initialize the resource or use \ @@ -195,26 +180,20 @@ impl<'a, T: HasSchema> SystemParam for Res<'a, T> { resource with the default value.", std::any::type_name::() ) - }) - } - - fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { - Res(state.borrow()) + })) } } impl<'a, T: HasSchema> SystemParam for Option> { - type State = Option>; + type State = AtomicResource; type Param<'p> = Option>; - fn initialize(_world: &mut World) {} - fn get_state(world: &World) -> Self::State { world.resources.get_cell::() } fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { - state.as_ref().map(|state| Res(state.borrow())) + state.borrow().map(|x| Res(x)) } } @@ -222,18 +201,14 @@ impl<'a, T: HasSchema + FromWorld> SystemParam for ResInit<'a, T> { type State = AtomicResource; type Param<'p> = ResInit<'p, T>; - fn initialize(world: &mut World) { - if !world.resources.contains::() { - world.init_resource::(); - } - } - fn get_state(world: &World) -> Self::State { - world.resources.get_cell::().unwrap() + let cell = world.resources.get_cell::(); + cell.init(world); + cell } fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { - ResInit(state.borrow()) + ResInit(state.borrow().unwrap()) } } @@ -241,10 +216,12 @@ impl<'a, T: HasSchema> SystemParam for ResMut<'a, T> { type State = AtomicResource; type Param<'p> = ResMut<'p, T>; - fn initialize(_world: &mut World) {} - fn get_state(world: &World) -> Self::State { - world.resources.get_cell::().unwrap_or_else(|| { + world.resources.get_cell::() + } + + fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { + ResMut(state.borrow_mut().unwrap_or_else(|| { panic!( "Resource of type `{}` not in world. \ You may need to insert or initialize the resource or use \ @@ -252,26 +229,20 @@ impl<'a, T: HasSchema> SystemParam for ResMut<'a, T> { resource with the default value.", std::any::type_name::() ) - }) - } - - fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { - ResMut(state.borrow_mut()) + })) } } impl<'a, T: HasSchema> SystemParam for Option> { - type State = Option>; + type State = AtomicResource; type Param<'p> = Option>; - fn initialize(_world: &mut World) {} - fn get_state(world: &World) -> Self::State { world.resources.get_cell::() } fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { - state.as_mut().map(|state| ResMut(state.borrow_mut())) + state.borrow_mut().map(|state| ResMut(state)) } } @@ -279,18 +250,14 @@ impl<'a, T: HasSchema + FromWorld> SystemParam for ResMutInit<'a, T> { type State = AtomicResource; type Param<'p> = ResMutInit<'p, T>; - fn initialize(world: &mut World) { - if !world.resources.contains::() { - world.init_resource::(); - } - } - fn get_state(world: &World) -> Self::State { - world.resources.get_cell::().unwrap() + let cell = world.resources.get_cell::(); + cell.init(world); + cell } fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> { - ResMutInit(state.borrow_mut()) + ResMutInit(state.borrow_mut().unwrap()) } } @@ -303,8 +270,6 @@ impl<'a, T: HasSchema> SystemParam for Comp<'a, T> { type State = Arc>>; type Param<'p> = Comp<'p, T>; - fn initialize(_world: &mut World) {} - fn get_state(world: &World) -> Self::State { world.components.get_cell::() } @@ -318,8 +283,6 @@ impl<'a, T: HasSchema> SystemParam for CompMut<'a, T> { type State = Arc>>; type Param<'p> = CompMut<'p, T>; - fn initialize(_world: &mut World) {} - fn get_state(world: &World) -> Self::State { world.components.get_cell::() } @@ -355,11 +318,6 @@ macro_rules! impl_system { fn system(mut self) -> Self::Sys { StaticSystem { name: std::any::type_name::(), - initialize: |_world| { - $( - $args::initialize(_world); - )* - }, run: Box::new(move |_world, _input| { $( #[allow(non_snake_case)] @@ -408,11 +366,6 @@ macro_rules! impl_system_with_input { fn system(mut self) -> Self::Sys { StaticSystem { name: std::any::type_name::(), - initialize: |_world| { - $( - $args::initialize(_world); - )* - }, run: Box::new(move |_world, input| { $( #[allow(non_snake_case)] @@ -535,11 +488,11 @@ mod tests { let mut world = World::new(); world.insert_resource(2usize); - let result = world.run_initialized_system(mul_by_res, 3); + let result = world.run_system(mul_by_res, 3); assert_eq!(result, 6); let mut n = 3; - world.run_initialized_system(sys_with_ref_in, &mut n) + world.run_system(sys_with_ref_in, &mut n) } #[test] @@ -550,22 +503,15 @@ mod tests { pub struct B { x: u32, } - let mut world = World::default(); - let mut my_system = (|_a: ResInit, mut b: ResMutInit| { + let world = World::default(); + let my_system = (|_a: ResInit, mut b: ResMutInit| { let b2 = B { x: 45 }; *b = b2; }) .system(); - assert!(world.resources.get_cell::().is_none()); - my_system.initialize(&mut world); - - { - let res = world.resource::(); - assert_eq!(res.x, 0); - } - - my_system.run(&world, ()); + assert!(world.resources.get_cell::().borrow().is_none()); + world.run_system(my_system, ()); let res = world.resource::(); assert_eq!(res.x, 45); diff --git a/framework_crates/bones_ecs/src/world.rs b/framework_crates/bones_ecs/src/world.rs index b368b7a2b3..130f615c8d 100644 --- a/framework_crates/bones_ecs/src/world.rs +++ b/framework_crates/bones_ecs/src/world.rs @@ -24,7 +24,7 @@ impl std::fmt::Debug for World { impl Default for World { fn default() -> Self { - let mut resources = Resources::new(); + let resources = Resources::new(); // Always initialize an Entities resource resources.insert(Entities::default()); @@ -42,16 +42,30 @@ impl World { Self::default() } + /// Create a new world that uses the provided entities resource. + /// + /// This allows multiple worlds to avoid allocating the same entity IDs. + pub fn with_entities(entities: AtomicResource) -> Self { + let resources = Resources::new(); + resources + .untyped() + .insert_cell(entities.into_untyped()) + .unwrap(); + World { + resources, + components: default(), + } + } + /// Remove the component info for dead entities. /// /// This should be called every game frame to cleanup entities that have been killed. /// /// This will remove the component storage for all killed entities, and allow their slots to be /// re-used for any new entities. - pub fn maintain(&mut self) { + pub fn maintain(&self) { let mut entities = self.resources.get_mut::().unwrap(); - - for components in &mut self.components.components.values_mut() { + for components in self.components.components.read_only_view().values() { let mut components = components.borrow_mut(); let killed = entities.killed(); for &entity in killed { @@ -68,7 +82,7 @@ impl World { /// Run a system once. /// /// This is good for initializing the world with setup systems. - pub fn run_system<'system, R, In, Out, S>(&mut self, system: S, input: In) -> Out + pub fn run_system<'system, R, In, Out, S>(&self, system: S, input: In) -> Out where In: 'system, Out: 'system, @@ -76,40 +90,11 @@ impl World { S::Sys: 'system, { let mut s = system.system(); - - s.initialize(self); - s.run(self, input) - } - - /// Run a system once, assuming any necessary initialization has already been performed for that - /// system. - /// - /// This **will not** intialize the system, like [`run_system()`][Self::run_system] will, but it - /// only requires an immutable reference to the world. - /// - /// # Panics - /// - /// Panics may occur if you pass in a system, for example, that takes a component type argument - /// and that component has not been initialized yet. - /// - /// If all the system parameters have already been initialized, by calling - /// [`initialize()`][System::initialize] on the system, then this will work fine. - /// - /// You can also use [`world.init_param()`][Self::init_param] to manually initialize specific - /// parameters if you know which ones will need to be initialized. - pub fn run_initialized_system<'system, Args, In, Out, S>(&self, system: S, input: In) -> Out - where - In: 'system, - Out: 'system, - S: IntoSystem, - S::Sys: 'system, - { - let mut s = system.system(); s.run(self, input) } /// Initialize a resource of type `T` by inserting it's default value. - pub fn init_resource(&mut self) -> RefMut<'_, R> { + pub fn init_resource(&mut self) -> RefMut { if unlikely(!self.resources.contains::()) { let value = R::from_world(self); self.resources.insert(value); @@ -117,18 +102,8 @@ impl World { self.resource_mut() } - /// Initialize a system parameter. - /// - /// It is not necessary to do this manually unless you are going to run a system using - /// [`world.run_initialized_system()`][Self::run_initialized_system()] and you need to make sure - /// one of it's parameters are pre-initialized. - pub fn init_param(&mut self) -> &mut Self { - P::initialize(self); - self - } - /// Insert a resource. - pub fn insert_resource(&mut self, resource: R) -> Option> { + pub fn insert_resource(&mut self, resource: R) -> Option { self.resources.insert(resource) } @@ -151,7 +126,7 @@ impl World { /// # Panics /// Panics if the resource does not exist in the store. #[track_caller] - pub fn resource_mut(&mut self) -> RefMut { + pub fn resource_mut(&self) -> RefMut { match self.resources.get_mut::() { Some(r) => r, None => panic!( @@ -179,11 +154,11 @@ impl World { /// This can be helpful for complex initialization or context-aware defaults. pub trait FromWorld { /// Creates `Self` using data from the given [`World`]. - fn from_world(world: &mut World) -> Self; + fn from_world(world: &World) -> Self; } impl FromWorld for T { - fn from_world(_world: &mut World) -> Self { + fn from_world(_world: &World) -> Self { T::default() } } @@ -272,7 +247,7 @@ mod tests { #[test] fn sanity_check() { - let mut world = World::new(); + let world = World::new(); world.run_system(setup_world, ()); @@ -289,11 +264,11 @@ mod tests { #[test] fn snapshot() { - let mut world1 = World::new(); + let world1 = World::new(); world1.run_system(setup_world, ()); // Snapshot world1 - let mut snap = world1.clone(); + let snap = world1.clone(); // Make sure the snapshot represents world1's state snap.run_system(test_after_setup_state, ()); @@ -332,7 +307,7 @@ mod tests { #[schema(opaque, no_default)] struct TestFromWorld(u32); impl FromWorld for TestFromWorld { - fn from_world(world: &mut World) -> Self { + fn from_world(world: &World) -> Self { let b = world.resource::(); Self(b.0) } diff --git a/framework_crates/bones_framework/src/localization.rs b/framework_crates/bones_framework/src/localization.rs index 40ee93e0e0..fd3746f64e 100644 --- a/framework_crates/bones_framework/src/localization.rs +++ b/framework_crates/bones_framework/src/localization.rs @@ -105,25 +105,19 @@ impl SystemParam for Localization<'_, T> { type State = (AssetServer, AtomicResource); type Param<'s> = Localization<'s, T>; - fn initialize(world: &mut World) { - world.init_resource::(); - } fn get_state(world: &World) -> Self::State { ( (*world.resources.get::().unwrap()).clone(), - world - .resources - .get_cell::() - .unwrap(), + world.resources.get_cell::(), ) } fn borrow<'s>( - _world: &'s World, + world: &'s World, (asset_server, field_idx): &'s mut Self::State, ) -> Self::Param<'s> { const ERR: &str = "Could not find a `Handle` field on root asset, \ needed for `Localization` parameter to work"; - let field_idx = field_idx.borrow(); + let field_idx = field_idx.init_borrow(world); let field_idx = field_idx.0.get_or_init(|| { let mut idx = None; for (i, field) in T::schema() diff --git a/framework_crates/bones_framework/src/params.rs b/framework_crates/bones_framework/src/params.rs index 9ece7fafa7..9dba2b85ab 100644 --- a/framework_crates/bones_framework/src/params.rs +++ b/framework_crates/bones_framework/src/params.rs @@ -15,7 +15,6 @@ impl<'a, T: HasSchema> SystemParam for Root<'a, T> { type State = AssetServer; type Param<'s> = Root<'s, T>; - fn initialize(_world: &mut World) {} fn get_state(world: &World) -> Self::State { (*world.resources.get::().unwrap()).clone() } diff --git a/framework_crates/bones_framework/src/render/sprite.rs b/framework_crates/bones_framework/src/render/sprite.rs index 88905ef43d..de92a278ab 100644 --- a/framework_crates/bones_framework/src/render/sprite.rs +++ b/framework_crates/bones_framework/src/render/sprite.rs @@ -3,10 +3,9 @@ use crate::prelude::*; /// Sprite session plugin. -pub fn sprite_plugin(session: &mut Session) { +pub fn sprite_plugin(_session: &mut Session) { Sprite::register_schema(); AtlasSprite::register_schema(); - session.world.init_param::>(); } /// Image component. diff --git a/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs b/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs index a5569a2aa4..42df4fbd7d 100644 --- a/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs +++ b/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs @@ -202,6 +202,7 @@ impl<'a> Widget for BorderedButton<'a> { map.get_temp::>(egui::Id::null()) .unwrap() .borrow() + .unwrap() .get(border.image) }); ui.painter() diff --git a/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs b/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs index debf2521a0..d34f56135a 100644 --- a/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs +++ b/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs @@ -269,6 +269,7 @@ impl BorderedFramePrepared { map.get_temp::>(egui::Id::null()) .unwrap() .borrow() + .unwrap() .get(self.frame.bg_handle) }); let shape = self.frame.paint(texture, paint_rect); diff --git a/framework_crates/bones_lib/src/lib.rs b/framework_crates/bones_lib/src/lib.rs index d28b94cca6..15e4ea9e99 100644 --- a/framework_crates/bones_lib/src/lib.rs +++ b/framework_crates/bones_lib/src/lib.rs @@ -19,7 +19,7 @@ pub mod prelude { pub use instant; pub mod time; -use std::fmt::Debug; +use std::{fmt::Debug, sync::Arc}; use crate::prelude::*; @@ -39,8 +39,6 @@ pub struct Session { pub priority: i32, /// The session runner to use for this session. pub runner: Box, - /// Whether or not the session systems in it's `stages` have been initialized yet. - pub has_initialized: bool, } impl std::fmt::Debug for Session { @@ -58,8 +56,19 @@ impl std::fmt::Debug for Session { impl Session { /// Create an empty [`Session`]. - pub fn new() -> Self { - Self::default() + pub fn new(entities: AtomicResource) -> Self { + Self { + world: { + let mut w = World::with_entities(entities); + w.init_resource::