diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index df5c51eef1..1b8b6fd1de 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1737,7 +1737,7 @@ impl<'w> BundleSpawner<'w> { caller, ); entities.set(entity.index(), Some(location)); - entities.mark_spawn_despawn(entity.index(), caller, self.change_tick); + entities.mark_construct_or_destruct(entity.index(), caller, self.change_tick); (location, after_effect) }; diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 038eaea9de..250900b263 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -666,6 +666,13 @@ pub(crate) struct EntitiesAllocator { } impl EntitiesAllocator { + /// Restarts the allocator. + pub(crate) fn restart(&mut self) { + self.free.clear(); + *self.free_len.get_mut() = 0; + *self.next_row.get_mut() = 0; + } + pub(crate) fn free(&mut self, freed: Entity) { self.free.truncate(*self.free_len.get_mut() as usize); self.free.push(freed); @@ -752,6 +759,11 @@ impl Entities { Self { meta: Vec::new() } } + /// Clears all entity information + pub fn clear(&mut self) { + self.meta.clear() + } + /// Returns the [`EntityLocation`] of an [`Entity`]. /// Note: for pending entities and entities not participating in the ECS (entities with a [`EntityIdLocation`] of `None`), returns `None`. #[inline] @@ -769,6 +781,25 @@ impl Entities { .map(|meta| meta.location) } + /// Provides information regarding if `entity` may be constructed. + #[inline] + pub fn validate_construction(&self, entity: Entity) -> Result<(), ConstructionError> { + if self + .resolve_from_id(entity.row()) + .is_some_and(|found| found.generation() != entity.generation()) + { + Err(ConstructionError::InvalidId) + } else if self + .meta + .get(entity.index() as usize) + .is_some_and(|meta| meta.location.is_some()) + { + Err(ConstructionError::AlreadyConstructed) + } else { + Ok(()) + } + } + /// Updates the location of an [`EntityRow`]. /// This must be called when moving the components of the existing entity around in storage. /// @@ -791,6 +822,15 @@ impl Entities { /// before handing control to unknown code. #[inline] pub(crate) unsafe fn declare(&mut self, row: EntityRow, location: EntityIdLocation) { + self.ensure_row(row); + // SAFETY: We just did `ensure_row` + let meta = unsafe { self.meta.get_unchecked_mut(row.index() as usize) }; + meta.location = location; + } + + /// Ensures row is valid. + #[inline] + fn ensure_row(&mut self, row: EntityRow) { #[cold] // to help with branch prediction fn expand(meta: &mut Vec, len: usize) { meta.resize(len, EntityMeta::EMPTY); @@ -801,44 +841,34 @@ impl Entities { // TODO: hint unlikely once stable. expand(&mut self.meta, index + 1); } - // SAFETY: We guarantee that `index` a valid entity index - let meta = unsafe { self.meta.get_unchecked_mut(index) }; - meta.location = location; } - /// Marks the entity as free if it exists, returning its [`EntityIdLocation`] and the [`Entity`] to reuse that [`EntityRow`]. - pub(crate) fn mark_free( - &mut self, - entity: Entity, - generations: u32, - ) -> Option<(EntityIdLocation, Entity)> { - let meta = self.meta.get_mut(entity.index() as usize)?; - if meta.generation != entity.generation { - return None; - } + /// Marks the `row` as free, returning the [`Entity`] to reuse that [`EntityRow`]. + /// + /// # Safety + /// + /// - `row` must its [`EntityIdLocation`] set to `None`. + pub(crate) unsafe fn mark_free(&mut self, row: EntityRow, generations: u32) -> Entity { + // We need to do this in case an entity is being freed that was never constructed. + self.ensure_row(row); + // SAFETY: We just did `ensure_row` + let meta = unsafe { self.meta.get_unchecked_mut(row.index() as usize) }; let (new_generation, aliased) = meta.generation.after_versions_and_could_alias(generations); meta.generation = new_generation; if aliased { - warn!( - "Entity({}) generation wrapped on Entities::free, aliasing may occur", - entity.row() - ); + warn!("EntityRow({row}) generation wrapped on Entities::free, aliasing may occur",); } - let loc = meta.location.take(); - Some(( - loc, - Entity::from_raw_and_generation(entity.row(), meta.generation), - )) + Entity::from_raw_and_generation(row, meta.generation) } - /// Mark an [`EntityRow`] as spawned or despawned in the given tick. + /// Mark an [`EntityRow`] as constructed or destructed in the given tick. /// /// # Safety - /// - `row` must have a [`EntityIdLocation`]. + /// - `row` must have either been constructed or destructed, ensuring its row is valid. #[inline] - pub(crate) unsafe fn mark_spawn_despawn( + pub(crate) unsafe fn mark_construct_or_destruct( &mut self, row: EntityRow, by: MaybeLocation, @@ -948,6 +978,11 @@ impl Entities { } } +pub enum ConstructionError { + InvalidId, + AlreadyConstructed, +} + /// An error that occurs when a specified [`Entity`] does not exist. #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] #[error("The entity with ID {entity} {details}")] diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 3012a65458..5a8978dc49 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -316,7 +316,9 @@ impl<'w, 's> Commands<'w, 's> { let tick = world.change_tick(); // SAFETY: Entity has been flushed unsafe { - world.entities_mut().mark_spawn_despawn(index, caller, tick); + world + .entities_mut() + .mark_construct_or_destruct(index, caller, tick); } }); entity_commands @@ -382,7 +384,9 @@ impl<'w, 's> Commands<'w, 's> { let tick = world.change_tick(); // SAFETY: Entity has been flushed unsafe { - world.entities_mut().mark_spawn_despawn(index, caller, tick); + world + .entities_mut() + .mark_construct_or_destruct(index, caller, tick); } }); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 9842ee54e3..765a71436a 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -10,8 +10,8 @@ use crate::{ StorageType, Tick, }, entity::{ - ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent, - EntityIdLocation, EntityLocation, + ConstructionError, ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, + EntityEquivalent, EntityIdLocation, EntityLocation, }, event::Event, observer::Observer, @@ -1169,9 +1169,8 @@ impl<'w> EntityWorldMut<'w> { pub(crate) unsafe fn new( world: &'w mut World, entity: Entity, - location: Option, + location: EntityIdLocation, ) -> Self { - debug_assert!(world.entities().contains(entity)); debug_assert_eq!(world.entities().get(entity), location); EntityWorldMut { @@ -1223,6 +1222,12 @@ impl<'w> EntityWorldMut<'w> { } } + /// Returns whether or not the entity is constructed. + #[inline] + pub fn is_constructed(&self) -> bool { + self.location.is_some() + } + /// Returns the archetype that the current entity belongs to. /// /// # Panics @@ -2328,35 +2333,39 @@ impl<'w> EntityWorldMut<'w> { self } - /// Despawns the current entity. - /// - /// See [`World::despawn`] for more details. - /// - /// # Note - /// - /// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured - /// to despawn descendants. This results in "recursive despawn" behavior. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] - pub fn despawn(self) { - self.despawn_with_caller(MaybeLocation::caller()); + pub fn construct(&mut self, bundle: B) -> Result<&mut Self, ConstructionError> { + let Self { + world, + entity, + location, + } = self; + let found = world.construct(*entity, bundle)?; + *location = found.location; + Ok(self) } - pub(crate) fn despawn_with_caller(self, caller: MaybeLocation) { - let location = self.location(); - let world = self.world; - let archetype = &world.archetypes[location.archetype_id]; + #[track_caller] + pub fn destruct(&mut self) -> &mut Self { + self.destruct_with_caller(MaybeLocation::caller()) + } + + pub(crate) fn destruct_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { + // setup + let Some(location) = self.location else { + // If there is no location, we are already destructed + return self; + }; + let archetype = &self.world.archetypes[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(); + let world = self.world.as_unsafe_world_cell(); (&*archetype, world.into_deferred()) }; + // Triggers // SAFETY: All components in the archetype exist in world unsafe { if archetype.has_despawn_observer() { @@ -2404,33 +2413,33 @@ impl<'w> EntityWorldMut<'w> { ); } + // do the destruct + let change_tick = self.world.change_tick(); for component_id in archetype.components() { - world.removed_components.send(component_id, self.entity); + self.world + .removed_components + .send(component_id, self.entity); + } + // SAFETY: Since we had a location, and it was valid, this is safe. + unsafe { + self.world.entities.update(self.entity.row(), None); + self.world + .entities + .mark_construct_or_destruct(self.entity.row(), caller, change_tick); } - // Observers and on_remove hooks may reserve new entities, which - // requires a flush before Entities::free may be called. - world.flush_entities(); - - let location = world - .entities - .free(self.entity) - .flatten() - .expect("entity should exist at this point."); let table_row; let moved_entity; - let change_tick = world.change_tick(); - { - let archetype = &mut world.archetypes[location.archetype_id]; + let archetype = &mut self.world.archetypes[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(); + let swapped_location = self.world.entities.get(swapped_entity).unwrap(); // SAFETY: swapped_entity is valid and the swapped entity's components are // moved to the new location immediately after. unsafe { - world.entities.set( - swapped_entity.index(), + self.world.entities.update( + swapped_entity.row(), Some(EntityLocation { archetype_id: swapped_location.archetype_id, archetype_row: location.archetype_row, @@ -2438,31 +2447,34 @@ impl<'w> EntityWorldMut<'w> { table_row: swapped_location.table_row, }), ); - world - .entities - .mark_spawn_despawn(swapped_entity.index(), caller, change_tick); } } table_row = remove_result.table_row; for component_id in archetype.sparse_set_components() { // set must have existed for the component to be added. - let sparse_set = world.storages.sparse_sets.get_mut(component_id).unwrap(); + let sparse_set = self + .world + .storages + .sparse_sets + .get_mut(component_id) + .unwrap(); sparse_set.remove(self.entity); } // SAFETY: table rows stored in archetypes always exist moved_entity = unsafe { - world.storages.tables[archetype.table_id()].swap_remove_unchecked(table_row) + self.world.storages.tables[archetype.table_id()].swap_remove_unchecked(table_row) }; }; + // Handle displaced entity if let Some(moved_entity) = moved_entity { - let moved_location = world.entities.get(moved_entity).unwrap(); + let moved_location = self.world.entities.get(moved_entity).unwrap(); // SAFETY: `moved_entity` is valid and the provided `EntityLocation` accurately reflects // the current location of the entity and its component data. unsafe { - world.entities.set( - moved_entity.index(), + self.world.entities.update( + moved_entity.row(), Some(EntityLocation { archetype_id: moved_location.archetype_id, archetype_row: moved_location.archetype_row, @@ -2470,14 +2482,40 @@ impl<'w> EntityWorldMut<'w> { table_row, }), ); - world - .entities - .mark_spawn_despawn(moved_entity.index(), caller, change_tick); } - world.archetypes[moved_location.archetype_id] + self.world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } - world.flush(); + + // finish + self.world.flush(); + self + } + + /// Despawns the current entity. + /// + /// See [`World::despawn`] for more details. + /// + /// # Note + /// + /// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured + /// to despawn descendants. This results in "recursive despawn" behavior. + /// + /// # Panics + /// + /// If the entity has been despawned while this `EntityWorldMut` is still alive. + #[track_caller] + pub fn despawn(self) { + self.despawn_with_caller(MaybeLocation::caller()); + } + + pub(crate) fn despawn_with_caller(mut self, caller: MaybeLocation) { + self.destruct_with_caller(caller); + // SAFETY: We just destructed. + unsafe { + self.world + .release_generations_unchecked(self.entity.row(), 1); + } } /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush`] diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index b79f189963..5ce7f181cd 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -14,11 +14,14 @@ pub mod unsafe_world_cell; #[cfg(feature = "bevy_reflect")] pub mod reflect; -use crate::error::{DefaultErrorHandler, ErrorHandler}; pub use crate::{ change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}, world::command_queue::CommandQueue, }; +use crate::{ + entity::{ConstructionError, EntitiesAllocator, EntityRow}, + error::{DefaultErrorHandler, ErrorHandler}, +}; pub use bevy_ecs_macros::FromWorld; pub use component_constants::*; pub use deferred_world::DeferredWorld; @@ -89,6 +92,7 @@ use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; pub struct World { id: WorldId, pub(crate) entities: Entities, + pub(crate) allocator: EntitiesAllocator, pub(crate) components: Components, pub(crate) component_ids: ComponentIds, pub(crate) archetypes: Archetypes, @@ -108,6 +112,7 @@ impl Default for World { let mut world = Self { id: WorldId::new().expect("More `bevy` `World`s have been created than is supported"), entities: Entities::new(), + allocator: EntitiesAllocator::default(), components: Default::default(), archetypes: Archetypes::new(), storages: Default::default(), @@ -1046,6 +1051,82 @@ impl World { (fetcher, commands) } + /// Spawns an [`Entity`] that is void/null. + /// The returned entity id is valid and unique, but it does not correspond to any conceptual entity. + /// The conceptual entity does not exist, and using the id as if it did may produce errors. + /// It can not be queried, and it has no [`EntityLocation`](crate::entity::EntityLocation). + /// + /// This is different from empty entities, which do exist in the world; + /// they just happen to have no components. + pub fn spawn_null(&self) -> Entity { + self.allocator.alloc() + } + + pub fn release(&mut self, entity: Entity) { + let Ok(entity) = self.get_entity_mut(entity) else { + return; // Already released then. + }; + + entity.despawn(); + } + + /// Releases `entity` to be reused. + /// + /// # Safety + /// + /// It must have been destructed. + pub(crate) unsafe fn release_generations_unchecked( + &mut self, + entity: EntityRow, + generations: u32, + ) { + self.allocator + .free(self.entities.mark_free(entity, generations)); + } + + #[track_caller] + pub fn construct( + &mut self, + entity: Entity, + bundle: B, + ) -> Result, ConstructionError> { + self.entities.validate_construction(entity)?; + // SAFETY: We just ensured it was valid. + Ok(unsafe { self.construct_unchecked(entity, bundle, MaybeLocation::caller()) }) + } + + /// Constructs `bundle` on `entity`. + /// + /// # Safety + /// + /// `entity` must be valid and have no location. + pub(crate) unsafe fn construct_unchecked( + &mut self, + entity: Entity, + bundle: B, + caller: MaybeLocation, + ) -> EntityWorldMut<'_> { + self.flush(); + let change_tick = self.change_tick(); + let mut bundle_spawner = BundleSpawner::new::(self, change_tick); + // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent + let (entity_location, after_effect) = + unsafe { bundle_spawner.spawn_non_existent(entity, bundle, caller) }; + + let mut entity_location = Some(entity_location); + + // SAFETY: command_queue is not referenced anywhere else + if !unsafe { self.command_queue.is_empty() } { + self.flush(); + entity_location = self.entities().get(entity); + } + + // SAFETY: entity and location are valid, as they were just created above + let mut entity = unsafe { EntityWorldMut::new(self, entity, entity_location) }; + after_effect.apply(&mut entity); + entity + } + /// Spawns a new [`Entity`] and returns a corresponding [`EntityWorldMut`], which can be used /// to add components to the entity or retrieve its id. /// @@ -1074,11 +1155,32 @@ impl World { #[track_caller] pub fn spawn_empty(&mut self) -> EntityWorldMut { self.flush(); - let entity = self.entities.alloc(); + let entity = self.allocator.alloc(); // SAFETY: entity was just allocated unsafe { self.spawn_at_empty_internal(entity, MaybeLocation::caller()) } } + /// # Safety + /// must be called on an entity that was just allocated + unsafe fn spawn_at_empty_internal( + &mut self, + entity: Entity, + caller: MaybeLocation, + ) -> EntityWorldMut { + let archetype = self.archetypes.empty_mut(); + // PERF: consider avoiding allocating entities in the empty archetype unless needed + let table_row = self.storages.tables[archetype.table_id()].allocate(entity); + // SAFETY: no components are allocated by archetype.allocate() because the archetype is + // empty + let location = unsafe { archetype.allocate(entity, table_row) }; + let change_tick = self.change_tick(); + self.entities.declare(entity.row(), Some(location)); + self.entities + .mark_construct_or_destruct(entity.row(), caller, change_tick); + + EntityWorldMut::new(self, entity, Some(location)) + } + /// Spawns a new [`Entity`] with a given [`Bundle`] of [components](`Component`) and returns /// a corresponding [`EntityWorldMut`], which can be used to add components to the entity or /// retrieve its id. In case large batches of entities need to be spawned, consider using @@ -1149,47 +1251,9 @@ impl World { bundle: B, caller: MaybeLocation, ) -> EntityWorldMut { - self.flush(); - let change_tick = self.change_tick(); - let entity = self.entities.alloc(); - let mut bundle_spawner = BundleSpawner::new::(self, change_tick); - // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent - let (entity_location, after_effect) = - unsafe { bundle_spawner.spawn_non_existent(entity, bundle, caller) }; - - let mut entity_location = Some(entity_location); - - // SAFETY: command_queue is not referenced anywhere else - if !unsafe { self.command_queue.is_empty() } { - self.flush(); - entity_location = self.entities().get(entity); - } - - // SAFETY: entity and location are valid, as they were just created above - let mut entity = unsafe { EntityWorldMut::new(self, entity, entity_location) }; - after_effect.apply(&mut entity); - entity - } - - /// # Safety - /// must be called on an entity that was just allocated - unsafe fn spawn_at_empty_internal( - &mut self, - entity: Entity, - caller: MaybeLocation, - ) -> EntityWorldMut { - let archetype = self.archetypes.empty_mut(); - // PERF: consider avoiding allocating entities in the empty archetype unless needed - let table_row = self.storages.tables[archetype.table_id()].allocate(entity); - // SAFETY: no components are allocated by archetype.allocate() because the archetype is - // empty - let location = unsafe { archetype.allocate(entity, table_row) }; - let change_tick = self.change_tick(); - self.entities.set(entity.index(), Some(location)); - self.entities - .mark_spawn_despawn(entity.index(), caller, change_tick); - - EntityWorldMut::new(self, entity, Some(location)) + let entity = self.spawn_null(); + // SAFETY: This was just spawned from null. + unsafe { self.construct_unchecked(entity, bundle, caller) } } /// Spawns a batch of entities with the same component [`Bundle`] type. Takes a given @@ -2708,30 +2772,6 @@ impl World { .initialize_with(component_id, &self.components) } - /// 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`]. - #[track_caller] - pub(crate) fn flush_entities(&mut self) { - let by = MaybeLocation::caller(); - let at = self.change_tick(); - 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 - // SAFETY: entity is set to a valid location - unsafe { - self.entities.flush( - |entity, location| { - // SAFETY: no components are allocated by archetype.allocate() because the archetype - // is empty - *location = Some(empty_archetype.allocate(entity, table.allocate(entity))); - }, - by, - at, - ); - } - } - /// Applies any commands in the world's internal [`CommandQueue`]. /// This does not apply commands from any systems, only those stored in the world. /// @@ -2765,7 +2805,6 @@ impl World { #[inline] #[track_caller] pub fn flush(&mut self) { - self.flush_entities(); self.flush_components(); self.flush_commands(); } @@ -2980,6 +3019,7 @@ impl World { self.storages.sparse_sets.clear_entities(); self.archetypes.clear_entities(); self.entities.clear(); + self.allocator.restart(); } /// Clears all resources in this [`World`].