diff --git a/Cargo.toml b/Cargo.toml index 3db341023bb5b..507d4ada76c37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1403,6 +1403,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 manage component lifecycle events" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "custom_schedule" path = "examples/ecs/custom_schedule.rs" diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 7699829425238..b3372469694d0 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -22,6 +22,7 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } bevy_ecs_macros = { path = "macros", version = "0.14.0-dev" } +bitflags = "2.3" concurrent-queue = "2.4.0" fixedbitset = "0.4.2" rustc-hash = "1.1" diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 62643cf77a066..baa16e2503baa 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -21,7 +21,7 @@ use crate::{ bundle::BundleId, - component::{ComponentId, StorageType}, + component::{ComponentId, Components, StorageType}, entity::{Entity, EntityLocation}, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, }; @@ -107,7 +107,7 @@ impl ArchetypeId { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq)] pub(crate) enum ComponentStatus { Added, Mutated, @@ -298,6 +298,18 @@ struct ArchetypeComponentInfo { archetype_component_id: ArchetypeComponentId, } +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(crate) 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 +322,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 +335,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 +351,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 +366,9 @@ impl Archetype { id, table_id, entities: Vec::new(), - components: components.into_immutable(), + components: archetype_components.into_immutable(), edges: Default::default(), + flags, } } @@ -356,6 +378,12 @@ impl Archetype { self.id } + /// Fetches the flags for the archetype. + #[inline] + pub(crate) fn flags(&self) -> ArchetypeFlags { + self.flags + } + /// Fetches the archetype's [`Table`] ID. /// /// [`Table`]: crate::storage::Table @@ -542,6 +570,24 @@ impl Archetype { pub(crate) fn clear_entities(&mut self) { self.entities.clear(); } + + /// Returns true if any of the components in this archetype have `on_add` hooks + #[inline] + 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(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(crate) fn has_on_remove(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) + } } /// The next [`ArchetypeId`] in an [`Archetypes`] collection. @@ -624,7 +670,15 @@ impl Archetypes { by_components: Default::default(), archetype_component_count: 0, }; - archetypes.get_id_or_insert(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 } @@ -717,8 +771,10 @@ 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, table_components: Vec, sparse_set_components: Vec, @@ -744,6 +800,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 81f025d1024a5..f6af2e9be024d 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -7,13 +7,15 @@ use bevy_utils::{HashMap, HashSet, TypeIdMap}; 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}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, }; use bevy_ptr::OwningPtr; use bevy_utils::all_tuples; @@ -346,86 +348,10 @@ 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, - } + /// 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() } /// This writes components from a given [`Bundle`] to the given entity. @@ -436,10 +362,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 @@ -493,7 +419,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, @@ -561,8 +489,13 @@ impl BundleInfo { new_sparse_set_components }; }; - let new_archetype_id = - archetypes.get_id_or_insert(table_id, table_components, sparse_set_components); + // SAFETY: ids in self must be valid + 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, @@ -574,194 +507,381 @@ 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, +// SAFETY: We have exclusive world access so our pointers can't be invalidated externally +pub(crate) struct BundleInserter<'w> { + 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<'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(crate) fn new( + world: &'w mut World, + archetype_id: ArchetypeId, + change_tick: Tick, + ) -> Self { + let bundle_id = world + .bundles + .init_info::(&mut world.components, &mut world.storages); + // SAFETY: We just ensured this bundle exists + unsafe { Self::new_with_id(world, archetype_id, bundle_id, change_tick) } + } + + /// Creates a new [`BundleInserter`]. + /// + /// # Safety + /// Caller must ensure that `bundle_id` exists in `world.bundles` + #[inline] + pub(crate) unsafe fn new_with_id( + world: &'w mut World, + archetype_id: ArchetypeId, + 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 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, + &mut world.storages, + &world.components, + archetype_id, + ); + 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 = unsafe { + archetype + .edges() + .get_add_bundle_internal(bundle_id) + .debug_checked_unwrap() + }; + let table_id = archetype.table_id(); + let table = &mut world.storages.tables[table_id]; + Self { + add_bundle, + archetype, + bundle_info, + table, + result: InsertBundleResult::SameArchetype, + 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 = unsafe { + archetype + .edges() + .get_add_bundle_internal(bundle_id) + .debug_checked_unwrap() + }; + 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 { + add_bundle, + archetype, + bundle_info, + table, + result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, + 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 { + add_bundle, + archetype, + bundle_info, + table, + result: InsertBundleResult::NewArchetypeNewTable { + new_archetype, + new_table, + }, + change_tick, + world: world.as_unsafe_world_cell(), + } + } + } + } /// # 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 unsafe fn insert( + pub(crate) unsafe fn insert( &mut self, entity: Entity, location: EntityLocation, bundle: T, ) -> EntityLocation { - match &mut 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, - add_bundle, + // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid + 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, - location.table_row, - self.change_tick, - bundle, + 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 => { + { + // 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; + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; + + bundle_info.write_components( + table, + sparse_sets, + add_bundle, + entity, + location.table_row, + self.change_tick, + bundle, + ); + } + // SAFETY: We have no outstanding mutable references to world as they were dropped + unsafe { + let archetype = &*self.archetype; + trigger_hooks(archetype, self.world.into_deferred()); + } location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { - 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, - }, + let new_location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + 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 { + 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); + + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; + bundle_info.write_components( + table, + sparse_sets, + add_bundle, + entity, + result.table_row, + self.change_tick, + bundle, ); - } - 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() + new_location }; - self.bundle_info.write_components( - self.table, - self.sparse_sets, - add_bundle, - entity, - result.table_row, - self.change_tick, - bundle, - ); + + // SAFETY: We have no outstanding mutable references to world as they were dropped + unsafe { trigger_hooks(&**new_archetype, self.world.into_deferred()) }; + new_location } InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, } => { - 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, - }, - ); - } - // 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 - { - &mut *self.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 - unsafe { - &mut *self - .archetypes_ptr - .add(swapped_location.archetype_id.index()) - } + let new_location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + let (archetypes_ptr, sparse_sets, entities) = { + let world = self.world.world_mut(); + let archetype_ptr: *mut Archetype = + world.archetypes.archetypes.as_mut_ptr(); + ( + 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 = + // 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_ptr.add(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); + } - 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, - }, + let bundle_info = &*self.bundle_info; + let add_bundle = &*self.add_bundle; + bundle_info.write_components( + new_table, + sparse_sets, + add_bundle, + entity, + move_result.new_row, + self.change_tick, + bundle, ); - swapped_archetype - .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() + new_location }; - self.bundle_info.write_components( - new_table, - self.sparse_sets, - add_bundle, - entity, - move_result.new_row, - self.change_tick, - bundle, - ); + + // SAFETY: We have no outstanding mutable references to world as they were dropped + unsafe { trigger_hooks(&**new_archetype, self.world.into_deferred()) }; + new_location } } } + + #[inline] + pub(crate) fn entities(&mut self) -> &mut Entities { + // SAFETY: No outstanding references to self.world, changes to entities cannot invalidate our internal pointers + unsafe { &mut self.world.world_mut().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, +// SAFETY: We have exclusive world access so our pointers can't be invalidated externally +pub(crate) struct BundleSpawner<'w> { + world: UnsafeWorldCell<'w>, + bundle_info: *const BundleInfo, + table: *mut Table, + archetype: *mut Archetype, 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_id = world + .bundles + .init_info::(&mut world.components, &mut world.storages); + // 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 `bundle_id` exists in `world.bundles` + #[inline] + pub(crate) unsafe fn new_with_id( + world: &'w mut World, + bundle_id: BundleId, + change_tick: Tick, + ) -> Self { + 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, + &world.components, + ArchetypeId::EMPTY, + ); + let archetype = &mut world.archetypes[new_archetype_id]; + let table = &mut world.storages.tables[archetype.table_id()]; + Self { + bundle_info, + table, + 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 outstanding world references + let (archetype, table) = unsafe { (&mut *self.archetype, &mut *self.table) }; + archetype.reserve(additional); + table.reserve(additional); } + /// # Safety /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type #[inline] @@ -770,18 +890,43 @@ 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, - &SpawnBundleStatus, - entity, - table_row, - self.change_tick, - bundle, - ); - self.entities.set(entity.index(), location); + // SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid + let location = { + // SAFETY: Mutable references do not alias and will be dropped after this block + 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, + &SpawnBundleStatus, + entity, + table_row, + self.change_tick, + bundle, + ); + entities.set(entity.index(), location); + location + }; + + // SAFETY: We have no outstanding mutable references to world as they were dropped + 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 } @@ -790,13 +935,27 @@ 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.entities().alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type unsafe { self.spawn_non_existent(entity, bundle); } entity } + + #[inline] + pub(crate) fn entities(&mut self) -> &mut 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. + #[inline] + pub(crate) unsafe fn flush_commands(&mut self) { + // SAFETY: pointers on self can be invalidated, + self.world.world_mut().flush_commands(); + } } /// Metadata for bundles. Stores a [`BundleInfo`] for each type of [`Bundle`] in a given world. @@ -806,9 +965,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 { @@ -828,13 +989,13 @@ 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 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()); @@ -847,8 +1008,24 @@ impl Bundles { bundle_infos.push(bundle_info); id }); - // SAFETY: index either exists, or was initialized - unsafe { self.bundle_infos.get_unchecked(id.0) } + id + } + + pub(crate) unsafe fn get_unchecked(&self, id: BundleId) -> &BundleInfo { + self.bundle_infos.get_unchecked(id.0) + } + + 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_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`]. @@ -861,25 +1038,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(|| { - ( - component_ids.into(), - 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); + (component_ids.into(), 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. @@ -891,22 +1065,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 } } @@ -934,3 +1104,138 @@ fn initialize_dynamic_bundle( (id, storage_types) } + +#[cfg(test)] +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 { + #[track_caller] + 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_hooks::() + .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_hooks::() + .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_hooks::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(0); + world.commands().entity(entity).insert(B); + }) + .on_remove(|mut world, entity, _| { + world.resource_mut::().assert_order(2); + world.commands().entity(entity).remove::(); + }); + + world + .register_component_hooks::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(1); + world.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!(!entity.contains::()); + assert!(!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_hooks::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(0); + world.commands().entity(entity).insert(B).insert(D); + }); + + world + .register_component_hooks::() + .on_add(|mut world, entity, _| { + world.resource_mut::().assert_order(1); + world.commands().entity(entity).insert(C); + }); + + world + .register_component_hooks::() + .on_add(|mut world, _, _| { + world.resource_mut::().assert_order(2); + }); + + 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 61349f4c86e0f..a65599bf7a043 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -2,10 +2,12 @@ use crate::{ self as bevy_ecs, + archetype::ArchetypeFlags, change_detection::MAX_CHANGE_AGE, + entity::Entity, storage::{SparseSetIndex, Storages}, system::{Local, Resource, SystemParam}, - world::{FromWorld, World}, + world::{DeferredWorld, FromWorld, World}, }; pub use bevy_ecs_macros::Component; use bevy_ptr::{OwningPtr, UnsafeCellDeref}; @@ -152,6 +154,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). @@ -203,11 +209,83 @@ 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, +} + +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 { id: ComponentId, descriptor: ComponentDescriptor, + hooks: ComponentHooks, } impl ComponentInfo { @@ -263,7 +341,30 @@ impl ComponentInfo { /// Create a new [`ComponentInfo`]. pub(crate) fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self { - ComponentInfo { id, descriptor } + ComponentInfo { + id, + descriptor, + hooks: ComponentHooks::default(), + } + } + + /// Update the given flags to include any [`ComponentHook`] registered to self + #[inline] + pub(crate) 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); + } + } + + /// Provides a reference to the collection of hooks associated with this [`Component`] + pub fn hooks(&self) -> &ComponentHooks { + &self.hooks } } @@ -474,7 +575,13 @@ impl Components { .. } = self; *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()]); + index }) } @@ -551,6 +658,11 @@ impl Components { unsafe { self.components.get_unchecked(id.0) } } + #[inline] + 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()`]. #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 3cd89b77e2b93..1f3464997bb04 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1067,7 +1067,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)); @@ -1550,7 +1550,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 5e246e8a66e69..a9dcd6f965de2 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -112,12 +112,12 @@ 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) { // flush the previously queued entities - world.flush(); + world.flush_entities(); self.apply_or_drop_queued(Some(world)); } @@ -131,39 +131,85 @@ 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, world.as_deref_mut()) }; - // 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) }; + // 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 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 { + 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)) = 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, world.as_deref_mut()) }; + // 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 { + // 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)); + } + let mut bytes = std::mem::take(&mut world.command_queue.bytes); + + // Start applying the new queue + 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); + } + } + } + // 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) }; + } } } @@ -171,6 +217,12 @@ impl CommandQueue { pub fn append(&mut self, other: &mut CommandQueue) { self.bytes.append(&mut other.bytes); } + + /// Returns false if there are any commands in the queue + #[inline] + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } } impl Drop for CommandQueue { diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index a019330856aec..d4351923adaf4 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -110,6 +110,7 @@ where ); 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); 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..81352445a9047 --- /dev/null +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -0,0 +1,317 @@ +use std::ops::Deref; + +use crate::{ + change_detection::MutUntyped, + component::ComponentId, + entity::Entity, + event::{Event, EventId, Events, SendBatchIds}, + prelude::{Component, QueryState}, + query::{QueryData, QueryFilter}, + system::{Commands, Query, Resource}, +}; + +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> { + // SAFETY: Implementors must not use this reference to make structural changes + world: UnsafeWorldCell<'w>, +} + +impl<'w> Deref for DeferredWorld<'w> { + type Target = World; + + fn deref(&self) -> &Self::Target { + // SAFETY: Structural changes cannot be made through &World + unsafe { self.world.world() } + } +} + +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] + pub fn commands(&mut self) -> Commands { + // SAFETY: &mut self ensure that there are no outstanding accesses to the queue + let queue = unsafe { self.world.get_command_queue() }; + Commands::new_from_entities(queue, self.world.entities()) + } + + /// 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: &mut self ensure that there are no outstanding accesses to the component + unsafe { self.world.get_entity(entity)?.get_mut() } + } + + /// 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, entity, location)) }) + } + + /// 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, D: QueryData, F: QueryFilter>( + &'w mut self, + 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 + unsafe { + let world_cell = self.world; + Query::new( + world_cell, + state, + world_cell.last_change_tick(), + world_cell.change_tick(), + ) + } + } + + /// Gets a mutable reference to the resource of the given type + /// + /// # Panics + /// + /// Panics if the resource does not exist. + /// Use [`get_resource_mut`](DeferredWorld::get_resource_mut) instead if you want to handle this case. + #[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: &mut self ensure that there are no outstanding accesses to the resource + 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: &mut self ensure that there are no outstanding accesses to the resource + 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 ensure that there are no outstanding accesses to the resource + 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 ensure that there are no outstanding accesses to the resource + 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 ensure that there are no outstanding accesses to the resource + 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) 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); + } + } + } + + /// Triggers all `on_insert` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. + #[inline] + 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); + } + } + } + + /// Triggers all `on_remove` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. + #[inline] + 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); + } + } + } +} diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index bc12b3343c93b..7448dddd8bec2 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1,6 +1,6 @@ use crate::{ archetype::{Archetype, ArchetypeId, Archetypes}, - bundle::{Bundle, BundleInfo, BundleInserter, DynamicBundle}, + bundle::{Bundle, BundleId, BundleInfo, BundleInserter, DynamicBundle}, change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, @@ -10,7 +10,6 @@ use crate::{ world::{Mut, World}, }; use bevy_ptr::{OwningPtr, Ptr}; -use bevy_utils::tracing::debug; use std::{any::TypeId, marker::PhantomData}; use thiserror::Error; @@ -653,23 +652,10 @@ 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 } @@ -689,17 +675,16 @@ impl<'w> EntityWorldMut<'w> { component: OwningPtr<'_>, ) -> &mut Self { let change_tick = self.world.change_tick(); + let bundle_id = self + .world + .bundles + .init_component_info(&self.world.components, component_id); + let storage_type = self.world.bundles.get_storage_unchecked(bundle_id); - 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, + let bundle_inserter = BundleInserter::new_with_id( + self.world, self.location.archetype_id, + bundle_id, change_tick, ); @@ -708,9 +693,8 @@ impl<'w> EntityWorldMut<'w> { self.entity, self.location, Some(component).into_iter(), - Some(storage_type).into_iter(), + Some(storage_type).iter().cloned(), ); - self } @@ -732,17 +716,16 @@ 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, + let bundle_id = self + .world + .bundles + .init_dynamic_info(&self.world.components, component_ids); + 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_id, change_tick, ); @@ -751,9 +734,9 @@ impl<'w> EntityWorldMut<'w> { self.entity, self.location, iter_components, - storage_types.iter().cloned(), + (*storage_types).iter().cloned(), ); - + *self.world.bundles.get_storages_unchecked(bundle_id) = std::mem::take(&mut storage_types); self } @@ -764,19 +747,18 @@ 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 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 { 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 world.archetypes, storages, components, old_location.archetype_id, @@ -789,8 +771,33 @@ impl<'w> EntityWorldMut<'w> { return None; } - let mut bundle_components = bundle_info.components().iter().cloned(); 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; + 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 { + deferred_world.trigger_on_remove(entity, bundle_info.iter_components()); + } + } + + let archetypes = &mut world.archetypes; + 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(); // SAFETY: bundle components are iterated in order, which guarantees that the component type // matches let result = unsafe { @@ -824,7 +831,6 @@ impl<'w> EntityWorldMut<'w> { new_archetype_id, ); } - Some(result) } @@ -914,49 +920,60 @@ impl<'w> EntityWorldMut<'w> { } } - /// Remove the components of `bundle_info` from `entity`, where `self_location` and `old_location` - /// are the location of this entity, and `self_location` is updated to the new location. + /// Remove the components of `bundle` from `entity`. /// - /// SAFETY: `old_location` must be valid and the components in `bundle_info` must exist. + /// SAFETY: The components in `bundle_info` must exist. #[allow(clippy::too_many_arguments)] - unsafe fn remove_bundle_info( - entity: Entity, - self_location: &mut EntityLocation, - old_location: EntityLocation, - bundle_info: &BundleInfo, - archetypes: &mut Archetypes, - storages: &mut Storages, - components: &Components, - entities: &mut Entities, - removed_components: &mut RemovedComponentEvents, - ) { - // SAFETY: `archetype_id` exists because it is referenced in `old_location` which is valid + unsafe fn remove_bundle(&mut self, bundle: BundleId) -> EntityLocation { + let entity = self.entity; + let world = &mut self.world; + let location = self.location; + let bundle_info = world.bundles.get_unchecked(bundle); + + // 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 = unsafe { - remove_bundle_from_archetype( - archetypes, - storages, - components, - old_location.archetype_id, - bundle_info, - true, - ) - } + let new_archetype_id = remove_bundle_from_archetype( + &mut world.archetypes, + &mut world.storages, + &world.components, + location.archetype_id, + bundle_info, + true, + ) .expect("intersections should always return a result"); - if new_archetype_id == old_location.archetype_id { - return; + if new_archetype_id == location.archetype_id { + return location; } - let old_archetype = &mut archetypes[old_location.archetype_id]; - for component_id in bundle_info.components().iter().cloned() { + // 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; + let world = world.as_unsafe_world_cell(); + ( + &world.archetypes()[location.archetype_id], + &*bundle_info, + world.into_deferred(), + ) + }; + + if old_archetype.has_on_remove() { + // SAFETY: All components in the archetype exist in world + unsafe { + deferred_world.trigger_on_remove(entity, bundle_info.iter_components()); + } + } + + let old_archetype = &world.archetypes[location.archetype_id]; + for component_id in bundle_info.iter_components() { if old_archetype.contains(component_id) { - 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) { - storages + world + .storages .sparse_sets .get_mut(component_id) .unwrap() @@ -967,18 +984,19 @@ impl<'w> EntityWorldMut<'w> { // SAFETY: `new_archetype_id` is a subset of the components in `old_location.archetype_id` // because it is created by removing a bundle from these components. - unsafe { - Self::move_entity_from_remove::( - entity, - self_location, - old_location.archetype_id, - old_location, - entities, - archetypes, - storages, - new_archetype_id, - ); - } + let mut new_location = location; + Self::move_entity_from_remove::( + entity, + &mut new_location, + location.archetype_id, + location, + &mut world.entities, + &mut world.archetypes, + &mut world.storages, + new_archetype_id, + ); + + new_location } /// Removes any components in the [`Bundle`] from the entity. @@ -986,30 +1004,13 @@ impl<'w> EntityWorldMut<'w> { /// See [`EntityCommands::remove`](crate::system::EntityCommands::remove) for more details. // 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 entities = &mut self.world.entities; - let removed_components = &mut self.world.removed_components; - let bundle_info = self.world.bundles.init_info::(components, storages); - let old_location = self.location; // SAFETY: Components exist in `bundle_info` because `Bundles::init_info` - // initializes a `BundleInfo` containing all components of the bundle type `T`. - unsafe { - Self::remove_bundle_info( - self.entity, - &mut self.location, - old_location, - bundle_info, - archetypes, - storages, - components, - entities, - removed_components, - ); - } + // initializes a: EntityLocation `BundleInfo` containing all components of the bundle type `T`. + self.location = unsafe { self.remove_bundle(bundle_info) }; self } @@ -1021,10 +1022,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 retained_bundle_info = self.world.bundles.init_info::(components, storages); + let retained_bundle = self.world.bundles.init_info::(components, storages); + // SAFETY: `retained_bundle` exists as we just initialized it. + let retained_bundle_info = unsafe { self.world.bundles.get_unchecked(retained_bundle) }; let old_location = self.location; let old_archetype = &mut archetypes[old_location.archetype_id]; @@ -1032,28 +1033,11 @@ impl<'w> EntityWorldMut<'w> { .components() .filter(|c| !retained_bundle_info.components().contains(c)) .collect::>(); - let remove_bundle_info = self - .world - .bundles - .init_dynamic_info(components, to_remove) - .0; + let remove_bundle = self.world.bundles.init_dynamic_info(components, to_remove); - // SAFETY: Components exist in `remove_bundle_info` because `Bundles::init_dynamic_info` + // SAFETY: Components exist in `remove_bundle` because `Bundles::init_dynamic_info` // initializes a `BundleInfo` containing all components in the to_remove Bundle. - unsafe { - Self::remove_bundle_info( - self.entity, - &mut self.location, - old_location, - remove_bundle_info, - archetypes, - storages, - components, - entities, - removed_components, - ); - } - + self.location = unsafe { self.remove_bundle(remove_bundle) }; self } @@ -1061,9 +1045,28 @@ impl<'w> EntityWorldMut<'w> { /// /// See [`World::despawn`] for more details. pub fn despawn(self) { - debug!("Despawning entity {:?}", self.entity); let world = self.world; - world.flush(); + 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() { + // SAFETY: All components in the archetype exist in world + unsafe { + deferred_world.trigger_on_remove(self.entity, archetype.components()); + } + } + + for component_id in archetype.components() { + world.removed_components.send(component_id, self.entity); + } + let location = world .entities .free(self.entity) @@ -1072,10 +1075,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(); @@ -1123,6 +1123,13 @@ impl<'w> EntityWorldMut<'w> { world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } + 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. @@ -2077,7 +2084,7 @@ unsafe fn insert_dynamic_bundle< I: Iterator>, S: Iterator, >( - mut bundle_inserter: BundleInserter<'_, '_>, + mut bundle_inserter: BundleInserter<'_>, entity: Entity, location: EntityLocation, components: I, @@ -2185,6 +2192,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, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 4d8e1af57f8f6..81691f6a6a70c 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,6 +8,7 @@ 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, FilteredEntityMut, FilteredEntityRef, OccupiedEntry, VacantEntry, @@ -19,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}, @@ -28,7 +30,7 @@ use crate::{ removal_detection::RemovedComponentEvents, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, - system::{Res, Resource}, + system::{CommandQueue, Commands, Res, Resource}, world::error::TryRunScheduleError, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -77,6 +79,7 @@ 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, } impl Default for World { @@ -95,6 +98,7 @@ 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(), } } } @@ -183,11 +187,39 @@ 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) } + /// Returns a mutable reference to the [`ComponentHooks`] for a [`Component`] type. + /// + /// Will panic if `T` exists in any archetypes. + 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 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_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. /// /// This method differs from [`World::init_component`] in that it uses a [`ComponentDescriptor`] @@ -422,7 +454,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 @@ -676,7 +708,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) } @@ -742,23 +774,13 @@ 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 = { - 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 @@ -1534,33 +1556,30 @@ impl World { I::IntoIter: Iterator, B: Bundle, { - self.flush(); + self.flush_entities(); let change_tick = self.change_tick(); - let bundle_info = self + let bundle_id = 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, - )); + // 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 { @@ -1577,14 +1596,15 @@ 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, - ); + // 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 = @@ -1597,13 +1617,9 @@ 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, - ); + // 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); @@ -1824,7 +1840,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 @@ -1838,6 +1854,16 @@ impl World { } } + /// Applies any commands in the world's internal [`CommandQueue`]. + /// This does not apply commands from any systems, only those stored in the 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); + } + } + /// 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 e21199f56444c..ab9cb8f2db01f 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> @@ -27,24 +27,15 @@ 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(); 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 { @@ -60,7 +51,11 @@ where I::Item: Bundle, { fn drop(&mut self) { - for _ in 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() }; } } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 7ae085d0b26ab..b786a3cfb4425 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -14,10 +14,10 @@ use crate::{ prelude::Component, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, - system::{Res, Resource}, + system::{CommandQueue, Res, Resource}, }; use bevy_ptr::Ptr; -use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr}; +use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr, 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. @@ -590,6 +590,18 @@ impl<'w> UnsafeWorldCell<'w> { .get(component_id)? .get_with_ticks() } + + // Returns a mutable reference to the underlying world's [`CommandQueue`]. + /// # Safety + /// It is the callers responsibility to ensure that + /// - the [`UnsafeWorldCell`] has permission to access the queue mutably + /// - no mutable references to the queue exist at the same time + pub(crate) unsafe fn get_command_queue(self) -> &'w mut CommandQueue { + // SAFETY: + // - caller ensures there are no existing mutable references + // - caller ensures that we have permission to access the queue + unsafe { &mut *addr_of_mut!((*self.0).command_queue) } + } } impl Debug for UnsafeWorldCell<'_> { diff --git a/examples/README.md b/examples/README.md index 932ecfbc5c620..3f22fcc0e67ac 100644 --- a/examples/README.md +++ b/examples/README.md @@ -229,6 +229,7 @@ Example | Description Example | Description --- | --- [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 [Custom Schedule](../examples/ecs/custom_schedule.rs) | Demonstrates how to add custom schedules [Dynamic ECS](../examples/ecs/dynamic.rs) | Dynamically create components, spawn entities with those components and query those components diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs new file mode 100644 index 0000000000000..7794ca84a8c3f --- /dev/null +++ b/examples/ecs/component_hooks.rs @@ -0,0 +1,96 @@ +//! 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(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); + +#[derive(Event)] +struct MyEvent; + +fn main() { + App::new() + .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 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_hooks::() + // 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 + // - 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` 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!( + "Component: {:?} removed from: {:?} with value {:?}", + component_id, entity, value + ); + world.resource_mut::().remove(&value); + // You can also issue commands through `.commands()` + world.commands().entity(entity).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)); + } +}