construct and destruct

This commit is contained in:
Elliott Pierce 2025-05-30 17:58:10 -04:00
parent 65a7ea9f6a
commit 4c6f613a1d
5 changed files with 265 additions and 148 deletions

View File

@ -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)
};

View File

@ -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<EntityMeta>, 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}")]

View File

@ -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);
}
});

View File

@ -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<EntityLocation>,
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<B: Bundle>(&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`]

View File

@ -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<B: Bundle>(
&mut self,
entity: Entity,
bundle: B,
) -> Result<EntityWorldMut<'_>, 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<B: Bundle>(
&mut self,
entity: Entity,
bundle: B,
caller: MaybeLocation,
) -> EntityWorldMut<'_> {
self.flush();
let change_tick = self.change_tick();
let mut bundle_spawner = BundleSpawner::new::<B>(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::<B>(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`].