From 779eabef07c735fb0595434b30070817d3190546 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 23 Nov 2023 22:10:46 -0800 Subject: [PATCH 01/35] Initial hooks implementation --- Cargo.toml | 11 + crates/bevy_ecs/src/bundle.rs | 401 ++++++++++------- crates/bevy_ecs/src/component.rs | 48 +- crates/bevy_ecs/src/lib.rs | 14 +- crates/bevy_ecs/src/query/builder.rs | 422 ++++++++++++++++++ crates/bevy_ecs/src/query/fetch.rs | 8 +- crates/bevy_ecs/src/query/filter.rs | 4 +- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- crates/bevy_ecs/src/storage/table.rs | 2 +- .../src/system/commands/command_queue.rs | 6 + crates/bevy_ecs/src/system/mod.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 241 ++++++++++ crates/bevy_ecs/src/world/entity_ref.rs | 176 ++++---- crates/bevy_ecs/src/world/mod.rs | 92 ++-- crates/bevy_ecs/src/world/spawn_batch.rs | 15 +- examples/ecs/component_hooks.rs | 42 ++ 16 files changed, 1153 insertions(+), 333 deletions(-) create mode 100644 crates/bevy_ecs/src/query/builder.rs create mode 100644 crates/bevy_ecs/src/world/deferred_world.rs create mode 100644 examples/ecs/component_hooks.rs diff --git a/Cargo.toml b/Cargo.toml index cc9c26056daa9..addf71590a71c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1294,6 +1294,17 @@ description = "Change detection on components" category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "component_hooks" +path = "examples/ecs/component_hooks.rs" +doc-scrape-examples = true + +[package.metadata.example.component_hooks] +name = "Component Hooks" +description = "Define component hooks to react to ECS events" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "custom_query_param" path = "examples/ecs/custom_query_param.rs" diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 361689e5793ad..9979c0c267c30 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -7,11 +7,12 @@ use bevy_utils::{HashMap, HashSet}; use crate::{ archetype::{ - Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, + AddBundle, Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, SpawnBundleStatus, }, component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, + prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, TypeIdMap, @@ -184,7 +185,7 @@ unsafe impl Bundle for C { storages: &mut Storages, ids: &mut impl FnMut(ComponentId), ) { - ids(components.init_component::(storages)); + ids(components.init_component::(storages).id()); } unsafe fn from_components(ctx: &mut T, func: &mut F) -> Self @@ -345,88 +346,6 @@ impl BundleInfo { &self.component_ids } - pub(crate) fn get_bundle_inserter<'a, 'b>( - &'b self, - entities: &'a mut Entities, - archetypes: &'a mut Archetypes, - components: &Components, - storages: &'a mut Storages, - archetype_id: ArchetypeId, - change_tick: Tick, - ) -> BundleInserter<'a, 'b> { - let new_archetype_id = - self.add_bundle_to_archetype(archetypes, storages, components, archetype_id); - let archetypes_ptr = archetypes.archetypes.as_mut_ptr(); - if new_archetype_id == archetype_id { - let archetype = &mut archetypes[archetype_id]; - let table_id = archetype.table_id(); - BundleInserter { - bundle_info: self, - archetype, - entities, - sparse_sets: &mut storages.sparse_sets, - table: &mut storages.tables[table_id], - archetypes_ptr, - change_tick, - result: InsertBundleResult::SameArchetype, - } - } else { - let (archetype, new_archetype) = archetypes.get_2_mut(archetype_id, new_archetype_id); - let table_id = archetype.table_id(); - if table_id == new_archetype.table_id() { - BundleInserter { - bundle_info: self, - archetype, - archetypes_ptr, - entities, - sparse_sets: &mut storages.sparse_sets, - table: &mut storages.tables[table_id], - change_tick, - result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, - } - } else { - let (table, new_table) = storages - .tables - .get_2_mut(table_id, new_archetype.table_id()); - BundleInserter { - bundle_info: self, - archetype, - sparse_sets: &mut storages.sparse_sets, - entities, - archetypes_ptr, - table, - change_tick, - result: InsertBundleResult::NewArchetypeNewTable { - new_archetype, - new_table, - }, - } - } - } - } - - pub(crate) fn get_bundle_spawner<'a, 'b>( - &'b self, - entities: &'a mut Entities, - archetypes: &'a mut Archetypes, - components: &Components, - storages: &'a mut Storages, - change_tick: Tick, - ) -> BundleSpawner<'a, 'b> { - let new_archetype_id = - self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY); - let archetype = &mut archetypes[new_archetype_id]; - let table = &mut storages.tables[archetype.table_id()]; - BundleSpawner { - archetype, - bundle_info: self, - table, - entities, - sparse_sets: &mut storages.sparse_sets, - change_tick, - } - } - /// This writes components from a given [`Bundle`] to the given entity. /// /// # Safety @@ -572,52 +491,146 @@ impl BundleInfo { } } -pub(crate) struct BundleInserter<'a, 'b> { - pub(crate) archetype: &'a mut Archetype, - pub(crate) entities: &'a mut Entities, - bundle_info: &'b BundleInfo, - table: &'a mut Table, - sparse_sets: &'a mut SparseSets, - result: InsertBundleResult<'a>, - archetypes_ptr: *mut Archetype, +pub(crate) struct BundleInserter<'w> { + world: &'w mut World, + archetype: *mut Archetype, + table: *mut Table, + bundle_info: *const BundleInfo, + add_bundle: *const AddBundle, + result: InsertBundleResult, change_tick: Tick, } -pub(crate) enum InsertBundleResult<'a> { +pub(crate) enum InsertBundleResult { SameArchetype, NewArchetypeSameTable { - new_archetype: &'a mut Archetype, + new_archetype: *mut Archetype, }, NewArchetypeNewTable { - new_archetype: &'a mut Archetype, - new_table: &'a mut Table, + new_archetype: *mut Archetype, + new_table: *mut Table, }, } -impl<'a, 'b> BundleInserter<'a, 'b> { +impl<'w> BundleInserter<'w> { + #[inline] + pub fn new( + world: &'w mut World, + archetype_id: ArchetypeId, + change_tick: Tick, + ) -> Self { + let bundle_info: *const BundleInfo = world + .bundles + .init_info::(&mut world.components, &mut world.storages); + unsafe { Self::new_with_info(world, archetype_id, bundle_info, change_tick) } + } + + #[inline] + pub(crate) unsafe fn new_with_info( + world: &'w mut World, + archetype_id: ArchetypeId, + bundle_info: *const BundleInfo, + change_tick: Tick, + ) -> Self { + let bundle_info = &*bundle_info; + let bundle_id = bundle_info.id(); + let new_archetype_id = bundle_info.add_bundle_to_archetype( + &mut world.archetypes, + &mut world.storages, + &world.components, + archetype_id, + ); + if new_archetype_id == archetype_id { + let archetype = &mut world.archetypes[archetype_id]; + let table_id = archetype.table_id(); + let add_bundle: *const AddBundle = unsafe { + archetype + .edges() + .get_add_bundle_internal(bundle_id) + .debug_checked_unwrap() + }; + let table: *mut Table = &mut world.storages.tables[table_id]; + let archetype: *mut Archetype = archetype; + Self { + world, + archetype, + table, + bundle_info, + add_bundle, + result: InsertBundleResult::SameArchetype, + change_tick, + } + } else { + let (archetype, new_archetype) = + world.archetypes.get_2_mut(archetype_id, new_archetype_id); + let table_id = archetype.table_id(); + let new_table_id = new_archetype.table_id(); + let add_bundle: *const AddBundle = unsafe { + archetype + .edges() + .get_add_bundle_internal(bundle_id) + .debug_checked_unwrap() + }; + let table: *mut Table = &mut world.storages.tables[table_id]; + let archetype: *mut Archetype = archetype; + let new_archetype: *mut Archetype = new_archetype; + if table_id == new_table_id { + Self { + world, + archetype, + table, + bundle_info, + add_bundle, + result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, + change_tick, + } + } else { + let new_table: *mut Table = &mut world.storages.tables[new_table_id]; + Self { + world, + archetype, + table, + bundle_info, + add_bundle, + result: InsertBundleResult::NewArchetypeNewTable { + new_archetype, + new_table, + }, + change_tick, + } + } + } + } /// # Safety /// `entity` must currently exist in the source archetype for this inserter. `archetype_row` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type #[inline] - pub unsafe fn insert( + pub(crate) unsafe fn insert( &mut self, entity: Entity, location: EntityLocation, bundle: T, ) -> EntityLocation { - match &mut self.result { + let bundle_info = &*self.bundle_info; + let add_bundle: &AddBundle = &*self.add_bundle; + let world = &mut *self.world; + for (i, component_id) in bundle_info.components().iter().cloned().enumerate() { + let hooks = unsafe { world.components.get_info_unchecked(component_id) }.hooks(); + if let ComponentStatus::Added = add_bundle.bundle_status[i] { + if let Some(hook) = hooks.on_add { + hook(unsafe { world.into_deferred() }, entity) + } + } + if let Some(hook) = hooks.on_insert { + hook(unsafe { world.into_deferred() }, entity) + } + } + + match self.result { InsertBundleResult::SameArchetype => { - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( - self.table, - self.sparse_sets, + bundle_info.write_components( + &mut *self.table, + &mut world.storages.sparse_sets, add_bundle, entity, location.table_row, @@ -627,12 +640,15 @@ impl<'a, 'b> BundleInserter<'a, 'b> { location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { - let result = self.archetype.swap_remove(location.archetype_row); + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let new_archetype = &mut *new_archetype; + let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( + unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; + world.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -643,19 +659,10 @@ impl<'a, 'b> BundleInserter<'a, 'b> { ); } let new_location = new_archetype.allocate(entity, result.table_row); - self.entities.set(entity.index(), new_location); - - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( - self.table, - self.sparse_sets, + world.entities.set(entity.index(), new_location); + bundle_info.write_components( + table, + &mut world.storages.sparse_sets, add_bundle, entity, result.table_row, @@ -668,12 +675,16 @@ impl<'a, 'b> BundleInserter<'a, 'b> { new_archetype, new_table, } => { - let result = self.archetype.swap_remove(location.archetype_row); + let table = &mut *self.table; + let new_table = &mut *new_table; + let archetype = &mut *self.archetype; + let new_archetype = &mut *new_archetype; + let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( + unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; + world.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -685,30 +696,25 @@ impl<'a, 'b> BundleInserter<'a, 'b> { } // PERF: store "non bundle" components in edge, then just move those to avoid // redundant copies - let move_result = self - .table - .move_to_superset_unchecked(result.table_row, new_table); + let move_result = table.move_to_superset_unchecked(result.table_row, new_table); let new_location = new_archetype.allocate(entity, move_result.new_row); - self.entities.set(entity.index(), new_location); + world.entities.set(entity.index(), new_location); // if an entity was moved into this entity's table spot, update its table row if let Some(swapped_entity) = move_result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id - { - &mut *self.archetype + unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; + let swapped_archetype = if archetype.id() == swapped_location.archetype_id { + &mut *archetype } else if new_archetype.id() == swapped_location.archetype_id { new_archetype } else { // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut *self - .archetypes_ptr - .add(swapped_location.archetype_id.index()) + &mut world.archetypes[swapped_location.archetype_id] }; - self.entities.set( + world.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -721,17 +727,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> { .set_entity_table_row(swapped_location.archetype_row, result.table_row); } - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( + bundle_info.write_components( new_table, - self.sparse_sets, + &mut world.storages.sparse_sets, add_bundle, entity, move_result.new_row, @@ -742,21 +740,59 @@ impl<'a, 'b> BundleInserter<'a, 'b> { } } } + + #[inline] + pub(crate) fn entities(&mut self) -> &mut Entities { + &mut self.world.entities + } } -pub(crate) struct BundleSpawner<'a, 'b> { - pub(crate) archetype: &'a mut Archetype, - pub(crate) entities: &'a mut Entities, - bundle_info: &'b BundleInfo, - table: &'a mut Table, - sparse_sets: &'a mut SparseSets, +pub(crate) struct BundleSpawner<'w> { + world: &'w mut World, + bundle_info: *const BundleInfo, + archetype: *mut Archetype, + table: *mut Table, change_tick: Tick, } -impl<'a, 'b> BundleSpawner<'a, 'b> { +impl<'w> BundleSpawner<'w> { + #[inline] + pub fn new(world: &'w mut World, change_tick: Tick) -> Self { + let bundle_info: *const BundleInfo = world + .bundles + .init_info::(&mut world.components, &mut world.storages); + unsafe { Self::new_with_info(world, bundle_info, change_tick) } + } + + pub(crate) unsafe fn new_with_info( + world: &'w mut World, + bundle_info: *const BundleInfo, + change_tick: Tick, + ) -> Self { + let bundle_info = &*bundle_info; + let new_archetype_id = bundle_info.add_bundle_to_archetype( + &mut world.archetypes, + &mut world.storages, + &world.components, + ArchetypeId::EMPTY, + ); + let archetype = &mut world.archetypes[new_archetype_id]; + let table: *mut Table = &mut world.storages.tables[archetype.table_id()]; + let archetype: *mut Archetype = archetype; + BundleSpawner { + world, + bundle_info, + archetype, + table, + change_tick, + } + } + pub fn reserve_storage(&mut self, additional: usize) { - self.archetype.reserve(additional); - self.table.reserve(additional); + unsafe { + (&mut *self.archetype).reserve(additional); + (&mut *self.table).reserve(additional); + } } /// # Safety /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type @@ -766,18 +802,36 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { entity: Entity, bundle: T, ) -> EntityLocation { - let table_row = self.table.allocate(entity); - let location = self.archetype.allocate(entity, table_row); - self.bundle_info.write_components( - self.table, - self.sparse_sets, + let bundle_info = &*self.bundle_info; + for component_id in bundle_info.components().iter().cloned() { + let hooks = self + .world + .components + .get_info_unchecked(component_id) + .hooks(); + if let Some(hook) = hooks.on_add { + hook(self.world.into_deferred(), entity); + } + if let Some(hook) = hooks.on_insert { + hook(self.world.into_deferred(), entity); + } + } + + let archetype = &mut *self.archetype; + let table = &mut *self.table; + + let table_row = table.allocate(entity); + let location = archetype.allocate(entity, table_row); + bundle_info.write_components( + table, + &mut self.world.storages.sparse_sets, &SpawnBundleStatus, entity, table_row, self.change_tick, bundle, ); - self.entities.set(entity.index(), location); + self.world.entities.set(entity.index(), location); location } @@ -786,11 +840,16 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { /// `T` must match this [`BundleInfo`]'s type #[inline] pub unsafe fn spawn(&mut self, bundle: T) -> Entity { - let entity = self.entities.alloc(); + let entity = self.world.entities.alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type self.spawn_non_existent(entity, bundle); entity } + + #[inline] + pub(crate) fn entities(&mut self) -> &mut Entities { + &mut self.world.entities + } } /// Metadata for bundles. Stores a [`BundleInfo`] for each type of [`Bundle`] in a given world. @@ -828,7 +887,7 @@ impl Bundles { storages: &mut Storages, ) -> &'a BundleInfo { let bundle_infos = &mut self.bundle_infos; - let id = self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { + let id = *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { let mut component_ids = Vec::new(); T::component_ids(components, storages, &mut |id| component_ids.push(id)); let id = BundleId(bundle_infos.len()); @@ -842,7 +901,11 @@ impl Bundles { id }); // SAFETY: index either exists, or was initialized - unsafe { self.bundle_infos.get_unchecked(id.0) } + unsafe { self.get_unchecked(id) } + } + + pub(crate) unsafe fn get_unchecked<'a>(&'a self, id: BundleId) -> &'a BundleInfo { + self.bundle_infos.get_unchecked(id.0) } /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`]. diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index ae1582b547e25..3155c9b54ae4a 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -3,9 +3,10 @@ use crate::{ self as bevy_ecs, change_detection::MAX_CHANGE_AGE, + entity::Entity, storage::{SparseSetIndex, Storages}, system::{Local, Resource, SystemParam}, - world::{FromWorld, World}, + world::{DeferredWorld, FromWorld, World}, TypeIdMap, }; pub use bevy_ecs_macros::Component; @@ -263,6 +264,25 @@ impl ComponentInfo { pub(crate) fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self { ComponentInfo { id, descriptor } } + + pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { + self.descriptor.hooks.on_add = Some(hook); + self + } + + pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { + self.descriptor.hooks.on_insert = Some(hook); + self + } + + pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { + self.descriptor.hooks.on_remove = Some(hook); + self + } + + pub fn hooks(&self) -> &ComponentHooks { + &self.descriptor.hooks + } } /// A value which uniquely identifies the type of a [`Component`] within a @@ -318,6 +338,15 @@ impl SparseSetIndex for ComponentId { } } +pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity); + +#[derive(Clone, Default)] +pub struct ComponentHooks { + pub(crate) on_add: Option, + pub(crate) on_insert: Option, + pub(crate) on_remove: Option, +} + /// A value describing a component or resource, which may or may not correspond to a Rust type. #[derive(Clone)] pub struct ComponentDescriptor { @@ -330,6 +359,7 @@ pub struct ComponentDescriptor { is_send_and_sync: bool, type_id: Option, layout: Layout, + hooks: ComponentHooks, // SAFETY: this function must be safe to call with pointers pointing to items of the type // this descriptor describes. // None if the underlying type doesn't need to be dropped @@ -363,6 +393,7 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), + hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -384,6 +415,7 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: None, layout, + hooks: ComponentHooks::default(), drop, } } @@ -400,6 +432,7 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), + hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -411,6 +444,7 @@ impl ComponentDescriptor { is_send_and_sync: false, type_id: Some(TypeId::of::()), layout: Layout::new::(), + hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -453,7 +487,7 @@ impl Components { /// * [`Components::component_id()`] /// * [`Components::init_component_with_descriptor()`] #[inline] - pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { + pub fn init_component(&mut self, storages: &mut Storages) -> &mut ComponentInfo { let type_id = TypeId::of::(); let Components { @@ -464,7 +498,7 @@ impl Components { let index = indices.entry(type_id).or_insert_with(|| { Components::init_component_inner(components, storages, ComponentDescriptor::new::()) }); - ComponentId(*index) + &mut components[*index] } /// Initializes a component described by `descriptor`. @@ -482,9 +516,9 @@ impl Components { &mut self, storages: &mut Storages, descriptor: ComponentDescriptor, - ) -> ComponentId { + ) -> &mut ComponentInfo { let index = Components::init_component_inner(&mut self.components, storages, descriptor); - ComponentId(index) + &mut self.components[index] } #[inline] @@ -563,7 +597,7 @@ impl Components { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::(); + /// let component_a_id = world.init_component::().id(); /// /// assert_eq!(component_a_id, world.components().component_id::().unwrap()) /// ``` @@ -872,7 +906,7 @@ struct InitComponentId { impl FromWorld for InitComponentId { fn from_world(world: &mut World) -> Self { Self { - component_id: world.init_component::(), + component_id: world.init_component::().id(), marker: PhantomData, } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 69ce32e8a8806..9cdcde75c062d 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -157,8 +157,8 @@ mod tests { assert_eq!( ids, &[ - world.init_component::(), - world.init_component::(), + world.init_component::().id(), + world.init_component::().id(), ] ); @@ -211,10 +211,10 @@ mod tests { assert_eq!( ids, &[ - world.init_component::(), - world.init_component::(), - world.init_component::(), - world.init_component::(), + world.init_component::().id(), + world.init_component::().id(), + world.init_component::().id(), + world.init_component::().id(), ] ); @@ -264,7 +264,7 @@ mod tests { }, ); - assert_eq!(ids, &[world.init_component::(),]); + assert_eq!(ids, &[world.init_component::().id(),]); let e4 = world .spawn(BundleWithIgnored { diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs new file mode 100644 index 0000000000000..d1beaa513b55f --- /dev/null +++ b/crates/bevy_ecs/src/query/builder.rs @@ -0,0 +1,422 @@ +use std::marker::PhantomData; + +use crate::{component::ComponentId, prelude::*}; + +use super::{FilteredAccess, ReadOnlyWorldQuery, WorldQuery}; + +/// Builder struct to create [`QueryState`] instances at runtime. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # +/// # #[derive(Component)] +/// # struct A; +/// # +/// # #[derive(Component)] +/// # struct B; +/// # +/// # #[derive(Component)] +/// # struct C; +/// # +/// let mut world = World::new(); +/// let entity_a = world.spawn((A, B)).id(); +/// let entity_b = world.spawn((A, C)).id(); +/// +/// // Instantiate the builder using the type signature of the iterator you will consume +/// let mut query = QueryBuilder::<(Entity, &B)>::new(&mut world) +/// // Add additional terms through builder methods +/// .with::() +/// .without::() +/// .build(); +/// +/// // Consume the QueryState +/// let (entity, b) = query.single(&world); +///``` +pub struct QueryBuilder<'w, Q: WorldQuery = (), F: ReadOnlyWorldQuery = ()> { + access: FilteredAccess, + world: &'w mut World, + or: bool, + first: bool, + _marker: PhantomData<(Q, F)>, +} + +impl<'w, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryBuilder<'w, Q, F> { + /// Creates a new builder with the accesses required for `Q` and `F` + pub fn new(world: &'w mut World) -> Self { + let fetch_state = Q::init_state(world); + let filter_state = F::init_state(world); + + let mut access = FilteredAccess::default(); + Q::update_component_access(&fetch_state, &mut access); + + // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the + // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch + // because they are evaluated *before* a specific reference is constructed. + let mut filter_access = FilteredAccess::default(); + F::update_component_access(&filter_state, &mut filter_access); + + // Merge the temporary filter access with the main access. This ensures that filter access is + // properly considered in a global "cross-query" context (both within systems and across systems). + access.extend(&filter_access); + + Self { + access, + world, + or: false, + first: false, + _marker: PhantomData, + } + } + + /// Returns a reference to the world passed to [`Self::new`]. + pub fn world(&self) -> &World { + self.world + } + + /// Returns a reference to the world passed to [`Self::new`]. + pub fn world_mut(&mut self) -> &mut World { + self.world + } + + /// Adds access to self's underlying [`FilteredAccess`] respecting [`Self::or`] and [`Self::and`] + pub fn extend_access(&mut self, mut access: FilteredAccess) { + if self.or { + if self.first { + access.required.clear(); + self.access.extend(&access); + self.first = false; + } else { + self.access.append_or(&access); + } + } else { + self.access.extend(&access); + } + } + + /// Adds accesses required for `T` to self. + pub fn push(&mut self) -> &mut Self { + let state = T::init_state(self.world); + let mut access = FilteredAccess::default(); + T::update_component_access(&state, &mut access); + self.extend_access(access); + self + } + + /// Adds [`With`] to the [`FilteredAccess`] of self. + pub fn with(&mut self) -> &mut Self { + self.push::>(); + self + } + + /// Adds [`With`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. + pub fn with_id(&mut self, id: ComponentId) -> &mut Self { + let mut access = FilteredAccess::default(); + access.and_with(id); + self.extend_access(access); + self + } + + /// Adds [`Without`] to the [`FilteredAccess`] of self. + pub fn without(&mut self) -> &mut Self { + self.push::>(); + self + } + + /// Adds [`Without`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. + pub fn without_id(&mut self, id: ComponentId) -> &mut Self { + let mut access = FilteredAccess::default(); + access.and_without(id); + self.extend_access(access); + self + } + + /// Adds `&T` to the [`FilteredAccess`] of self. + pub fn ref_id(&mut self, id: ComponentId) -> &mut Self { + self.with_id(id); + self.access.add_read(id); + self + } + + /// Adds `&mut T` to the [`FilteredAccess`] of self. + pub fn mut_id(&mut self, id: ComponentId) -> &mut Self { + self.with_id(id); + self.access.add_write(id); + self + } + + /// Takes a function over mutable access to a [`QueryBuilder`], calls that function + /// on an empty builder and then adds all accesses from that builder to self as optional. + pub fn optional(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { + let mut builder = QueryBuilder::new(self.world); + f(&mut builder); + self.access.extend_access(builder.access()); + self + } + + /// Takes a function over mutable access to a [`QueryBuilder`], calls that function + /// on an empty builder and then adds all accesses from that builder to self. + /// + /// Primarily used when inside a [`Self::or`] closure to group several terms. + pub fn and(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { + let mut builder = QueryBuilder::new(self.world); + f(&mut builder); + let access = builder.access().clone(); + self.extend_access(access); + self + } + + /// Takes a function over mutable access to a [`QueryBuilder`], calls that function + /// on an empty builder, all accesses added to that builder will become terms in an or expression. + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # #[derive(Component)] + /// # struct A; + /// # + /// # #[derive(Component)] + /// # struct B; + /// # + /// # let mut world = World::new(); + /// # + /// QueryBuilder::::new(&mut world).or(|builder| { + /// builder.with::(); + /// builder.with::(); + /// }); + /// // is equivalent to + /// QueryBuilder::::new(&mut world).push::, With)>>(); + /// ``` + pub fn or(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { + let mut builder = QueryBuilder::new(self.world); + builder.or = true; + builder.first = true; + f(&mut builder); + self.access.extend(builder.access()); + self + } + + /// Returns a reference to the the [`FilteredAccess`] that will be provided to the built [`Query`]. + pub fn access(&self) -> &FilteredAccess { + &self.access + } + + /// Transmute the existing builder adding required accesses. + /// This will maintain all exisiting accesses. + /// + /// If including a filter type see [`Self::transmute_filtered`] + pub fn transmute(&mut self) -> &mut QueryBuilder<'w, NewQ> { + self.transmute_filtered::() + } + + /// Transmute the existing builder adding required accesses. + /// This will maintain all existing accesses. + pub fn transmute_filtered( + &mut self, + ) -> &mut QueryBuilder<'w, NewQ, NewF> { + let mut fetch_state = NewQ::init_state(self.world); + let filter_state = NewF::init_state(self.world); + + NewQ::set_access(&mut fetch_state, &self.access); + + let mut access = FilteredAccess::default(); + NewQ::update_component_access(&fetch_state, &mut access); + NewF::update_component_access(&filter_state, &mut access); + + self.extend_access(access); + // SAFETY: + // - We have included all required acceses for NewQ and NewF + // - The layout of all QueryBuilder instances is the same + unsafe { std::mem::transmute(self) } + } + + /// Create a [`QueryState`] with the accesses of the builder. + pub fn build(&mut self) -> QueryState { + QueryState::::from_builder(self) + } +} + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use crate::prelude::*; + use crate::world::FilteredEntityRef; + + use super::QueryBuilder; + + #[derive(Component, PartialEq, Debug)] + struct A(usize); + + #[derive(Component, PartialEq, Debug)] + struct B(usize); + + #[derive(Component, PartialEq, Debug)] + struct C(usize); + + #[test] + fn builder_with_without_static() { + let mut world = World::new(); + let entity_a = world.spawn((A(0), B(0))).id(); + let entity_b = world.spawn((A(0), C(0))).id(); + + let mut query_a = QueryBuilder::::new(&mut world) + .with::() + .without::() + .build(); + assert_eq!(entity_a, query_a.single(&world)); + + let mut query_b = QueryBuilder::::new(&mut world) + .with::() + .without::() + .build(); + assert_eq!(entity_b, query_b.single(&world)); + } + + #[test] + fn builder_with_without_dynamic() { + let mut world = World::new(); + let entity_a = world.spawn((A(0), B(0))).id(); + let entity_b = world.spawn((A(0), C(0))).id(); + let component_id_a = world.init_component::().id(); + let component_id_b = world.init_component::().id(); + let component_id_c = world.init_component::().id(); + + let mut query_a = QueryBuilder::::new(&mut world) + .with_id(component_id_a) + .without_id(component_id_c) + .build(); + assert_eq!(entity_a, query_a.single(&world)); + + let mut query_b = QueryBuilder::::new(&mut world) + .with_id(component_id_a) + .without_id(component_id_b) + .build(); + assert_eq!(entity_b, query_b.single(&world)); + } + + #[test] + fn builder_or() { + let mut world = World::new(); + world.spawn((A(0), B(0))); + world.spawn(B(0)); + world.spawn(C(0)); + + let mut query_a = QueryBuilder::::new(&mut world) + .or(|builder| { + builder.with::(); + builder.with::(); + }) + .build(); + assert_eq!(2, query_a.iter(&world).count()); + + let mut query_b = QueryBuilder::::new(&mut world) + .or(|builder| { + builder.with::(); + builder.without::(); + }) + .build(); + dbg!(&query_b.component_access); + assert_eq!(2, query_b.iter(&world).count()); + + let mut query_c = QueryBuilder::::new(&mut world) + .or(|builder| { + builder.with::(); + builder.with::(); + builder.with::(); + }) + .build(); + assert_eq!(3, query_c.iter(&world).count()); + } + + #[test] + fn builder_transmute() { + let mut world = World::new(); + world.spawn(A(0)); + world.spawn((A(1), B(0))); + let mut query = QueryBuilder::<()>::new(&mut world) + .with::() + .transmute::<&A>() + .build(); + + query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); + } + + #[test] + fn builder_static_components() { + let mut world = World::new(); + let entity = world.spawn((A(0), B(1))).id(); + + let mut query = QueryBuilder::::new(&mut world) + .push::<&A>() + .push::<&B>() + .build(); + + let entity_ref = query.single(&world); + + assert_eq!(entity, entity_ref.id()); + + let a = entity_ref.get::().unwrap(); + let b = entity_ref.get::().unwrap(); + + assert_eq!(0, a.0); + assert_eq!(1, b.0); + } + + #[test] + fn builder_dynamic_components() { + let mut world = World::new(); + let entity = world.spawn((A(0), B(1))).id(); + let component_id_a = world.init_component::().id(); + let component_id_b = world.init_component::().id(); + + let mut query = QueryBuilder::::new(&mut world) + .ref_id(component_id_a) + .ref_id(component_id_b) + .build(); + + let entity_ref = query.single(&world); + + assert_eq!(entity, entity_ref.id()); + + let a = entity_ref.get_by_id(component_id_a).unwrap(); + let b = entity_ref.get_by_id(component_id_b).unwrap(); + + // SAFETY: We set these pointers to point to these components + unsafe { + assert_eq!(0, a.deref::().0); + assert_eq!(1, b.deref::().0); + } + } + + #[test] + fn builder_query_system() { + let mut world = World::new(); + world.spawn(A(0)); + let entity = world.spawn((A(1), B(0))).id(); + + let sys = move |query: Query<(Entity, &A)>| { + let (e, a) = query.single(); + assert_eq!(e, entity); + assert_eq!(1, a.0); + }; + + // Add additional terms that don't appear in the original query + let query = QueryBuilder::<(Entity, &A)>::new(&mut world) + .with::() + .build(); + let mut system = IntoSystem::into_system(sys); + system.initialize(&mut world); + + // SAFETY: We know the system param we are modifying has a compatible type signature + unsafe { system.state_mut().0 = query }; + system.run((), &mut world); + + // Alternatively truncate terms from a query to match the system + let query = QueryBuilder::<(Entity, &A, &B)>::new(&mut world).build(); + let mut system = IntoSystem::into_system(sys); + system.initialize(&mut world); + + // SAFETY: We know the system param we are modifying has a compatible type signature + unsafe { system.state_mut().0 = query.transmute(&world) }; + system.run((), &mut world); + } +} diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 00df1fe63e17c..3656931cb0398 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -639,7 +639,7 @@ unsafe impl WorldQuery for &T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -806,7 +806,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -973,7 +973,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -1229,7 +1229,7 @@ unsafe impl WorldQuery for Has { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index dcc2cedeea20b..8e49391dcee60 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -184,7 +184,7 @@ unsafe impl WorldQuery for With { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( @@ -296,7 +296,7 @@ unsafe impl WorldQuery for Without { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::() + world.init_component::().id() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 5b042590d5cf2..c1cce703f4361 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -118,7 +118,7 @@ impl Schedules { /// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`. pub fn allow_ambiguous_component(&mut self, world: &mut World) { self.ignored_scheduling_ambiguities - .insert(world.init_component::()); + .insert(world.init_component::().id()); } /// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`. diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index e1955bc598f89..ddec964226d61 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -918,7 +918,7 @@ mod tests { fn table() { let mut components = Components::default(); let mut storages = Storages::default(); - let component_id = components.init_component::>(&mut storages); + let component_id = components.init_component::>(&mut storages).id(); let columns = &[component_id]; let mut table = TableBuilder::with_capacity(0, columns.len()) .add_column(components.get_info(component_id).unwrap()) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index f68031c3d39b0..f1319e716f4f7 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -151,12 +151,18 @@ impl CommandQueue { // or 1 byte past the end, so this addition will not overflow the pointer's allocation. cursor = unsafe { cursor.add(size) }; } + + world.flush_commands(); } /// Take all commands from `other` and append them to `self`, leaving `other` empty pub fn append(&mut self, other: &mut CommandQueue) { self.bytes.append(&mut other.bytes); } + + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } } impl Drop for CommandQueue { diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 4bd91ea692a76..547e963ff175b 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1442,7 +1442,7 @@ mod tests { let mut world = World::default(); let mut system = IntoSystem::into_system(a_not_b_system); let mut expected_ids = HashSet::::new(); - let a_id = world.init_component::(); + let a_id = world.init_component::().id(); // set up system and verify its access is empty system.initialize(&mut world); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs new file mode 100644 index 0000000000000..99f6a917e5010 --- /dev/null +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -0,0 +1,241 @@ +use std::ops::Deref; + +use crate::{ + change_detection::MutUntyped, + component::ComponentId, + entity::Entity, + event::{Event, EventId, Events, SendBatchIds}, + prelude::{Component, QueryState}, + query::{ReadOnlyWorldQuery, WorldQuery}, + system::{CommandQueue, Commands, Query, Resource}, +}; + +use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; + +pub struct DeferredWorld<'w> { + world: UnsafeWorldCell<'w>, + command_queue: CommandQueue, +} + +impl<'w> Deref for DeferredWorld<'w> { + type Target = World; + + fn deref(&self) -> &'w Self::Target { + unsafe { self.world.world() } + } +} + +impl<'w> DeferredWorld<'w> { + pub fn commands(&mut self) -> Commands { + Commands::new(&mut self.command_queue, unsafe { self.world.world() }) + } + + /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. + #[inline] + pub fn get_mut(&mut self, entity: Entity) -> Option> { + // SAFETY: + // - `as_unsafe_world_cell` is the only thing that is borrowing world + // - `as_unsafe_world_cell` provides mutable permission to everything + // - `&mut self` ensures no other borrows on world data + unsafe { self.world.get_entity(entity)?.get_mut() } + } + + /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently + /// run queries on the [`World`] by storing and reusing the [`QueryState`]. + #[inline] + pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( + &mut self, + state: &'s mut QueryState, + ) -> Query<'_, 's, Q, F> { + unsafe { + state.update_archetypes(self.world.world()); + Query::new( + self.world, + state, + self.world.last_change_tick(), + self.world.change_tick(), + false, + ) + } + } + + /// Gets a mutable reference to the resource of the given type + /// + /// # Panics + /// + /// Panics if the resource does not exist. + /// Use [`get_resource_mut`](World::get_resource_mut) instead if you want to handle this case. + /// + /// If you want to instead insert a value if the resource does not exist, + /// use [`get_resource_or_insert_with`](World::get_resource_or_insert_with). + #[inline] + #[track_caller] + pub fn resource_mut(&mut self) -> Mut<'_, R> { + match self.get_resource_mut() { + Some(x) => x, + None => panic!( + "Requested resource {} does not exist in the `World`. + Did you forget to add it using `app.insert_resource` / `app.init_resource`? + Resources are also implicitly added via `app.add_event`, + and can be added by plugins.", + std::any::type_name::() + ), + } + } + + /// Gets a mutable reference to the resource of the given type if it exists + #[inline] + pub fn get_resource_mut(&mut self) -> Option> { + // SAFETY: + // - `as_unsafe_world_cell` gives permission to access everything mutably + // - `&mut self` ensures nothing in world is borrowed + unsafe { self.world.get_resource_mut() } + } + + /// Gets a mutable reference to the non-send resource of the given type, if it exists. + /// + /// # Panics + /// + /// Panics if the resource does not exist. + /// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case. + /// + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + #[track_caller] + pub fn non_send_resource_mut(&mut self) -> Mut<'_, R> { + match self.get_non_send_resource_mut() { + Some(x) => x, + None => panic!( + "Requested non-send resource {} does not exist in the `World`. + Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? + Non-send resources can also be be added by plugins.", + std::any::type_name::() + ), + } + } + + /// Gets a mutable reference to the non-send resource of the given type, if it exists. + /// Otherwise returns `None`. + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + pub fn get_non_send_resource_mut(&mut self) -> Option> { + // SAFETY: + // - `as_unsafe_world_cell` gives permission to access the entire world mutably + // - `&mut self` ensures that there are no borrows of world data + unsafe { self.world.get_non_send_resource_mut() } + } + + /// Sends an [`Event`]. + /// This method returns the [ID](`EventId`) of the sent `event`, + /// or [`None`] if the `event` could not be sent. + #[inline] + pub fn send_event(&mut self, event: E) -> Option> { + self.send_event_batch(std::iter::once(event))?.next() + } + + /// Sends the default value of the [`Event`] of type `E`. + /// This method returns the [ID](`EventId`) of the sent `event`, + /// or [`None`] if the `event` could not be sent. + #[inline] + pub fn send_event_default(&mut self) -> Option> { + self.send_event(E::default()) + } + + /// Sends a batch of [`Event`]s from an iterator. + /// This method returns the [IDs](`EventId`) of the sent `events`, + /// or [`None`] if the `event` could not be sent. + #[inline] + pub fn send_event_batch( + &mut self, + events: impl IntoIterator, + ) -> Option> { + let Some(mut events_resource) = self.get_resource_mut::>() else { + bevy_utils::tracing::error!( + "Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ", + std::any::type_name::() + ); + return None; + }; + Some(events_resource.send_batch(events)) + } + + /// Gets a pointer to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// of the [`World`] is still valid. + /// + /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + #[inline] + pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { + // SAFETY: + // - `&mut self` ensures that all accessed data is unaliased + // - `as_unsafe_world_cell` provides mutable permission to the whole world + unsafe { self.world.get_resource_mut_by_id(component_id) } + } + + /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// of the [`World`] is still valid. + /// + /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { + // SAFETY: + // - `&mut self` ensures that all accessed data is unaliased + // - `as_unsafe_world_cell` provides mutable permission to the whole world + unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } + } + + /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. + /// + /// **You should prefer to use the typed API [`World::get_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + #[inline] + pub fn get_mut_by_id( + &mut self, + entity: Entity, + component_id: ComponentId, + ) -> Option> { + // SAFETY: + // - `&mut self` ensures that all accessed data is unaliased + // - `as_unsafe_world_cell` provides mutable permission to the whole world + unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } + } +} + +impl World { + pub unsafe fn into_deferred(&self) -> DeferredWorld<'_> { + DeferredWorld { + world: self.as_unsafe_world_cell_readonly(), + command_queue: CommandQueue::default(), + } + } +} + +impl<'w> Into> for &'w mut World { + fn into(self) -> DeferredWorld<'w> { + DeferredWorld { + world: self.as_unsafe_world_cell(), + command_queue: CommandQueue::default(), + } + } +} + +impl<'w> Drop for DeferredWorld<'w> { + fn drop(&mut self) { + unsafe { + self.world + .world_mut() + .command_queue + .append(&mut self.command_queue) + } + } +} diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c94b45dad10ee..c24a47505acb3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,7 +4,6 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, - removal_detection::RemovedComponentEvents, storage::Storages, world::{Mut, World}, }; @@ -569,23 +568,11 @@ impl<'w> EntityWorldMut<'w> { /// This will overwrite any previous value(s) of the same component type. pub fn insert(&mut self, bundle: T) -> &mut Self { let change_tick = self.world.change_tick(); - let bundle_info = self - .world - .bundles - .init_info::(&mut self.world.components, &mut self.world.storages); - let mut bundle_inserter = bundle_info.get_bundle_inserter( - &mut self.world.entities, - &mut self.world.archetypes, - &self.world.components, - &mut self.world.storages, - self.location.archetype_id, - change_tick, - ); + let mut bundle_inserter = + BundleInserter::new::(self.world, self.location.archetype_id, change_tick); // SAFETY: location matches current entity. `T` matches `bundle_info` - unsafe { - self.location = bundle_inserter.insert(self.entity, self.location, bundle); - } - + self.location = unsafe { bundle_inserter.insert(self.entity, self.location, bundle) }; + self.world.flush_commands(); self } @@ -605,28 +592,28 @@ impl<'w> EntityWorldMut<'w> { component: OwningPtr<'_>, ) -> &mut Self { let change_tick = self.world.change_tick(); - - let bundles = &mut self.world.bundles; - let components = &mut self.world.components; - - let (bundle_info, storage_type) = bundles.init_component_info(components, component_id); - let bundle_inserter = bundle_info.get_bundle_inserter( - &mut self.world.entities, - &mut self.world.archetypes, - &self.world.components, - &mut self.world.storages, - self.location.archetype_id, - change_tick, - ); + let (bundle_info, storage_type) = self + .world + .bundles + .init_component_info(&self.world.components, component_id); + let bundle_info: *const BundleInfo = bundle_info; + let bundle_inserter = unsafe { + BundleInserter::new_with_info( + self.world, + self.location.archetype_id, + bundle_info, + change_tick, + ) + }; self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, Some(component).into_iter(), - Some(storage_type).into_iter(), + Some(storage_type).iter().cloned(), ); - + self.world.flush_commands(); self } @@ -648,28 +635,29 @@ impl<'w> EntityWorldMut<'w> { iter_components: I, ) -> &mut Self { let change_tick = self.world.change_tick(); - - let bundles = &mut self.world.bundles; - let components = &mut self.world.components; - - let (bundle_info, storage_types) = bundles.init_dynamic_info(components, component_ids); - let bundle_inserter = bundle_info.get_bundle_inserter( - &mut self.world.entities, - &mut self.world.archetypes, - &self.world.components, - &mut self.world.storages, - self.location.archetype_id, - change_tick, - ); + let (bundle_info, storage_types) = self + .world + .bundles + .init_dynamic_info(&self.world.components, component_ids); + let bundle_info: *const BundleInfo = bundle_info; + let storage_types: *const Vec = storage_types; + let bundle_inserter = unsafe { + BundleInserter::new_with_info( + self.world, + self.location.archetype_id, + bundle_info, + change_tick, + ) + }; self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, iter_components, - storage_types.iter().cloned(), + (&*storage_types).iter().cloned(), ); - + self.world.flush_commands(); self } @@ -680,19 +668,17 @@ impl<'w> EntityWorldMut<'w> { // TODO: BundleRemover? #[must_use] pub fn take(&mut self) -> Option { - let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; - let entities = &mut self.world.entities; - let removed_components = &mut self.world.removed_components; - - let bundle_info = self.world.bundles.init_info::(components, storages); + let bundle_id = self.world.bundles.init_info::(components, storages).id(); + // Reborrow to drop &mut World + let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, // components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T` let new_archetype_id = unsafe { remove_bundle_from_archetype( - archetypes, + &mut self.world.archetypes, storages, components, old_location.archetype_id, @@ -705,8 +691,24 @@ impl<'w> EntityWorldMut<'w> { return None; } - let mut bundle_components = bundle_info.components().iter().cloned(); + // Reborrow so world is not mutably borrowed let entity = self.entity; + for component_id in bundle_info.components().iter().cloned() { + self.world.removed_components.send(component_id, entity); + unsafe { + let info = self.world.components.get_info_unchecked(component_id); + if let Some(hook) = info.hooks().on_remove { + hook(self.world.into_deferred(), self.entity) + } + } + } + let archetypes = &mut self.world.archetypes; + let storages = &mut self.world.storages; + let components = &mut self.world.components; + let entities = &mut self.world.entities; + + let entity = self.entity; + let mut bundle_components = bundle_info.components().iter().cloned(); // SAFETY: bundle components are iterated in order, which guarantees that the component type // matches let result = unsafe { @@ -716,14 +718,7 @@ impl<'w> EntityWorldMut<'w> { // - entity location is valid // - table row is removed below, without dropping the contents // - `components` comes from the same world as `storages` - take_component( - storages, - components, - removed_components, - component_id, - entity, - old_location, - ) + take_component(storages, components, component_id, entity, old_location) }) }; @@ -740,7 +735,7 @@ impl<'w> EntityWorldMut<'w> { new_archetype_id, ); } - + self.world.flush_commands(); Some(result) } @@ -831,10 +826,10 @@ impl<'w> EntityWorldMut<'w> { let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; - let entities = &mut self.world.entities; - let removed_components = &mut self.world.removed_components; - let bundle_info = self.world.bundles.init_info::(components, storages); + let bundle_id = self.world.bundles.init_info::(components, storages).id(); + // Reborrow to drop &mut World + let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, @@ -855,16 +850,24 @@ impl<'w> EntityWorldMut<'w> { return self; } - let old_archetype = &mut archetypes[old_location.archetype_id]; + let old_archetype = &self.world.archetypes[old_location.archetype_id]; + // Reborrow so world is not mutably borrowed let entity = self.entity; for component_id in bundle_info.components().iter().cloned() { if old_archetype.contains(component_id) { - removed_components.send(component_id, entity); + self.world.removed_components.send(component_id, entity); + unsafe { + let info = self.world.components.get_info_unchecked(component_id); + if let Some(hook) = info.hooks().on_remove { + hook(self.world.into_deferred(), self.entity) + } + } // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. if let Some(StorageType::SparseSet) = old_archetype.get_storage_type(component_id) { - storages + self.world + .storages .sparse_sets .get_mut(component_id) .unwrap() @@ -880,13 +883,13 @@ impl<'w> EntityWorldMut<'w> { &mut self.location, old_location.archetype_id, old_location, - entities, - archetypes, - storages, + &mut self.world.entities, + &mut self.world.archetypes, + &mut self.world.storages, new_archetype_id, ); } - + self.world.flush_commands(); self } @@ -895,6 +898,17 @@ impl<'w> EntityWorldMut<'w> { debug!("Despawning entity {:?}", self.entity); let world = self.world; world.flush(); + let archetype = &world.archetypes[self.location.archetype_id]; + for component_id in archetype.components() { + world.removed_components.send(component_id, self.entity); + unsafe { + let info = world.components().get_info_unchecked(component_id); + if let Some(hook) = info.hooks().on_remove { + hook(world.into_deferred(), self.entity) + } + } + } + let location = world .entities .free(self.entity) @@ -903,10 +917,7 @@ impl<'w> EntityWorldMut<'w> { let moved_entity; { - let archetype = &mut world.archetypes[location.archetype_id]; - for component_id in archetype.components() { - world.removed_components.send(component_id, self.entity); - } + let archetype = &mut world.archetypes[self.location.archetype_id]; let remove_result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = remove_result.swapped_entity { let swapped_location = world.entities.get(swapped_entity).unwrap(); @@ -954,6 +965,7 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } + world.flush_commands(); } /// Gets read-only access to the world that the current entity belongs to. @@ -1431,7 +1443,7 @@ unsafe fn insert_dynamic_bundle< I: Iterator>, S: Iterator, >( - mut bundle_inserter: BundleInserter<'_, '_>, + mut bundle_inserter: BundleInserter<'_>, entity: Entity, location: EntityLocation, components: I, @@ -1587,14 +1599,12 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { pub(crate) unsafe fn take_component<'a>( storages: &'a mut Storages, components: &Components, - removed_components: &mut RemovedComponentEvents, component_id: ComponentId, entity: Entity, location: EntityLocation, ) -> OwningPtr<'a> { // SAFETY: caller promises component_id to be valid let component_info = components.get_info_unchecked(component_id); - removed_components.send(component_id, entity); match component_info.storage_type() { StorageType::Table => { let table = &mut storages.tables[location.table_id]; @@ -1897,7 +1907,7 @@ mod tests { #[test] fn entity_mut_insert_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::(); + let test_component_id = world.init_component::().id(); let mut entity = world.spawn_empty(); OwningPtr::make(TestComponent(42), |ptr| { @@ -1925,8 +1935,8 @@ mod tests { #[test] fn entity_mut_insert_bundle_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::(); - let test_component_2_id = world.init_component::(); + let test_component_id = world.init_component::().id(); + let test_component_2_id = world.init_component::().id(); let component_ids = [test_component_id, test_component_2_id]; let test_component_value = TestComponent(42); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 397f38bc795f3..9af73d8c6870d 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1,5 +1,6 @@ //! Defines the [`World`] and APIs for accessing it directly. +mod deferred_world; mod entity_ref; pub mod error; mod spawn_batch; @@ -7,13 +8,14 @@ pub mod unsafe_world_cell; mod world_cell; pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}; +pub use deferred_world::DeferredWorld; pub use entity_ref::{EntityMut, EntityRef, EntityWorldMut, Entry, OccupiedEntry, VacantEntry}; pub use spawn_batch::*; pub use world_cell::*; use crate::{ archetype::{ArchetypeComponentId, ArchetypeId, ArchetypeRow, Archetypes}, - bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, + bundle::{Bundle, BundleInfo, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, TicksMut}, component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, Tick}, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, @@ -22,7 +24,7 @@ use crate::{ removal_detection::RemovedComponentEvents, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, - system::Resource, + system::{CommandQueue, Resource}, world::error::TryRunScheduleError, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -71,6 +73,8 @@ pub struct World { pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, + pub(crate) command_queue: CommandQueue, + flushing_commands: bool, } impl Default for World { @@ -89,6 +93,8 @@ impl Default for World { change_tick: AtomicU32::new(1), last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), + command_queue: CommandQueue::default(), + flushing_commands: false, } } } @@ -178,7 +184,7 @@ impl World { } /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. - pub fn init_component(&mut self) -> ComponentId { + pub fn init_component(&mut self) -> &mut ComponentInfo { self.components.init_component::(&mut self.storages) } @@ -194,7 +200,7 @@ impl World { pub fn init_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, - ) -> ComponentId { + ) -> &mut ComponentInfo { self.components .init_component_with_descriptor(&mut self.storages, descriptor) } @@ -215,7 +221,7 @@ impl World { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::(); + /// let component_a_id = world.init_component::().id(); /// /// assert_eq!(component_a_id, world.component_id::().unwrap()) /// ``` @@ -740,19 +746,9 @@ impl World { let change_tick = self.change_tick(); let entity = self.entities.alloc(); let entity_location = { - let bundle_info = self - .bundles - .init_info::(&mut self.components, &mut self.storages); - let mut spawner = bundle_info.get_bundle_spawner( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - change_tick, - ); - + let mut bundle_spawner = BundleSpawner::new::(self, change_tick); // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent - unsafe { spawner.spawn_non_existent(entity, bundle) } + unsafe { bundle_spawner.spawn_non_existent(entity, bundle) } }; // SAFETY: entity and location are valid, as they were just created above @@ -1438,26 +1434,23 @@ impl World { let bundle_info = self .bundles .init_info::(&mut self.components, &mut self.storages); - enum SpawnOrInsert<'a, 'b> { - Spawn(BundleSpawner<'a, 'b>), - Insert(BundleInserter<'a, 'b>, ArchetypeId), + enum SpawnOrInsert<'w> { + Spawn(BundleSpawner<'w>), + Insert(BundleInserter<'w>, ArchetypeId), } - impl<'a, 'b> SpawnOrInsert<'a, 'b> { + impl<'w> SpawnOrInsert<'w> { fn entities(&mut self) -> &mut Entities { match self { - SpawnOrInsert::Spawn(spawner) => spawner.entities, - SpawnOrInsert::Insert(inserter, _) => inserter.entities, + SpawnOrInsert::Spawn(spawner) => spawner.entities(), + SpawnOrInsert::Insert(inserter, _) => inserter.entities(), } } } - let mut spawn_or_insert = SpawnOrInsert::Spawn(bundle_info.get_bundle_spawner( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - change_tick, - )); + let bundle_info: *const BundleInfo = bundle_info; + let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { + BundleSpawner::new_with_info(self, bundle_info, change_tick) + }); let mut invalid_entities = Vec::new(); for (entity, bundle) in iter { @@ -1474,14 +1467,14 @@ impl World { unsafe { inserter.insert(entity, location, bundle) }; } _ => { - let mut inserter = bundle_info.get_bundle_inserter( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - location.archetype_id, - change_tick, - ); + let mut inserter = unsafe { + BundleInserter::new_with_info( + self, + location.archetype_id, + bundle_info, + change_tick, + ) + }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = @@ -1494,13 +1487,8 @@ impl World { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; } else { - let mut spawner = bundle_info.get_bundle_spawner( - &mut self.entities, - &mut self.archetypes, - &self.components, - &mut self.storages, - change_tick, - ); + let mut spawner = + unsafe { BundleSpawner::new_with_info(self, bundle_info, change_tick) }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); @@ -1735,6 +1723,18 @@ impl World { } } + pub fn flush_commands(&mut self) { + if !self.flushing_commands { + self.flushing_commands = true; + while !self.command_queue.is_empty() { + let mut commands = CommandQueue::default(); + std::mem::swap(&mut commands, &mut self.command_queue); + commands.apply(self) + } + self.flushing_commands = false; + } + } + /// Increments the world's current change tick and returns the old value. #[inline] pub fn increment_change_tick(&self) -> Tick { @@ -2347,7 +2347,7 @@ mod tests { ) }; - let component_id = world.init_component_with_descriptor(descriptor); + let component_id = world.init_component_with_descriptor(descriptor).id(); let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; OwningPtr::make(value, |ptr| { diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index e21199f56444c..ef90947e54d12 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -15,7 +15,7 @@ where I::Item: Bundle, { inner: I, - spawner: BundleSpawner<'w, 'w>, + spawner: BundleSpawner<'w>, } impl<'w, I> SpawnBatchIter<'w, I> @@ -33,18 +33,9 @@ where let (lower, upper) = iter.size_hint(); let length = upper.unwrap_or(lower); - - let bundle_info = world - .bundles - .init_info::(&mut world.components, &mut world.storages); world.entities.reserve(length as u32); - let mut spawner = bundle_info.get_bundle_spawner( - &mut world.entities, - &mut world.archetypes, - &world.components, - &mut world.storages, - change_tick, - ); + + let mut spawner = BundleSpawner::new::(world, change_tick); spawner.reserve_storage(length); Self { diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs new file mode 100644 index 0000000000000..510474e455db1 --- /dev/null +++ b/examples/ecs/component_hooks.rs @@ -0,0 +1,42 @@ +use std::collections::HashSet; + +use bevy::prelude::*; + +#[derive(Component, Debug)] +struct MyComponent(usize); + +#[derive(Resource, Default, Debug, Deref, DerefMut)] +struct MyComponentIndex(HashSet); + +fn main() { + App::new() + .add_systems(Startup, (setup, trigger_hooks).chain()) + .init_resource::() + .run(); +} + +fn setup(world: &mut World) { + world + .init_component::() + .on_add(|mut world, entity| { + println!("Added MyComponent to: {:?}", entity); + world.resource_mut::().insert(entity); + }) + .on_remove(|mut world, entity| { + println!( + "Removed MyComponent from: {:?} {:?}", + entity, + world.get::(entity) + ); + let mut index = world.resource_mut::(); + index.remove(&entity); + println!("Current index: {:?}", *index) + }); +} + +fn trigger_hooks(mut commands: Commands) { + let entity_a = commands.spawn(MyComponent(0)).id(); + let entity_b = commands.spawn(MyComponent(1)).id(); + commands.entity(entity_b).despawn(); + commands.entity(entity_a).despawn(); +} From 6ae319e8c2b63f7af5741a18a7c9a823ee9ad4fe Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 24 Nov 2023 18:20:32 -0800 Subject: [PATCH 02/35] Perf improvements, archetype flags --- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/archetype.rs | 97 +++++++++++++++++-- crates/bevy_ecs/src/bundle.rs | 86 ++++++++++------ crates/bevy_ecs/src/component.rs | 31 ++++-- crates/bevy_ecs/src/lib.rs | 14 +-- crates/bevy_ecs/src/query/fetch.rs | 8 +- crates/bevy_ecs/src/query/filter.rs | 4 +- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- crates/bevy_ecs/src/storage/table.rs | 2 +- crates/bevy_ecs/src/system/mod.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 85 +++++++--------- crates/bevy_ecs/src/world/entity_ref.rs | 71 ++++++++------ crates/bevy_ecs/src/world/mod.rs | 23 ++++- .../bevy_ecs/src/world/unsafe_world_cell.rs | 8 +- examples/ecs/component_hooks.rs | 6 +- 15 files changed, 296 insertions(+), 144 deletions(-) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 3231e7b4101ff..0746eb72d2f87 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -22,6 +22,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.12.0" } bevy_ecs_macros = { path = "macros", version = "0.12.0" } async-channel = "1.4" +bitflags = "2.3" event-listener = "2.5" thread_local = "1.1.4" fixedbitset = "0.4.2" diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 3e23da6457739..8de848ba5590e 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -21,9 +21,10 @@ use crate::{ bundle::BundleId, - component::{ComponentId, StorageType}, + component::{ComponentId, Components, StorageType}, entity::{Entity, EntityLocation}, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, + world::unsafe_world_cell::UnsafeWorldCell, }; use std::{ hash::Hash, @@ -107,7 +108,7 @@ impl ArchetypeId { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq)] pub(crate) enum ComponentStatus { Added, Mutated, @@ -298,6 +299,15 @@ struct ArchetypeComponentInfo { archetype_component_id: ArchetypeComponentId, } +bitflags::bitflags! { + #[derive(Clone, Copy)] + pub struct ArchetypeFlags: u32 { + const ON_ADD_HOOK = (1 << 0); + const ON_INSERT_HOOK = (1 << 1); + const ON_REMOVE_HOOK = (1 << 2); + } +} + /// Metadata for a single archetype within a [`World`]. /// /// For more information, see the *[module level documentation]*. @@ -310,10 +320,12 @@ pub struct Archetype { edges: Edges, entities: Vec, components: ImmutableSparseSet, + flags: ArchetypeFlags, } impl Archetype { pub(crate) fn new( + components: &Components, id: ArchetypeId, table_id: TableId, table_components: impl Iterator, @@ -321,9 +333,13 @@ impl Archetype { ) -> Self { let (min_table, _) = table_components.size_hint(); let (min_sparse, _) = sparse_set_components.size_hint(); - let mut components = SparseSet::with_capacity(min_table + min_sparse); + let mut flags = ArchetypeFlags::empty(); + let mut archetype_components = SparseSet::with_capacity(min_table + min_sparse); for (component_id, archetype_component_id) in table_components { - components.insert( + // SAFETY: We are creating an archetype that includes this component so it must exist + let info = unsafe { components.get_info_unchecked(component_id) }; + info.update_archetype_flags(&mut flags); + archetype_components.insert( component_id, ArchetypeComponentInfo { storage_type: StorageType::Table, @@ -333,7 +349,10 @@ impl Archetype { } for (component_id, archetype_component_id) in sparse_set_components { - components.insert( + // SAFETY: We are creating an archetype that includes this component so it must exist + let info = unsafe { components.get_info_unchecked(component_id) }; + info.update_archetype_flags(&mut flags); + archetype_components.insert( component_id, ArchetypeComponentInfo { storage_type: StorageType::SparseSet, @@ -345,8 +364,9 @@ impl Archetype { id, table_id, entities: Vec::new(), - components: components.into_immutable(), + components: archetype_components.into_immutable(), edges: Default::default(), + flags, } } @@ -356,6 +376,11 @@ impl Archetype { self.id } + #[inline] + pub fn flags(&self) -> ArchetypeFlags { + self.flags + } + /// Fetches the archetype's [`Table`] ID. /// /// [`Table`]: crate::storage::Table @@ -536,6 +561,57 @@ impl Archetype { pub(crate) fn clear_entities(&mut self) { self.entities.clear(); } + + #[inline] + pub(crate) unsafe fn trigger_on_add( + &self, + world: UnsafeWorldCell, + entity: Entity, + targets: impl Iterator, + ) { + if self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) { + for component_id in targets { + let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_add { + hook(world.into_deferred(), entity, component_id) + } + } + } + } + + #[inline] + pub(crate) unsafe fn trigger_on_insert( + &self, + world: UnsafeWorldCell, + entity: Entity, + targets: impl Iterator, + ) { + if self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) { + for component_id in targets { + let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_insert { + hook(world.into_deferred(), entity, component_id) + } + } + } + } + + #[inline] + pub(crate) unsafe fn trigger_on_remove( + &self, + world: UnsafeWorldCell, + entity: Entity, + targets: impl Iterator, + ) { + if self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) { + for component_id in targets { + let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_remove { + hook(world.into_deferred(), entity, component_id); + } + } + } + } } /// The next [`ArchetypeId`] in an [`Archetypes`] collection. @@ -625,7 +701,12 @@ impl Archetypes { by_components: Default::default(), archetype_component_count: 0, }; - archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new()); + archetypes.get_id_or_insert( + &Components::default(), + TableId::empty(), + Vec::new(), + Vec::new(), + ); archetypes } @@ -704,6 +785,7 @@ impl Archetypes { /// [`TableId`] must exist in tables pub(crate) fn get_id_or_insert( &mut self, + components: &Components, table_id: TableId, table_components: Vec, sparse_set_components: Vec, @@ -729,6 +811,7 @@ impl Archetypes { let sparse_set_archetype_components = (sparse_start..*archetype_component_count).map(ArchetypeComponentId); archetypes.push(Archetype::new( + components, id, table_id, table_components.into_iter().zip(table_archetype_components), diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 9979c0c267c30..1ea87f76ed6f9 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -185,7 +185,7 @@ unsafe impl Bundle for C { storages: &mut Storages, ids: &mut impl FnMut(ComponentId), ) { - ids(components.init_component::(storages).id()); + ids(components.init_component::(storages)); } unsafe fn from_components(ctx: &mut T, func: &mut F) -> Self @@ -346,6 +346,11 @@ impl BundleInfo { &self.component_ids } + #[inline] + pub fn iter_components(&self) -> impl Iterator + '_ { + self.component_ids.iter().cloned() + } + /// This writes components from a given [`Bundle`] to the given entity. /// /// # Safety @@ -478,8 +483,12 @@ impl BundleInfo { new_sparse_set_components }; }; - let new_archetype_id = - archetypes.get_id_or_insert(table_id, table_components, sparse_set_components); + let new_archetype_id = archetypes.get_id_or_insert( + components, + table_id, + table_components, + sparse_set_components, + ); // add an edge from the old archetype to the new archetype archetypes[archetype_id].edges_mut().insert_add_bundle( self.id, @@ -612,22 +621,17 @@ impl<'w> BundleInserter<'w> { bundle: T, ) -> EntityLocation { let bundle_info = &*self.bundle_info; - let add_bundle: &AddBundle = &*self.add_bundle; + let add_bundle = &*self.add_bundle; let world = &mut *self.world; - for (i, component_id) in bundle_info.components().iter().cloned().enumerate() { - let hooks = unsafe { world.components.get_info_unchecked(component_id) }.hooks(); - if let ComponentStatus::Added = add_bundle.bundle_status[i] { - if let Some(hook) = hooks.on_add { - hook(unsafe { world.into_deferred() }, entity) - } - } - if let Some(hook) = hooks.on_insert { - hook(unsafe { world.into_deferred() }, entity) - } - } match self.result { InsertBundleResult::SameArchetype => { + let archetype = &mut *self.archetype; + archetype.trigger_on_insert( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.components().iter().cloned(), + ); bundle_info.write_components( &mut *self.table, &mut world.storages.sparse_sets, @@ -643,6 +647,20 @@ impl<'w> BundleInserter<'w> { let table = &mut *self.table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; + new_archetype.trigger_on_add( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info + .components() + .iter() + .cloned() + .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), + ); + new_archetype.trigger_on_insert( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.components().iter().cloned(), + ); let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -679,6 +697,20 @@ impl<'w> BundleInserter<'w> { let new_table = &mut *new_table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; + new_archetype.trigger_on_add( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info + .components() + .iter() + .cloned() + .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), + ); + new_archetype.trigger_on_insert( + world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.components().iter().cloned(), + ); let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -803,19 +835,17 @@ impl<'w> BundleSpawner<'w> { bundle: T, ) -> EntityLocation { let bundle_info = &*self.bundle_info; - for component_id in bundle_info.components().iter().cloned() { - let hooks = self - .world - .components - .get_info_unchecked(component_id) - .hooks(); - if let Some(hook) = hooks.on_add { - hook(self.world.into_deferred(), entity); - } - if let Some(hook) = hooks.on_insert { - hook(self.world.into_deferred(), entity); - } - } + let archetype = &*self.archetype; + archetype.trigger_on_add( + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.iter_components(), + ); + archetype.trigger_on_insert( + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.iter_components(), + ); let archetype = &mut *self.archetype; let table = &mut *self.table; diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 3155c9b54ae4a..21c7ea3d26fa1 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -2,6 +2,7 @@ use crate::{ self as bevy_ecs, + archetype::ArchetypeFlags, change_detection::MAX_CHANGE_AGE, entity::Entity, storage::{SparseSetIndex, Storages}, @@ -280,6 +281,19 @@ impl ComponentInfo { self } + #[inline] + pub fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) { + if self.hooks().on_add.is_some() { + flags.insert(ArchetypeFlags::ON_ADD_HOOK); + } + if self.hooks().on_insert.is_some() { + flags.insert(ArchetypeFlags::ON_INSERT_HOOK); + } + if self.hooks().on_remove.is_some() { + flags.insert(ArchetypeFlags::ON_REMOVE_HOOK); + } + } + pub fn hooks(&self) -> &ComponentHooks { &self.descriptor.hooks } @@ -338,7 +352,7 @@ impl SparseSetIndex for ComponentId { } } -pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity); +pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId); #[derive(Clone, Default)] pub struct ComponentHooks { @@ -487,7 +501,7 @@ impl Components { /// * [`Components::component_id()`] /// * [`Components::init_component_with_descriptor()`] #[inline] - pub fn init_component(&mut self, storages: &mut Storages) -> &mut ComponentInfo { + pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { let type_id = TypeId::of::(); let Components { @@ -498,7 +512,7 @@ impl Components { let index = indices.entry(type_id).or_insert_with(|| { Components::init_component_inner(components, storages, ComponentDescriptor::new::()) }); - &mut components[*index] + ComponentId(*index) } /// Initializes a component described by `descriptor`. @@ -516,9 +530,9 @@ impl Components { &mut self, storages: &mut Storages, descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { + ) -> ComponentId { let index = Components::init_component_inner(&mut self.components, storages, descriptor); - &mut self.components[index] + ComponentId(index) } #[inline] @@ -574,6 +588,11 @@ impl Components { self.components.get_unchecked(id.0) } + #[inline] + pub(crate) unsafe fn get_info_mut(&mut self, id: ComponentId) -> &mut ComponentInfo { + self.components.get_unchecked_mut(id.0) + } + /// Type-erased equivalent of [`Components::component_id()`]. #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { @@ -906,7 +925,7 @@ struct InitComponentId { impl FromWorld for InitComponentId { fn from_world(world: &mut World) -> Self { Self { - component_id: world.init_component::().id(), + component_id: world.init_component::(), marker: PhantomData, } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 9cdcde75c062d..69ce32e8a8806 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -157,8 +157,8 @@ mod tests { assert_eq!( ids, &[ - world.init_component::().id(), - world.init_component::().id(), + world.init_component::(), + world.init_component::(), ] ); @@ -211,10 +211,10 @@ mod tests { assert_eq!( ids, &[ - world.init_component::().id(), - world.init_component::().id(), - world.init_component::().id(), - world.init_component::().id(), + world.init_component::(), + world.init_component::(), + world.init_component::(), + world.init_component::(), ] ); @@ -264,7 +264,7 @@ mod tests { }, ); - assert_eq!(ids, &[world.init_component::().id(),]); + assert_eq!(ids, &[world.init_component::(),]); let e4 = world .spawn(BundleWithIgnored { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 3656931cb0398..00df1fe63e17c 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -639,7 +639,7 @@ unsafe impl WorldQuery for &T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -806,7 +806,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -973,7 +973,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -1229,7 +1229,7 @@ unsafe impl WorldQuery for Has { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 8e49391dcee60..dcc2cedeea20b 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -184,7 +184,7 @@ unsafe impl WorldQuery for With { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( @@ -296,7 +296,7 @@ unsafe impl WorldQuery for Without { } fn init_state(world: &mut World) -> ComponentId { - world.init_component::().id() + world.init_component::() } fn matches_component_set( diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index c1cce703f4361..5b042590d5cf2 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -118,7 +118,7 @@ impl Schedules { /// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`. pub fn allow_ambiguous_component(&mut self, world: &mut World) { self.ignored_scheduling_ambiguities - .insert(world.init_component::().id()); + .insert(world.init_component::()); } /// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`. diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index ddec964226d61..e1955bc598f89 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -918,7 +918,7 @@ mod tests { fn table() { let mut components = Components::default(); let mut storages = Storages::default(); - let component_id = components.init_component::>(&mut storages).id(); + let component_id = components.init_component::>(&mut storages); let columns = &[component_id]; let mut table = TableBuilder::with_capacity(0, columns.len()) .add_column(components.get_info(component_id).unwrap()) diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 547e963ff175b..4bd91ea692a76 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1442,7 +1442,7 @@ mod tests { let mut world = World::default(); let mut system = IntoSystem::into_system(a_not_b_system); let mut expected_ids = HashSet::::new(); - let a_id = world.init_component::().id(); + let a_id = world.init_component::(); // set up system and verify its access is empty system.initialize(&mut world); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 99f6a917e5010..bf492993ddfd5 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -7,27 +7,27 @@ use crate::{ event::{Event, EventId, Events, SendBatchIds}, prelude::{Component, QueryState}, query::{ReadOnlyWorldQuery, WorldQuery}, - system::{CommandQueue, Commands, Query, Resource}, + system::{Commands, Query, Resource}, }; use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; pub struct DeferredWorld<'w> { - world: UnsafeWorldCell<'w>, - command_queue: CommandQueue, + world: &'w mut World, } impl<'w> Deref for DeferredWorld<'w> { - type Target = World; + type Target = &'w mut World; - fn deref(&self) -> &'w Self::Target { - unsafe { self.world.world() } + fn deref(&self) -> &Self::Target { + &self.world } } impl<'w> DeferredWorld<'w> { pub fn commands(&mut self) -> Commands { - Commands::new(&mut self.command_queue, unsafe { self.world.world() }) + let world = self.world.as_unsafe_world_cell(); + unsafe { Commands::new(world.get_command_queue(), world.world()) } } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -38,23 +38,29 @@ impl<'w> DeferredWorld<'w> { // - `as_unsafe_world_cell` is the only thing that is borrowing world // - `as_unsafe_world_cell` provides mutable permission to everything // - `&mut self` ensures no other borrows on world data - unsafe { self.world.get_entity(entity)?.get_mut() } + unsafe { + self.world + .as_unsafe_world_cell() + .get_entity(entity)? + .get_mut() + } } /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. #[inline] pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( - &mut self, + &'w mut self, state: &'s mut QueryState, - ) -> Query<'_, 's, Q, F> { + ) -> Query<'w, 's, Q, F> { unsafe { - state.update_archetypes(self.world.world()); + state.update_archetypes(self.world); + let world = self.world.as_unsafe_world_cell(); Query::new( - self.world, + world, state, - self.world.last_change_tick(), - self.world.change_tick(), + world.last_change_tick(), + world.change_tick(), false, ) } @@ -87,10 +93,7 @@ impl<'w> DeferredWorld<'w> { /// Gets a mutable reference to the resource of the given type if it exists #[inline] pub fn get_resource_mut(&mut self) -> Option> { - // SAFETY: - // - `as_unsafe_world_cell` gives permission to access everything mutably - // - `&mut self` ensures nothing in world is borrowed - unsafe { self.world.get_resource_mut() } + self.world.get_resource_mut() } /// Gets a mutable reference to the non-send resource of the given type, if it exists. @@ -122,10 +125,7 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_resource_mut(&mut self) -> Option> { - // SAFETY: - // - `as_unsafe_world_cell` gives permission to access the entire world mutably - // - `&mut self` ensures that there are no borrows of world data - unsafe { self.world.get_non_send_resource_mut() } + self.world.get_non_send_resource_mut() } /// Sends an [`Event`]. @@ -170,10 +170,7 @@ impl<'w> DeferredWorld<'w> { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: - // - `&mut self` ensures that all accessed data is unaliased - // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { self.world.get_resource_mut_by_id(component_id) } + self.world.get_resource_mut_by_id(component_id) } /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. @@ -187,10 +184,7 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: - // - `&mut self` ensures that all accessed data is unaliased - // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } + self.world.get_non_send_mut_by_id(component_id) } /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. @@ -207,35 +201,26 @@ impl<'w> DeferredWorld<'w> { // SAFETY: // - `&mut self` ensures that all accessed data is unaliased // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } + unsafe { + self.world + .as_unsafe_world_cell() + .get_entity(entity)? + .get_mut_by_id(component_id) + } } } -impl World { - pub unsafe fn into_deferred(&self) -> DeferredWorld<'_> { +impl<'w> UnsafeWorldCell<'w> { + pub unsafe fn into_deferred(&self) -> DeferredWorld<'w> { DeferredWorld { - world: self.as_unsafe_world_cell_readonly(), - command_queue: CommandQueue::default(), + // SAFETY: Not + world: self.world_mut(), } } } impl<'w> Into> for &'w mut World { fn into(self) -> DeferredWorld<'w> { - DeferredWorld { - world: self.as_unsafe_world_cell(), - command_queue: CommandQueue::default(), - } - } -} - -impl<'w> Drop for DeferredWorld<'w> { - fn drop(&mut self) { - unsafe { - self.world - .world_mut() - .command_queue - .append(&mut self.command_queue) - } + DeferredWorld { world: self } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c24a47505acb3..2741919e8ffbd 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -693,14 +693,17 @@ impl<'w> EntityWorldMut<'w> { // Reborrow so world is not mutably borrowed let entity = self.entity; - for component_id in bundle_info.components().iter().cloned() { + let new_archetype = &self.world.archetypes[new_archetype_id]; + unsafe { + new_archetype.trigger_on_remove( + // SAFETY: the only outstanding borrow of the world is to an Archetype which cannot be modified through a deferred world + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info.iter_components(), + ); + } + for component_id in bundle_info.iter_components() { self.world.removed_components.send(component_id, entity); - unsafe { - let info = self.world.components.get_info_unchecked(component_id); - if let Some(hook) = info.hooks().on_remove { - hook(self.world.into_deferred(), self.entity) - } - } } let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; @@ -708,7 +711,7 @@ impl<'w> EntityWorldMut<'w> { let entities = &mut self.world.entities; let entity = self.entity; - let mut bundle_components = bundle_info.components().iter().cloned(); + let mut bundle_components = bundle_info.iter_components(); // SAFETY: bundle components are iterated in order, which guarantees that the component type // matches let result = unsafe { @@ -735,6 +738,7 @@ impl<'w> EntityWorldMut<'w> { new_archetype_id, ); } + drop(bundle_components); self.world.flush_commands(); Some(result) } @@ -850,18 +854,21 @@ impl<'w> EntityWorldMut<'w> { return self; } - let old_archetype = &self.world.archetypes[old_location.archetype_id]; // Reborrow so world is not mutably borrowed - let entity = self.entity; - for component_id in bundle_info.components().iter().cloned() { + let entity: Entity = self.entity; + let old_archetype = &self.world.archetypes[old_location.archetype_id]; + unsafe { + old_archetype.trigger_on_remove( + self.world.as_unsafe_world_cell_readonly(), + entity, + bundle_info + .iter_components() + .filter(|id| old_archetype.contains(*id)), + ); + } + for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { self.world.removed_components.send(component_id, entity); - unsafe { - let info = self.world.components.get_info_unchecked(component_id); - if let Some(hook) = info.hooks().on_remove { - hook(self.world.into_deferred(), self.entity) - } - } // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. @@ -896,19 +903,22 @@ impl<'w> EntityWorldMut<'w> { /// Despawns the current entity. pub fn despawn(self) { debug!("Despawning entity {:?}", self.entity); - let world = self.world; - world.flush(); - let archetype = &world.archetypes[self.location.archetype_id]; + self.world.flush(); + let archetype = &self.world.archetypes[self.location.archetype_id]; + unsafe { + archetype.trigger_on_remove( + self.world.as_unsafe_world_cell_readonly(), + self.entity, + archetype.components(), + ); + } for component_id in archetype.components() { - world.removed_components.send(component_id, self.entity); - unsafe { - let info = world.components().get_info_unchecked(component_id); - if let Some(hook) = info.hooks().on_remove { - hook(world.into_deferred(), self.entity) - } - } + self.world + .removed_components + .send(component_id, self.entity); } + let world = self.world; let location = world .entities .free(self.entity) @@ -1549,6 +1559,7 @@ unsafe fn remove_bundle_from_archetype( } let new_archetype_id = archetypes.get_id_or_insert( + components, next_table_id, next_table_components, next_sparse_set_components, @@ -1907,7 +1918,7 @@ mod tests { #[test] fn entity_mut_insert_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::().id(); + let test_component_id = world.init_component::(); let mut entity = world.spawn_empty(); OwningPtr::make(TestComponent(42), |ptr| { @@ -1935,8 +1946,8 @@ mod tests { #[test] fn entity_mut_insert_bundle_by_id() { let mut world = World::new(); - let test_component_id = world.init_component::().id(); - let test_component_2_id = world.init_component::().id(); + let test_component_id = world.init_component::(); + let test_component_2_id = world.init_component::(); let component_ids = [test_component_id, test_component_2_id]; let test_component_value = TestComponent(42); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 9af73d8c6870d..03a8926f8c08c 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -183,11 +183,28 @@ impl World { WorldCell::new(self) } + pub fn register_component(&mut self) -> &mut ComponentInfo { + let type_id = TypeId::of::(); + assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); + let index = self.init_component::(); + // SAFETY: We just created this component + unsafe { self.components.get_info_mut(index) } + } + /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. - pub fn init_component(&mut self) -> &mut ComponentInfo { + pub fn init_component(&mut self) -> ComponentId { self.components.init_component::(&mut self.storages) } + pub fn register_component_with_descriptor( + &mut self, + descriptor: ComponentDescriptor, + ) -> &mut ComponentInfo { + let index = self.init_component_with_descriptor(descriptor); + // SAFETY: We just created this component + unsafe { self.components.get_info_mut(index) } + } + /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. /// /// This method differs from [`World::init_component`] in that it uses a [`ComponentDescriptor`] @@ -200,7 +217,7 @@ impl World { pub fn init_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { + ) -> ComponentId { self.components .init_component_with_descriptor(&mut self.storages, descriptor) } @@ -2347,7 +2364,7 @@ mod tests { ) }; - let component_id = world.init_component_with_descriptor(descriptor).id(); + let component_id = world.init_component_with_descriptor(descriptor); let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; OwningPtr::make(value, |ptr| { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 25d5ac62c0305..667d6bfaa95e3 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -14,7 +14,7 @@ use crate::{ prelude::Component, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, - system::Resource, + system::{CommandQueue, Resource}, }; use bevy_ptr::Ptr; use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData}; @@ -566,6 +566,12 @@ impl<'w> UnsafeWorldCell<'w> { .get(component_id)? .get_with_ticks() } + + #[inline] + pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue { + let world = unsafe { &mut *self.0 }; + &mut world.command_queue + } } impl Debug for UnsafeWorldCell<'_> { diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 510474e455db1..9309de94e9fc9 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -17,12 +17,12 @@ fn main() { fn setup(world: &mut World) { world - .init_component::() - .on_add(|mut world, entity| { + .register_component::() + .on_add(|mut world, entity, _| { println!("Added MyComponent to: {:?}", entity); world.resource_mut::().insert(entity); }) - .on_remove(|mut world, entity| { + .on_remove(|mut world, entity, _| { println!( "Removed MyComponent from: {:?} {:?}", entity, From 5c4702863fa51dc506f426911f9990895d933d4c Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 24 Nov 2023 19:23:02 -0800 Subject: [PATCH 03/35] Refactoring --- crates/bevy_ecs/src/archetype.rs | 49 ++--------- crates/bevy_ecs/src/bundle.rs | 92 +++++++++++---------- crates/bevy_ecs/src/world/deferred_world.rs | 51 +++++++++++- crates/bevy_ecs/src/world/entity_ref.rs | 33 +++----- 4 files changed, 113 insertions(+), 112 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 8de848ba5590e..0608002bbd431 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -24,7 +24,6 @@ use crate::{ component::{ComponentId, Components, StorageType}, entity::{Entity, EntityLocation}, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, - world::unsafe_world_cell::UnsafeWorldCell, }; use std::{ hash::Hash, @@ -563,54 +562,18 @@ impl Archetype { } #[inline] - pub(crate) unsafe fn trigger_on_add( - &self, - world: UnsafeWorldCell, - entity: Entity, - targets: impl Iterator, - ) { - if self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) { - for component_id in targets { - let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_add { - hook(world.into_deferred(), entity, component_id) - } - } - } + pub fn has_on_add(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) } #[inline] - pub(crate) unsafe fn trigger_on_insert( - &self, - world: UnsafeWorldCell, - entity: Entity, - targets: impl Iterator, - ) { - if self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) { - for component_id in targets { - let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_insert { - hook(world.into_deferred(), entity, component_id) - } - } - } + pub fn has_on_insert(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) } #[inline] - pub(crate) unsafe fn trigger_on_remove( - &self, - world: UnsafeWorldCell, - entity: Entity, - targets: impl Iterator, - ) { - if self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) { - for component_id in targets { - let hooks = unsafe { world.components().get_info_unchecked(component_id) }.hooks(); - if let Some(hook) = hooks.on_remove { - hook(world.into_deferred(), entity, component_id); - } - } - } + pub fn has_on_remove(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 1ea87f76ed6f9..7ab66d4690859 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -627,11 +627,19 @@ impl<'w> BundleInserter<'w> { match self.result { InsertBundleResult::SameArchetype => { let archetype = &mut *self.archetype; - archetype.trigger_on_insert( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.components().iter().cloned(), - ); + if archetype.has_on_add() { + world.into_deferred().trigger_on_add( + entity, + bundle_info.iter_components().filter(|id| { + add_bundle.get_status(id.index()) == ComponentStatus::Added + }), + ); + } + if archetype.has_on_insert() { + world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } bundle_info.write_components( &mut *self.table, &mut world.storages.sparse_sets, @@ -647,20 +655,19 @@ impl<'w> BundleInserter<'w> { let table = &mut *self.table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; - new_archetype.trigger_on_add( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info - .components() - .iter() - .cloned() - .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), - ); - new_archetype.trigger_on_insert( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.components().iter().cloned(), - ); + if new_archetype.has_on_add() { + world.into_deferred().trigger_on_add( + entity, + bundle_info.iter_components().filter(|id| { + add_bundle.get_status(id.index()) == ComponentStatus::Added + }), + ); + } + if new_archetype.has_on_insert() { + world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -697,20 +704,19 @@ impl<'w> BundleInserter<'w> { let new_table = &mut *new_table; let archetype = &mut *self.archetype; let new_archetype = &mut *new_archetype; - new_archetype.trigger_on_add( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info - .components() - .iter() - .cloned() - .filter(|id| add_bundle.get_status(id.index()) == ComponentStatus::Added), - ); - new_archetype.trigger_on_insert( - world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.components().iter().cloned(), - ); + if new_archetype.has_on_add() { + world.into_deferred().trigger_on_add( + entity, + bundle_info.iter_components().filter(|id| { + add_bundle.get_status(id.index()) == ComponentStatus::Added + }), + ); + } + if new_archetype.has_on_insert() { + world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -836,16 +842,16 @@ impl<'w> BundleSpawner<'w> { ) -> EntityLocation { let bundle_info = &*self.bundle_info; let archetype = &*self.archetype; - archetype.trigger_on_add( - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.iter_components(), - ); - archetype.trigger_on_insert( - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.iter_components(), - ); + if archetype.has_on_add() { + self.world + .into_deferred() + .trigger_on_add(entity, bundle_info.iter_components()); + } + if archetype.has_on_insert() { + self.world + .into_deferred() + .trigger_on_insert(entity, bundle_info.iter_components()) + } let archetype = &mut *self.archetype; let table = &mut *self.table; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index bf492993ddfd5..932b9a66a3a82 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -10,7 +10,7 @@ use crate::{ system::{Commands, Query, Resource}, }; -use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; +use super::{Mut, World}; pub struct DeferredWorld<'w> { world: &'w mut World, @@ -208,13 +208,56 @@ impl<'w> DeferredWorld<'w> { .get_mut_by_id(component_id) } } + + #[inline] + pub(crate) fn trigger_on_add( + &mut self, + entity: Entity, + targets: impl Iterator, + ) { + for component_id in targets { + let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_add { + hook(DeferredWorld { world: self.world }, entity, component_id) + } + } + } + + #[inline] + pub(crate) fn trigger_on_insert( + &mut self, + entity: Entity, + targets: impl Iterator, + ) { + for component_id in targets { + let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_insert { + hook(DeferredWorld { world: self.world }, entity, component_id) + } + } + } + + #[inline] + pub(crate) fn trigger_on_remove( + &mut self, + entity: Entity, + targets: impl Iterator, + ) { + for component_id in targets { + let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_remove { + hook(DeferredWorld { world: self.world }, entity, component_id) + } + } + } } -impl<'w> UnsafeWorldCell<'w> { - pub unsafe fn into_deferred(&self) -> DeferredWorld<'w> { +impl World { + #[inline] + pub unsafe fn into_deferred(&self) -> DeferredWorld { DeferredWorld { // SAFETY: Not - world: self.world_mut(), + world: self.as_unsafe_world_cell_readonly().world_mut(), } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 2741919e8ffbd..50ddf83797a93 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -693,14 +693,10 @@ impl<'w> EntityWorldMut<'w> { // Reborrow so world is not mutably borrowed let entity = self.entity; - let new_archetype = &self.world.archetypes[new_archetype_id]; - unsafe { - new_archetype.trigger_on_remove( - // SAFETY: the only outstanding borrow of the world is to an Archetype which cannot be modified through a deferred world - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info.iter_components(), - ); + let old_archetype = &self.world.archetypes[old_location.archetype_id]; + if old_archetype.has_on_remove() { + unsafe { self.world.into_deferred() } + .trigger_on_remove(entity, bundle_info.iter_components()); } for component_id in bundle_info.iter_components() { self.world.removed_components.send(component_id, entity); @@ -857,14 +853,9 @@ impl<'w> EntityWorldMut<'w> { // Reborrow so world is not mutably borrowed let entity: Entity = self.entity; let old_archetype = &self.world.archetypes[old_location.archetype_id]; - unsafe { - old_archetype.trigger_on_remove( - self.world.as_unsafe_world_cell_readonly(), - entity, - bundle_info - .iter_components() - .filter(|id| old_archetype.contains(*id)), - ); + if old_archetype.has_on_remove() { + unsafe { self.world.into_deferred() } + .trigger_on_remove(entity, bundle_info.iter_components()) } for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { @@ -905,13 +896,11 @@ impl<'w> EntityWorldMut<'w> { debug!("Despawning entity {:?}", self.entity); self.world.flush(); let archetype = &self.world.archetypes[self.location.archetype_id]; - unsafe { - archetype.trigger_on_remove( - self.world.as_unsafe_world_cell_readonly(), - self.entity, - archetype.components(), - ); + if archetype.has_on_remove() { + unsafe { self.world.into_deferred() } + .trigger_on_remove(self.entity, archetype.components()); } + for component_id in archetype.components() { self.world .removed_components From cdd1ee06da31d6acd8e48d83c62108c0f7f70397 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 25 Nov 2023 00:43:31 -0800 Subject: [PATCH 04/35] Improve command application logic --- .../src/system/commands/command_queue.rs | 3 ++ crates/bevy_ecs/src/world/deferred_world.rs | 6 +++ crates/bevy_ecs/src/world/mod.rs | 53 +++++++++++-------- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index f1319e716f4f7..7129b8c63f06e 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -104,6 +104,7 @@ impl CommandQueue { pub fn apply(&mut self, world: &mut World) { // flush the previously queued entities world.flush(); + world.set_flushing(true); self.apply_or_drop_queued(Some(world)); } @@ -152,6 +153,7 @@ impl CommandQueue { cursor = unsafe { cursor.add(size) }; } + world.set_flushing(false); world.flush_commands(); } @@ -160,6 +162,7 @@ impl CommandQueue { self.bytes.append(&mut other.bytes); } + /// Returns false if there are any commands in the queue pub fn is_empty(&self) -> bool { self.bytes.is_empty() } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 932b9a66a3a82..ea6293ac25e20 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -12,6 +12,8 @@ use crate::{ use super::{Mut, World}; +/// An [`World`] reference that prevents structural ECS changes. +/// This includes creating tables, registering components or spawning entities. pub struct DeferredWorld<'w> { world: &'w mut World, } @@ -25,6 +27,7 @@ impl<'w> Deref for DeferredWorld<'w> { } impl<'w> DeferredWorld<'w> { + /// Creates a [`Commands`] instance that pushes to the world's command queue pub fn commands(&mut self) -> Commands { let world = self.world.as_unsafe_world_cell(); unsafe { Commands::new(world.get_command_queue(), world.world()) } @@ -253,6 +256,9 @@ impl<'w> DeferredWorld<'w> { } impl World { + /// Turn a [`World`] reference into a [`DeferredWorld`] + /// + /// Caller must ensure there are no outstanding references to the world's command queue, resource or component data #[inline] pub unsafe fn into_deferred(&self) -> DeferredWorld { DeferredWorld { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 03a8926f8c08c..b47d9467aa49e 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -183,24 +183,19 @@ impl World { WorldCell::new(self) } - pub fn register_component(&mut self) -> &mut ComponentInfo { - let type_id = TypeId::of::(); - assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); - let index = self.init_component::(); - // SAFETY: We just created this component - unsafe { self.components.get_info_mut(index) } - } - /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. pub fn init_component(&mut self) -> ComponentId { self.components.init_component::(&mut self.storages) } - pub fn register_component_with_descriptor( - &mut self, - descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { - let index = self.init_component_with_descriptor(descriptor); + /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. + /// Primarily used for registering hooks. + /// + /// Will panic if a component for `T`` already exists. + pub fn register_component(&mut self) -> &mut ComponentInfo { + let type_id = TypeId::of::(); + assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); + let index = self.init_component::(); // SAFETY: We just created this component unsafe { self.components.get_info_mut(index) } } @@ -222,6 +217,19 @@ impl World { .init_component_with_descriptor(&mut self.storages, descriptor) } + /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. + /// Primarily used for registering hooks. + /// + /// Will panic if a component for `T`` already exists. + pub fn register_component_with_descriptor( + &mut self, + descriptor: ComponentDescriptor, + ) -> &mut ComponentInfo { + let index = self.init_component_with_descriptor(descriptor); + // SAFETY: We just created this component + unsafe { self.components.get_info_mut(index) } + } + /// Returns the [`ComponentId`] of the given [`Component`] type `T`. /// /// The returned `ComponentId` is specific to the `World` instance @@ -1740,18 +1748,21 @@ impl World { } } + /// Attempts to apply the internal [`CommandQueue`] to self + /// Will do nothing if already flushing commands. + #[inline] pub fn flush_commands(&mut self) { - if !self.flushing_commands { - self.flushing_commands = true; - while !self.command_queue.is_empty() { - let mut commands = CommandQueue::default(); - std::mem::swap(&mut commands, &mut self.command_queue); - commands.apply(self) - } - self.flushing_commands = false; + if !self.flushing_commands && !self.command_queue.is_empty() { + let mut commands = std::mem::take(&mut self.command_queue); + commands.apply(self); } } + #[inline] + pub(crate) fn set_flushing(&mut self, state: bool) { + self.flushing_commands = state; + } + /// Increments the world's current change tick and returns the old value. #[inline] pub fn increment_change_tick(&self) -> Tick { From 21cd6504f6e77cc7e0493e6c5937166b36418ae6 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 25 Nov 2023 15:44:22 -0800 Subject: [PATCH 05/35] Simplify command application to get recursive ordering --- crates/bevy_ecs/src/lib.rs | 4 ++-- .../src/system/commands/command_queue.rs | 9 ++++----- crates/bevy_ecs/src/world/entity_ref.rs | 20 +++++++++---------- crates/bevy_ecs/src/world/mod.rs | 19 ++++++------------ crates/bevy_ecs/src/world/spawn_batch.rs | 2 +- 5 files changed, 23 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 69ce32e8a8806..cbc1c3e05ca90 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1063,7 +1063,7 @@ mod tests { fn reserve_and_spawn() { let mut world = World::default(); let e = world.entities().reserve_entity(); - world.flush(); + world.flush_entities(); let mut e_mut = world.entity_mut(e); e_mut.insert(A(0)); assert_eq!(e_mut.get::().unwrap(), &A(0)); @@ -1546,7 +1546,7 @@ mod tests { let e1 = world_a.spawn(A(1)).id(); let e2 = world_a.spawn(A(2)).id(); let e3 = world_a.entities().reserve_entity(); - world_a.flush(); + world_a.flush_entities(); let world_a_max_entities = world_a.entities().len(); world_b.entities.reserve_entities(world_a_max_entities); diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index 7129b8c63f06e..f5735fc77c156 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -103,8 +103,7 @@ impl CommandQueue { #[inline] pub fn apply(&mut self, world: &mut World) { // flush the previously queued entities - world.flush(); - world.set_flushing(true); + world.flush_entities(); self.apply_or_drop_queued(Some(world)); } @@ -151,10 +150,9 @@ impl CommandQueue { // SAFETY: The address just past the command is either within the buffer, // or 1 byte past the end, so this addition will not overflow the pointer's allocation. cursor = unsafe { cursor.add(size) }; - } - world.set_flushing(false); - world.flush_commands(); + world.flush_commands(); + } } /// Take all commands from `other` and append them to `self`, leaving `other` empty @@ -163,6 +161,7 @@ impl CommandQueue { } /// Returns false if there are any commands in the queue + #[inline] pub fn is_empty(&self) -> bool { self.bytes.is_empty() } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 50ddf83797a93..e3349fb23282e 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -698,9 +698,9 @@ impl<'w> EntityWorldMut<'w> { unsafe { self.world.into_deferred() } .trigger_on_remove(entity, bundle_info.iter_components()); } - for component_id in bundle_info.iter_components() { - self.world.removed_components.send(component_id, entity); - } + // for component_id in bundle_info.iter_components() { + // self.world.removed_components.send(component_id, entity); + // } let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; @@ -859,7 +859,7 @@ impl<'w> EntityWorldMut<'w> { } for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { - self.world.removed_components.send(component_id, entity); + // self.world.removed_components.send(component_id, entity); // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. @@ -894,18 +894,18 @@ impl<'w> EntityWorldMut<'w> { /// Despawns the current entity. pub fn despawn(self) { debug!("Despawning entity {:?}", self.entity); - self.world.flush(); + self.world.flush_entities(); let archetype = &self.world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { unsafe { self.world.into_deferred() } .trigger_on_remove(self.entity, archetype.components()); } - for component_id in archetype.components() { - self.world - .removed_components - .send(component_id, self.entity); - } + // for component_id in archetype.components() { + // self.world + // .removed_components + // .send(component_id, self.entity); + // } let world = self.world; let location = world diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index b47d9467aa49e..8099bb505cb67 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -74,7 +74,6 @@ pub struct World { pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, pub(crate) command_queue: CommandQueue, - flushing_commands: bool, } impl Default for World { @@ -94,7 +93,6 @@ impl Default for World { last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), command_queue: CommandQueue::default(), - flushing_commands: false, } } } @@ -447,7 +445,7 @@ impl World { /// scheme worked out to share an ID space (which doesn't happen by default). #[inline] pub fn get_or_spawn(&mut self, entity: Entity) -> Option { - self.flush(); + self.flush_entities(); match self.entities.alloc_at_without_replacement(entity) { AllocAtWithoutReplacement::Exists(location) => { // SAFETY: `entity` exists and `location` is that entity's location @@ -701,7 +699,7 @@ impl World { /// assert_eq!(position.x, 0.0); /// ``` pub fn spawn_empty(&mut self) -> EntityWorldMut { - self.flush(); + self.flush_entities(); let entity = self.entities.alloc(); // SAFETY: entity was just allocated unsafe { self.spawn_at_empty_internal(entity) } @@ -767,7 +765,7 @@ impl World { /// assert_eq!(position.x, 2.0); /// ``` pub fn spawn(&mut self, bundle: B) -> EntityWorldMut { - self.flush(); + self.flush_entities(); let change_tick = self.change_tick(); let entity = self.entities.alloc(); let entity_location = { @@ -1452,7 +1450,7 @@ impl World { I::IntoIter: Iterator, B: Bundle, { - self.flush(); + self.flush_entities(); let change_tick = self.change_tick(); @@ -1734,7 +1732,7 @@ impl World { /// Empties queued entities and adds them to the empty [`Archetype`](crate::archetype::Archetype). /// This should be called before doing operations that might operate on queued entities, /// such as inserting a [`Component`]. - pub(crate) fn flush(&mut self) { + pub(crate) fn flush_entities(&mut self) { let empty_archetype = self.archetypes.empty_mut(); let table = &mut self.storages.tables[empty_archetype.table_id()]; // PERF: consider pre-allocating space for flushed entities @@ -1752,17 +1750,12 @@ impl World { /// Will do nothing if already flushing commands. #[inline] pub fn flush_commands(&mut self) { - if !self.flushing_commands && !self.command_queue.is_empty() { + if !self.command_queue.is_empty() { let mut commands = std::mem::take(&mut self.command_queue); commands.apply(self); } } - #[inline] - pub(crate) fn set_flushing(&mut self, state: bool) { - self.flushing_commands = state; - } - /// Increments the world's current change tick and returns the old value. #[inline] pub fn increment_change_tick(&self) -> Tick { diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index ef90947e54d12..b0586e4c7c8f8 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -27,7 +27,7 @@ where pub(crate) fn new(world: &'w mut World, iter: I) -> Self { // Ensure all entity allocations are accounted for so `self.entities` can realloc if // necessary - world.flush(); + world.flush_entities(); let change_tick = world.change_tick(); From 4e086553293551a412800576749e0501ffa5e5c5 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 25 Nov 2023 20:27:48 -0800 Subject: [PATCH 06/35] First docs and safety comment pass --- crates/bevy_ecs/src/archetype.rs | 11 +- crates/bevy_ecs/src/bundle.rs | 359 ++++++++++-------- crates/bevy_ecs/src/component.rs | 47 ++- crates/bevy_ecs/src/world/deferred_world.rs | 112 +++--- crates/bevy_ecs/src/world/entity_ref.rs | 99 +++-- crates/bevy_ecs/src/world/mod.rs | 40 +- crates/bevy_ecs/src/world/spawn_batch.rs | 4 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 4 + examples/ecs/component_hooks.rs | 2 +- 9 files changed, 398 insertions(+), 280 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 0608002bbd431..80b5ac47c8a65 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -299,8 +299,11 @@ struct ArchetypeComponentInfo { } bitflags::bitflags! { + /// Flags used to keep track of metadata about the component in this [`Archetype`] + /// + /// Used primarily to early-out when there are no [`ComponentHook`] registered for any contained components. #[derive(Clone, Copy)] - pub struct ArchetypeFlags: u32 { + pub(crate) struct ArchetypeFlags: u32 { const ON_ADD_HOOK = (1 << 0); const ON_INSERT_HOOK = (1 << 1); const ON_REMOVE_HOOK = (1 << 2); @@ -375,8 +378,9 @@ impl Archetype { self.id } + /// Fetches the flags for the archetype. #[inline] - pub fn flags(&self) -> ArchetypeFlags { + pub(crate) fn flags(&self) -> ArchetypeFlags { self.flags } @@ -561,16 +565,19 @@ impl Archetype { self.entities.clear(); } + /// Returns true if any of the components in this archetype have `on_add` hooks #[inline] pub fn has_on_add(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) } + /// Returns true if any of the components in this archetype have `on_insert` hooks #[inline] pub fn has_on_insert(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) } + /// Returns true if any of the components in this archetype have `on_remove` hooks #[inline] pub fn has_on_remove(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 7ab66d4690859..fe57cc96af184 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,6 +15,7 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, TypeIdMap, }; use bevy_ptr::OwningPtr; @@ -346,6 +347,7 @@ impl BundleInfo { &self.component_ids } + /// Returns an iterator over the the [ID](ComponentId) of each component stored in this bundle. #[inline] pub fn iter_components(&self) -> impl Iterator + '_ { self.component_ids.iter().cloned() @@ -501,23 +503,26 @@ impl BundleInfo { } pub(crate) struct BundleInserter<'w> { - world: &'w mut World, - archetype: *mut Archetype, - table: *mut Table, - bundle_info: *const BundleInfo, - add_bundle: *const AddBundle, - result: InsertBundleResult, + world: DeferredWorld<'w>, + archetype: &'w mut Archetype, + entities: &'w mut Entities, + bundle_info: &'w BundleInfo, + table: &'w mut Table, + sparse_sets: &'w mut SparseSets, + result: InsertBundleResult<'w>, + add_bundle_ptr: *const AddBundle, + archetypes_ptr: *mut Archetype, change_tick: Tick, } -pub(crate) enum InsertBundleResult { +pub(crate) enum InsertBundleResult<'w> { SameArchetype, NewArchetypeSameTable { - new_archetype: *mut Archetype, + new_archetype: &'w mut Archetype, }, NewArchetypeNewTable { - new_archetype: *mut Archetype, - new_table: *mut Table, + new_archetype: &'w mut Archetype, + new_table: &'w mut Table, }, } @@ -528,20 +533,31 @@ impl<'w> BundleInserter<'w> { archetype_id: ArchetypeId, change_tick: Tick, ) -> Self { - let bundle_info: *const BundleInfo = world + let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - unsafe { Self::new_with_info(world, archetype_id, bundle_info, change_tick) } + // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; + Self::new_with_info( + world.as_unsafe_world_cell_readonly(), + archetype_id, + bundle_info, + change_tick, + ) } + /// Creates a new [`BundleInserter`]. + /// + /// Implementation guarantees it will not alias `bundle_info`. #[inline] - pub(crate) unsafe fn new_with_info( - world: &'w mut World, + pub(crate) fn new_with_info( + world: UnsafeWorldCell<'w>, archetype_id: ArchetypeId, - bundle_info: *const BundleInfo, + bundle_info: &'w BundleInfo, change_tick: Tick, ) -> Self { - let bundle_info = &*bundle_info; + // SAFETY: We will not make any accesses to the command queue, component or resource data of this world + let (world, deferred_world) = unsafe { world.split_deferred() }; let bundle_id = bundle_info.id(); let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, @@ -549,62 +565,71 @@ impl<'w> BundleInserter<'w> { &world.components, archetype_id, ); + let archetypes_ptr = world.archetypes.archetypes.as_mut_ptr(); if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; - let table_id = archetype.table_id(); - let add_bundle: *const AddBundle = unsafe { + // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype + let add_bundle_ptr: *const AddBundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) .debug_checked_unwrap() }; - let table: *mut Table = &mut world.storages.tables[table_id]; - let archetype: *mut Archetype = archetype; + let table_id = archetype.table_id(); + let table = &mut world.storages.tables[table_id]; Self { - world, + world: deferred_world, archetype, - table, + entities: &mut world.entities, bundle_info, - add_bundle, + table, + sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::SameArchetype, + archetypes_ptr, + add_bundle_ptr, change_tick, } } else { let (archetype, new_archetype) = world.archetypes.get_2_mut(archetype_id, new_archetype_id); - let table_id = archetype.table_id(); - let new_table_id = new_archetype.table_id(); - let add_bundle: *const AddBundle = unsafe { + // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype + let add_bundle_ptr: *const AddBundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) .debug_checked_unwrap() }; - let table: *mut Table = &mut world.storages.tables[table_id]; - let archetype: *mut Archetype = archetype; - let new_archetype: *mut Archetype = new_archetype; + let table_id = archetype.table_id(); + let new_table_id = new_archetype.table_id(); if table_id == new_table_id { + let table = &mut world.storages.tables[table_id]; Self { - world, + world: deferred_world, archetype, - table, + entities: &mut world.entities, bundle_info, - add_bundle, + table, + sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, + archetypes_ptr, + add_bundle_ptr, change_tick, } } else { - let new_table: *mut Table = &mut world.storages.tables[new_table_id]; + let (table, new_table) = world.storages.tables.get_2_mut(table_id, new_table_id); Self { - world, + world: deferred_world, archetype, - table, + entities: &mut world.entities, bundle_info, - add_bundle, + table, + sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, }, + archetypes_ptr, + add_bundle_ptr, change_tick, } } @@ -620,29 +645,33 @@ impl<'w> BundleInserter<'w> { location: EntityLocation, bundle: T, ) -> EntityLocation { - let bundle_info = &*self.bundle_info; - let add_bundle = &*self.add_bundle; - let world = &mut *self.world; - - match self.result { + // SAFETY: We must ensure we do not use self.archetype to invalidate this reference + let add_bundle = &*self.add_bundle_ptr; + match &mut self.result { InsertBundleResult::SameArchetype => { - let archetype = &mut *self.archetype; - if archetype.has_on_add() { - world.into_deferred().trigger_on_add( + if self.archetype.has_on_add() { + self.world.trigger_on_add( entity, - bundle_info.iter_components().filter(|id| { + self.bundle_info.iter_components().filter(|id| { add_bundle.get_status(id.index()) == ComponentStatus::Added }), ); } - if archetype.has_on_insert() { - world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + if self.archetype.has_on_insert() { + self.world + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - bundle_info.write_components( - &mut *self.table, - &mut world.storages.sparse_sets, + // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) + // SAFETY: The edge is assured to be initialized when creating the BundleInserter + let add_bundle = unsafe { + self.archetype + .edges() + .get_add_bundle_internal(self.bundle_info.id) + .debug_checked_unwrap() + }; + self.bundle_info.write_components( + self.table, + self.sparse_sets, add_bundle, entity, location.table_row, @@ -652,28 +681,24 @@ impl<'w> BundleInserter<'w> { location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { - let table = &mut *self.table; - let archetype = &mut *self.archetype; - let new_archetype = &mut *new_archetype; if new_archetype.has_on_add() { - world.into_deferred().trigger_on_add( + self.world.trigger_on_add( entity, - bundle_info.iter_components().filter(|id| { + self.bundle_info.iter_components().filter(|id| { add_bundle.get_status(id.index()) == ComponentStatus::Added }), ); } if new_archetype.has_on_insert() { - world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + self.world + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - let result = archetype.swap_remove(location.archetype_row); + let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; - world.entities.set( + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; + self.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -684,10 +709,10 @@ impl<'w> BundleInserter<'w> { ); } let new_location = new_archetype.allocate(entity, result.table_row); - world.entities.set(entity.index(), new_location); - bundle_info.write_components( - table, - &mut world.storages.sparse_sets, + self.entities.set(entity.index(), new_location); + self.bundle_info.write_components( + self.table, + self.sparse_sets, add_bundle, entity, result.table_row, @@ -700,29 +725,24 @@ impl<'w> BundleInserter<'w> { new_archetype, new_table, } => { - let table = &mut *self.table; - let new_table = &mut *new_table; - let archetype = &mut *self.archetype; - let new_archetype = &mut *new_archetype; if new_archetype.has_on_add() { - world.into_deferred().trigger_on_add( + self.world.trigger_on_add( entity, - bundle_info.iter_components().filter(|id| { + self.bundle_info.iter_components().filter(|id| { add_bundle.get_status(id.index()) == ComponentStatus::Added }), ); } if new_archetype.has_on_insert() { - world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + self.world + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - let result = archetype.swap_remove(location.archetype_row); + let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; - world.entities.set( + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; + self.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -734,25 +754,31 @@ impl<'w> BundleInserter<'w> { } // PERF: store "non bundle" components in edge, then just move those to avoid // redundant copies - let move_result = table.move_to_superset_unchecked(result.table_row, new_table); + let move_result = self + .table + .move_to_superset_unchecked(result.table_row, new_table); let new_location = new_archetype.allocate(entity, move_result.new_row); - world.entities.set(entity.index(), new_location); + self.entities.set(entity.index(), new_location); // if an entity was moved into this entity's table spot, update its table row if let Some(swapped_entity) = move_result.swapped_entity { let swapped_location = // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { world.entities.get(swapped_entity).debug_checked_unwrap() }; - let swapped_archetype = if archetype.id() == swapped_location.archetype_id { + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; + let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id + { + let archetype: *mut Archetype = self.archetype; &mut *archetype } else if new_archetype.id() == swapped_location.archetype_id { new_archetype } else { // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut world.archetypes[swapped_location.archetype_id] + &mut *self + .archetypes_ptr + .add(swapped_location.archetype_id.index()) }; - world.entities.set( + self.entities.set( swapped_entity.index(), EntityLocation { archetype_id: swapped_location.archetype_id, @@ -765,9 +791,9 @@ impl<'w> BundleInserter<'w> { .set_entity_table_row(swapped_location.archetype_row, result.table_row); } - bundle_info.write_components( + self.bundle_info.write_components( new_table, - &mut world.storages.sparse_sets, + self.sparse_sets, add_bundle, entity, move_result.new_row, @@ -781,33 +807,43 @@ impl<'w> BundleInserter<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - &mut self.world.entities + self.entities } } pub(crate) struct BundleSpawner<'w> { - world: &'w mut World, - bundle_info: *const BundleInfo, - archetype: *mut Archetype, - table: *mut Table, + world: DeferredWorld<'w>, + bundle_info: &'w BundleInfo, + archetype: &'w mut Archetype, + table: &'w mut Table, + sparse_sets: &'w mut SparseSets, + entities: &'w mut Entities, change_tick: Tick, } impl<'w> BundleSpawner<'w> { #[inline] pub fn new(world: &'w mut World, change_tick: Tick) -> Self { - let bundle_info: *const BundleInfo = world + let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - unsafe { Self::new_with_info(world, bundle_info, change_tick) } + // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; + Self::new_with_info( + world.as_unsafe_world_cell_readonly(), + bundle_info, + change_tick, + ) } - pub(crate) unsafe fn new_with_info( - world: &'w mut World, - bundle_info: *const BundleInfo, + #[inline] + pub(crate) fn new_with_info( + world: UnsafeWorldCell<'w>, + bundle_info: &'w BundleInfo, change_tick: Tick, ) -> Self { - let bundle_info = &*bundle_info; + // SAFETY: We will not make any accesses to the command queue, component or resource data of this world + let (world, deferred_world) = unsafe { world.split_deferred() }; let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, &mut world.storages, @@ -815,23 +851,24 @@ impl<'w> BundleSpawner<'w> { ArchetypeId::EMPTY, ); let archetype = &mut world.archetypes[new_archetype_id]; - let table: *mut Table = &mut world.storages.tables[archetype.table_id()]; - let archetype: *mut Archetype = archetype; - BundleSpawner { - world, + let table = &mut world.storages.tables[archetype.table_id()]; + Self { + world: deferred_world, bundle_info, archetype, table, + sparse_sets: &mut world.storages.sparse_sets, + entities: &mut world.entities, change_tick, } } + #[inline] pub fn reserve_storage(&mut self, additional: usize) { - unsafe { - (&mut *self.archetype).reserve(additional); - (&mut *self.table).reserve(additional); - } + self.archetype.reserve(additional); + self.table.reserve(additional); } + /// # Safety /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type #[inline] @@ -840,34 +877,28 @@ impl<'w> BundleSpawner<'w> { entity: Entity, bundle: T, ) -> EntityLocation { - let bundle_info = &*self.bundle_info; - let archetype = &*self.archetype; - if archetype.has_on_add() { + if self.archetype.has_on_add() { self.world - .into_deferred() - .trigger_on_add(entity, bundle_info.iter_components()); + .trigger_on_add(entity, self.bundle_info.iter_components()); } - if archetype.has_on_insert() { + if self.archetype.has_on_insert() { self.world - .into_deferred() - .trigger_on_insert(entity, bundle_info.iter_components()) + .trigger_on_insert(entity, self.bundle_info.iter_components()); } - let archetype = &mut *self.archetype; let table = &mut *self.table; - let table_row = table.allocate(entity); - let location = archetype.allocate(entity, table_row); - bundle_info.write_components( + let location = self.archetype.allocate(entity, table_row); + self.bundle_info.write_components( table, - &mut self.world.storages.sparse_sets, + self.sparse_sets, &SpawnBundleStatus, entity, table_row, self.change_tick, bundle, ); - self.world.entities.set(entity.index(), location); + self.entities.set(entity.index(), location); location } @@ -876,7 +907,7 @@ impl<'w> BundleSpawner<'w> { /// `T` must match this [`BundleInfo`]'s type #[inline] pub unsafe fn spawn(&mut self, bundle: T) -> Entity { - let entity = self.world.entities.alloc(); + let entity = self.entities.alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type self.spawn_non_existent(entity, bundle); entity @@ -884,7 +915,19 @@ impl<'w> BundleSpawner<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - &mut self.world.entities + self.entities + } + + /// # Safety: + /// - `Self` must be dropped after running this function as it may invalidate internal pointers. + /// - Caller must ensure that no there are no outstanding access to `self.world` + #[inline] + pub(crate) unsafe fn flush_commands(&mut self) { + // SAFETY: pointers on self can be invalidated, + self.world + .as_unsafe_world_cell_readonly() + .world_mut() + .flush_commands(); } } @@ -895,9 +938,11 @@ pub struct Bundles { /// Cache static [`BundleId`] bundle_ids: TypeIdMap, /// Cache dynamic [`BundleId`] with multiple components - dynamic_bundle_ids: HashMap, (BundleId, Vec)>, + dynamic_bundle_ids: HashMap, BundleId>, + dynamic_bundle_storages: HashMap>, /// Cache optimized dynamic [`BundleId`] with single component - dynamic_component_bundle_ids: HashMap, + dynamic_component_bundle_ids: HashMap, + dynamic_component_storages: HashMap, } impl Bundles { @@ -917,11 +962,11 @@ impl Bundles { } /// Initializes a new [`BundleInfo`] for a statically known type. - pub(crate) fn init_info<'a, T: Bundle>( - &'a mut self, + pub(crate) fn init_info( + &mut self, components: &mut Components, storages: &mut Storages, - ) -> &'a BundleInfo { + ) -> BundleId { let bundle_infos = &mut self.bundle_infos; let id = *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { let mut component_ids = Vec::new(); @@ -936,14 +981,35 @@ impl Bundles { bundle_infos.push(bundle_info); id }); - // SAFETY: index either exists, or was initialized - unsafe { self.get_unchecked(id) } + id } - pub(crate) unsafe fn get_unchecked<'a>(&'a self, id: BundleId) -> &'a BundleInfo { + pub(crate) unsafe fn get_unchecked(&self, id: BundleId) -> &BundleInfo { self.bundle_infos.get_unchecked(id.0) } + pub(crate) unsafe fn get_with_storage_unchecked( + &self, + id: BundleId, + ) -> (&BundleInfo, &StorageType) { + ( + self.bundle_infos.get_unchecked(id.0), + self.dynamic_component_storages + .get(&id) + .debug_checked_unwrap(), + ) + } + + pub(crate) unsafe fn get_with_storages_unchecked( + &self, + id: BundleId, + ) -> (&BundleInfo, &Vec) { + ( + self.bundle_infos.get_unchecked(id.0), + self.dynamic_bundle_storages.get(&id).debug_checked_unwrap(), + ) + } + /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`]. /// /// # Panics @@ -954,25 +1020,22 @@ impl Bundles { &mut self, components: &Components, component_ids: &[ComponentId], - ) -> (&BundleInfo, &Vec) { + ) -> BundleId { let bundle_infos = &mut self.bundle_infos; // Use `raw_entry_mut` to avoid cloning `component_ids` to access `Entry` - let (_, (bundle_id, storage_types)) = self + let (_, bundle_id) = self .dynamic_bundle_ids .raw_entry_mut() .from_key(component_ids) .or_insert_with(|| { - ( - Vec::from(component_ids), - initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)), - ) + let (id, storages) = + initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)); + self.dynamic_bundle_storages + .insert_unique_unchecked(id, storages); + (Vec::from(component_ids), id) }); - - // SAFETY: index either exists, or was initialized - let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) }; - - (bundle_info, storage_types) + *bundle_id } /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`] with single component. @@ -984,22 +1047,18 @@ impl Bundles { &mut self, components: &Components, component_id: ComponentId, - ) -> (&BundleInfo, StorageType) { + ) -> BundleId { let bundle_infos = &mut self.bundle_infos; - let (bundle_id, storage_types) = self + let bundle_id = self .dynamic_component_bundle_ids .entry(component_id) .or_insert_with(|| { let (id, storage_type) = initialize_dynamic_bundle(bundle_infos, components, vec![component_id]); - // SAFETY: `storage_type` guaranteed to have length 1 - (id, storage_type[0]) + self.dynamic_component_storages.insert(id, storage_type[0]); + id }); - - // SAFETY: index either exists, or was initialized - let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) }; - - (bundle_info, *storage_types) + *bundle_id } } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 21c7ea3d26fa1..0ee72faf749be 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -203,11 +203,23 @@ pub enum StorageType { SparseSet, } +/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove` +pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId); + +/// Lifecycle hooks for a given [`Component`], stored in it's [`ComponentInfo`] +#[derive(Debug, Clone, Default)] +pub struct ComponentHooks { + pub(crate) on_add: Option, + pub(crate) on_insert: Option, + pub(crate) on_remove: Option, +} + /// Stores metadata for a type of component or resource stored in a specific [`World`]. #[derive(Debug, Clone)] pub struct ComponentInfo { id: ComponentId, descriptor: ComponentDescriptor, + hooks: ComponentHooks, } impl ComponentInfo { @@ -263,26 +275,34 @@ impl ComponentInfo { /// Create a new [`ComponentInfo`]. pub(crate) fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self { - ComponentInfo { id, descriptor } + ComponentInfo { + id, + descriptor, + hooks: ComponentHooks::default(), + } } + /// Register a [`ComponentHook`] that will be run when this component is added to an entity pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { - self.descriptor.hooks.on_add = Some(hook); + self.hooks.on_add = Some(hook); self } + /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { - self.descriptor.hooks.on_insert = Some(hook); + self.hooks.on_insert = Some(hook); self } + /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { - self.descriptor.hooks.on_remove = Some(hook); + self.hooks.on_remove = Some(hook); self } + /// Update the given flags to include any [`ComponentHook`] registered to self #[inline] - pub fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) { + pub(crate) fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) { if self.hooks().on_add.is_some() { flags.insert(ArchetypeFlags::ON_ADD_HOOK); } @@ -294,8 +314,9 @@ impl ComponentInfo { } } + /// Provides a reference to the collection of hooks associated with this [`Component`] pub fn hooks(&self) -> &ComponentHooks { - &self.descriptor.hooks + &self.hooks } } @@ -352,15 +373,6 @@ impl SparseSetIndex for ComponentId { } } -pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId); - -#[derive(Clone, Default)] -pub struct ComponentHooks { - pub(crate) on_add: Option, - pub(crate) on_insert: Option, - pub(crate) on_remove: Option, -} - /// A value describing a component or resource, which may or may not correspond to a Rust type. #[derive(Clone)] pub struct ComponentDescriptor { @@ -373,7 +385,6 @@ pub struct ComponentDescriptor { is_send_and_sync: bool, type_id: Option, layout: Layout, - hooks: ComponentHooks, // SAFETY: this function must be safe to call with pointers pointing to items of the type // this descriptor describes. // None if the underlying type doesn't need to be dropped @@ -407,7 +418,6 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), - hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -429,7 +439,6 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: None, layout, - hooks: ComponentHooks::default(), drop, } } @@ -446,7 +455,6 @@ impl ComponentDescriptor { is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), - hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } @@ -458,7 +466,6 @@ impl ComponentDescriptor { is_send_and_sync: false, type_id: Some(TypeId::of::()), layout: Layout::new::(), - hooks: ComponentHooks::default(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), } } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index ea6293ac25e20..d12ba0b4248b2 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -10,27 +10,28 @@ use crate::{ system::{Commands, Query, Resource}, }; -use super::{Mut, World}; +use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; /// An [`World`] reference that prevents structural ECS changes. /// This includes creating tables, registering components or spawning entities. pub struct DeferredWorld<'w> { - world: &'w mut World, + world: UnsafeWorldCell<'w>, } impl<'w> Deref for DeferredWorld<'w> { - type Target = &'w mut World; + type Target = World; fn deref(&self) -> &Self::Target { - &self.world + // SAFETY: &self ensures ther are no active mutable borrows + unsafe { self.world.world() } } } impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue pub fn commands(&mut self) -> Commands { - let world = self.world.as_unsafe_world_cell(); - unsafe { Commands::new(world.get_command_queue(), world.world()) } + // SAFETY: Commands::new only take &World which has no way to access the command queue + unsafe { Commands::new(self.world.get_command_queue(), self.world.world()) } } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -41,29 +42,28 @@ impl<'w> DeferredWorld<'w> { // - `as_unsafe_world_cell` is the only thing that is borrowing world // - `as_unsafe_world_cell` provides mutable permission to everything // - `&mut self` ensures no other borrows on world data - unsafe { - self.world - .as_unsafe_world_cell() - .get_entity(entity)? - .get_mut() - } + unsafe { self.world.get_entity(entity)?.get_mut() } } /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently /// run queries on the [`World`] by storing and reusing the [`QueryState`]. + /// + /// # Panics + /// If state is from a different world then self #[inline] pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( &'w mut self, state: &'s mut QueryState, ) -> Query<'w, 's, Q, F> { + state.validate_world(self.world.id()); + state.update_archetypes(self); + // SAFETY: We ran validate_world to ensure our state matches unsafe { - state.update_archetypes(self.world); - let world = self.world.as_unsafe_world_cell(); Query::new( - world, + self.world, state, - world.last_change_tick(), - world.change_tick(), + self.world.last_change_tick(), + self.world.change_tick(), false, ) } @@ -96,7 +96,8 @@ impl<'w> DeferredWorld<'w> { /// Gets a mutable reference to the resource of the given type if it exists #[inline] pub fn get_resource_mut(&mut self) -> Option> { - self.world.get_resource_mut() + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_resource_mut() } } /// Gets a mutable reference to the non-send resource of the given type, if it exists. @@ -128,7 +129,8 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_resource_mut(&mut self) -> Option> { - self.world.get_non_send_resource_mut() + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_non_send_resource_mut() } } /// Sends an [`Event`]. @@ -173,7 +175,8 @@ impl<'w> DeferredWorld<'w> { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - self.world.get_resource_mut_by_id(component_id) + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_resource_mut_by_id(component_id) } } /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. @@ -187,7 +190,8 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - self.world.get_non_send_mut_by_id(component_id) + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } } /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. @@ -201,75 +205,91 @@ impl<'w> DeferredWorld<'w> { entity: Entity, component_id: ComponentId, ) -> Option> { - // SAFETY: - // - `&mut self` ensures that all accessed data is unaliased - // - `as_unsafe_world_cell` provides mutable permission to the whole world - unsafe { - self.world - .as_unsafe_world_cell() - .get_entity(entity)? - .get_mut_by_id(component_id) - } + // SAFETY: `&mut self` ensures that all accessed data is unaliased + unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } } + /// Triggers all `on_add` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] - pub(crate) fn trigger_on_add( + pub(crate) unsafe fn trigger_on_add( &mut self, entity: Entity, targets: impl Iterator, ) { for component_id in targets { + // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); if let Some(hook) = hooks.on_add { - hook(DeferredWorld { world: self.world }, entity, component_id) + hook(DeferredWorld { world: self.world }, entity, component_id); } } } + /// Triggers all `on_insert` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] - pub(crate) fn trigger_on_insert( + pub(crate) unsafe fn trigger_on_insert( &mut self, entity: Entity, targets: impl Iterator, ) { for component_id in targets { + // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); if let Some(hook) = hooks.on_insert { - hook(DeferredWorld { world: self.world }, entity, component_id) + hook(DeferredWorld { world: self.world }, entity, component_id); } } } + /// Triggers all `on_remove` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. #[inline] - pub(crate) fn trigger_on_remove( + pub(crate) unsafe fn trigger_on_remove( &mut self, entity: Entity, targets: impl Iterator, ) { for component_id in targets { + // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks(); if let Some(hook) = hooks.on_remove { - hook(DeferredWorld { world: self.world }, entity, component_id) + hook(DeferredWorld { world: self.world }, entity, component_id); } } } } -impl World { - /// Turn a [`World`] reference into a [`DeferredWorld`] +impl<'w> UnsafeWorldCell<'w> { + /// Turn self into a [`DeferredWorld`] /// + /// # Safety /// Caller must ensure there are no outstanding references to the world's command queue, resource or component data #[inline] - pub unsafe fn into_deferred(&self) -> DeferredWorld { - DeferredWorld { - // SAFETY: Not - world: self.as_unsafe_world_cell_readonly().world_mut(), - } + pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { + DeferredWorld { world: self } + } + + /// Turn self into a mutable world reference and a [`DeferredWorld`] + /// + /// # Safety + /// Caller must ensure that the world reference is not used to construct any references to the world's command queue, resource or component data + pub unsafe fn split_deferred(self) -> (&'w mut World, DeferredWorld<'w>) { + (self.world_mut(), self.into_deferred()) } } -impl<'w> Into> for &'w mut World { - fn into(self) -> DeferredWorld<'w> { - DeferredWorld { world: self } +impl<'w> From<&'w mut World> for DeferredWorld<'w> { + fn from(world: &'w mut World) -> DeferredWorld<'w> { + DeferredWorld { + world: world.as_unsafe_world_cell(), + } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index e3349fb23282e..c7763d7c87c82 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -592,26 +592,26 @@ impl<'w> EntityWorldMut<'w> { component: OwningPtr<'_>, ) -> &mut Self { let change_tick = self.world.change_tick(); - let (bundle_info, storage_type) = self + let bundle_id = self .world .bundles .init_component_info(&self.world.components, component_id); - let bundle_info: *const BundleInfo = bundle_info; - let bundle_inserter = unsafe { - BundleInserter::new_with_info( - self.world, - self.location.archetype_id, - bundle_info, - change_tick, - ) - }; + let (bundle_info, storage_type) = self.world.bundles.get_with_storage_unchecked(bundle_id); + + let bundle_inserter = BundleInserter::new_with_info( + // SAFETY: The only outstanding reference to world is bundle_info + self.world.as_unsafe_world_cell_readonly(), + self.location.archetype_id, + bundle_info, + change_tick, + ); self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, Some(component).into_iter(), - Some(storage_type).iter().cloned(), + Some(*storage_type).iter().cloned(), ); self.world.flush_commands(); self @@ -635,27 +635,27 @@ impl<'w> EntityWorldMut<'w> { iter_components: I, ) -> &mut Self { let change_tick = self.world.change_tick(); - let (bundle_info, storage_types) = self + let bundle_id = self .world .bundles .init_dynamic_info(&self.world.components, component_ids); - let bundle_info: *const BundleInfo = bundle_info; - let storage_types: *const Vec = storage_types; - let bundle_inserter = unsafe { - BundleInserter::new_with_info( - self.world, - self.location.archetype_id, - bundle_info, - change_tick, - ) - }; + let (bundle_info, storage_types) = + self.world.bundles.get_with_storages_unchecked(bundle_id); + + let bundle_inserter = BundleInserter::new_with_info( + // SAFETY: The only outstanding reference to world is bundle_info + self.world.as_unsafe_world_cell_readonly(), + self.location.archetype_id, + bundle_info, + change_tick, + ); self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, iter_components, - (&*storage_types).iter().cloned(), + (*storage_types).iter().cloned(), ); self.world.flush_commands(); self @@ -670,8 +670,8 @@ impl<'w> EntityWorldMut<'w> { pub fn take(&mut self) -> Option { let storages = &mut self.world.storages; let components = &mut self.world.components; - let bundle_id = self.world.bundles.init_info::(components, storages).id(); - // Reborrow to drop &mut World + let bundle_id = self.world.bundles.init_info::(components, storages); + // SAFETY: We just ensured this bundle exists let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, @@ -695,12 +695,17 @@ impl<'w> EntityWorldMut<'w> { let entity = self.entity; let old_archetype = &self.world.archetypes[old_location.archetype_id]; if old_archetype.has_on_remove() { - unsafe { self.world.into_deferred() } - .trigger_on_remove(entity, bundle_info.iter_components()); + // SAFETY: None of our oustanding references can be modified through DeferredWorld + unsafe { + self.world + .as_unsafe_world_cell_readonly() + .into_deferred() + .trigger_on_remove(entity, bundle_info.iter_components()); + }; + } + for component_id in bundle_info.iter_components() { + self.world.removed_components.send(component_id, entity); } - // for component_id in bundle_info.iter_components() { - // self.world.removed_components.send(component_id, entity); - // } let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; @@ -827,8 +832,8 @@ impl<'w> EntityWorldMut<'w> { let storages = &mut self.world.storages; let components = &mut self.world.components; - let bundle_id = self.world.bundles.init_info::(components, storages).id(); - // Reborrow to drop &mut World + let bundle_id = self.world.bundles.init_info::(components, storages); + // SAFETY: We just ensured this bundle exists let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; @@ -854,12 +859,17 @@ impl<'w> EntityWorldMut<'w> { let entity: Entity = self.entity; let old_archetype = &self.world.archetypes[old_location.archetype_id]; if old_archetype.has_on_remove() { - unsafe { self.world.into_deferred() } - .trigger_on_remove(entity, bundle_info.iter_components()) + // SAFETY: None of our oustanding references can be modified through DeferredWorld + unsafe { + self.world + .as_unsafe_world_cell_readonly() + .into_deferred() + .trigger_on_remove(entity, bundle_info.iter_components()); + }; } for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { - // self.world.removed_components.send(component_id, entity); + self.world.removed_components.send(component_id, entity); // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. @@ -897,15 +907,20 @@ impl<'w> EntityWorldMut<'w> { self.world.flush_entities(); let archetype = &self.world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { - unsafe { self.world.into_deferred() } - .trigger_on_remove(self.entity, archetype.components()); + // SAFETY: None of our oustanding references can be modified through DeferredWorld + unsafe { + self.world + .as_unsafe_world_cell_readonly() + .into_deferred() + .trigger_on_remove(self.entity, archetype.components()); + }; } - // for component_id in archetype.components() { - // self.world - // .removed_components - // .send(component_id, self.entity); - // } + for component_id in archetype.components() { + self.world + .removed_components + .send(component_id, self.entity); + } let world = self.world; let location = world diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 8099bb505cb67..12fc45066a817 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -15,7 +15,7 @@ pub use world_cell::*; use crate::{ archetype::{ArchetypeComponentId, ArchetypeId, ArchetypeRow, Archetypes}, - bundle::{Bundle, BundleInfo, BundleInserter, BundleSpawner, Bundles}, + bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, TicksMut}, component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, Tick}, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, @@ -189,7 +189,7 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. /// - /// Will panic if a component for `T`` already exists. + /// Will panic if a component for `T` already exists. pub fn register_component(&mut self) -> &mut ComponentInfo { let type_id = TypeId::of::(); assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); @@ -218,7 +218,7 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. /// - /// Will panic if a component for `T`` already exists. + /// Will panic if a component for `T` already exists. pub fn register_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, @@ -1454,9 +1454,11 @@ impl World { let change_tick = self.change_tick(); - let bundle_info = self + let bundle_id = self .bundles .init_info::(&mut self.components, &mut self.storages); + // SAFETY: We just ensured this bundle exists + let bundle_info = unsafe { self.bundles.get_unchecked(bundle_id) }; enum SpawnOrInsert<'w> { Spawn(BundleSpawner<'w>), Insert(BundleInserter<'w>, ArchetypeId), @@ -1470,10 +1472,11 @@ impl World { } } } - let bundle_info: *const BundleInfo = bundle_info; - let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { - BundleSpawner::new_with_info(self, bundle_info, change_tick) - }); + let mut spawn_or_insert = SpawnOrInsert::Spawn(BundleSpawner::new_with_info( + self.as_unsafe_world_cell_readonly(), + bundle_info, + change_tick, + )); let mut invalid_entities = Vec::new(); for (entity, bundle) in iter { @@ -1490,14 +1493,12 @@ impl World { unsafe { inserter.insert(entity, location, bundle) }; } _ => { - let mut inserter = unsafe { - BundleInserter::new_with_info( - self, - location.archetype_id, - bundle_info, - change_tick, - ) - }; + let mut inserter = BundleInserter::new_with_info( + self.as_unsafe_world_cell_readonly(), + location.archetype_id, + bundle_info, + change_tick, + ); // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = @@ -1510,8 +1511,11 @@ impl World { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; } else { - let mut spawner = - unsafe { BundleSpawner::new_with_info(self, bundle_info, change_tick) }; + let mut spawner = BundleSpawner::new_with_info( + self.as_unsafe_world_cell_readonly(), + bundle_info, + change_tick, + ); // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index b0586e4c7c8f8..71e367fd1a86f 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -51,7 +51,9 @@ where I::Item: Bundle, { fn drop(&mut self) { - for _ in self {} + for _ in &mut *self {} + // SAFETY: `self.spawner` will be dropped immediately after this call. + unsafe { self.spawner.flush_commands() }; } } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 667d6bfaa95e3..1056549a7a059 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -567,8 +567,12 @@ impl<'w> UnsafeWorldCell<'w> { .get_with_ticks() } + // Returns a mutable reference to worlds internal [`CommandQueue`] + /// # Safety + /// It is the callers responsibility to ensure that no mutable references exist to the command queue at the same time #[inline] pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue { + // SAFETY: caller ensures there is no `&mut World` let world = unsafe { &mut *self.0 }; &mut world.command_queue } diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 9309de94e9fc9..8e8e63de0322f 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -30,7 +30,7 @@ fn setup(world: &mut World) { ); let mut index = world.resource_mut::(); index.remove(&entity); - println!("Current index: {:?}", *index) + println!("Current index: {:?}", *index); }); } From b620001a13f04906e84e9c0b44c757dd427c0c40 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 16:32:37 -0800 Subject: [PATCH 07/35] Miri pass --- crates/bevy_ecs/src/bundle.rs | 653 +++++++++++------- crates/bevy_ecs/src/system/system_registry.rs | 4 + crates/bevy_ecs/src/world/deferred_world.rs | 17 +- crates/bevy_ecs/src/world/entity_ref.rs | 146 ++-- crates/bevy_ecs/src/world/mod.rs | 34 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 12 +- examples/ecs/component_hooks.rs | 77 ++- 7 files changed, 577 insertions(+), 366 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index fe57cc96af184..a32f98a9b0ce6 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,7 +15,7 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, + world::unsafe_world_cell::UnsafeWorldCell, TypeIdMap, }; use bevy_ptr::OwningPtr; @@ -502,27 +502,25 @@ impl BundleInfo { } } +// SAFETY: We have exclusive world access so our pointers can't be invalidated externally pub(crate) struct BundleInserter<'w> { - world: DeferredWorld<'w>, - archetype: &'w mut Archetype, - entities: &'w mut Entities, - bundle_info: &'w BundleInfo, - table: &'w mut Table, - sparse_sets: &'w mut SparseSets, - result: InsertBundleResult<'w>, - add_bundle_ptr: *const AddBundle, - archetypes_ptr: *mut Archetype, + world: UnsafeWorldCell<'w>, + bundle_info: *const BundleInfo, + add_bundle: *const AddBundle, + table: *mut Table, + archetype: *mut Archetype, + result: InsertBundleResult, change_tick: Tick, } -pub(crate) enum InsertBundleResult<'w> { +pub(crate) enum InsertBundleResult { SameArchetype, NewArchetypeSameTable { - new_archetype: &'w mut Archetype, + new_archetype: *mut Archetype, }, NewArchetypeNewTable { - new_archetype: &'w mut Archetype, - new_table: &'w mut Table, + new_archetype: *mut Archetype, + new_table: *mut Table, }, } @@ -536,28 +534,23 @@ impl<'w> BundleInserter<'w> { let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World - let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; - Self::new_with_info( - world.as_unsafe_world_cell_readonly(), - archetype_id, - bundle_info, - change_tick, - ) + // SAFETY: We just ensured this bundle exists + unsafe { Self::new_with_id(world, archetype_id, bundle_id, change_tick) } } /// Creates a new [`BundleInserter`]. /// - /// Implementation guarantees it will not alias `bundle_info`. + /// # Safety + /// Caller must ensure that BundleId exists in `world.bundles` #[inline] - pub(crate) fn new_with_info( - world: UnsafeWorldCell<'w>, + pub(crate) unsafe fn new_with_id( + world: &'w mut World, archetype_id: ArchetypeId, - bundle_info: &'w BundleInfo, + bundle_id: BundleId, change_tick: Tick, ) -> Self { // SAFETY: We will not make any accesses to the command queue, component or resource data of this world - let (world, deferred_world) = unsafe { world.split_deferred() }; + let bundle_info = world.bundles.get_unchecked(bundle_id); let bundle_id = bundle_info.id(); let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, @@ -565,11 +558,10 @@ impl<'w> BundleInserter<'w> { &world.components, archetype_id, ); - let archetypes_ptr = world.archetypes.archetypes.as_mut_ptr(); if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype - let add_bundle_ptr: *const AddBundle = unsafe { + let add_bundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) @@ -578,22 +570,19 @@ impl<'w> BundleInserter<'w> { let table_id = archetype.table_id(); let table = &mut world.storages.tables[table_id]; Self { - world: deferred_world, + add_bundle, archetype, - entities: &mut world.entities, bundle_info, table, - sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::SameArchetype, - archetypes_ptr, - add_bundle_ptr, change_tick, + world: world.as_unsafe_world_cell(), } } else { let (archetype, new_archetype) = world.archetypes.get_2_mut(archetype_id, new_archetype_id); // SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype - let add_bundle_ptr: *const AddBundle = unsafe { + let add_bundle = unsafe { archetype .edges() .get_add_bundle_internal(bundle_id) @@ -604,33 +593,27 @@ impl<'w> BundleInserter<'w> { if table_id == new_table_id { let table = &mut world.storages.tables[table_id]; Self { - world: deferred_world, + add_bundle, archetype, - entities: &mut world.entities, bundle_info, table, - sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, - archetypes_ptr, - add_bundle_ptr, change_tick, + world: world.as_unsafe_world_cell(), } } else { let (table, new_table) = world.storages.tables.get_2_mut(table_id, new_table_id); Self { - world: deferred_world, + add_bundle, archetype, - entities: &mut world.entities, bundle_info, table, - sparse_sets: &mut world.storages.sparse_sets, result: InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, }, - archetypes_ptr, - add_bundle_ptr, change_tick, + world: world.as_unsafe_world_cell(), } } } @@ -645,161 +628,205 @@ impl<'w> BundleInserter<'w> { location: EntityLocation, bundle: T, ) -> EntityLocation { - // SAFETY: We must ensure we do not use self.archetype to invalidate this reference - let add_bundle = &*self.add_bundle_ptr; + // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; + let archetype = &*self.archetype; match &mut self.result { InsertBundleResult::SameArchetype => { - if self.archetype.has_on_add() { - self.world.trigger_on_add( + { + // SAFETY: Mutable references do not alias and will be dropped after this block + let sparse_sets = { + let world = self.world.world_mut(); + &mut world.storages.sparse_sets + }; + let table = &mut *self.table; + bundle_info.write_components( + table, + sparse_sets, + add_bundle, + entity, + location.table_row, + self.change_tick, + bundle, + ); + } + // SAFETY: We have no oustanding mutable references to world as they were dropped + let mut world = self.world.into_deferred(); + if archetype.has_on_add() { + world.trigger_on_add( entity, - self.bundle_info.iter_components().filter(|id| { - add_bundle.get_status(id.index()) == ComponentStatus::Added - }), + bundle_info + .iter_components() + .enumerate() + .filter(|(index, _)| { + add_bundle.get_status(*index) == ComponentStatus::Added + }) + .map(|(_, id)| id), ); } - if self.archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); + if archetype.has_on_insert() { + world.trigger_on_insert(entity, bundle_info.iter_components()); } - // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - // SAFETY: The edge is assured to be initialized when creating the BundleInserter - let add_bundle = unsafe { - self.archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .debug_checked_unwrap() - }; - self.bundle_info.write_components( - self.table, - self.sparse_sets, - add_bundle, - entity, - location.table_row, - self.change_tick, - bundle, - ); location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { + let new_location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let new_archetype = &mut **new_archetype; + let (sparse_sets, entities) = { + let world = self.world.world_mut(); + (&mut world.storages.sparse_sets, &mut world.entities) + }; + + let result = archetype.swap_remove(location.archetype_row); + if let Some(swapped_entity) = result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: location.archetype_row, + table_id: swapped_location.table_id, + table_row: swapped_location.table_row, + }, + ); + } + let new_location = new_archetype.allocate(entity, result.table_row); + entities.set(entity.index(), new_location); + bundle_info.write_components( + table, + sparse_sets, + add_bundle, + entity, + result.table_row, + self.change_tick, + bundle, + ); + new_location + }; + + // SAFETY: We have no oustanding mutable references to world as they were dropped + let new_archetype = &**new_archetype; + let mut world = self.world.into_deferred(); if new_archetype.has_on_add() { - self.world.trigger_on_add( + world.trigger_on_add( entity, - self.bundle_info.iter_components().filter(|id| { - add_bundle.get_status(id.index()) == ComponentStatus::Added - }), + bundle_info + .iter_components() + .enumerate() + .filter(|(index, _)| { + add_bundle.get_status(*index) == ComponentStatus::Added + }) + .map(|(_, id)| id), ); } if new_archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); - } - let result = self.archetype.swap_remove(location.archetype_row); - if let Some(swapped_entity) = result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: location.archetype_row, - table_id: swapped_location.table_id, - table_row: swapped_location.table_row, - }, - ); + world.trigger_on_insert(entity, bundle_info.iter_components()); } - let new_location = new_archetype.allocate(entity, result.table_row); - self.entities.set(entity.index(), new_location); - self.bundle_info.write_components( - self.table, - self.sparse_sets, - add_bundle, - entity, - result.table_row, - self.change_tick, - bundle, - ); new_location } InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, } => { + let new_location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + let table = &mut *self.table; + let new_table = &mut **new_table; + let archetype = &mut *self.archetype; + let new_archetype = &mut **new_archetype; + let (archetypes, sparse_sets, entities) = { + let world = self.world.world_mut(); + ( + &mut world.archetypes, + &mut world.storages.sparse_sets, + &mut world.entities, + ) + }; + let result = archetype.swap_remove(location.archetype_row); + if let Some(swapped_entity) = result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: location.archetype_row, + table_id: swapped_location.table_id, + table_row: swapped_location.table_row, + }, + ); + } + // PERF: store "non bundle" components in edge, then just move those to avoid + // redundant copies + let move_result = table.move_to_superset_unchecked(result.table_row, new_table); + let new_location = new_archetype.allocate(entity, move_result.new_row); + entities.set(entity.index(), new_location); + + // if an entity was moved into this entity's table spot, update its table row + if let Some(swapped_entity) = move_result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + let swapped_archetype = if archetype.id() == swapped_location.archetype_id { + archetype + } else if new_archetype.id() == swapped_location.archetype_id { + new_archetype + } else { + // SAFETY: the only two borrowed archetypes are above and we just did collision checks + &mut archetypes.archetypes[swapped_location.archetype_id.index()] + }; + + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: swapped_location.archetype_row, + table_id: swapped_location.table_id, + table_row: result.table_row, + }, + ); + swapped_archetype + .set_entity_table_row(swapped_location.archetype_row, result.table_row); + } + + bundle_info.write_components( + new_table, + sparse_sets, + add_bundle, + entity, + move_result.new_row, + self.change_tick, + bundle, + ); + + new_location + }; + + // SAFETY: We have no oustanding mutable references to world as they were dropped + let new_archetype = &**new_archetype; + let mut world = self.world.into_deferred(); if new_archetype.has_on_add() { - self.world.trigger_on_add( + world.trigger_on_add( entity, - self.bundle_info.iter_components().filter(|id| { - add_bundle.get_status(id.index()) == ComponentStatus::Added - }), + bundle_info + .iter_components() + .enumerate() + .filter(|(index, _)| { + add_bundle.get_status(*index) == ComponentStatus::Added + }) + .map(|(_, id)| id), ); } if new_archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); - } - let result = self.archetype.swap_remove(location.archetype_row); - if let Some(swapped_entity) = result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - self.entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: location.archetype_row, - table_id: swapped_location.table_id, - table_row: swapped_location.table_row, - }, - ); + world.trigger_on_insert(entity, bundle_info.iter_components()); } - // PERF: store "non bundle" components in edge, then just move those to avoid - // redundant copies - let move_result = self - .table - .move_to_superset_unchecked(result.table_row, new_table); - let new_location = new_archetype.allocate(entity, move_result.new_row); - self.entities.set(entity.index(), new_location); - - // if an entity was moved into this entity's table spot, update its table row - if let Some(swapped_entity) = move_result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; - let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id - { - let archetype: *mut Archetype = self.archetype; - &mut *archetype - } else if new_archetype.id() == swapped_location.archetype_id { - new_archetype - } else { - // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut *self - .archetypes_ptr - .add(swapped_location.archetype_id.index()) - }; - - self.entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: swapped_location.archetype_row, - table_id: swapped_location.table_id, - table_row: result.table_row, - }, - ); - swapped_archetype - .set_entity_table_row(swapped_location.archetype_row, result.table_row); - } - - self.bundle_info.write_components( - new_table, - self.sparse_sets, - add_bundle, - entity, - move_result.new_row, - self.change_tick, - bundle, - ); new_location } } @@ -807,17 +834,17 @@ impl<'w> BundleInserter<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - self.entities + // SAFETY: No outstanding references to self.world, changes to entities cannot invalidate our internal pointers + unsafe { &mut self.world.world_mut().entities } } } +// SAFETY: We have exclusive world access so our pointers can't be invalidated externally pub(crate) struct BundleSpawner<'w> { - world: DeferredWorld<'w>, - bundle_info: &'w BundleInfo, - archetype: &'w mut Archetype, - table: &'w mut Table, - sparse_sets: &'w mut SparseSets, - entities: &'w mut Entities, + world: UnsafeWorldCell<'w>, + bundle_info: *const BundleInfo, + table: *mut Table, + archetype: *mut Archetype, change_tick: Tick, } @@ -827,23 +854,21 @@ impl<'w> BundleSpawner<'w> { let bundle_id = world .bundles .init_info::(&mut world.components, &mut world.storages); - // SAFETY: We just ensured this bundle exists, re-borrow to drop &mut World - let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; - Self::new_with_info( - world.as_unsafe_world_cell_readonly(), - bundle_info, - change_tick, - ) + // SAFETY: we initialized this bundle_id in `init_info` + unsafe { Self::new_with_id(world, bundle_id, change_tick) } } + /// Creates a new [`BundleSpawner`]. + /// + /// # Safety + /// Caller must ensure that BundleId exists in `world.bundles` #[inline] - pub(crate) fn new_with_info( - world: UnsafeWorldCell<'w>, - bundle_info: &'w BundleInfo, + pub(crate) unsafe fn new_with_id( + world: &'w mut World, + bundle_id: BundleId, change_tick: Tick, ) -> Self { - // SAFETY: We will not make any accesses to the command queue, component or resource data of this world - let (world, deferred_world) = unsafe { world.split_deferred() }; + let bundle_info = world.bundles.get_unchecked(bundle_id); let new_archetype_id = bundle_info.add_bundle_to_archetype( &mut world.archetypes, &mut world.storages, @@ -853,20 +878,20 @@ impl<'w> BundleSpawner<'w> { let archetype = &mut world.archetypes[new_archetype_id]; let table = &mut world.storages.tables[archetype.table_id()]; Self { - world: deferred_world, bundle_info, - archetype, table, - sparse_sets: &mut world.storages.sparse_sets, - entities: &mut world.entities, + archetype, change_tick, + world: world.as_unsafe_world_cell(), } } #[inline] pub fn reserve_storage(&mut self, additional: usize) { - self.archetype.reserve(additional); - self.table.reserve(additional); + // SAFETY: There are no oustanding world references + let (archetype, table) = unsafe { (&mut *self.archetype, &mut *self.table) }; + archetype.reserve(additional); + table.reserve(additional); } /// # Safety @@ -877,29 +902,41 @@ impl<'w> BundleSpawner<'w> { entity: Entity, bundle: T, ) -> EntityLocation { - if self.archetype.has_on_add() { - self.world - .trigger_on_add(entity, self.bundle_info.iter_components()); + // SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid + let bundle_info = &*self.bundle_info; + let location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let (sparse_sets, entities) = { + let world = self.world.world_mut(); + (&mut world.storages.sparse_sets, &mut world.entities) + }; + let table_row = table.allocate(entity); + let location = archetype.allocate(entity, table_row); + bundle_info.write_components( + table, + sparse_sets, + &SpawnBundleStatus, + entity, + table_row, + self.change_tick, + bundle, + ); + entities.set(entity.index(), location); + location + }; + + // SAFETY: We have no oustanding mutable references to world as they were dropped + let archetype = &*self.archetype; + let mut world = self.world.into_deferred(); + if archetype.has_on_add() { + world.trigger_on_add(entity, bundle_info.iter_components()); } - if self.archetype.has_on_insert() { - self.world - .trigger_on_insert(entity, self.bundle_info.iter_components()); + if archetype.has_on_insert() { + world.trigger_on_insert(entity, bundle_info.iter_components()); } - let table = &mut *self.table; - let table_row = table.allocate(entity); - let location = self.archetype.allocate(entity, table_row); - self.bundle_info.write_components( - table, - self.sparse_sets, - &SpawnBundleStatus, - entity, - table_row, - self.change_tick, - bundle, - ); - self.entities.set(entity.index(), location); - location } @@ -907,7 +944,7 @@ impl<'w> BundleSpawner<'w> { /// `T` must match this [`BundleInfo`]'s type #[inline] pub unsafe fn spawn(&mut self, bundle: T) -> Entity { - let entity = self.entities.alloc(); + let entity = self.entities().alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type self.spawn_non_existent(entity, bundle); entity @@ -915,19 +952,16 @@ impl<'w> BundleSpawner<'w> { #[inline] pub(crate) fn entities(&mut self) -> &mut Entities { - self.entities + // SAFETY: No outstanding references to self.world, changes to entities cannot invalidate our internal pointers + unsafe { &mut self.world.world_mut().entities } } /// # Safety: /// - `Self` must be dropped after running this function as it may invalidate internal pointers. - /// - Caller must ensure that no there are no outstanding access to `self.world` #[inline] pub(crate) unsafe fn flush_commands(&mut self) { // SAFETY: pointers on self can be invalidated, - self.world - .as_unsafe_world_cell_readonly() - .world_mut() - .flush_commands(); + self.world.world_mut().flush_commands(); } } @@ -988,26 +1022,17 @@ impl Bundles { self.bundle_infos.get_unchecked(id.0) } - pub(crate) unsafe fn get_with_storage_unchecked( - &self, - id: BundleId, - ) -> (&BundleInfo, &StorageType) { - ( - self.bundle_infos.get_unchecked(id.0), - self.dynamic_component_storages - .get(&id) - .debug_checked_unwrap(), - ) + pub(crate) unsafe fn get_storage_unchecked(&self, id: BundleId) -> StorageType { + *self + .dynamic_component_storages + .get(&id) + .debug_checked_unwrap() } - pub(crate) unsafe fn get_with_storages_unchecked( - &self, - id: BundleId, - ) -> (&BundleInfo, &Vec) { - ( - self.bundle_infos.get_unchecked(id.0), - self.dynamic_bundle_storages.get(&id).debug_checked_unwrap(), - ) + pub(crate) unsafe fn get_storages_unchecked(&mut self, id: BundleId) -> &mut Vec { + self.dynamic_bundle_storages + .get_mut(&id) + .debug_checked_unwrap() } /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`]. @@ -1086,3 +1111,145 @@ fn initialize_dynamic_bundle( (id, storage_types) } + +mod tests { + use crate as bevy_ecs; + use crate::prelude::*; + + #[derive(Component)] + struct A; + + #[derive(Component)] + struct B; + + #[derive(Component)] + struct C; + + #[derive(Component)] + struct D; + + #[derive(Resource, Default)] + struct R(usize); + + impl R { + fn assert_order(&mut self, count: usize) { + assert_eq!(count, self.0); + self.0 += 1; + } + } + + #[test] + fn component_hook_order_spawn_despawn() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, _, _| { + world.resource_mut::().assert_order(0); + }) + .on_insert(|mut world, _, _| world.resource_mut::().assert_order(1)) + .on_remove(|mut world, _, _| world.resource_mut::().assert_order(2)); + + let entity = world.spawn(A).id(); + world.despawn(entity); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn component_hook_order_insert_remove() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, _, _| { + world.resource_mut::().assert_order(0); + }) + .on_insert(|mut world, _, _| { + world.resource_mut::().assert_order(1); + }) + .on_remove(|mut world, _, _| { + world.resource_mut::().assert_order(2); + }); + + let mut entity = world.spawn_empty(); + entity.insert(A); + entity.remove::(); + entity.flush(); + assert_eq!(3, world.resource::().0); + } + + #[test] + fn component_hook_order_recursive() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(0); + + world.with_commands(|mut commands| { + commands.entity(entity).insert(B); + }); + }) + .on_remove(|mut world, entity, _| { + world.resource_mut::().assert_order(2); + + world.with_commands(|mut commands| { + commands.entity(entity).remove::(); + }); + }); + + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(1); + + world.with_commands(|mut commands| { + commands.entity(entity).remove::(); + }); + }) + .on_remove(|mut world, _, _| { + world.resource_mut::().assert_order(3); + }); + + let entity = world.spawn(A).flush(); + let entity = world.get_entity(entity).unwrap(); + assert_eq!(false, entity.contains::()); + assert_eq!(false, entity.contains::()); + assert_eq!(4, world.resource::().0); + } + + #[test] + fn component_hook_order_recursive_multiple() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(0); + world.with_commands(|mut commands| { + commands.entity(entity).insert(B).insert(D); + }); + }); + + world + .register_component::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(1); + world.with_commands(|mut commands| { + commands.entity(entity).insert(C); + }); + }); + + world.register_component::().on_add(|mut world, _, _| { + world.resource_mut::().assert_order(2); + }); + + world.register_component::().on_add(|mut world, _, _| { + world.resource_mut::().assert_order(3); + }); + + world.spawn(A).flush(); + assert_eq!(4, world.resource::().0); + } +} diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 5a9d7269d1032..f74c78673bbf7 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -280,6 +280,10 @@ impl World { .take::>() .ok_or(RegisteredSystemError::Recursive(id))?; + // Flush commands from removing component + // TODO: Consider refactoring to an RegisteredSystem to an Option to avoid removal + drop(entity); + // run the system if !initialized { system.initialize(self); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index d12ba0b4248b2..1f688f15e3911 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -29,9 +29,12 @@ impl<'w> Deref for DeferredWorld<'w> { impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue - pub fn commands(&mut self) -> Commands { - // SAFETY: Commands::new only take &World which has no way to access the command queue - unsafe { Commands::new(self.world.get_command_queue(), self.world.world()) } + #[inline] + pub fn with_commands(&mut self, f: impl Fn(Commands)) { + let world = unsafe { self.world.world_mut() }; + let mut queue = std::mem::take(&mut world.command_queue); + f(Commands::new(&mut queue, world)); + world.command_queue = std::mem::take(&mut queue); } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -276,14 +279,6 @@ impl<'w> UnsafeWorldCell<'w> { pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { DeferredWorld { world: self } } - - /// Turn self into a mutable world reference and a [`DeferredWorld`] - /// - /// # Safety - /// Caller must ensure that the world reference is not used to construct any references to the world's command queue, resource or component data - pub unsafe fn split_deferred(self) -> (&'w mut World, DeferredWorld<'w>) { - (self.world_mut(), self.into_deferred()) - } } impl<'w> From<&'w mut World> for DeferredWorld<'w> { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c7763d7c87c82..49a74a0bccf6a 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -8,7 +8,6 @@ use crate::{ world::{Mut, World}, }; use bevy_ptr::{OwningPtr, Ptr}; -use bevy_utils::tracing::debug; use std::{any::TypeId, marker::PhantomData}; use super::{unsafe_world_cell::UnsafeEntityCell, Ref}; @@ -572,7 +571,6 @@ impl<'w> EntityWorldMut<'w> { BundleInserter::new::(self.world, self.location.archetype_id, change_tick); // SAFETY: location matches current entity. `T` matches `bundle_info` self.location = unsafe { bundle_inserter.insert(self.entity, self.location, bundle) }; - self.world.flush_commands(); self } @@ -596,13 +594,12 @@ impl<'w> EntityWorldMut<'w> { .world .bundles .init_component_info(&self.world.components, component_id); - let (bundle_info, storage_type) = self.world.bundles.get_with_storage_unchecked(bundle_id); + let storage_type = self.world.bundles.get_storage_unchecked(bundle_id); - let bundle_inserter = BundleInserter::new_with_info( - // SAFETY: The only outstanding reference to world is bundle_info - self.world.as_unsafe_world_cell_readonly(), + let bundle_inserter = BundleInserter::new_with_id( + self.world, self.location.archetype_id, - bundle_info, + bundle_id, change_tick, ); @@ -611,9 +608,8 @@ impl<'w> EntityWorldMut<'w> { self.entity, self.location, Some(component).into_iter(), - Some(*storage_type).iter().cloned(), + Some(storage_type).iter().cloned(), ); - self.world.flush_commands(); self } @@ -639,14 +635,12 @@ impl<'w> EntityWorldMut<'w> { .world .bundles .init_dynamic_info(&self.world.components, component_ids); - let (bundle_info, storage_types) = - self.world.bundles.get_with_storages_unchecked(bundle_id); - - let bundle_inserter = BundleInserter::new_with_info( - // SAFETY: The only outstanding reference to world is bundle_info - self.world.as_unsafe_world_cell_readonly(), + let mut storage_types = + std::mem::take(self.world.bundles.get_storages_unchecked(bundle_id)); + let bundle_inserter = BundleInserter::new_with_id( + self.world, self.location.archetype_id, - bundle_info, + bundle_id, change_tick, ); @@ -657,7 +651,7 @@ impl<'w> EntityWorldMut<'w> { iter_components, (*storage_types).iter().cloned(), ); - self.world.flush_commands(); + *self.world.bundles.get_storages_unchecked(bundle_id) = std::mem::take(&mut storage_types); self } @@ -668,17 +662,18 @@ impl<'w> EntityWorldMut<'w> { // TODO: BundleRemover? #[must_use] pub fn take(&mut self) -> Option { - let storages = &mut self.world.storages; - let components = &mut self.world.components; - let bundle_id = self.world.bundles.init_info::(components, storages); + let world = &mut self.world; + let storages = &mut world.storages; + let components = &mut world.components; + let bundle_id = world.bundles.init_info::(components, storages); // SAFETY: We just ensured this bundle exists - let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, // components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T` let new_archetype_id = unsafe { remove_bundle_from_archetype( - &mut self.world.archetypes, + &mut world.archetypes, storages, components, old_location.archetype_id, @@ -691,25 +686,32 @@ impl<'w> EntityWorldMut<'w> { return None; } - // Reborrow so world is not mutably borrowed let entity = self.entity; - let old_archetype = &self.world.archetypes[old_location.archetype_id]; + let old_archetype = &world.archetypes[old_location.archetype_id]; + + // Drop borrow on world so it can be turned into DeferredWorld + // SAFETY: Changes to Bundles cannot happen through DeferredWorld + let bundle_info = { + let bundle_info: *const BundleInfo = bundle_info; + unsafe { &*bundle_info } + }; if old_archetype.has_on_remove() { - // SAFETY: None of our oustanding references can be modified through DeferredWorld + // SAFETY: All components in the archetype exist in world unsafe { - self.world - .as_unsafe_world_cell_readonly() + world + .as_unsafe_world_cell() .into_deferred() .trigger_on_remove(entity, bundle_info.iter_components()); - }; + } } + for component_id in bundle_info.iter_components() { - self.world.removed_components.send(component_id, entity); + world.removed_components.send(component_id, entity); } - let archetypes = &mut self.world.archetypes; - let storages = &mut self.world.storages; - let components = &mut self.world.components; - let entities = &mut self.world.entities; + let archetypes = &mut world.archetypes; + let storages = &mut world.storages; + let components = &mut world.components; + let entities = &mut world.entities; let entity = self.entity; let mut bundle_components = bundle_info.iter_components(); @@ -739,8 +741,6 @@ impl<'w> EntityWorldMut<'w> { new_archetype_id, ); } - drop(bundle_components); - self.world.flush_commands(); Some(result) } @@ -828,13 +828,14 @@ impl<'w> EntityWorldMut<'w> { /// Removes any components in the [`Bundle`] from the entity. // TODO: BundleRemover? pub fn remove(&mut self) -> &mut Self { - let archetypes = &mut self.world.archetypes; - let storages = &mut self.world.storages; - let components = &mut self.world.components; + let world = &mut self.world; + let archetypes = &mut world.archetypes; + let storages = &mut world.storages; + let components = &mut world.components; - let bundle_id = self.world.bundles.init_info::(components, storages); + let bundle_id = world.bundles.init_info::(components, storages); // SAFETY: We just ensured this bundle exists - let bundle_info = unsafe { self.world.bundles.get_unchecked(bundle_id) }; + let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; let old_location = self.location; // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, @@ -855,26 +856,34 @@ impl<'w> EntityWorldMut<'w> { return self; } - // Reborrow so world is not mutably borrowed let entity: Entity = self.entity; - let old_archetype = &self.world.archetypes[old_location.archetype_id]; + let old_archetype = &world.archetypes[old_location.archetype_id]; + + // Drop borrow on world so it can be turned into DeferredWorld + // SAFETY: Changes to Bundles cannot happen through DeferredWorld + let bundle_info = { + let bundle_info: *const BundleInfo = bundle_info; + unsafe { &*bundle_info } + }; if old_archetype.has_on_remove() { - // SAFETY: None of our oustanding references can be modified through DeferredWorld + // SAFETY: All components in the archetype exist in world unsafe { - self.world - .as_unsafe_world_cell_readonly() + world + .as_unsafe_world_cell() .into_deferred() .trigger_on_remove(entity, bundle_info.iter_components()); - }; + } } + + let old_archetype = &world.archetypes[old_location.archetype_id]; for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { - self.world.removed_components.send(component_id, entity); + world.removed_components.send(component_id, entity); // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. if let Some(StorageType::SparseSet) = old_archetype.get_storage_type(component_id) { - self.world + world .storages .sparse_sets .get_mut(component_id) @@ -891,38 +900,41 @@ impl<'w> EntityWorldMut<'w> { &mut self.location, old_location.archetype_id, old_location, - &mut self.world.entities, - &mut self.world.archetypes, - &mut self.world.storages, + &mut world.entities, + &mut world.archetypes, + &mut world.storages, new_archetype_id, ); } - self.world.flush_commands(); self } /// Despawns the current entity. pub fn despawn(self) { - debug!("Despawning entity {:?}", self.entity); - self.world.flush_entities(); - let archetype = &self.world.archetypes[self.location.archetype_id]; + let world = self.world; + world.flush_entities(); + let archetype = &world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { - // SAFETY: None of our oustanding references can be modified through DeferredWorld + // Drop borrow on world so it can be turned into DeferredWorld + // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld + let archetype = { + let archetype: *const Archetype = archetype; + unsafe { &*archetype } + }; + // SAFETY: All components in the archetype exist in world unsafe { - self.world - .as_unsafe_world_cell_readonly() + world + .as_unsafe_world_cell() .into_deferred() .trigger_on_remove(self.entity, archetype.components()); - }; + } } + let archetype = &world.archetypes[self.location.archetype_id]; for component_id in archetype.components() { - self.world - .removed_components - .send(component_id, self.entity); + world.removed_components.send(component_id, self.entity); } - let world = self.world; let location = world .entities .free(self.entity) @@ -979,7 +991,13 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } - world.flush_commands(); + world.flush_commands() + } + + /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`] + pub fn flush(self) -> Entity { + self.world.flush_commands(); + self.entity } /// Gets read-only access to the world that the current entity belongs to. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 12fc45066a817..d1d46429527d6 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1457,8 +1457,6 @@ impl World { let bundle_id = self .bundles .init_info::(&mut self.components, &mut self.storages); - // SAFETY: We just ensured this bundle exists - let bundle_info = unsafe { self.bundles.get_unchecked(bundle_id) }; enum SpawnOrInsert<'w> { Spawn(BundleSpawner<'w>), Insert(BundleInserter<'w>, ArchetypeId), @@ -1472,11 +1470,10 @@ impl World { } } } - let mut spawn_or_insert = SpawnOrInsert::Spawn(BundleSpawner::new_with_info( - self.as_unsafe_world_cell_readonly(), - bundle_info, - change_tick, - )); + // SAFETY: we initialized this bundle_id in `init_info` + let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { + BundleSpawner::new_with_id(self, bundle_id, change_tick) + }); let mut invalid_entities = Vec::new(); for (entity, bundle) in iter { @@ -1493,12 +1490,15 @@ impl World { unsafe { inserter.insert(entity, location, bundle) }; } _ => { - let mut inserter = BundleInserter::new_with_info( - self.as_unsafe_world_cell_readonly(), - location.archetype_id, - bundle_info, - change_tick, - ); + // SAFETY: we initialized this bundle_id in `init_info` + let mut inserter = unsafe { + BundleInserter::new_with_id( + self, + location.archetype_id, + bundle_id, + change_tick, + ) + }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = @@ -1511,11 +1511,9 @@ impl World { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; } else { - let mut spawner = BundleSpawner::new_with_info( - self.as_unsafe_world_cell_readonly(), - bundle_info, - change_tick, - ); + // SAFETY: we initialized this bundle_id in `init_info` + let mut spawner = + unsafe { BundleSpawner::new_with_id(self, bundle_id, change_tick) }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 1056549a7a059..25d5ac62c0305 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -14,7 +14,7 @@ use crate::{ prelude::Component, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, - system::{CommandQueue, Resource}, + system::Resource, }; use bevy_ptr::Ptr; use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData}; @@ -566,16 +566,6 @@ impl<'w> UnsafeWorldCell<'w> { .get(component_id)? .get_with_ticks() } - - // Returns a mutable reference to worlds internal [`CommandQueue`] - /// # Safety - /// It is the callers responsibility to ensure that no mutable references exist to the command queue at the same time - #[inline] - pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue { - // SAFETY: caller ensures there is no `&mut World` - let world = unsafe { &mut *self.0 }; - &mut world.command_queue - } } impl Debug for UnsafeWorldCell<'_> { diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 8e8e63de0322f..78deb6f2f5966 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -1,42 +1,81 @@ -use std::collections::HashSet; +//! This examples illustrates the different ways you can employ component lifecycle hooks use bevy::prelude::*; +use std::collections::HashMap; #[derive(Component, Debug)] -struct MyComponent(usize); +struct MyComponent(KeyCode); #[derive(Resource, Default, Debug, Deref, DerefMut)] -struct MyComponentIndex(HashSet); +struct MyComponentIndex(HashMap); + +#[derive(Event)] +struct MyEvent; fn main() { App::new() - .add_systems(Startup, (setup, trigger_hooks).chain()) + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, trigger_hooks) .init_resource::() + .add_event::() .run(); } fn setup(world: &mut World) { + // In order to register component hooks it must be the first time you register the component + // This is to prevent users from overwriting hooks that may be used internally in foreign crates world .register_component::() - .on_add(|mut world, entity, _| { - println!("Added MyComponent to: {:?}", entity); - world.resource_mut::().insert(entity); + // There are 3 component lifecyle hooks: `on_add`, `on_insert` and `on_remove` + // A hook has 3 arguments: + // - a `DeferredWorld`, this allows access to resource and component data as well as `Commands` + // - the entity that triggered the hook + // - the component id of the triggering component, this is mostly used for dynamic components + // + // `on_add` will trigger when a component is inserted onto an entity without it + .on_add(|mut world, entity, component_id| { + // You can access component data from within the hook + let value = world.get::(entity).unwrap().0; + println!( + "Component: {:?} added to: {:?} with value {:?}", + component_id, entity, value + ); + // Or access resources + world + .resource_mut::() + .insert(value, entity); + // Or send events + world.send_event(MyEvent); + }) + // `on_insert` will trigger when a component is inserted onto an entity, + // regardless of whether or not it already had it and after `on_add` if it ran + .on_insert(|world, _, _| { + println!("Current Index: {:?}", world.resource::()); }) - .on_remove(|mut world, entity, _| { + // `on_remove` will trigger when a component is removed from an entity, + // since it runs before the component is removed you can still access the component data + .on_remove(|mut world, entity, component_id| { + let value = world.get::(entity).unwrap().0; println!( - "Removed MyComponent from: {:?} {:?}", - entity, - world.get::(entity) + "Component: {:?} removed from: {:?} with value {:?}", + component_id, entity, value ); - let mut index = world.resource_mut::(); - index.remove(&entity); - println!("Current index: {:?}", *index); + world.resource_mut::().remove(&value); + // You can also issue commands through `.with_commands` + world.with_commands(|mut commands| { + commands.entity(entity).despawn(); + }); }); } -fn trigger_hooks(mut commands: Commands) { - let entity_a = commands.spawn(MyComponent(0)).id(); - let entity_b = commands.spawn(MyComponent(1)).id(); - commands.entity(entity_b).despawn(); - commands.entity(entity_a).despawn(); +fn trigger_hooks(mut commands: Commands, keys: Res>, index: Res) { + for (key, entity) in index.iter() { + if !keys.pressed(*key) { + commands.entity(*entity).remove::(); + } + } + for key in keys.get_just_pressed() { + commands.spawn(MyComponent(*key)); + } } From 9b76944ea7cffb4485760e05e5b1830ce9be2805 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 16:38:13 -0800 Subject: [PATCH 08/35] Add missing cfg(test) --- crates/bevy_ecs/src/bundle.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index a32f98a9b0ce6..5a7c793f2663c 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1112,6 +1112,7 @@ fn initialize_dynamic_bundle( (id, storage_types) } +#[cfg(test)] mod tests { use crate as bevy_ecs; use crate::prelude::*; From 924fb5dad89618d22ea31dd3c7dfbb56a9f10c91 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 16:43:28 -0800 Subject: [PATCH 09/35] Tidy iterator chains --- crates/bevy_ecs/src/bundle.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 5a7c793f2663c..9ee95c210567b 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -658,11 +658,9 @@ impl<'w> BundleInserter<'w> { entity, bundle_info .iter_components() - .enumerate() - .filter(|(index, _)| { - add_bundle.get_status(*index) == ComponentStatus::Added - }) - .map(|(_, id)| id), + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), ); } if archetype.has_on_insert() { @@ -718,11 +716,9 @@ impl<'w> BundleInserter<'w> { entity, bundle_info .iter_components() - .enumerate() - .filter(|(index, _)| { - add_bundle.get_status(*index) == ComponentStatus::Added - }) - .map(|(_, id)| id), + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), ); } if new_archetype.has_on_insert() { @@ -817,11 +813,9 @@ impl<'w> BundleInserter<'w> { entity, bundle_info .iter_components() - .enumerate() - .filter(|(index, _)| { - add_bundle.get_status(*index) == ComponentStatus::Added - }) - .map(|(_, id)| id), + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), ); } if new_archetype.has_on_insert() { From d23d1b23ee17dd5e358f03e0c2ecd00d61dad074 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 17:00:51 -0800 Subject: [PATCH 10/35] Cleanup --- Cargo.toml | 2 +- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/query/builder.rs | 422 ------------------ crates/bevy_ecs/src/system/system_registry.rs | 4 - crates/bevy_ecs/src/world/deferred_world.rs | 6 +- crates/bevy_ecs/src/world/entity_ref.rs | 13 +- crates/bevy_ecs/src/world/mod.rs | 3 +- 7 files changed, 18 insertions(+), 434 deletions(-) delete mode 100644 crates/bevy_ecs/src/query/builder.rs diff --git a/Cargo.toml b/Cargo.toml index addf71590a71c..b233686e88a5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1301,7 +1301,7 @@ doc-scrape-examples = true [package.metadata.example.component_hooks] name = "Component Hooks" -description = "Define component hooks to react to ECS events" +description = "Define component hooks to manage component lifecycle events" category = "ECS (Entity Component System)" wasm = false diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 9ee95c210567b..f8b563bdf9325 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -619,7 +619,7 @@ impl<'w> BundleInserter<'w> { } } /// # Safety - /// `entity` must currently exist in the source archetype for this inserter. `archetype_row` + /// `entity` must currently exist in the source archetype for this inserter. `location` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type #[inline] pub(crate) unsafe fn insert( diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs deleted file mode 100644 index d1beaa513b55f..0000000000000 --- a/crates/bevy_ecs/src/query/builder.rs +++ /dev/null @@ -1,422 +0,0 @@ -use std::marker::PhantomData; - -use crate::{component::ComponentId, prelude::*}; - -use super::{FilteredAccess, ReadOnlyWorldQuery, WorldQuery}; - -/// Builder struct to create [`QueryState`] instances at runtime. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # -/// # #[derive(Component)] -/// # struct A; -/// # -/// # #[derive(Component)] -/// # struct B; -/// # -/// # #[derive(Component)] -/// # struct C; -/// # -/// let mut world = World::new(); -/// let entity_a = world.spawn((A, B)).id(); -/// let entity_b = world.spawn((A, C)).id(); -/// -/// // Instantiate the builder using the type signature of the iterator you will consume -/// let mut query = QueryBuilder::<(Entity, &B)>::new(&mut world) -/// // Add additional terms through builder methods -/// .with::() -/// .without::() -/// .build(); -/// -/// // Consume the QueryState -/// let (entity, b) = query.single(&world); -///``` -pub struct QueryBuilder<'w, Q: WorldQuery = (), F: ReadOnlyWorldQuery = ()> { - access: FilteredAccess, - world: &'w mut World, - or: bool, - first: bool, - _marker: PhantomData<(Q, F)>, -} - -impl<'w, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryBuilder<'w, Q, F> { - /// Creates a new builder with the accesses required for `Q` and `F` - pub fn new(world: &'w mut World) -> Self { - let fetch_state = Q::init_state(world); - let filter_state = F::init_state(world); - - let mut access = FilteredAccess::default(); - Q::update_component_access(&fetch_state, &mut access); - - // Use a temporary empty FilteredAccess for filters. This prevents them from conflicting with the - // main Query's `fetch_state` access. Filters are allowed to conflict with the main query fetch - // because they are evaluated *before* a specific reference is constructed. - let mut filter_access = FilteredAccess::default(); - F::update_component_access(&filter_state, &mut filter_access); - - // Merge the temporary filter access with the main access. This ensures that filter access is - // properly considered in a global "cross-query" context (both within systems and across systems). - access.extend(&filter_access); - - Self { - access, - world, - or: false, - first: false, - _marker: PhantomData, - } - } - - /// Returns a reference to the world passed to [`Self::new`]. - pub fn world(&self) -> &World { - self.world - } - - /// Returns a reference to the world passed to [`Self::new`]. - pub fn world_mut(&mut self) -> &mut World { - self.world - } - - /// Adds access to self's underlying [`FilteredAccess`] respecting [`Self::or`] and [`Self::and`] - pub fn extend_access(&mut self, mut access: FilteredAccess) { - if self.or { - if self.first { - access.required.clear(); - self.access.extend(&access); - self.first = false; - } else { - self.access.append_or(&access); - } - } else { - self.access.extend(&access); - } - } - - /// Adds accesses required for `T` to self. - pub fn push(&mut self) -> &mut Self { - let state = T::init_state(self.world); - let mut access = FilteredAccess::default(); - T::update_component_access(&state, &mut access); - self.extend_access(access); - self - } - - /// Adds [`With`] to the [`FilteredAccess`] of self. - pub fn with(&mut self) -> &mut Self { - self.push::>(); - self - } - - /// Adds [`With`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. - pub fn with_id(&mut self, id: ComponentId) -> &mut Self { - let mut access = FilteredAccess::default(); - access.and_with(id); - self.extend_access(access); - self - } - - /// Adds [`Without`] to the [`FilteredAccess`] of self. - pub fn without(&mut self) -> &mut Self { - self.push::>(); - self - } - - /// Adds [`Without`] to the [`FilteredAccess`] of self from a runtime [`ComponentId`]. - pub fn without_id(&mut self, id: ComponentId) -> &mut Self { - let mut access = FilteredAccess::default(); - access.and_without(id); - self.extend_access(access); - self - } - - /// Adds `&T` to the [`FilteredAccess`] of self. - pub fn ref_id(&mut self, id: ComponentId) -> &mut Self { - self.with_id(id); - self.access.add_read(id); - self - } - - /// Adds `&mut T` to the [`FilteredAccess`] of self. - pub fn mut_id(&mut self, id: ComponentId) -> &mut Self { - self.with_id(id); - self.access.add_write(id); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder and then adds all accesses from that builder to self as optional. - pub fn optional(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - f(&mut builder); - self.access.extend_access(builder.access()); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder and then adds all accesses from that builder to self. - /// - /// Primarily used when inside a [`Self::or`] closure to group several terms. - pub fn and(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - f(&mut builder); - let access = builder.access().clone(); - self.extend_access(access); - self - } - - /// Takes a function over mutable access to a [`QueryBuilder`], calls that function - /// on an empty builder, all accesses added to that builder will become terms in an or expression. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct A; - /// # - /// # #[derive(Component)] - /// # struct B; - /// # - /// # let mut world = World::new(); - /// # - /// QueryBuilder::::new(&mut world).or(|builder| { - /// builder.with::(); - /// builder.with::(); - /// }); - /// // is equivalent to - /// QueryBuilder::::new(&mut world).push::, With)>>(); - /// ``` - pub fn or(&mut self, f: impl Fn(&mut QueryBuilder)) -> &mut Self { - let mut builder = QueryBuilder::new(self.world); - builder.or = true; - builder.first = true; - f(&mut builder); - self.access.extend(builder.access()); - self - } - - /// Returns a reference to the the [`FilteredAccess`] that will be provided to the built [`Query`]. - pub fn access(&self) -> &FilteredAccess { - &self.access - } - - /// Transmute the existing builder adding required accesses. - /// This will maintain all exisiting accesses. - /// - /// If including a filter type see [`Self::transmute_filtered`] - pub fn transmute(&mut self) -> &mut QueryBuilder<'w, NewQ> { - self.transmute_filtered::() - } - - /// Transmute the existing builder adding required accesses. - /// This will maintain all existing accesses. - pub fn transmute_filtered( - &mut self, - ) -> &mut QueryBuilder<'w, NewQ, NewF> { - let mut fetch_state = NewQ::init_state(self.world); - let filter_state = NewF::init_state(self.world); - - NewQ::set_access(&mut fetch_state, &self.access); - - let mut access = FilteredAccess::default(); - NewQ::update_component_access(&fetch_state, &mut access); - NewF::update_component_access(&filter_state, &mut access); - - self.extend_access(access); - // SAFETY: - // - We have included all required acceses for NewQ and NewF - // - The layout of all QueryBuilder instances is the same - unsafe { std::mem::transmute(self) } - } - - /// Create a [`QueryState`] with the accesses of the builder. - pub fn build(&mut self) -> QueryState { - QueryState::::from_builder(self) - } -} - -#[cfg(test)] -mod tests { - use crate as bevy_ecs; - use crate::prelude::*; - use crate::world::FilteredEntityRef; - - use super::QueryBuilder; - - #[derive(Component, PartialEq, Debug)] - struct A(usize); - - #[derive(Component, PartialEq, Debug)] - struct B(usize); - - #[derive(Component, PartialEq, Debug)] - struct C(usize); - - #[test] - fn builder_with_without_static() { - let mut world = World::new(); - let entity_a = world.spawn((A(0), B(0))).id(); - let entity_b = world.spawn((A(0), C(0))).id(); - - let mut query_a = QueryBuilder::::new(&mut world) - .with::() - .without::() - .build(); - assert_eq!(entity_a, query_a.single(&world)); - - let mut query_b = QueryBuilder::::new(&mut world) - .with::() - .without::() - .build(); - assert_eq!(entity_b, query_b.single(&world)); - } - - #[test] - fn builder_with_without_dynamic() { - let mut world = World::new(); - let entity_a = world.spawn((A(0), B(0))).id(); - let entity_b = world.spawn((A(0), C(0))).id(); - let component_id_a = world.init_component::().id(); - let component_id_b = world.init_component::().id(); - let component_id_c = world.init_component::().id(); - - let mut query_a = QueryBuilder::::new(&mut world) - .with_id(component_id_a) - .without_id(component_id_c) - .build(); - assert_eq!(entity_a, query_a.single(&world)); - - let mut query_b = QueryBuilder::::new(&mut world) - .with_id(component_id_a) - .without_id(component_id_b) - .build(); - assert_eq!(entity_b, query_b.single(&world)); - } - - #[test] - fn builder_or() { - let mut world = World::new(); - world.spawn((A(0), B(0))); - world.spawn(B(0)); - world.spawn(C(0)); - - let mut query_a = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.with::(); - }) - .build(); - assert_eq!(2, query_a.iter(&world).count()); - - let mut query_b = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.without::(); - }) - .build(); - dbg!(&query_b.component_access); - assert_eq!(2, query_b.iter(&world).count()); - - let mut query_c = QueryBuilder::::new(&mut world) - .or(|builder| { - builder.with::(); - builder.with::(); - builder.with::(); - }) - .build(); - assert_eq!(3, query_c.iter(&world).count()); - } - - #[test] - fn builder_transmute() { - let mut world = World::new(); - world.spawn(A(0)); - world.spawn((A(1), B(0))); - let mut query = QueryBuilder::<()>::new(&mut world) - .with::() - .transmute::<&A>() - .build(); - - query.iter(&world).for_each(|a| assert_eq!(a.0, 1)); - } - - #[test] - fn builder_static_components() { - let mut world = World::new(); - let entity = world.spawn((A(0), B(1))).id(); - - let mut query = QueryBuilder::::new(&mut world) - .push::<&A>() - .push::<&B>() - .build(); - - let entity_ref = query.single(&world); - - assert_eq!(entity, entity_ref.id()); - - let a = entity_ref.get::().unwrap(); - let b = entity_ref.get::().unwrap(); - - assert_eq!(0, a.0); - assert_eq!(1, b.0); - } - - #[test] - fn builder_dynamic_components() { - let mut world = World::new(); - let entity = world.spawn((A(0), B(1))).id(); - let component_id_a = world.init_component::().id(); - let component_id_b = world.init_component::().id(); - - let mut query = QueryBuilder::::new(&mut world) - .ref_id(component_id_a) - .ref_id(component_id_b) - .build(); - - let entity_ref = query.single(&world); - - assert_eq!(entity, entity_ref.id()); - - let a = entity_ref.get_by_id(component_id_a).unwrap(); - let b = entity_ref.get_by_id(component_id_b).unwrap(); - - // SAFETY: We set these pointers to point to these components - unsafe { - assert_eq!(0, a.deref::().0); - assert_eq!(1, b.deref::().0); - } - } - - #[test] - fn builder_query_system() { - let mut world = World::new(); - world.spawn(A(0)); - let entity = world.spawn((A(1), B(0))).id(); - - let sys = move |query: Query<(Entity, &A)>| { - let (e, a) = query.single(); - assert_eq!(e, entity); - assert_eq!(1, a.0); - }; - - // Add additional terms that don't appear in the original query - let query = QueryBuilder::<(Entity, &A)>::new(&mut world) - .with::() - .build(); - let mut system = IntoSystem::into_system(sys); - system.initialize(&mut world); - - // SAFETY: We know the system param we are modifying has a compatible type signature - unsafe { system.state_mut().0 = query }; - system.run((), &mut world); - - // Alternatively truncate terms from a query to match the system - let query = QueryBuilder::<(Entity, &A, &B)>::new(&mut world).build(); - let mut system = IntoSystem::into_system(sys); - system.initialize(&mut world); - - // SAFETY: We know the system param we are modifying has a compatible type signature - unsafe { system.state_mut().0 = query.transmute(&world) }; - system.run((), &mut world); - } -} diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index f74c78673bbf7..5a9d7269d1032 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -280,10 +280,6 @@ impl World { .take::>() .ok_or(RegisteredSystemError::Recursive(id))?; - // Flush commands from removing component - // TODO: Consider refactoring to an RegisteredSystem to an Option to avoid removal - drop(entity); - // run the system if !initialized { system.initialize(self); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 1f688f15e3911..da1d30e6f67d4 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -12,8 +12,8 @@ use crate::{ use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; -/// An [`World`] reference that prevents structural ECS changes. -/// This includes creating tables, registering components or spawning entities. +/// A [`World`] reference that disallows structural ECS changes. +/// This includes initializing resources, registering components or spawning entities. pub struct DeferredWorld<'w> { world: UnsafeWorldCell<'w>, } @@ -22,7 +22,7 @@ impl<'w> Deref for DeferredWorld<'w> { type Target = World; fn deref(&self) -> &Self::Target { - // SAFETY: &self ensures ther are no active mutable borrows + // SAFETY: &self ensures there are no active mutable borrows unsafe { self.world.world() } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 49a74a0bccf6a..b115c943e09a3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,6 +4,7 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, + removal_detection::RemovedComponentEvents, storage::Storages, world::{Mut, World}, }; @@ -712,6 +713,7 @@ impl<'w> EntityWorldMut<'w> { let storages = &mut world.storages; let components = &mut world.components; let entities = &mut world.entities; + let removed_components = &mut world.removed_components; let entity = self.entity; let mut bundle_components = bundle_info.iter_components(); @@ -724,7 +726,14 @@ impl<'w> EntityWorldMut<'w> { // - entity location is valid // - table row is removed below, without dropping the contents // - `components` comes from the same world as `storages` - take_component(storages, components, component_id, entity, old_location) + take_component( + storages, + components, + removed_components, + component_id, + entity, + old_location, + ) }) }; @@ -1632,12 +1641,14 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { pub(crate) unsafe fn take_component<'a>( storages: &'a mut Storages, components: &Components, + removed_components: &mut RemovedComponentEvents, component_id: ComponentId, entity: Entity, location: EntityLocation, ) -> OwningPtr<'a> { // SAFETY: caller promises component_id to be valid let component_info = components.get_info_unchecked(component_id); + removed_components.send(component_id, entity); match component_info.storage_type() { StorageType::Table => { let table = &mut storages.tables[location.table_id]; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d1d46429527d6..ab2fa5cba918b 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1748,8 +1748,7 @@ impl World { } } - /// Attempts to apply the internal [`CommandQueue`] to self - /// Will do nothing if already flushing commands. + /// Applies the internal [`CommandQueue`] to self #[inline] pub fn flush_commands(&mut self) { if !self.command_queue.is_empty() { From 9b490ad74eae3b65fa7d57c82dbaa36da7be2536 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 17:42:10 -0800 Subject: [PATCH 11/35] CI pass --- crates/bevy_ecs/src/bundle.rs | 4 ++-- crates/bevy_ecs/src/component.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 1 + crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- crates/bevy_ecs/src/world/mod.rs | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index f8b563bdf9325..67587f18599d7 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -541,7 +541,7 @@ impl<'w> BundleInserter<'w> { /// Creates a new [`BundleInserter`]. /// /// # Safety - /// Caller must ensure that BundleId exists in `world.bundles` + /// Caller must ensure that `bundle_id` exists in `world.bundles` #[inline] pub(crate) unsafe fn new_with_id( world: &'w mut World, @@ -855,7 +855,7 @@ impl<'w> BundleSpawner<'w> { /// Creates a new [`BundleSpawner`]. /// /// # Safety - /// Caller must ensure that BundleId exists in `world.bundles` + /// Caller must ensure that `bundle_id` exists in `world.bundles` #[inline] pub(crate) unsafe fn new_with_id( world: &'w mut World, diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 0ee72faf749be..a798340d5eef9 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -623,7 +623,7 @@ impl Components { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::().id(); + /// let component_a_id = world.init_component::(); /// /// assert_eq!(component_a_id, world.components().component_id::().unwrap()) /// ``` diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index da1d30e6f67d4..e3e241a6d43a3 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -31,6 +31,7 @@ impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] pub fn with_commands(&mut self, f: impl Fn(Commands)) { + // SAFETY: We have exclusive access to the world's command queue let world = unsafe { self.world.world_mut() }; let mut queue = std::mem::take(&mut world.command_queue); f(Commands::new(&mut queue, world)); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index b115c943e09a3..187effc55d88d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -691,9 +691,9 @@ impl<'w> EntityWorldMut<'w> { let old_archetype = &world.archetypes[old_location.archetype_id]; // Drop borrow on world so it can be turned into DeferredWorld - // SAFETY: Changes to Bundles cannot happen through DeferredWorld let bundle_info = { let bundle_info: *const BundleInfo = bundle_info; + // SAFETY: Changes to Bundles cannot happen through DeferredWorld unsafe { &*bundle_info } }; if old_archetype.has_on_remove() { @@ -869,9 +869,9 @@ impl<'w> EntityWorldMut<'w> { let old_archetype = &world.archetypes[old_location.archetype_id]; // Drop borrow on world so it can be turned into DeferredWorld - // SAFETY: Changes to Bundles cannot happen through DeferredWorld let bundle_info = { let bundle_info: *const BundleInfo = bundle_info; + // SAFETY: Changes to Bundles cannot happen through DeferredWorld unsafe { &*bundle_info } }; if old_archetype.has_on_remove() { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index ab2fa5cba918b..c5e08a1c69fbe 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -244,7 +244,7 @@ impl World { /// #[derive(Component)] /// struct ComponentA; /// - /// let component_a_id = world.init_component::().id(); + /// let component_a_id = world.init_component::(); /// /// assert_eq!(component_a_id, world.component_id::().unwrap()) /// ``` From b7e5462be3c736ea6dc9ab02d51ab0c80f5c6333 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 17:57:56 -0800 Subject: [PATCH 12/35] Re-generate README.md --- crates/bevy_ecs/src/bundle.rs | 4 ++-- examples/README.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 67587f18599d7..b61b55983f8ce 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -361,10 +361,10 @@ impl BundleInfo { /// in the [`Bundle`], with respect to the entity's original archetype (prior to the bundle being added) /// For example, if the original archetype already has `ComponentA` and `T` also has `ComponentA`, the status /// should be `Mutated`. If the original archetype does not have `ComponentA`, the status should be `Added`. - /// When "inserting" a bundle into an existing entity, [`AddBundle`](crate::archetype::AddBundle) + /// When "inserting" a bundle into an existing entity, [`AddBundle`] /// should be used, which will report `Added` vs `Mutated` status based on the current archetype's structure. /// When spawning a bundle, [`SpawnBundleStatus`] can be used instead, which removes the need - /// to look up the [`AddBundle`](crate::archetype::AddBundle) in the archetype graph, which requires + /// to look up the [`AddBundle`] in the archetype graph, which requires /// ownership of the entity's current archetype. /// /// `table` must be the "new" table for `entity`. `table_row` must have space allocated for the diff --git a/examples/README.md b/examples/README.md index 0450ef88159b8..fa7b2cc7f727c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -219,6 +219,7 @@ Example | Description --- | --- [Apply System Buffers](../examples/ecs/apply_deferred.rs) | Show how to use `apply_deferred` system [Component Change Detection](../examples/ecs/component_change_detection.rs) | Change detection on components +[Component Hooks](../examples/ecs/component_hooks.rs) | Define component hooks to manage component lifecycle events [Custom Query Parameters](../examples/ecs/custom_query_param.rs) | Groups commonly used compound queries and query filters into a single type [ECS Guide](../examples/ecs/ecs_guide.rs) | Full guide to Bevy's ECS [Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception From a4d164a330c7598a80d72fff6490bedd65640b40 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 18:08:13 -0800 Subject: [PATCH 13/35] Add missing safety comment --- crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 187effc55d88d..93d47d3e7ae8d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -925,9 +925,9 @@ impl<'w> EntityWorldMut<'w> { let archetype = &world.archetypes[self.location.archetype_id]; if archetype.has_on_remove() { // Drop borrow on world so it can be turned into DeferredWorld - // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld let archetype = { let archetype: *const Archetype = archetype; + // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld unsafe { &*archetype } }; // SAFETY: All components in the archetype exist in world @@ -1000,7 +1000,7 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } - world.flush_commands() + world.flush_commands(); } /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`] From 78207c3bc0d1282c82cc0c4ea46a0a5f707d74d2 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 26 Nov 2023 18:13:48 -0800 Subject: [PATCH 14/35] Simplify asserts --- crates/bevy_ecs/src/bundle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index b61b55983f8ce..9e989b3c45e43 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1209,8 +1209,8 @@ mod tests { let entity = world.spawn(A).flush(); let entity = world.get_entity(entity).unwrap(); - assert_eq!(false, entity.contains::()); - assert_eq!(false, entity.contains::()); + assert!(!entity.contains::()); + assert!(!entity.contains::()); assert_eq!(4, world.resource::().0); } From a7b5f281db4f8a743e59131156e913cf400faa39 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 27 Nov 2023 09:17:30 -0800 Subject: [PATCH 15/35] Relax restrictions on register_component --- crates/bevy_ecs/src/component.rs | 21 +++++++++++++++++++++ crates/bevy_ecs/src/world/mod.rs | 7 ++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index a798340d5eef9..7a3b40545ddb1 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -283,19 +283,40 @@ impl ComponentInfo { } /// Register a [`ComponentHook`] that will be run when this component is added to an entity + /// + /// Will panic if the component already has an `on_add` hook pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { + assert!( + self.hooks.on_add.is_none(), + "Component id: {:?}, already has an on_add hook", + self.id() + ); self.hooks.on_add = Some(hook); self } /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` + /// + /// Will panic if the component already has an `on_insert` hook pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { + assert!( + self.hooks.on_insert.is_none(), + "Component id: {:?}, already has an on_insert hook", + self.id() + ); self.hooks.on_insert = Some(hook); self } /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. + /// + /// Will panic if the component already has an `on_remove` hook pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { + assert!( + self.hooks.on_remove.is_none(), + "Component id: {:?}, already has an on_remove hook", + self.id() + ); self.hooks.on_remove = Some(hook); self } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c5e08a1c69fbe..a78bf059608df 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -189,11 +189,10 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. /// - /// Will panic if a component for `T` already exists. + /// Will panic if `T` exists in any archetypes. pub fn register_component(&mut self) -> &mut ComponentInfo { - let type_id = TypeId::of::(); - assert!(self.components.get_id(type_id).is_none(), "Components cannot be registered twice, use init_component instead if this component may already exist in the world."); let index = self.init_component::(); + assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(index)), "Components cannot be registered if they already exist in an archetype, use init_component if {} may already be in use", std::any::type_name::()); // SAFETY: We just created this component unsafe { self.components.get_info_mut(index) } } @@ -217,8 +216,6 @@ impl World { /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. /// Primarily used for registering hooks. - /// - /// Will panic if a component for `T` already exists. pub fn register_component_with_descriptor( &mut self, descriptor: ComponentDescriptor, From 7ab60b9a16b0e960533ba9cdc4768bb469c65dca Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 27 Nov 2023 14:29:29 -0800 Subject: [PATCH 16/35] Update example comments --- examples/ecs/component_hooks.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 78deb6f2f5966..5158e1497d82b 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -23,8 +23,10 @@ fn main() { } fn setup(world: &mut World) { - // In order to register component hooks it must be the first time you register the component - // This is to prevent users from overwriting hooks that may be used internally in foreign crates + // In order to register component hooks the component must: + // - not belong to any created archetypes + // - not already have a hook of that kind registered + // This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast world .register_component::() // There are 3 component lifecyle hooks: `on_add`, `on_insert` and `on_remove` From ae2688610ed69f7e5cb4c7b34a0ff7869f9bfb96 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Wed, 29 Nov 2023 16:56:39 -0800 Subject: [PATCH 17/35] API improvements, simplify some usages of unsafe --- crates/bevy_ecs/src/bundle.rs | 23 ++------ crates/bevy_ecs/src/world/deferred_world.rs | 9 +-- crates/bevy_ecs/src/world/entity_ref.rs | 61 ++++++++++----------- crates/bevy_ecs/src/world/mod.rs | 9 ++- examples/ecs/component_hooks.rs | 6 +- 5 files changed, 48 insertions(+), 60 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 9e989b3c45e43..7f58f1ef6edab 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1181,27 +1181,18 @@ mod tests { .register_component::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(0); - - world.with_commands(|mut commands| { - commands.entity(entity).insert(B); - }); + world.commands().entity(entity).insert(B); }) .on_remove(|mut world, entity, _| { world.resource_mut::().assert_order(2); - - world.with_commands(|mut commands| { - commands.entity(entity).remove::(); - }); + world.commands().entity(entity).remove::(); }); world .register_component::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(1); - - world.with_commands(|mut commands| { - commands.entity(entity).remove::(); - }); + world.commands().entity(entity).remove::(); }) .on_remove(|mut world, _, _| { world.resource_mut::().assert_order(3); @@ -1222,18 +1213,14 @@ mod tests { .register_component::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(0); - world.with_commands(|mut commands| { - commands.entity(entity).insert(B).insert(D); - }); + world.commands().entity(entity).insert(B).insert(D); }); world .register_component::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(1); - world.with_commands(|mut commands| { - commands.entity(entity).insert(C); - }); + world.commands().entity(entity).insert(C); }); world.register_component::().on_add(|mut world, _, _| { diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index e3e241a6d43a3..85613b6fb5327 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -30,12 +30,9 @@ impl<'w> Deref for DeferredWorld<'w> { impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] - pub fn with_commands(&mut self, f: impl Fn(Commands)) { - // SAFETY: We have exclusive access to the world's command queue - let world = unsafe { self.world.world_mut() }; - let mut queue = std::mem::take(&mut world.command_queue); - f(Commands::new(&mut queue, world)); - world.command_queue = std::mem::take(&mut queue); + pub fn commands(&mut self) -> Commands { + // SAFETY: Commands cannot make structural changes. + unsafe { self.world.world_mut().commands() } } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 93d47d3e7ae8d..a8db8697b89f3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -688,21 +688,21 @@ impl<'w> EntityWorldMut<'w> { } let entity = self.entity; - let old_archetype = &world.archetypes[old_location.archetype_id]; - - // Drop borrow on world so it can be turned into DeferredWorld - let bundle_info = { + // SAFETY: Archetypes and Bundles cannot be mutably aliased through DeferredWorld + let (old_archetype, bundle_info, mut deferred_world) = unsafe { let bundle_info: *const BundleInfo = bundle_info; - // SAFETY: Changes to Bundles cannot happen through DeferredWorld - unsafe { &*bundle_info } + let world = world.as_unsafe_world_cell(); + ( + &world.archetypes()[old_location.archetype_id], + &*bundle_info, + world.into_deferred(), + ) }; + if old_archetype.has_on_remove() { // SAFETY: All components in the archetype exist in world unsafe { - world - .as_unsafe_world_cell() - .into_deferred() - .trigger_on_remove(entity, bundle_info.iter_components()); + deferred_world.trigger_on_remove(entity, bundle_info.iter_components()); } } @@ -865,22 +865,22 @@ impl<'w> EntityWorldMut<'w> { return self; } - let entity: Entity = self.entity; - let old_archetype = &world.archetypes[old_location.archetype_id]; - - // Drop borrow on world so it can be turned into DeferredWorld - let bundle_info = { + let entity = self.entity; + // SAFETY: Archetypes and Bundles cannot be mutably aliased through DeferredWorld + let (old_archetype, bundle_info, mut deferred_world) = unsafe { let bundle_info: *const BundleInfo = bundle_info; - // SAFETY: Changes to Bundles cannot happen through DeferredWorld - unsafe { &*bundle_info } + let world = world.as_unsafe_world_cell(); + ( + &world.archetypes()[old_location.archetype_id], + &*bundle_info, + world.into_deferred(), + ) }; + if old_archetype.has_on_remove() { // SAFETY: All components in the archetype exist in world unsafe { - world - .as_unsafe_world_cell() - .into_deferred() - .trigger_on_remove(entity, bundle_info.iter_components()); + deferred_world.trigger_on_remove(entity, bundle_info.iter_components()); } } @@ -923,19 +923,18 @@ impl<'w> EntityWorldMut<'w> { let world = self.world; world.flush_entities(); let archetype = &world.archetypes[self.location.archetype_id]; + + // SAFETY: Archetype cannot be mutably aliased by DeferredWorld + let (archetype, mut deferred_world) = unsafe { + let archetype: *const Archetype = archetype; + let world = world.as_unsafe_world_cell(); + (&*archetype, world.into_deferred()) + }; + if archetype.has_on_remove() { - // Drop borrow on world so it can be turned into DeferredWorld - let archetype = { - let archetype: *const Archetype = archetype; - // SAFETY: Changes to Archetypes cannot happpen through DeferredWorld - unsafe { &*archetype } - }; // SAFETY: All components in the archetype exist in world unsafe { - world - .as_unsafe_world_cell() - .into_deferred() - .trigger_on_remove(self.entity, archetype.components()); + deferred_world.trigger_on_remove(self.entity, archetype.components()); } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index a78bf059608df..e19a71aebd0e5 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -24,7 +24,7 @@ use crate::{ removal_detection::RemovedComponentEvents, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, - system::{CommandQueue, Resource}, + system::{CommandQueue, Commands, Resource}, world::error::TryRunScheduleError, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -181,6 +181,13 @@ impl World { WorldCell::new(self) } + /// Creates a new [`Commands`] instance that writes to the world's command queue + /// Use [`World::flush_commands`] to apply all queued commands + #[inline] + pub fn commands(&mut self) -> Commands { + Commands::new_from_entities(&mut self.command_queue, &self.entities) + } + /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. pub fn init_component(&mut self) -> ComponentId { self.components.init_component::(&mut self.storages) diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 5158e1497d82b..119af24d42754 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -64,10 +64,8 @@ fn setup(world: &mut World) { component_id, entity, value ); world.resource_mut::().remove(&value); - // You can also issue commands through `.with_commands` - world.with_commands(|mut commands| { - commands.entity(entity).despawn(); - }); + // You can also issue commands through `.commands()` + world.commands().entity(entity).despawn(); }); } From b61f62ae235481210e891d6ece3890916edc3715 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Wed, 29 Nov 2023 17:13:24 -0800 Subject: [PATCH 18/35] Rebase main. --- crates/bevy_ecs/src/system/commands/command_queue.rs | 4 +++- crates/bevy_ecs/src/world/deferred_world.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index f5735fc77c156..37826d30af3cd 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -151,7 +151,9 @@ impl CommandQueue { // or 1 byte past the end, so this addition will not overflow the pointer's allocation. cursor = unsafe { cursor.add(size) }; - world.flush_commands(); + if let Some(world) = &mut world { + world.flush_commands(); + } } } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 85613b6fb5327..f17800dbdf6c0 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -6,7 +6,7 @@ use crate::{ entity::Entity, event::{Event, EventId, Events, SendBatchIds}, prelude::{Component, QueryState}, - query::{ReadOnlyWorldQuery, WorldQuery}, + query::{WorldQueryData, WorldQueryFilter}, system::{Commands, Query, Resource}, }; @@ -52,7 +52,7 @@ impl<'w> DeferredWorld<'w> { /// # Panics /// If state is from a different world then self #[inline] - pub fn query<'s, Q: WorldQuery, F: ReadOnlyWorldQuery>( + pub fn query<'s, Q: WorldQueryData, F: WorldQueryFilter>( &'w mut self, state: &'s mut QueryState, ) -> Query<'w, 's, Q, F> { From 4d1c719297fc962a74cf3e870ee6f9bfb10ed3ec Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 1 Dec 2023 19:05:12 -0800 Subject: [PATCH 19/35] Move command queue's off the function stack to prevent overflows --- .../src/system/commands/command_queue.rs | 83 +++++++++++-------- crates/bevy_ecs/src/world/mod.rs | 4 +- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index 37826d30af3cd..0435c88131518 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -117,44 +117,61 @@ impl CommandQueue { let bytes_range = self.bytes.as_mut_ptr_range(); // Pointer that will iterate over the entries of the buffer. - let mut cursor = bytes_range.start; + let cursor = bytes_range.start; + + let end = bytes_range.end; // Reset the buffer, so it can be reused after this function ends. // In the loop below, ownership of each command will be transferred into user code. - // SAFETY: `set_len(0)` is always valid. - unsafe { self.bytes.set_len(0) }; - - while cursor < bytes_range.end { - // SAFETY: The cursor is either at the start of the buffer, or just after the previous command. - // Since we know that the cursor is in bounds, it must point to the start of a new command. - let meta = unsafe { cursor.cast::().read_unaligned() }; - // Advance to the bytes just after `meta`, which represent a type-erased command. - // SAFETY: For most types of `Command`, the pointer immediately following the metadata - // is guaranteed to be in bounds. If the command is a zero-sized type (ZST), then the cursor - // might be 1 byte past the end of the buffer, which is safe. - cursor = unsafe { cursor.add(std::mem::size_of::()) }; - // Construct an owned pointer to the command. - // SAFETY: It is safe to transfer ownership out of `self.bytes`, since the call to `set_len(0)` above - // guarantees that nothing stored in the buffer will get observed after this function ends. - // `cmd` points to a valid address of a stored command, so it must be non-null. - let cmd = unsafe { - OwningPtr::::new(std::ptr::NonNull::new_unchecked(cursor.cast())) - }; - // SAFETY: The data underneath the cursor must correspond to the type erased in metadata, - // since they were stored next to each other by `.push()`. - // For ZSTs, the type doesn't matter as long as the pointer is non-null. - let size = unsafe { (meta.consume_command_and_get_size)(cmd, &mut world) }; - // Advance the cursor past the command. For ZSTs, the cursor will not move. - // At this point, it will either point to the next `CommandMeta`, - // or the cursor will be out of bounds and the loop will end. - // SAFETY: The address just past the command is either within the buffer, - // or 1 byte past the end, so this addition will not overflow the pointer's allocation. - cursor = unsafe { cursor.add(size) }; - - if let Some(world) = &mut world { - world.flush_commands(); + let bytes = std::mem::take(&mut self.bytes); + + let mut resolving_commands = vec![(cursor, end, bytes)]; + + while let Some((mut cursor, mut end, mut bytes)) = resolving_commands.pop() { + while cursor < end { + // SAFETY: The cursor is either at the start of the buffer, or just after the previous command. + // Since we know that the cursor is in bounds, it must point to the start of a new command. + let meta = unsafe { cursor.cast::().read_unaligned() }; + // Advance to the bytes just after `meta`, which represent a type-erased command. + // SAFETY: For most types of `Command`, the pointer immediately following the metadata + // is guaranteed to be in bounds. If the command is a zero-sized type (ZST), then the cursor + // might be 1 byte past the end of the buffer, which is safe. + cursor = unsafe { cursor.add(std::mem::size_of::()) }; + // Construct an owned pointer to the command. + // SAFETY: It is safe to transfer ownership out of `self.bytes`, since the call to `set_len(0)` above + // guarantees that nothing stored in the buffer will get observed after this function ends. + // `cmd` points to a valid address of a stored command, so it must be non-null. + let cmd = unsafe { + OwningPtr::::new(std::ptr::NonNull::new_unchecked(cursor.cast())) + }; + // SAFETY: The data underneath the cursor must correspond to the type erased in metadata, + // since they were stored next to each other by `.push()`. + // For ZSTs, the type doesn't matter as long as the pointer is non-null. + let size = unsafe { (meta.consume_command_and_get_size)(cmd, &mut world) }; + // Advance the cursor past the command. For ZSTs, the cursor will not move. + // At this point, it will either point to the next `CommandMeta`, + // or the cursor will be out of bounds and the loop will end. + // SAFETY: The address just past the command is either within the buffer, + // or 1 byte past the end, so this addition will not overflow the pointer's allocation. + cursor = unsafe { cursor.add(size) }; + + if let Some(world) = &mut world { + world.flushing_commands = true; + if !world.command_queue.is_empty() { + if cursor < end { + resolving_commands.push((cursor, end, bytes)); + } + bytes = std::mem::take(&mut world.command_queue.bytes); + let bytes_range = bytes.as_mut_ptr_range(); + cursor = bytes_range.start; + end = bytes_range.end; + } + } } } + if let Some(world) = world { + world.flushing_commands = false; + } } /// Take all commands from `other` and append them to `self`, leaving `other` empty diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index e19a71aebd0e5..b4284e2b45e31 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -74,6 +74,7 @@ pub struct World { pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, pub(crate) command_queue: CommandQueue, + pub(crate) flushing_commands: bool, } impl Default for World { @@ -93,6 +94,7 @@ impl Default for World { last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), command_queue: CommandQueue::default(), + flushing_commands: false, } } } @@ -1755,7 +1757,7 @@ impl World { /// Applies the internal [`CommandQueue`] to self #[inline] pub fn flush_commands(&mut self) { - if !self.command_queue.is_empty() { + if !self.flushing_commands && !self.command_queue.is_empty() { let mut commands = std::mem::take(&mut self.command_queue); commands.apply(self); } From 3bef2ce7f54cdfa210a5365f028cc333c358f1ce Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 2 Dec 2023 14:43:40 -0800 Subject: [PATCH 20/35] Add method to register hooks in component trait definition --- crates/bevy_ecs/src/component.rs | 12 +++++++++++- examples/ecs/component_hooks.rs | 13 ++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 7a3b40545ddb1..361087db59060 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -152,6 +152,10 @@ pub trait Component: Send + Sync + 'static { /// A marker type indicating the storage type used for this component. /// This must be either [`TableStorage`] or [`SparseStorage`]. type Storage: ComponentStorage; + + /// Called when registering this component, allowing mutable access to it's [`ComponentInfo`]. + /// This is currently used for registering hooks. + fn init_component_info(_info: &mut ComponentInfo) {} } /// Marker type for components stored in a [`Table`](crate::storage::Table). @@ -538,7 +542,13 @@ impl Components { .. } = self; let index = indices.entry(type_id).or_insert_with(|| { - Components::init_component_inner(components, storages, ComponentDescriptor::new::()) + let index = Components::init_component_inner( + components, + storages, + ComponentDescriptor::new::(), + ); + T::init_component_info(&mut components[index]); + index }); ComponentId(*index) } diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 119af24d42754..98df67daab1b5 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -1,11 +1,22 @@ //! This examples illustrates the different ways you can employ component lifecycle hooks +use bevy::ecs::component::{ComponentInfo, TableStorage}; use bevy::prelude::*; use std::collections::HashMap; -#[derive(Component, Debug)] +#[derive(Debug)] struct MyComponent(KeyCode); +impl Component for MyComponent { + type Storage = TableStorage; + + /// Hooks can also be registered during component initialisation by + /// implementing `init_component_info` + fn init_component_info(_info: &mut ComponentInfo) { + // Register hooks... + } +} + #[derive(Resource, Default, Debug, Deref, DerefMut)] struct MyComponentIndex(HashMap); From 233fd306bd55cb18847868ac83f4d305cb10250f Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 3 Dec 2023 18:36:58 -0800 Subject: [PATCH 21/35] Remove most instances of unsafe in DeferredWorld --- crates/bevy_ecs/src/world/deferred_world.rs | 88 ++++++++++++++------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index f17800dbdf6c0..dfde9cbfa508d 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -10,20 +10,23 @@ use crate::{ system::{Commands, Query, Resource}, }; -use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; +use super::{ + unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}, + EntityMut, Mut, World, +}; /// A [`World`] reference that disallows structural ECS changes. /// This includes initializing resources, registering components or spawning entities. pub struct DeferredWorld<'w> { - world: UnsafeWorldCell<'w>, + // SAFETY: Implementors must not use this reference to make structural changes + world: &'w mut World, } impl<'w> Deref for DeferredWorld<'w> { type Target = World; fn deref(&self) -> &Self::Target { - // SAFETY: &self ensures there are no active mutable borrows - unsafe { self.world.world() } + self.world } } @@ -31,19 +34,49 @@ impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] pub fn commands(&mut self) -> Commands { - // SAFETY: Commands cannot make structural changes. - unsafe { self.world.world_mut().commands() } + self.world.commands() } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. /// Returns `None` if the `entity` does not have a [`Component`] of the given type. #[inline] pub fn get_mut(&mut self, entity: Entity) -> Option> { - // SAFETY: - // - `as_unsafe_world_cell` is the only thing that is borrowing world - // - `as_unsafe_world_cell` provides mutable permission to everything - // - `&mut self` ensures no other borrows on world data - unsafe { self.world.get_entity(entity)?.get_mut() } + self.world.get_mut(entity) + } + + /// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`. + /// This will panic if the `entity` does not exist. Use [`Self::get_entity_mut`] if you want + /// to check for entity existence instead of implicitly panic-ing. + #[inline] + #[track_caller] + pub fn entity_mut(&mut self, entity: Entity) -> EntityMut { + #[inline(never)] + #[cold] + #[track_caller] + fn panic_no_entity(entity: Entity) -> ! { + panic!("Entity {entity:?} does not exist"); + } + + match self.get_entity_mut(entity) { + Some(entity) => entity, + None => panic_no_entity(entity), + } + } + + /// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`. + /// Returns [`None`] if the `entity` does not exist. + /// Instead of unwrapping the value returned from this function, prefer [`Self::entity_mut`]. + #[inline] + pub fn get_entity_mut(&mut self, entity: Entity) -> Option { + let location = self.entities.get(entity)?; + // SAFETY: `entity` exists and `location` is that entity's location + Some(unsafe { + EntityMut::new(UnsafeEntityCell::new( + self.world.as_unsafe_world_cell(), + entity, + location, + )) + }) } /// Returns [`Query`] for the given [`QueryState`], which is used to efficiently @@ -60,11 +93,12 @@ impl<'w> DeferredWorld<'w> { state.update_archetypes(self); // SAFETY: We ran validate_world to ensure our state matches unsafe { + let world_cell = self.world.as_unsafe_world_cell(); Query::new( - self.world, + world_cell, state, - self.world.last_change_tick(), - self.world.change_tick(), + world_cell.last_change_tick(), + world_cell.change_tick(), false, ) } @@ -97,8 +131,7 @@ impl<'w> DeferredWorld<'w> { /// Gets a mutable reference to the resource of the given type if it exists #[inline] pub fn get_resource_mut(&mut self) -> Option> { - // SAFETY: `&mut self` ensures that all accessed data is unaliased - unsafe { self.world.get_resource_mut() } + self.world.get_resource_mut() } /// Gets a mutable reference to the non-send resource of the given type, if it exists. @@ -130,8 +163,7 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_resource_mut(&mut self) -> Option> { - // SAFETY: `&mut self` ensures that all accessed data is unaliased - unsafe { self.world.get_non_send_resource_mut() } + self.world.get_non_send_resource_mut() } /// Sends an [`Event`]. @@ -176,8 +208,7 @@ impl<'w> DeferredWorld<'w> { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: `&mut self` ensures that all accessed data is unaliased - unsafe { self.world.get_resource_mut_by_id(component_id) } + self.world.get_resource_mut_by_id(component_id) } /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. @@ -191,8 +222,7 @@ impl<'w> DeferredWorld<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: `&mut self` ensures that all accessed data is unaliased - unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } + self.world.get_non_send_mut_by_id(component_id) } /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. @@ -206,8 +236,7 @@ impl<'w> DeferredWorld<'w> { entity: Entity, component_id: ComponentId, ) -> Option> { - // SAFETY: `&mut self` ensures that all accessed data is unaliased - unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) } + self.world.get_mut_by_id(entity, component_id) } /// Triggers all `on_add` hooks for [`ComponentId`] in target. @@ -272,17 +301,18 @@ impl<'w> UnsafeWorldCell<'w> { /// Turn self into a [`DeferredWorld`] /// /// # Safety - /// Caller must ensure there are no outstanding references to the world's command queue, resource or component data + /// Caller must ensure there are no outstanding mutable references to world and no + /// outstanding references to the world's command queue, resource or component data #[inline] pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { - DeferredWorld { world: self } + DeferredWorld { + world: self.world_mut(), + } } } impl<'w> From<&'w mut World> for DeferredWorld<'w> { fn from(world: &'w mut World) -> DeferredWorld<'w> { - DeferredWorld { - world: world.as_unsafe_world_cell(), - } + DeferredWorld { world } } } From cdde4d2137b7180665384f20f84896f7d4d2ef9d Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Mon, 11 Dec 2023 18:54:08 -0800 Subject: [PATCH 22/35] Simplify code in BundleInserter --- crates/bevy_ecs/src/bundle.rs | 66 +++++++++++------------------------ 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 0a7284176e077..6d572dd7425e6 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,7 +15,7 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::unsafe_world_cell::UnsafeWorldCell, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, TypeIdMap, }; use bevy_ptr::OwningPtr; @@ -632,6 +632,21 @@ impl<'w> BundleInserter<'w> { let bundle_info = &*self.bundle_info; let add_bundle = &*self.add_bundle; let archetype = &*self.archetype; + let trigger_hooks = |archetype: &Archetype, mut world: DeferredWorld| { + if archetype.has_on_add() { + world.trigger_on_add( + entity, + bundle_info + .iter_components() + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), + ); + } + if archetype.has_on_insert() { + world.trigger_on_insert(entity, bundle_info.iter_components()); + } + }; match &mut self.result { InsertBundleResult::SameArchetype => { { @@ -652,20 +667,7 @@ impl<'w> BundleInserter<'w> { ); } // SAFETY: We have no oustanding mutable references to world as they were dropped - let mut world = self.world.into_deferred(); - if archetype.has_on_add() { - world.trigger_on_add( - entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), - ); - } - if archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); - } + trigger_hooks(archetype, self.world.into_deferred()); location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { @@ -709,21 +711,8 @@ impl<'w> BundleInserter<'w> { }; // SAFETY: We have no oustanding mutable references to world as they were dropped - let new_archetype = &**new_archetype; - let mut world = self.world.into_deferred(); - if new_archetype.has_on_add() { - world.trigger_on_add( - entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), - ); - } - if new_archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); - } + trigger_hooks(&**new_archetype, self.world.into_deferred()); + new_location } InsertBundleResult::NewArchetypeNewTable { @@ -806,21 +795,8 @@ impl<'w> BundleInserter<'w> { }; // SAFETY: We have no oustanding mutable references to world as they were dropped - let new_archetype = &**new_archetype; - let mut world = self.world.into_deferred(); - if new_archetype.has_on_add() { - world.trigger_on_add( - entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), - ); - } - if new_archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); - } + trigger_hooks(&**new_archetype, self.world.into_deferred()); + new_location } } From 298c598d23200a12ad26f6c30d8952adf8bad3e2 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 14 Dec 2023 12:50:38 -0800 Subject: [PATCH 23/35] Address non-complex feedback --- crates/bevy_ecs/src/world/entity_ref.rs | 6 +----- crates/bevy_ecs/src/world/unsafe_world_cell.rs | 6 ++---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index bee07ea5f35f1..5fe0a069431fb 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -706,9 +706,6 @@ impl<'w> EntityWorldMut<'w> { } } - for component_id in bundle_info.iter_components() { - world.removed_components.send(component_id, entity); - } let archetypes = &mut world.archetypes; let storages = &mut world.storages; let components = &mut world.components; @@ -844,7 +841,7 @@ impl<'w> EntityWorldMut<'w> { let location = self.location; let bundle_info = world.bundles.get_unchecked(bundle); - // SAFETY: `archetype_id` exists because it is referenced in `old_location` which is valid + // SAFETY: `archetype_id` exists because it is referenced in `location` which is valid // and components in `bundle_info` must exist due to this functions safety invariants. let new_archetype_id = remove_bundle_from_archetype( &mut world.archetypes, @@ -971,7 +968,6 @@ impl<'w> EntityWorldMut<'w> { } } - let archetype = &world.archetypes[self.location.archetype_id]; for component_id in archetype.components() { world.removed_components.send(component_id, self.entity); } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 6699d1dab3215..eb4ba71681d1b 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -17,7 +17,7 @@ use crate::{ system::{CommandQueue, Resource}, }; use bevy_ptr::Ptr; -use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData}; +use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr::addr_of_mut}; /// Variant of the [`World`] where resource and component accesses take `&self`, and the responsibility to avoid /// aliasing violations are given to the caller instead of being checked at compile-time by rust's unique XOR shared rule. @@ -576,9 +576,7 @@ impl<'w> UnsafeWorldCell<'w> { // SAFETY: // - caller ensures there are no existing mutable references // - caller ensures that we have permission to access the queue - let ptr = unsafe { &mut (*self.0).command_queue as *mut _ }; - // SAFETY: this pointer is valid as we just constructed it - unsafe { &mut *ptr } + unsafe { &mut *addr_of_mut!((*self.0).command_queue) } } } From d800e7be6a0ed8d55bebba374c5d271fafc38d1d Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 14 Dec 2023 13:00:01 -0800 Subject: [PATCH 24/35] Merge main --- crates/bevy_ecs/src/world/deferred_world.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index b8675d46c0eb7..9950a953eea58 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -6,7 +6,7 @@ use crate::{ entity::Entity, event::{Event, EventId, Events, SendBatchIds}, prelude::{Component, QueryState}, - query::{WorldQueryData, WorldQueryFilter}, + query::{QueryData, QueryFilter}, system::{Commands, Query, Resource}, }; @@ -83,10 +83,10 @@ impl<'w> DeferredWorld<'w> { /// # Panics /// If state is from a different world then self #[inline] - pub fn query<'s, Q: WorldQueryData, F: WorldQueryFilter>( + pub fn query<'s, D: QueryData, F: QueryFilter>( &'w mut self, - state: &'s mut QueryState, - ) -> Query<'w, 's, Q, F> { + state: &'s mut QueryState, + ) -> Query<'w, 's, D, F> { state.validate_world(self.world.id()); state.update_archetypes(self); // SAFETY: We ran validate_world to ensure our state matches From 642fb4394909dfa2a5e1b440e8be0b0c8c25863c Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 14 Dec 2023 13:21:19 -0800 Subject: [PATCH 25/35] Address soundness concerns --- crates/bevy_ecs/src/bundle.rs | 44 ++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 6d572dd7425e6..96895ea956ff8 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -629,10 +629,10 @@ impl<'w> BundleInserter<'w> { bundle: T, ) -> EntityLocation { // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid - let bundle_info = &*self.bundle_info; - let add_bundle = &*self.add_bundle; - let archetype = &*self.archetype; let trigger_hooks = |archetype: &Archetype, mut world: DeferredWorld| { + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; + if archetype.has_on_add() { world.trigger_on_add( entity, @@ -656,6 +656,9 @@ impl<'w> BundleInserter<'w> { &mut world.storages.sparse_sets }; let table = &mut *self.table; + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; + bundle_info.write_components( table, sparse_sets, @@ -667,19 +670,20 @@ impl<'w> BundleInserter<'w> { ); } // SAFETY: We have no oustanding mutable references to world as they were dropped + let archetype = &*self.archetype; trigger_hooks(archetype, self.world.into_deferred()); location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { let new_location = { // SAFETY: Mutable references do not alias and will be dropped after this block - let table = &mut *self.table; - let archetype = &mut *self.archetype; - let new_archetype = &mut **new_archetype; let (sparse_sets, entities) = { let world = self.world.world_mut(); (&mut world.storages.sparse_sets, &mut world.entities) }; + let table = &mut *self.table; + let archetype = &mut *self.archetype; + let new_archetype = &mut **new_archetype; let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { @@ -698,6 +702,9 @@ impl<'w> BundleInserter<'w> { } let new_location = new_archetype.allocate(entity, result.table_row); entities.set(entity.index(), new_location); + + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; bundle_info.write_components( table, sparse_sets, @@ -721,18 +728,20 @@ impl<'w> BundleInserter<'w> { } => { let new_location = { // SAFETY: Mutable references do not alias and will be dropped after this block - let table = &mut *self.table; - let new_table = &mut **new_table; - let archetype = &mut *self.archetype; - let new_archetype = &mut **new_archetype; - let (archetypes, sparse_sets, entities) = { + let (archetypes_ptr, sparse_sets, entities) = { let world = self.world.world_mut(); + let archetype_ptr: *mut Archetype = + world.archetypes.archetypes.as_mut_ptr(); ( - &mut world.archetypes, + archetype_ptr, &mut world.storages.sparse_sets, &mut world.entities, ) }; + let table = &mut *self.table; + let new_table = &mut **new_table; + let archetype = &mut *self.archetype; + let new_archetype = &mut **new_archetype; let result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { let swapped_location = @@ -765,7 +774,7 @@ impl<'w> BundleInserter<'w> { new_archetype } else { // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut archetypes.archetypes[swapped_location.archetype_id.index()] + &mut *archetypes_ptr.add(swapped_location.archetype_id.index()) }; entities.set( @@ -781,6 +790,8 @@ impl<'w> BundleInserter<'w> { .set_entity_table_row(swapped_location.archetype_row, result.table_row); } + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; bundle_info.write_components( new_table, sparse_sets, @@ -873,17 +884,17 @@ impl<'w> BundleSpawner<'w> { bundle: T, ) -> EntityLocation { // SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid - let bundle_info = &*self.bundle_info; let location = { // SAFETY: Mutable references do not alias and will be dropped after this block - let table = &mut *self.table; - let archetype = &mut *self.archetype; let (sparse_sets, entities) = { let world = self.world.world_mut(); (&mut world.storages.sparse_sets, &mut world.entities) }; + let table = &mut *self.table; + let archetype = &mut *self.archetype; let table_row = table.allocate(entity); let location = archetype.allocate(entity, table_row); + let bundle_info = &*self.bundle_info; bundle_info.write_components( table, sparse_sets, @@ -899,6 +910,7 @@ impl<'w> BundleSpawner<'w> { // SAFETY: We have no oustanding mutable references to world as they were dropped let archetype = &*self.archetype; + let bundle_info = &*self.bundle_info; let mut world = self.world.into_deferred(); if archetype.has_on_add() { world.trigger_on_add(entity, bundle_info.iter_components()); From 95cfbe3262e8647ddd39cef9582c8b2b529c38d2 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Tue, 16 Jan 2024 10:25:49 -0800 Subject: [PATCH 26/35] Improve docs --- crates/bevy_ecs/src/component.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 84246f453670c..cb87ee6e6fe22 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -313,6 +313,7 @@ impl ComponentInfo { } /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. + /// Despawning an entity counts as removing all of it's components. /// /// Will panic if the component already has an `on_remove` hook pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { From 22ed1766f09cd729e7f035e5d446159bc5901d4c Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 2 Feb 2024 14:41:39 -0800 Subject: [PATCH 27/35] Incorporate feedback, fix command application --- crates/bevy_ecs/src/component.rs | 60 ++++++++++++------- .../src/system/commands/command_queue.rs | 39 ++++++++---- crates/bevy_ecs/src/world/deferred_world.rs | 4 +- crates/bevy_ecs/src/world/mod.rs | 6 +- 4 files changed, 72 insertions(+), 37 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 75791f76c0b00..5c2cd82c24295 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -286,30 +286,23 @@ impl ComponentInfo { } } - /// Register a [`ComponentHook`] that will be run when this component is added to an entity + /// Register a [`ComponentHook`] that will be run when this component is added to an entity. + /// An `on_add` hook will always be followed by `on_insert`. /// /// Will panic if the component already has an `on_add` hook pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { - assert!( - self.hooks.on_add.is_none(), - "Component id: {:?}, already has an on_add hook", - self.id() - ); - self.hooks.on_add = Some(hook); - self + self.try_on_add(hook) + .expect("Component id: {:?}, already has an on_add hook") } /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` + /// An `on_insert` hook will run even if the entity already has the component unlike `on_add`, + /// `on_insert` also always runs after any `on_add` hooks. /// /// Will panic if the component already has an `on_insert` hook pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { - assert!( - self.hooks.on_insert.is_none(), - "Component id: {:?}, already has an on_insert hook", - self.id() - ); - self.hooks.on_insert = Some(hook); - self + self.try_on_insert(hook) + .expect("Component id: {:?}, already has an on_insert hook") } /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. @@ -317,13 +310,38 @@ impl ComponentInfo { /// /// Will panic if the component already has an `on_remove` hook pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { - assert!( - self.hooks.on_remove.is_none(), - "Component id: {:?}, already has an on_remove hook", - self.id() - ); + self.try_on_remove(hook) + .expect("Component id: {:?}, already has an on_remove hook") + } + + /// Fallible version of [`Self::on_add`]. + /// Returns `None` if the component already has an `on_add` hook. + pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.hooks.on_add.is_some() { + return None; + } + self.hooks.on_add = Some(hook); + Some(self) + } + + /// Fallible version of [`Self::on_insert`]. + /// Returns `None` if the component already has an `on_insert` hook. + pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.hooks.on_insert.is_some() { + return None; + } + self.hooks.on_insert = Some(hook); + Some(self) + } + + /// Fallible version of [`Self::on_remove`]. + /// Returns `None` if the component already has an `on_remove` hook. + pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.hooks.on_remove.is_some() { + return None; + } self.hooks.on_remove = Some(hook); - self + Some(self) } /// Update the given flags to include any [`ComponentHook`] registered to self diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index b7679959b417a..3f43d98df2e95 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -112,7 +112,7 @@ impl CommandQueue { } } - /// Execute the queued [`Command`]s in the world. + /// Execute the queued [`Command`]s in the world after applying any commands in the world's internal queue. /// This clears the queue. #[inline] pub fn apply(&mut self, world: &mut World) { @@ -139,9 +139,23 @@ impl CommandQueue { // In the loop below, ownership of each command will be transferred into user code. let bytes = std::mem::take(&mut self.bytes); - let mut resolving_commands = vec![(cursor, end, bytes)]; + // Create a stack for the command queue's we will be applying as commands may queue additional commands. + // This is preferred over recursion to avoid stack overflows. + let mut resolving_commands = vec![(cursor, end)]; + // Take ownership of buffers so they are not free'd uintil they are iterated. + let mut buffers = vec![bytes]; + + // Add any commands in the world's internal queue to the top of the stack. + if let Some(world) = &mut world { + if !world.command_queue.is_empty() { + let mut bytes = std::mem::take(&mut world.command_queue.bytes); + let bytes_range = bytes.as_mut_ptr_range(); + resolving_commands.push((bytes_range.start, bytes_range.end)); + buffers.push(bytes); + } + } - while let Some((mut cursor, mut end, mut bytes)) = resolving_commands.pop() { + while let Some((mut cursor, mut end)) = resolving_commands.pop() { while cursor < end { // SAFETY: The cursor is either at the start of the buffer, or just after the previous command. // Since we know that the cursor is in bounds, it must point to the start of a new command. @@ -170,26 +184,31 @@ impl CommandQueue { cursor = unsafe { cursor.add(size) }; if let Some(world) = &mut world { - // Mark world as flushing so calls to `World::flush_commands` don't cause multiple queues to be applying simultaneously - world.flushing_commands = true; // If the command we just applied generated more commands we must apply those first if !world.command_queue.is_empty() { // If our current list of commands isn't complete push it to the `resolving_commands` stack to be applied after if cursor < end { - resolving_commands.push((cursor, end, bytes)); + resolving_commands.push((cursor, end)); } + let mut bytes = std::mem::take(&mut world.command_queue.bytes); + // Set our variables to start applying the new queue - bytes = std::mem::take(&mut world.command_queue.bytes); let bytes_range = bytes.as_mut_ptr_range(); cursor = bytes_range.start; end = bytes_range.end; + + // Store our buffer so it is not dropped; + buffers.push(bytes); } } } } - if let Some(world) = world { - // Unblock future calls to `World::flush_commands` - world.flushing_commands = false; + + // Reset the buffer, so it can be reused after this function ends. + if let Some(bytes) = buffers.first_mut() { + self.bytes = std::mem::take(bytes); + // SAFETY: `set_len(0)` is always valid. + unsafe { self.bytes.set_len(0) } } } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 9950a953eea58..f2574a93919f8 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -107,10 +107,10 @@ impl<'w> DeferredWorld<'w> { /// # Panics /// /// Panics if the resource does not exist. - /// Use [`get_resource_mut`](World::get_resource_mut) instead if you want to handle this case. + /// Use [`get_resource_mut`](DeferredWorld::get_resource_mut) instead if you want to handle this case. /// /// If you want to instead insert a value if the resource does not exist, - /// use [`get_resource_or_insert_with`](World::get_resource_or_insert_with). + /// use [`get_resource_or_insert_with`](DeferredWorld::get_resource_or_insert_with). #[inline] #[track_caller] pub fn resource_mut(&mut self) -> Mut<'_, R> { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index fb4f44627afb7..cbe21d2c46427 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -80,7 +80,6 @@ pub struct World { pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, pub(crate) command_queue: CommandQueue, - pub(crate) flushing_commands: bool, } impl Default for World { @@ -100,7 +99,6 @@ impl Default for World { last_change_tick: Tick::new(0), last_check_tick: Tick::new(0), command_queue: CommandQueue::default(), - flushing_commands: false, } } } @@ -1855,10 +1853,10 @@ impl World { } } - /// Applies the internal [`CommandQueue`] to self + /// Applies any commands in the world's internal [`CommandQueue`]. #[inline] pub fn flush_commands(&mut self) { - if !self.flushing_commands && !self.command_queue.is_empty() { + if !self.command_queue.is_empty() { let mut commands = std::mem::take(&mut self.command_queue); commands.apply(self); } From e92c7459d3fa38ccebdbeacc401c187b3f5f7c8c Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 2 Feb 2024 14:50:01 -0800 Subject: [PATCH 28/35] Fix example metadata, simplify flush_commands --- Cargo.toml | 2 ++ crates/bevy_ecs/src/world/mod.rs | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c16dc5d834f64..5d45635064d8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1377,6 +1377,8 @@ doc-scrape-examples = true [package.metadata.example.component_hooks] name = "Component Hooks" description = "Define component hooks to manage component lifecycle events" +category = "ECS (Entity Component System)" +wasm = false [[example]] name = "custom_schedule" diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index cbe21d2c46427..c2e97c0192771 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1857,8 +1857,7 @@ impl World { #[inline] pub fn flush_commands(&mut self) { if !self.command_queue.is_empty() { - let mut commands = std::mem::take(&mut self.command_queue); - commands.apply(self); + CommandQueue::default().apply(self); } } From fe4fca3ced47d3dcfeb1ae94d07d35419c5df1d4 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Fri, 2 Feb 2024 15:14:33 -0800 Subject: [PATCH 29/35] Remove missing doc link --- crates/bevy_ecs/src/world/deferred_world.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index f2574a93919f8..24641a77bd00f 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -108,9 +108,6 @@ impl<'w> DeferredWorld<'w> { /// /// Panics if the resource does not exist. /// Use [`get_resource_mut`](DeferredWorld::get_resource_mut) instead if you want to handle this case. - /// - /// If you want to instead insert a value if the resource does not exist, - /// use [`get_resource_or_insert_with`](DeferredWorld::get_resource_or_insert_with). #[inline] #[track_caller] pub fn resource_mut(&mut self) -> Mut<'_, R> { From 1c2b18881cfdbedbcc6a4daf820f4ec1665794a2 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sat, 3 Feb 2024 18:04:23 -0800 Subject: [PATCH 30/35] Improve documentation --- crates/bevy_ecs/src/system/exclusive_function_system.rs | 1 + crates/bevy_ecs/src/world/mod.rs | 1 + crates/bevy_ecs/src/world/spawn_batch.rs | 2 ++ 3 files changed, 4 insertions(+) diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 1d9e1241d2232..d31e68cd7adaa 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -121,6 +121,7 @@ where self.system_meta.last_run.set(*change_tick); *change_tick = change_tick.wrapping_add(1); world.last_change_tick = saved_last_tick; + world.flush_commands(); out } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c2e97c0192771..44d62b99f8cab 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1854,6 +1854,7 @@ impl World { } /// Applies any commands in the world's internal [`CommandQueue`]. + /// This does not apply commands from any systems, only those in stored in the world. #[inline] pub fn flush_commands(&mut self) { if !self.command_queue.is_empty() { diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index 71e367fd1a86f..ab9cb8f2db01f 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -51,7 +51,9 @@ where I::Item: Bundle, { fn drop(&mut self) { + // Iterate through self in order to spawn remaining bundles. for _ in &mut *self {} + // Apply any commands from those operations. // SAFETY: `self.spawner` will be dropped immediately after this call. unsafe { self.spawner.flush_commands() }; } From 0022c7cd630d040fa5acf11597137ada39b14769 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Sun, 11 Feb 2024 11:53:14 -0800 Subject: [PATCH 31/35] Improve handling of command queue buffers --- .../src/system/commands/command_queue.rs | 22 +++++----- .../src/system/exclusive_function_system.rs | 2 +- crates/bevy_ecs/src/world/deferred_world.rs | 40 +++++++++---------- crates/bevy_ecs/src/world/mod.rs | 1 + 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index 3f43d98df2e95..fa55c3a4b8d5a 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -137,13 +137,14 @@ impl CommandQueue { // Reset the buffer, so it can be reused after this function ends. // In the loop below, ownership of each command will be transferred into user code. - let bytes = std::mem::take(&mut self.bytes); + // SAFETY: `set_len(0)` is always valid. + unsafe { self.bytes.set_len(0) }; // Create a stack for the command queue's we will be applying as commands may queue additional commands. // This is preferred over recursion to avoid stack overflows. let mut resolving_commands = vec![(cursor, end)]; - // Take ownership of buffers so they are not free'd uintil they are iterated. - let mut buffers = vec![bytes]; + // Take ownership of any additional buffers so they are not free'd uintil they are iterated. + let mut buffers = Vec::new(); // Add any commands in the world's internal queue to the top of the stack. if let Some(world) = &mut world { @@ -192,7 +193,7 @@ impl CommandQueue { } let mut bytes = std::mem::take(&mut world.command_queue.bytes); - // Set our variables to start applying the new queue + // Start applying the new queue let bytes_range = bytes.as_mut_ptr_range(); cursor = bytes_range.start; end = bytes_range.end; @@ -202,13 +203,12 @@ impl CommandQueue { } } } - } - - // Reset the buffer, so it can be reused after this function ends. - if let Some(bytes) = buffers.first_mut() { - self.bytes = std::mem::take(bytes); - // SAFETY: `set_len(0)` is always valid. - unsafe { self.bytes.set_len(0) } + // Re-use last buffer to avoid re-allocation + if let (Some(world), Some(buffer)) = (&mut world, buffers.pop()) { + world.command_queue.bytes = buffer; + // SAFETY: `set_len(0)` is always valid. + unsafe { world.command_queue.bytes.set_len(0) }; + } } } diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index d31e68cd7adaa..f7675fe813d25 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -116,12 +116,12 @@ where &self.system_meta, ); let out = self.func.run(world, input, params); + world.flush_commands(); let change_tick = world.change_tick.get_mut(); self.system_meta.last_run.set(*change_tick); *change_tick = change_tick.wrapping_add(1); world.last_change_tick = saved_last_tick; - world.flush_commands(); out } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 24641a77bd00f..2df839634a987 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -31,6 +31,26 @@ impl<'w> Deref for DeferredWorld<'w> { } } +impl<'w> UnsafeWorldCell<'w> { + /// Turn self into a [`DeferredWorld`] + /// + /// # Safety + /// Caller must ensure there are no outstanding mutable references to world and no + /// outstanding references to the world's command queue, resource or component data + #[inline] + pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { + DeferredWorld { world: self } + } +} + +impl<'w> From<&'w mut World> for DeferredWorld<'w> { + fn from(world: &'w mut World) -> DeferredWorld<'w> { + DeferredWorld { + world: world.as_unsafe_world_cell(), + } + } +} + impl<'w> DeferredWorld<'w> { /// Creates a [`Commands`] instance that pushes to the world's command queue #[inline] @@ -296,23 +316,3 @@ impl<'w> DeferredWorld<'w> { } } } - -impl<'w> UnsafeWorldCell<'w> { - /// Turn self into a [`DeferredWorld`] - /// - /// # Safety - /// Caller must ensure there are no outstanding mutable references to world and no - /// outstanding references to the world's command queue, resource or component data - #[inline] - pub unsafe fn into_deferred(self) -> DeferredWorld<'w> { - DeferredWorld { world: self } - } -} - -impl<'w> From<&'w mut World> for DeferredWorld<'w> { - fn from(world: &'w mut World) -> DeferredWorld<'w> { - DeferredWorld { - world: world.as_unsafe_world_cell(), - } - } -} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 4be31225f03da..35ba2c1294647 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1854,6 +1854,7 @@ impl World { #[inline] pub fn flush_commands(&mut self) { if !self.command_queue.is_empty() { + // `CommandQueue` application always applies commands from the world queue first so this will apply all stored commands CommandQueue::default().apply(self); } } From 629d694460a034e50d9992d67078f7decf459a82 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 29 Feb 2024 22:44:43 -0800 Subject: [PATCH 32/35] Address soundness concern, improve safety comments --- crates/bevy_ecs/src/archetype.rs | 18 +++-- crates/bevy_ecs/src/bundle.rs | 35 +++++---- crates/bevy_ecs/src/component.rs | 122 ++++++++++++++++--------------- crates/bevy_ecs/src/world/mod.rs | 35 +++++---- examples/ecs/component_hooks.rs | 2 +- 5 files changed, 112 insertions(+), 100 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index dc908928db745..fb9915643441c 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -664,12 +664,15 @@ impl Archetypes { by_components: Default::default(), archetype_component_count: 0, }; - archetypes.get_id_or_insert( - &Components::default(), - TableId::empty(), - Vec::new(), - Vec::new(), - ); + // SAFETY: Empty archetype has no components + unsafe { + archetypes.get_id_or_insert( + &Components::default(), + TableId::empty(), + Vec::new(), + Vec::new(), + ); + } archetypes } @@ -762,7 +765,8 @@ impl Archetypes { /// /// # Safety /// [`TableId`] must exist in tables - pub(crate) fn get_id_or_insert( + /// `table_components` and `sparse_set_components` must exist in `components` + pub(crate) unsafe fn get_id_or_insert( &mut self, components: &Components, table_id: TableId, diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 68b2076470569..677d660984ed7 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -416,7 +416,9 @@ impl BundleInfo { /// Adds a bundle to the given archetype and returns the resulting archetype. This could be the /// same [`ArchetypeId`], in the event that adding the given bundle does not result in an /// [`Archetype`] change. Results are cached in the [`Archetype`] graph to avoid redundant work. - pub(crate) fn add_bundle_to_archetype( + /// # Safety + /// `components` must be the same components as passed in [`Self::new`] + pub(crate) unsafe fn add_bundle_to_archetype( &self, archetypes: &mut Archetypes, storages: &mut Storages, @@ -484,6 +486,7 @@ impl BundleInfo { new_sparse_set_components }; }; + // SAFETY: ids in self must be valid let new_archetype_id = archetypes.get_id_or_insert( components, table_id, @@ -525,7 +528,7 @@ pub(crate) enum InsertBundleResult { impl<'w> BundleInserter<'w> { #[inline] - pub fn new( + pub(crate) fn new( world: &'w mut World, archetype_id: ArchetypeId, change_tick: Tick, @@ -1126,7 +1129,7 @@ mod tests { let mut world = World::new(); world.init_resource::(); world - .register_component::() + .register_component_hooks::() .on_add(|mut world, _, _| { world.resource_mut::().assert_order(0); }) @@ -1143,7 +1146,7 @@ mod tests { let mut world = World::new(); world.init_resource::(); world - .register_component::() + .register_component_hooks::() .on_add(|mut world, _, _| { world.resource_mut::().assert_order(0); }) @@ -1166,7 +1169,7 @@ mod tests { let mut world = World::new(); world.init_resource::(); world - .register_component::() + .register_component_hooks::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(0); world.commands().entity(entity).insert(B); @@ -1177,7 +1180,7 @@ mod tests { }); world - .register_component::() + .register_component_hooks::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(1); world.commands().entity(entity).remove::(); @@ -1198,26 +1201,30 @@ mod tests { let mut world = World::new(); world.init_resource::(); world - .register_component::() + .register_component_hooks::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(0); world.commands().entity(entity).insert(B).insert(D); }); world - .register_component::() + .register_component_hooks::() .on_add(|mut world, entity, _| { world.resource_mut::().assert_order(1); world.commands().entity(entity).insert(C); }); - world.register_component::().on_add(|mut world, _, _| { - world.resource_mut::().assert_order(2); - }); + world + .register_component_hooks::() + .on_add(|mut world, _, _| { + world.resource_mut::().assert_order(2); + }); - world.register_component::().on_add(|mut world, _, _| { - world.resource_mut::().assert_order(3); - }); + world + .register_component_hooks::() + .on_add(|mut world, _, _| { + world.resource_mut::().assert_order(3); + }); world.spawn(A).flush(); assert_eq!(4, world.resource::().0); diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 28c66d7e810d7..046cad9501eea 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -220,6 +220,66 @@ pub struct ComponentHooks { pub(crate) on_remove: Option, } +impl ComponentHooks { + /// Register a [`ComponentHook`] that will be run when this component is added to an entity. + /// An `on_add` hook will always be followed by `on_insert`. + /// + /// Will panic if the component already has an `on_add` hook + pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { + self.try_on_add(hook) + .expect("Component id: {:?}, already has an on_add hook") + } + + /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` + /// An `on_insert` hook will run even if the entity already has the component unlike `on_add`, + /// `on_insert` also always runs after any `on_add` hooks. + /// + /// Will panic if the component already has an `on_insert` hook + pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { + self.try_on_insert(hook) + .expect("Component id: {:?}, already has an on_insert hook") + } + + /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. + /// Despawning an entity counts as removing all of it's components. + /// + /// Will panic if the component already has an `on_remove` hook + pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { + self.try_on_remove(hook) + .expect("Component id: {:?}, already has an on_remove hook") + } + + /// Fallible version of [`Self::on_add`]. + /// Returns `None` if the component already has an `on_add` hook. + pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.on_add.is_some() { + return None; + } + self.on_add = Some(hook); + Some(self) + } + + /// Fallible version of [`Self::on_insert`]. + /// Returns `None` if the component already has an `on_insert` hook. + pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.on_insert.is_some() { + return None; + } + self.on_insert = Some(hook); + Some(self) + } + + /// Fallible version of [`Self::on_remove`]. + /// Returns `None` if the component already has an `on_remove` hook. + pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.on_remove.is_some() { + return None; + } + self.on_remove = Some(hook); + Some(self) + } +} + /// Stores metadata for a type of component or resource stored in a specific [`World`]. #[derive(Debug, Clone)] pub struct ComponentInfo { @@ -288,64 +348,6 @@ impl ComponentInfo { } } - /// Register a [`ComponentHook`] that will be run when this component is added to an entity. - /// An `on_add` hook will always be followed by `on_insert`. - /// - /// Will panic if the component already has an `on_add` hook - pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_add(hook) - .expect("Component id: {:?}, already has an on_add hook") - } - - /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` - /// An `on_insert` hook will run even if the entity already has the component unlike `on_add`, - /// `on_insert` also always runs after any `on_add` hooks. - /// - /// Will panic if the component already has an `on_insert` hook - pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_insert(hook) - .expect("Component id: {:?}, already has an on_insert hook") - } - - /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. - /// Despawning an entity counts as removing all of it's components. - /// - /// Will panic if the component already has an `on_remove` hook - pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_remove(hook) - .expect("Component id: {:?}, already has an on_remove hook") - } - - /// Fallible version of [`Self::on_add`]. - /// Returns `None` if the component already has an `on_add` hook. - pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.hooks.on_add.is_some() { - return None; - } - self.hooks.on_add = Some(hook); - Some(self) - } - - /// Fallible version of [`Self::on_insert`]. - /// Returns `None` if the component already has an `on_insert` hook. - pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.hooks.on_insert.is_some() { - return None; - } - self.hooks.on_insert = Some(hook); - Some(self) - } - - /// Fallible version of [`Self::on_remove`]. - /// Returns `None` if the component already has an `on_remove` hook. - pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.hooks.on_remove.is_some() { - return None; - } - self.hooks.on_remove = Some(hook); - Some(self) - } - /// Update the given flags to include any [`ComponentHook`] registered to self #[inline] pub(crate) fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) { @@ -651,8 +653,8 @@ impl Components { } #[inline] - pub(crate) unsafe fn get_info_mut(&mut self, id: ComponentId) -> &mut ComponentInfo { - self.components.get_unchecked_mut(id.0) + pub(crate) fn get_hooks_mut(&mut self, id: ComponentId) -> Option<&mut ComponentHooks> { + self.components.get_mut(id.0).map(|info| &mut info.hooks) } /// Type-erased equivalent of [`Components::component_id()`]. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 317467ac6ae1a..4f2ef6aa5e931 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -21,8 +21,8 @@ use crate::{ bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, TicksMut}, component::{ - Component, ComponentDescriptor, ComponentId, ComponentInfo, ComponentTicks, Components, - Tick, + Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentInfo, ComponentTicks, + Components, Tick, }, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, event::{Event, EventId, Events, SendBatchIds}, @@ -199,15 +199,25 @@ impl World { self.components.init_component::(&mut self.storages) } - /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. - /// Primarily used for registering hooks. + /// Returns a mutable reference to the [`ComponentHooks`] for a [`Component`] type. /// /// Will panic if `T` exists in any archetypes. - pub fn register_component(&mut self) -> &mut ComponentInfo { + pub fn register_component_hooks(&mut self) -> &mut ComponentHooks { let index = self.init_component::(); - assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(index)), "Components cannot be registered if they already exist in an archetype, use init_component if {} may already be in use", std::any::type_name::()); + assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(index)), "Components hooks cannot be modified if the component already exists in an archetype, use init_component if {} may already be in use", std::any::type_name::()); // SAFETY: We just created this component - unsafe { self.components.get_info_mut(index) } + unsafe { self.components.get_hooks_mut(index).debug_checked_unwrap() } + } + + /// Returns a mutable reference to the [`ComponentHooks`] for a [`Component`] with the given id if it exists. + /// + /// Will panic if `id` exists in any archetypes. + pub fn register_component_hooks_by_id( + &mut self, + id: ComponentId, + ) -> Option<&mut ComponentHooks> { + assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(id)), "Components hooks cannot be modified if the component already exists in an archetype, use init_component if the component with id {:?} may already be in use", id); + self.components.get_hooks_mut(id) } /// Initializes a new [`Component`] type and returns the [`ComponentId`] created for it. @@ -227,17 +237,6 @@ impl World { .init_component_with_descriptor(&mut self.storages, descriptor) } - /// Initializes a new [`Component`] type and returns a mutable reference to the [`ComponentInfo`] created for it. - /// Primarily used for registering hooks. - pub fn register_component_with_descriptor( - &mut self, - descriptor: ComponentDescriptor, - ) -> &mut ComponentInfo { - let index = self.init_component_with_descriptor(descriptor); - // SAFETY: We just created this component - unsafe { self.components.get_info_mut(index) } - } - /// Returns the [`ComponentId`] of the given [`Component`] type `T`. /// /// The returned `ComponentId` is specific to the `World` instance diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index d43259176cd66..20ec3ecd81efc 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -39,7 +39,7 @@ fn setup(world: &mut World) { // - not already have a hook of that kind registered // This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast world - .register_component::() + .register_component_hooks::() // There are 3 component lifecyle hooks: `on_add`, `on_insert` and `on_remove` // A hook has 3 arguments: // - a `DeferredWorld`, this allows access to resource and component data as well as `Commands` From d55830a52b5b9b6601aa17caa92039bee3417756 Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 29 Feb 2024 23:06:10 -0800 Subject: [PATCH 33/35] Get hit by the new spelling callout --- crates/bevy_ecs/src/bundle.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index f8c7ff0c2aefe..f415a5b567084 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -674,7 +674,7 @@ impl<'w> BundleInserter<'w> { bundle, ); } - // SAFETY: We have no oustanding mutable references to world as they were dropped + // SAFETY: We have no outstanding mutable references to world as they were dropped let archetype = &*self.archetype; trigger_hooks(archetype, self.world.into_deferred()); location @@ -722,7 +722,7 @@ impl<'w> BundleInserter<'w> { new_location }; - // SAFETY: We have no oustanding mutable references to world as they were dropped + // SAFETY: We have no outstanding mutable references to world as they were dropped trigger_hooks(&**new_archetype, self.world.into_deferred()); new_location @@ -810,7 +810,7 @@ impl<'w> BundleInserter<'w> { new_location }; - // SAFETY: We have no oustanding mutable references to world as they were dropped + // SAFETY: We have no outstanding mutable references to world as they were dropped trigger_hooks(&**new_archetype, self.world.into_deferred()); new_location @@ -874,7 +874,7 @@ impl<'w> BundleSpawner<'w> { #[inline] pub fn reserve_storage(&mut self, additional: usize) { - // SAFETY: There are no oustanding world references + // SAFETY: There are no outstanding world references let (archetype, table) = unsafe { (&mut *self.archetype, &mut *self.table) }; archetype.reserve(additional); table.reserve(additional); @@ -913,7 +913,7 @@ impl<'w> BundleSpawner<'w> { location }; - // SAFETY: We have no oustanding mutable references to world as they were dropped + // SAFETY: We have no outstanding mutable references to world as they were dropped let archetype = &*self.archetype; let bundle_info = &*self.bundle_info; let mut world = self.world.into_deferred(); From 5b9f2bab61116ba09db579707a68496e47b0d56d Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 29 Feb 2024 23:10:21 -0800 Subject: [PATCH 34/35] Wrap unsafe operations in additional unsafe blocks, take archetype flag functions private --- crates/bevy_ecs/src/archetype.rs | 6 +++--- crates/bevy_ecs/src/bundle.rs | 28 ++++++++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index b1d60291d5ed2..baa16e2503baa 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -573,19 +573,19 @@ impl Archetype { /// Returns true if any of the components in this archetype have `on_add` hooks #[inline] - pub fn has_on_add(&self) -> bool { + pub(crate) fn has_on_add(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_ADD_HOOK) } /// Returns true if any of the components in this archetype have `on_insert` hooks #[inline] - pub fn has_on_insert(&self) -> bool { + pub(crate) fn has_on_insert(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK) } /// Returns true if any of the components in this archetype have `on_remove` hooks #[inline] - pub fn has_on_remove(&self) -> bool { + pub(crate) fn has_on_remove(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index f415a5b567084..f6af2e9be024d 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -675,8 +675,10 @@ impl<'w> BundleInserter<'w> { ); } // SAFETY: We have no outstanding mutable references to world as they were dropped - let archetype = &*self.archetype; - trigger_hooks(archetype, self.world.into_deferred()); + unsafe { + let archetype = &*self.archetype; + trigger_hooks(archetype, self.world.into_deferred()); + } location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { @@ -723,7 +725,7 @@ impl<'w> BundleInserter<'w> { }; // SAFETY: We have no outstanding mutable references to world as they were dropped - trigger_hooks(&**new_archetype, self.world.into_deferred()); + unsafe { trigger_hooks(&**new_archetype, self.world.into_deferred()) }; new_location } @@ -811,7 +813,7 @@ impl<'w> BundleInserter<'w> { }; // SAFETY: We have no outstanding mutable references to world as they were dropped - trigger_hooks(&**new_archetype, self.world.into_deferred()); + unsafe { trigger_hooks(&**new_archetype, self.world.into_deferred()) }; new_location } @@ -914,14 +916,16 @@ impl<'w> BundleSpawner<'w> { }; // SAFETY: We have no outstanding mutable references to world as they were dropped - let archetype = &*self.archetype; - let bundle_info = &*self.bundle_info; - let mut world = self.world.into_deferred(); - if archetype.has_on_add() { - world.trigger_on_add(entity, bundle_info.iter_components()); - } - if archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); + unsafe { + let archetype = &*self.archetype; + let bundle_info = &*self.bundle_info; + let mut world = self.world.into_deferred(); + if archetype.has_on_add() { + world.trigger_on_add(entity, bundle_info.iter_components()); + } + if archetype.has_on_insert() { + world.trigger_on_insert(entity, bundle_info.iter_components()); + } } location From 492bbb4eba2605ff61a93afd81228e1af9ab88bd Mon Sep 17 00:00:00 2001 From: James O'Brien Date: Thu, 29 Feb 2024 23:12:54 -0800 Subject: [PATCH 35/35] Get got again --- examples/ecs/component_hooks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 20ec3ecd81efc..7794ca84a8c3f 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -40,7 +40,7 @@ fn setup(world: &mut World) { // This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast world .register_component_hooks::() - // There are 3 component lifecyle hooks: `on_add`, `on_insert` and `on_remove` + // There are 3 component lifecycle hooks: `on_add`, `on_insert` and `on_remove` // A hook has 3 arguments: // - a `DeferredWorld`, this allows access to resource and component data as well as `Commands` // - the entity that triggered the hook