diff --git a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs index 1de4f4c029584..d3f87660e4423 100644 --- a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs @@ -43,6 +43,6 @@ impl EntityCountDiagnosticsPlugin { /// Updates entity count measurement. pub fn diagnostic_system(mut diagnostics: Diagnostics, entities: &Entities) { - diagnostics.add_measurement(&Self::ENTITY_COUNT, || entities.len() as f64); + diagnostics.add_measurement(&Self::ENTITY_COUNT, || entities.count_spawned() as f64); } } diff --git a/crates/bevy_ecs/src/bundle/insert.rs b/crates/bevy_ecs/src/bundle/insert.rs index 4dffb138c3fe6..f931800e1e102 100644 --- a/crates/bevy_ecs/src/bundle/insert.rs +++ b/crates/bevy_ecs/src/bundle/insert.rs @@ -231,9 +231,9 @@ impl<'w> BundleInserter<'w> { 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(), + unsafe { entities.get_spawned(swapped_entity).debug_checked_unwrap() }; + entities.update_existing_location( + swapped_entity.row(), Some(EntityLocation { archetype_id: swapped_location.archetype_id, archetype_row: location.archetype_row, @@ -243,7 +243,7 @@ impl<'w> BundleInserter<'w> { ); } let new_location = new_archetype.allocate(entity, result.table_row); - entities.set(entity.index(), Some(new_location)); + entities.update_existing_location(entity.row(), Some(new_location)); bundle_info.write_components( table, sparse_sets, @@ -280,9 +280,9 @@ impl<'w> BundleInserter<'w> { 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(), + unsafe { entities.get_spawned(swapped_entity).debug_checked_unwrap() }; + entities.update_existing_location( + swapped_entity.row(), Some(EntityLocation { archetype_id: swapped_location.archetype_id, archetype_row: location.archetype_row, @@ -295,16 +295,16 @@ impl<'w> BundleInserter<'w> { // 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(), Some(new_location)); + entities.update_existing_location(entity.row(), Some(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() }; + unsafe { entities.get_spawned(swapped_entity).debug_checked_unwrap() }; - entities.set( - swapped_entity.index(), + entities.update_existing_location( + swapped_entity.row(), Some(EntityLocation { archetype_id: swapped_location.archetype_id, archetype_row: swapped_location.archetype_row, diff --git a/crates/bevy_ecs/src/bundle/remove.rs b/crates/bevy_ecs/src/bundle/remove.rs index f40c82c7f7d67..4e0cc058b0dc6 100644 --- a/crates/bevy_ecs/src/bundle/remove.rs +++ b/crates/bevy_ecs/src/bundle/remove.rs @@ -228,10 +228,10 @@ impl<'w> BundleRemover<'w> { .swap_remove(location.archetype_row); // if an entity was moved into this entity's archetype row, update its archetype row if let Some(swapped_entity) = remove_result.swapped_entity { - let swapped_location = world.entities.get(swapped_entity).unwrap(); + let swapped_location = world.entities.get_spawned(swapped_entity).unwrap(); - world.entities.set( - swapped_entity.index(), + world.entities.update_existing_location( + swapped_entity.row(), Some(EntityLocation { archetype_id: swapped_location.archetype_id, archetype_row: location.archetype_row, @@ -269,10 +269,10 @@ impl<'w> BundleRemover<'w> { // if an entity was moved into this entity's table row, update its table row if let Some(swapped_entity) = move_result.swapped_entity { - let swapped_location = world.entities.get(swapped_entity).unwrap(); + let swapped_location = world.entities.get_spawned(swapped_entity).unwrap(); - world.entities.set( - swapped_entity.index(), + world.entities.update_existing_location( + swapped_entity.row(), Some(EntityLocation { archetype_id: swapped_location.archetype_id, archetype_row: swapped_location.archetype_row, @@ -294,7 +294,9 @@ impl<'w> BundleRemover<'w> { // SAFETY: The entity is valid and has been moved to the new location already. unsafe { - world.entities.set(entity.index(), Some(new_location)); + world + .entities + .update_existing_location(entity.row(), Some(new_location)); } (new_location, pre_remove_result) diff --git a/crates/bevy_ecs/src/bundle/spawner.rs b/crates/bevy_ecs/src/bundle/spawner.rs index 571fe8c39e58d..1120383c92fc1 100644 --- a/crates/bevy_ecs/src/bundle/spawner.rs +++ b/crates/bevy_ecs/src/bundle/spawner.rs @@ -5,9 +5,8 @@ use bevy_ptr::{ConstNonNull, MovingPtr}; use crate::{ archetype::{Archetype, ArchetypeCreated, ArchetypeId, SpawnBundleStatus}, bundle::{Bundle, BundleId, BundleInfo, DynamicBundle, InsertMode}, - change_detection::MaybeLocation, - change_detection::Tick, - entity::{Entities, Entity, EntityLocation}, + change_detection::{MaybeLocation, Tick}, + entity::{EntitiesAllocator, Entity, EntityLocation}, event::EntityComponentsTrigger, lifecycle::{Add, Insert, ADD, INSERT}, relationship::RelationshipHookMode, @@ -89,7 +88,7 @@ impl<'w> BundleSpawner<'w> { /// [`apply_effect`]: crate::bundle::DynamicBundle::apply_effect #[inline] #[track_caller] - pub unsafe fn spawn_non_existent( + pub unsafe fn spawn_at( &mut self, entity: Entity, bundle: MovingPtr<'_, T>, @@ -120,8 +119,8 @@ impl<'w> BundleSpawner<'w> { InsertMode::Replace, caller, ); - entities.set(entity.index(), Some(location)); - entities.mark_spawn_despawn(entity.index(), caller, self.change_tick); + entities.set_location(entity.row(), Some(location)); + entities.mark_spawned_or_despawned(entity.row(), caller, self.change_tick); location }; @@ -186,22 +185,16 @@ impl<'w> BundleSpawner<'w> { bundle: MovingPtr<'_, T>, caller: MaybeLocation, ) -> Entity { - let entity = self.entities().alloc(); - // SAFETY: - // - `entity` is allocated above - // - The caller ensures that `T` matches this `BundleSpawner`'s type. - // - The caller ensures that if `T::Effect: !NoBundleEffect.`, then [`apply_effect`] must be called exactly once on `bundle` - // after this function returns before returning to safe code. - // - The caller ensures that the value pointed to by `bundle` must not be accessed for anything other than [`apply_effect`] - // or dropped. - unsafe { self.spawn_non_existent::(entity, bundle, caller) }; + let entity = self.allocator().alloc(); + // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type + let _ = unsafe { self.spawn_at(entity, bundle, caller) }; entity } #[inline] - pub(crate) fn entities(&mut self) -> &mut Entities { + pub(crate) fn allocator(&mut self) -> &'w mut EntitiesAllocator { // SAFETY: No outstanding references to self.world, changes to entities cannot invalidate our internal pointers - unsafe { &mut self.world.world_mut().entities } + unsafe { &mut self.world.world_mut().allocator } } /// # Safety diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 3b68f854beb96..4f556a71ad996 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -1,21 +1,20 @@ -use alloc::{boxed::Box, collections::VecDeque, vec::Vec}; -use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet}; -use bevy_ptr::{Ptr, PtrMut}; -use bevy_utils::prelude::DebugName; -use bumpalo::Bump; -use core::{any::TypeId, cell::LazyCell, ops::Range}; -use derive_more::derive::From; - use crate::{ archetype::Archetype, bundle::{Bundle, BundleRemover, InsertMode}, change_detection::MaybeLocation, component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo}, - entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper}, + entity::{hash_map::EntityHashMap, EntitiesAllocator, Entity, EntityMapper}, query::DebugCheckedUnwrap, relationship::RelationshipHookMode, world::World, }; +use alloc::{boxed::Box, collections::VecDeque, vec::Vec}; +use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet}; +use bevy_ptr::{Ptr, PtrMut}; +use bevy_utils::prelude::DebugName; +use bumpalo::Bump; +use core::{any::TypeId, cell::LazyCell, ops::Range}; +use derive_more::From; /// Provides read access to the source component (the component being cloned) in a [`ComponentCloneFn`]. pub struct SourceComponent<'a> { @@ -80,7 +79,7 @@ pub struct ComponentCloneCtx<'a, 'b> { target_component_moved: bool, bundle_scratch: &'a mut BundleScratch<'b>, bundle_scratch_allocator: &'b Bump, - entities: &'a Entities, + allocator: &'a EntitiesAllocator, source: Entity, target: Entity, component_info: &'a ComponentInfo, @@ -106,7 +105,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { target: Entity, bundle_scratch_allocator: &'b Bump, bundle_scratch: &'a mut BundleScratch<'b>, - entities: &'a Entities, + allocator: &'a EntitiesAllocator, component_info: &'a ComponentInfo, entity_cloner: &'a mut EntityClonerState, mapper: &'a mut dyn EntityMapper, @@ -121,7 +120,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { target_component_written: false, target_component_moved: false, bundle_scratch_allocator, - entities, + allocator, mapper, component_info, state: entity_cloner, @@ -279,7 +278,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// Queues the `entity` to be cloned by the current [`EntityCloner`] pub fn queue_entity_clone(&mut self, entity: Entity) { - let target = self.entities.reserve_entity(); + let target = self.allocator.alloc(); self.mapper.set_mapped(entity, target); self.state.clone_queue.push_back(entity); } @@ -565,6 +564,10 @@ impl EntityCloner { relationship_hook_insert_mode: RelationshipHookMode, ) -> Entity { let target = mapper.get_mapped(source); + // The target may need to be constructed if it hasn't been already. + // If this fails, it either didn't need to be constructed (ok) or doesn't exist (caught better later). + let _ = world.spawn_at_empty(target); + // PERF: reusing allocated space across clones would be more efficient. Consider an allocation model similar to `Commands`. let bundle_scratch_allocator = Bump::new(); let mut bundle_scratch: BundleScratch; @@ -572,7 +575,10 @@ impl EntityCloner { let mut deferred_cloned_component_ids: Vec = Vec::new(); { let world = world.as_unsafe_world_cell(); - let source_entity = world.get_entity(source).expect("Source entity must exist"); + let source_entity = world + .get_entity(source) + .expect("Source entity must exist constructed"); + let source_archetype = source_entity.archetype(); #[cfg(feature = "bevy_reflect")] // SAFETY: we have unique access to `world`, nothing else accesses the registry at this moment, and we clone @@ -585,13 +591,12 @@ impl EntityCloner { #[cfg(not(feature = "bevy_reflect"))] let app_registry = Option::<()>::None; - let source_archetype = source_entity.archetype(); bundle_scratch = BundleScratch::with_capacity(source_archetype.component_count()); let target_archetype = LazyCell::new(|| { world .get_entity(target) - .expect("Target entity must exist") + .expect("Target entity must exist constructed") .archetype() }); @@ -641,7 +646,7 @@ impl EntityCloner { target, &bundle_scratch_allocator, &mut bundle_scratch, - world.entities(), + world.entities_allocator(), info, state, mapper, diff --git a/crates/bevy_ecs/src/entity/map_entities.rs b/crates/bevy_ecs/src/entity/map_entities.rs index 0ac3ed52fd676..10f1740351161 100644 --- a/crates/bevy_ecs/src/entity/map_entities.rs +++ b/crates/bevy_ecs/src/entity/map_entities.rs @@ -56,7 +56,7 @@ use super::EntityIndexSet; pub trait MapEntities { /// Updates all [`Entity`] references stored inside using `entity_mapper`. /// - /// Implementors should look up any and all [`Entity`] values stored within `self` and + /// Implementers should look up any and all [`Entity`] values stored within `self` and /// update them to the mapped values via `entity_mapper`. fn map_entities(&mut self, entity_mapper: &mut E); } @@ -202,7 +202,7 @@ impl> MapEntities for SmallVec { /// /// More generally, this can be used to map [`Entity`] references between any two [`Worlds`](World). /// -/// This is used by [`MapEntities`] implementors. +/// This is used by [`MapEntities`] implementers. /// /// ## Example /// @@ -336,14 +336,10 @@ impl<'m> SceneEntityMapper<'m> { } /// Creates a new [`SceneEntityMapper`], spawning a temporary base [`Entity`] in the provided [`World`] - pub fn new(map: &'m mut EntityHashMap, world: &mut World) -> Self { - // We're going to be calling methods on `Entities` that require advance - // flushing, such as `alloc` and `free`. - world.flush_entities(); + pub fn new(map: &'m mut EntityHashMap, world: &World) -> Self { Self { map, - // SAFETY: Entities data is kept in a valid state via `EntityMapper::world_scope` - dead_start: unsafe { world.entities_mut().alloc() }, + dead_start: world.allocator.alloc(), generations: 0, } } @@ -352,10 +348,13 @@ impl<'m> SceneEntityMapper<'m> { /// [`Entity`] while reserving extra generations. Because this makes the [`SceneEntityMapper`] unable to /// safely allocate any more references, this method takes ownership of `self` in order to render it unusable. pub fn finish(self, world: &mut World) { - // SAFETY: Entities data is kept in a valid state via `EntityMap::world_scope` - let entities = unsafe { world.entities_mut() }; - assert!(entities.free(self.dead_start).is_some()); - assert!(entities.reserve_generations(self.dead_start.index(), self.generations)); + // SAFETY: We never constructed the entity and never released it for something else to construct. + let reuse_row = unsafe { + world + .entities + .mark_free(self.dead_start.row(), self.generations) + }; + world.allocator.free(reuse_row); } /// Creates an [`SceneEntityMapper`] from a provided [`World`] and [`EntityHashMap`], then calls the @@ -388,7 +387,7 @@ mod tests { fn entity_mapper() { let mut map = EntityHashMap::default(); let mut world = World::new(); - let mut mapper = SceneEntityMapper::new(&mut map, &mut world); + let mut mapper = SceneEntityMapper::new(&mut map, &world); let mapped_ent = Entity::from_raw_u32(1).unwrap(); let dead_ref = mapper.get_mapped(mapped_ent); @@ -431,21 +430,4 @@ mod tests { .cmp_approx(&dead_ref.generation()) .is_gt()); } - - #[test] - fn entity_mapper_no_panic() { - let mut world = World::new(); - // "Dirty" the `Entities`, requiring a flush afterward. - world.entities.reserve_entity(); - assert!(world.entities.needs_flush()); - - // Create and exercise a SceneEntityMapper - should not panic because it flushes - // `Entities` first. - SceneEntityMapper::world_scope(&mut Default::default(), &mut world, |_, m| { - m.get_mapped(Entity::PLACEHOLDER); - }); - - // The SceneEntityMapper should leave `Entities` in a flushed state. - assert!(!world.entities.needs_flush()); - } } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index dd1b1b24f7c78..0e3414baee971 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -1,40 +1,183 @@ -//! Entity handling types. +//! This module contains all entity types and utilities for interacting with their ids. //! -//! An **entity** exclusively owns zero or more [component] instances, all of different types, and can dynamically acquire or lose them over its lifetime. +//! # What is an Entity? //! -//! **empty entity**: Entity with zero components. -//! **pending entity**: Entity reserved, but not flushed yet (see [`Entities::flush`] docs for reference). -//! **reserved entity**: same as **pending entity**. -//! **invalid entity**: **pending entity** flushed with invalid (see [`Entities::flush_as_invalid`] docs for reference). +//! The ecs [docs](crate) give an overview of what entities are and generally how to use them. +//! These docs provide more detail into how they actually work. +//! In these docs [`Entity`] and "entity id" are synonymous and refer to the [`Entity`] type, which identifies an entity. +//! The term "entity" used on its own refers to the "thing"/"game object" that id references. //! -//! See [`Entity`] to learn more. +//! # In this Module //! -//! [component]: crate::component::Component +//! This module contains four main things: //! -//! # Usage +//! - Core ECS types like [`Entity`], [`Entities`], and [`EntitiesAllocator`]. +//! - Utilities for [`Entity`] ids like [`MapEntities`], [`EntityHash`], and [`UniqueEntityVec`]. +//! - Helpers for entity tasks like [`EntityCloner`]. +//! - Entity-related error types like [`EntityNotSpawnedError`]. //! -//! Operations involving entities and their components are performed either from a system by submitting commands, -//! or from the outside (or from an exclusive system) by directly using [`World`] methods: +//! # Entity Life Cycle //! -//! |Operation|Command|Method| -//! |:---:|:---:|:---:| -//! |Spawn an entity with components|[`Commands::spawn`]|[`World::spawn`]| -//! |Spawn an entity without components|[`Commands::spawn_empty`]|[`World::spawn_empty`]| -//! |Despawn an entity|[`EntityCommands::despawn`]|[`World::despawn`]| -//! |Insert a component, bundle, or tuple of components and bundles to an entity|[`EntityCommands::insert`]|[`EntityWorldMut::insert`]| -//! |Remove a component, bundle, or tuple of components and bundles from an entity|[`EntityCommands::remove`]|[`EntityWorldMut::remove`]| +//! Entities have life cycles. +//! They are created, used for a while, and eventually destroyed. +//! Let's start from the top: +//! +//! **Spawn:** An entity is created. +//! In bevy, this is called spawning. +//! Most commonly, this is done through [`World::spawn`](crate::world::World::spawn) or [`Commands::spawn`](crate::system::Commands::spawn). +//! This creates a fresh entity in the world and returns its [`Entity`] id, which can be used to interact with the entity it identifies. +//! These methods initialize the entity with a [`Bundle`], a group of [components](crate::component::Component) that it starts with. +//! It is also possible to use [`World::spawn_empty`](crate::world::World::spawn_empty) or [`Commands::spawn_empty`](crate::system::Commands::spawn_empty), which are similar but do not add any components to the entity. +//! In either case, the returned [`Entity`] id is used to further interact with the entity. +//! +//! **Update:** Once an entity is created, you will need its [`Entity`] id to progress its life cycle. +//! This can be done through [`World::entity_mut`](crate::world::World::entity_mut) and [`Commands::entity`](crate::system::Commands::entity). +//! Even if you don't store the id, you can still find the entity you spawned by searching for it in a [`Query`]. +//! Queries are also the primary way of interacting with an entity's components. +//! You can use [`EntityWorldMut::remove`](crate::world::EntityWorldMut::remove) and [`EntityCommands::remove`](crate::system::EntityCommands::remove) to remove components, +//! and you can use [`EntityWorldMut::insert`](crate::world::EntityWorldMut::insert) and [`EntityCommands::insert`](crate::system::EntityCommands::insert) to insert more components. +//! Be aware that each entity can only have 0 or 1 values for each kind of component, so inserting a bundle may overwrite existing component values. +//! This can also be further configured based on the insert method. +//! +//! **Despawn:** Despawn an entity when it is no longer needed. +//! This destroys it and all its components. +//! The entity is no longer reachable through the [`World`], [`Commands`], or [`Query`]s. +//! Note that this means an [`Entity`] id may refer to an entity that has since been despawned! +//! Not all [`Entity`] ids refer to active entities. +//! If an [`Entity`] id is used when its entity has been despawned, an [`EntityNotSpawnedError`] is emitted. +//! Any [`System`](crate::system) could despawn any entity; even if you never share its id, it could still be despawned unexpectedly. +//! Your code should do its best to handle these errors gracefully. +//! +//! In short: +//! +//! - Entities are spawned through methods like [`World::spawn`](crate::world::World::spawn), which return an [`Entity`] id for the new entity. +//! - Once spawned, they can be accessed and modified through [`Query`]s and other apis. +//! - You can get the [`Entity`] id of an entity through [`Query`]s, so losing an [`Entity`] id is not a problem. +//! - Entities can have components inserted and removed via [`World::entity_mut`](crate::world::World::entity_mut) and [`Commands::entity`](crate::system::Commands::entity). +//! - Entities are eventually despawned, destroying the entity and causing its [`Entity`] id to no longer refer to an entity. +//! - Not all [`Entity`] ids point to actual entities, which makes many entity methods fallible. +//! +//! # Entity Ids +//! +//! As mentioned entities each have an [`Entity`] id, which is used to interact with that entity. +//! But what actually is this id? +//! This [`Entity`] id is the combination of two ideas: [`EntityRow`] and [`EntityGeneration`]. +//! You can think of the [`Entity`] type as a `struct Entity { row: u32, generation: u32 }`. +//! +//! To understand these ids, picture the ECS [`World`] as a spreadsheet. +//! Each kind of component is represented by a column in the spreadsheet and each entity is a row. +//! That's what the `row` does in [`Entity`]; it identifies where in the spreadsheet to find component values. +//! If an entity doesn't have a component, picture leaving the cell at that entity row and component column blank or `None`. +//! To find the component values of an entity, Bevy searches through the spreadsheet at the [`EntityRow`] for the entity and the [`ComponentId`](crate::component::ComponentId) for the component. +//! +//! An [`EntityRow`] always references exactly 1 entity in the [`World`]. +//! Think about it, even if the spreadsheet only *uses* rows 1, 2, and 12, it still *has* millions of rows. +//! In the spreadsheet analogy, you can think of each row as being in one of 3 states: +//! +//! 1. The row is not used. +//! Think of this as graying out the row or otherwise hiding it. +//! This row doesn't just have no components; it isn't even participating at this point. +//! 2. The row is empty. +//! The row is being used; it's visible, discoverable, etc; it just happens to not have any component values. +//! 3. The row is full. +//! This is the "normal" state of a row. +//! It has some component values and is being used. +//! +//! [`EntityRow`] behaves much the same way as the spreadsheet row. +//! Each row has a [`EntityRowLocation`] which defines that row/entity's state. +//! The [`EntityRowLocation`] is an `Option` of [`EntityLocation`]. +//! If this is `Some`, the row is considered spawned (think *used* in the spreadsheet), otherwise it is considered despawned (think *grayed out* in the spreadsheet). +//! Only spawned entities, entities with `Some` [`EntityLocation`], participate in the [`World`]. +//! The [`EntityLocation`] further describes which components an entity has and where to find them; it determines which spreadsheet cells are blank and which ones have values. +//! Only spawned rows are discoverable through [`Query`]s, etc. +//! +//! With that spreadsheet intuition, lets get a bit more precise with some definitions: +//! +//! - An entity that is used, not grayed out in the spreadsheet, is considered *spawned*. +//! - An entity that is is grayed out in the spreadsheet, not used, is considered *despawned*. +//! - A spawned entity that has no components is considered *empty* or *void* though still *spawned*. +//! This is different from despawned since these are still participating, discoverable through queries and interact-able through commands; +//! they just happen to have no components. +//! +//! An [`EntityRow`] always references exactly 1 entity in the [`World`]; they always exist (even though they may still be despawned). +//! This differs from [`Entity`] which references 0 or 1 entities, depending on if the entity it refers to still exists. +//! The rows are represented with 32 bits, so there are always over 4 billion entities in the world. +//! However, not all these entities are usable or stored in memory; Bevy doesn't store information for rows that have never been spawned. +//! +//! Rows can be repeatedly spawned and despawned. +//! Each spawning and despawning corresponds to an [`EntityGeneration`]. +//! The first time a row is spawned, it has a generation of 0, and when it is despawned, it gets a generation of 1. +//! This differentiates each spawning of that [`EntityRow`]. +//! Again, all an [`Entity`] id is is an [`EntityRow`] (where to find the component values) and an [`EntityGeneration`] (which version of that row it references). +//! When an [`Entity`] id is invalid, it just means that that generation of its row has been despawned. +//! It could still be despawned or it could have been re-spawned after it was despawned. +//! Either way, that row-generation pair no longer exists. +//! This produces the [`InvalidEntityError`]. +//! +//! As mentioned, once an [`EntityRow`] is despawned, it is not discoverable until it is spawned again. +//! To prevent these rows from being forgotten, bevy tracks them in an [`EntitiesAllocator`]. +//! When a new entity is spawned, all bevy does is allocate a new [`Entity`] id from the allocator and [`World::spawn_at`](crate::world::World::spawn_at) it. +//! When it is despawned, all bevy does is [`World::despawn_no_free`](crate::world::World::despawn_no_free) it and return the [`Entity`] id (with the next [`EntityGeneration`] for that [`EntityRow`]) to the allocator. +//! It's that simple. +//! +//! Bevy exposes this functionality as well. +//! Spawning an entity requires full access to the [`World`], but using [`EntitiesAllocator::alloc`] can be done fully concurrently. +//! Of course, to make that entity usable, it will need to be passed to [`World::spawn_at`](crate::world::World::spawn_at). +//! Managing entity ids manually is advanced but can be very useful for concurrency, custom entity allocators, etc. +//! But there are risks when used improperly: +//! Losing a despawned entity row without returning it to bevy's allocator will cause that row to be unreachable, effectively a memory leak. +//! Further, spawning an arbitrary [`EntityRow`] can cause problems if that same row is queued for reuse in the allocator. +//! This is powerful, but use it with caution. +//! +//! Lots of information about the state of an [`EntityRow`] can be obtained through [`Entities`]. +//! For example, this can be used to get the most recent [`Entity`] of an [`EntityRow`] in [`Entities::resolve_from_row`]. +//! See its docs for more. +//! +//! In general, you won't need to interact with the [`Entities`] type directly, but you will still feel its impact, largely because of its error types. +//! The big three to be aware of are: +//! +//! - [`InvalidEntityError`]: when the [`EntityGeneration`] of an [`Entity`] unexpectedly does not match the most recent generation of its [`EntityRow`], +//! - [`EntityRowNotSpawnedError`]: when an [`EntityRow`] unexpectedly is not spawned, +//! - [`EntityNotSpawnedError`]: the union of the other two; when [`Entity`]'s [`EntityGeneration`] doesn't match the most recent generation of its [`EntityRow`] or when that row is not spawned, +//! +//! +//! To summarize: +//! +//! - An [`Entity`] id is just a [`EntityRow`] and a [`EntityGeneration`] of that row. +//! - [`EntityRow`]s can be spawned and despawned repeatedly, where each spawning gets its own [`EntityGeneration`]. +//! - Bevy exposes this functionality through [`EntitiesAllocator::alloc`], [`World::spawn_at`](crate::world::World::spawn_at), and [`World::despawn_no_free`](crate::world::World::despawn_no_free). +//! - While understanding these details help build an intuition for how bevy handles entities, using these apis directly is risky but powerful, +//! and you can usually use the easier [`World::spawn`](crate::world::World::spawn`), and [`World::despawn`](crate::world::World::despawn). +//! - Lots of id information can be obtained from [`Entities`]. +//! - Watch out for the errors [`InvalidEntityError`], [`EntityRowNotSpawnedError`], and [`EntityNotSpawnedError`], explained above. +//! +//! # Storage +//! +//! As mentioned above, an ecs [`World`] can be imagined as a spreadsheet. +//! One way that spreadsheet could be implemented is a list of [`Entity`]s and a hashmap for each component that maps an [`EntityRow`] to a component value if that row has the entity. +//! Bevy's ECS is quite different from that implementation (and much, much faster). +//! For details on how component storage actually works, see [`storage`](crate::storage). +//! +//! Regardless, the spreadsheet also needs a special column that tracks metadata about an entity. +//! This column does not represents a component and is specific to the [`EntityRow`], not the [`Entity`]. +//! For example, one thing Bevy stores in this metadata is the current [`EntityGeneration`] of the row. +//! It also stores more information like the [`Tick`] a row was last spawned or despawned, and the [`EntityRowLocation`] itself. +//! For more information about what's stored here, see [`Entities`], Bevy's implementation of this special column. +//! +//! Entity spawning is done in two stages: +//! First we create an entity id (alloc step), and then we spawn it (add components and other metadata). +//! The reason for this split is that we need to be able to assign entity ids concurrently, +//! while for spawning we need exclusive (non-concurrent) access to the world. +//! That leaves three states for any given [`EntityRow`], where those two spawning stages serve to transition between states. +//! First, a row could be unallocated; only the allocator has any knowledge of this [`Entity`]. +//! Second, the row is allocated, making it a "null" entity; it exists, and other code can have knowledge of its existence, but it is not spawned. +//! Third, the row is spawned, adding any (or no) components to the entity; it is now discoverable through queries, etc. //! //! [`World`]: crate::world::World -//! [`Commands::spawn`]: crate::system::Commands::spawn -//! [`Commands::spawn_empty`]: crate::system::Commands::spawn_empty -//! [`EntityCommands::despawn`]: crate::system::EntityCommands::despawn -//! [`EntityCommands::insert`]: crate::system::EntityCommands::insert -//! [`EntityCommands::remove`]: crate::system::EntityCommands::remove -//! [`World::spawn`]: crate::world::World::spawn -//! [`World::spawn_empty`]: crate::world::World::spawn_empty -//! [`World::despawn`]: crate::world::World::despawn -//! [`EntityWorldMut::insert`]: crate::world::EntityWorldMut::insert -//! [`EntityWorldMut::remove`]: crate::world::EntityWorldMut::remove +//! [`Query`]: crate::system::Query +//! [`Bundle`]: crate::bundle::Bundle +//! [`Component`]: crate::component::Component +//! [`Commands`]: crate::system::Commands mod clone_entities; mod entity_set; @@ -79,26 +222,13 @@ use crate::{ storage::{SparseSetIndex, TableId, TableRow}, }; use alloc::vec::Vec; -use bevy_platform::sync::atomic::Ordering; +use bevy_platform::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; use core::{fmt, hash::Hash, mem, num::NonZero, panic::Location}; use log::warn; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; -#[cfg(target_has_atomic = "64")] -use bevy_platform::sync::atomic::AtomicI64 as AtomicIdCursor; -#[cfg(target_has_atomic = "64")] -type IdCursor = i64; - -/// Most modern platforms support 64-bit atomics, but some less-common platforms -/// do not. This fallback allows compilation using a 32-bit cursor instead, with -/// the caveat that some conversions may fail (and panic) at runtime. -#[cfg(not(target_has_atomic = "64"))] -use bevy_platform::sync::atomic::AtomicIsize as AtomicIdCursor; -#[cfg(not(target_has_atomic = "64"))] -type IdCursor = isize; - /// This represents the row or "index" of an [`Entity`] within the [`Entities`] table. /// This is a lighter weight version of [`Entity`]. /// @@ -300,15 +430,10 @@ impl EntityGeneration { } } -/// Lightweight identifier of an [entity](crate::entity). -/// -/// The identifier is implemented using a [generational index]: a combination of an index ([`EntityRow`]) and a generation ([`EntityGeneration`]). -/// This allows fast insertion after data removal in an array while minimizing loss of spatial locality. -/// -/// These identifiers are only valid on the [`World`] it's sourced from. Attempting to use an `Entity` to -/// fetch entity components or metadata from a different world will either fail or return unexpected results. -/// -/// [generational index]: https://lucassardois.medium.com/generational-indices-guide-8e3c5f7fd594 +/// Unique identifier for an entity in a [`World`]. +/// Note that this is just an id, not the entity itself. +/// Further, the entity this id refers to may no longer exist in the [`World`]. +/// For more information about entities, their ids, and how to use them, see the module [docs](crate::entity). /// /// # Aliasing /// @@ -320,7 +445,7 @@ impl EntityGeneration { /// Aliasing can happen without warning. /// Holding onto a [`Entity`] id corresponding to an entity well after that entity was despawned can cause un-intuitive behavior for both ordering, and comparing in general. /// To prevent these bugs, it is generally best practice to stop holding an [`Entity`] or [`EntityGeneration`] value as soon as you know it has been despawned. -/// If you must do otherwise, do not assume the [`Entity`] corresponds to the same conceptual entity it originally did. +/// If you must do otherwise, do not assume the [`Entity`] id corresponds to the same entity it originally did. /// See [`EntityGeneration`]'s docs for more information about aliasing and why it occurs. /// /// # Stability warning @@ -660,487 +785,360 @@ impl SparseSetIndex for Entity { } } -/// An [`Iterator`] returning a sequence of [`Entity`] values from -pub struct ReserveEntitiesIterator<'a> { - // Metas, so we can recover the current generation for anything in the freelist. - meta: &'a [EntityMeta], - - // Reserved indices formerly in the freelist to hand out. - freelist_indices: core::slice::Iter<'a, EntityRow>, - - // New Entity indices to hand out, outside the range of meta.len(). - new_indices: core::ops::Range, +/// Allocates [`Entity`] ids uniquely. +/// This is used in [`World::spawn_at`](crate::world::World::spawn_at) and [`World::despawn_no_free`](crate::world::World::despawn_no_free) to track entity ids no longer in use. +/// Allocating is fully concurrent and can be done from multiple threads. +/// +/// Conceptually, this is a collection of [`Entity`] ids who's [`EntityRow`] is despawned and who's [`EntityGeneration`] is the most recent. +/// See the module docs for how these ids and this allocator participate in the life cycle of an entity. +#[derive(Default, Debug)] +pub struct EntitiesAllocator { + /// All the entities to reuse. + /// This is a buffer, which contains an array of [`Entity`] ids to hand out. + /// The next id to hand out is tracked by `free_len`. + free: Vec, + /// This is continually subtracted from. + /// If it wraps to a very large number, it will be outside the bounds of `free`, + /// and a new row will be needed. + free_len: AtomicUsize, + /// This is the next "fresh" row to hand out. + /// If there are no rows to reuse, this row, which has a generation of 0, is the next to return. + next_row: AtomicU32, } -impl<'a> Iterator for ReserveEntitiesIterator<'a> { - type Item = Entity; - - fn next(&mut self) -> Option { - self.freelist_indices - .next() - .map(|&row| { - Entity::from_row_and_generation(row, self.meta[row.index() as usize].generation) - }) - .or_else(|| { - self.new_indices.next().map(|index| { - // SAFETY: This came from an exclusive range so the max can't be hit. - let row = unsafe { EntityRow::new(NonMaxU32::new_unchecked(index)) }; - Entity::from_row(row) - }) - }) - } - - fn size_hint(&self) -> (usize, Option) { - let len = self.freelist_indices.len() + self.new_indices.len(); - (len, Some(len)) +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; + } + + /// This allows `freed` to be retrieved from [`alloc`](Self::alloc), etc. + /// Freeing an [`Entity`] such that one [`EntityRow`] is in the allocator in multiple places can cause panics when spawning the allocated entity. + /// Additionally, to differentiate versions of an [`Entity`], updating the [`EntityGeneration`] before freeing is a good idea + /// (but not strictly necessary if you don't mind [`Entity`] id aliasing.) + pub fn free(&mut self, freed: Entity) { + let expected_len = *self.free_len.get_mut(); + if expected_len > self.free.len() { + self.free.clear(); + } else { + self.free.truncate(expected_len); + } + self.free.push(freed); + *self.free_len.get_mut() = self.free.len(); } -} - -impl<'a> ExactSizeIterator for ReserveEntitiesIterator<'a> {} - -impl<'a> core::iter::FusedIterator for ReserveEntitiesIterator<'a> {} - -// SAFETY: Newly reserved entity values are unique. -unsafe impl EntitySetIterator for ReserveEntitiesIterator<'_> {} -/// A [`World`]'s internal metadata store on all of its entities. -/// -/// Contains metadata on: -/// - The generation of every entity. -/// - The alive/dead status of a particular entity. (i.e. "has entity 3 been despawned?") -/// - The location of the entity's components in memory (via [`EntityLocation`]) -/// -/// [`World`]: crate::world::World -#[derive(Debug)] -pub struct Entities { - meta: Vec, - - /// The `pending` and `free_cursor` fields describe three sets of Entity IDs - /// that have been freed or are in the process of being allocated: + /// Allocates some [`Entity`]. + /// The result could have come from a [`free`](Self::free) or be a brand new [`EntityRow`]. /// - /// - The `freelist` IDs, previously freed by `free()`. These IDs are available to any of - /// [`alloc`], [`reserve_entity`] or [`reserve_entities`]. Allocation will always prefer - /// these over brand new IDs. + /// The returned entity is valid and unique, but it is not yet spawned. + /// Using the id as if it were spawned may produce errors. + /// It can not be queried, and it has no [`EntityLocation`]. + /// See module [docs](crate::entity) for more information about entity validity vs spawning. /// - /// - The `reserved` list of IDs that were once in the freelist, but got reserved by - /// [`reserve_entities`] or [`reserve_entity`]. They are now waiting for [`flush`] to make them - /// fully allocated. + /// This is different from empty entities, which are spawned and + /// just happen to have no components. /// - /// - The count of new IDs that do not yet exist in `self.meta`, but which we have handed out - /// and reserved. [`flush`] will allocate room for them in `self.meta`. + /// These ids must be used; otherwise, they will be forgotten. + /// For example, the result must be eventually used to either spawn an entity or be [`free`](Self::free)d. /// - /// The contents of `pending` look like this: + /// # Panics /// - /// ```txt - /// ---------------------------- - /// | freelist | reserved | - /// ---------------------------- - /// ^ ^ - /// free_cursor pending.len() - /// ``` + /// If there are no more entities available, this panics. /// - /// As IDs are allocated, `free_cursor` is atomically decremented, moving - /// items from the freelist into the reserved list by sliding over the boundary. /// - /// Once the freelist runs out, `free_cursor` starts going negative. - /// The more negative it is, the more IDs have been reserved starting exactly at - /// the end of `meta.len()`. + /// # Example /// - /// This formulation allows us to reserve any number of IDs first from the freelist - /// and then from the new IDs, using only a single atomic subtract. + /// This is particularly useful when spawning entities in special ways. + /// For example, [`Commands`](crate::system::Commands) uses this to allocate an entity and [`spawn_at`](crate::world::World::spawn_at) it later. + /// But remember, since this entity is not queryable and is not discoverable, losing the returned [`Entity`] effectively leaks it, never to be used again! /// - /// Once [`flush`] is done, `free_cursor` will equal `pending.len()`. + /// ``` + /// # use bevy_ecs::{prelude::*}; + /// let mut world = World::new(); + /// let entity = world.entities_allocator().alloc(); + /// // wait as long as you like + /// let entity_access = world.spawn_at_empty(entity).unwrap(); // or spawn_at(entity, my_bundle) + /// // treat it as a normal entity + /// entity_access.despawn(); + /// ``` /// - /// [`alloc`]: Entities::alloc - /// [`reserve_entity`]: Entities::reserve_entity - /// [`reserve_entities`]: Entities::reserve_entities - /// [`flush`]: Entities::flush - pending: Vec, - free_cursor: AtomicIdCursor, -} - -impl Entities { - pub(crate) const fn new() -> Self { - Entities { - meta: Vec::new(), - pending: Vec::new(), - free_cursor: AtomicIdCursor::new(0), - } + /// More generally, manually spawning and [`despawn_no_free`](crate::world::World::despawn_no_free)ing entities allows you to skip Bevy's default entity allocator. + /// This is useful if you want to enforce properties about the [`EntityRow`]s of a group of entities, make a custom allocator, etc. + pub fn alloc(&self) -> Entity { + let index = self + .free_len + .fetch_sub(1, Ordering::Relaxed) + .wrapping_sub(1); + self.free.get(index).copied().unwrap_or_else(|| { + let row = self.next_row.fetch_add(1, Ordering::Relaxed); + let row = NonMaxU32::new(row).expect("too many entities"); + Entity::from_row(EntityRow::new(row)) + }) } - /// Reserve entity IDs concurrently. + /// A more efficient way of calling [`alloc`](Self::alloc) repeatedly `count` times. + /// See [`alloc`](Self::alloc) for details. /// - /// Storage for entity generation and location is lazily allocated by calling [`flush`](Entities::flush). - #[expect( - clippy::allow_attributes, - reason = "`clippy::unnecessary_fallible_conversions` may not always lint." - )] - #[allow( - clippy::unnecessary_fallible_conversions, - reason = "`IdCursor::try_from` may fail on 32-bit platforms." - )] - pub fn reserve_entities(&self, count: u32) -> ReserveEntitiesIterator<'_> { - // Use one atomic subtract to grab a range of new IDs. The range might be - // entirely nonnegative, meaning all IDs come from the freelist, or entirely - // negative, meaning they are all new IDs to allocate, or a mix of both. - let range_end = self.free_cursor.fetch_sub( - IdCursor::try_from(count) - .expect("64-bit atomic operations are not supported on this platform."), - Ordering::Relaxed, - ); - let range_start = range_end - - IdCursor::try_from(count) - .expect("64-bit atomic operations are not supported on this platform."); - - let freelist_range = range_start.max(0) as usize..range_end.max(0) as usize; - - let (new_id_start, new_id_end) = if range_start >= 0 { - // We satisfied all requests from the freelist. - (0, 0) + /// Like [`alloc`](Self::alloc), these entities must be used, otherwise they will be forgotten. + /// If the iterator is not exhausted, its remaining entities are forgotten. + /// See [`AllocEntitiesIterator`] docs for more. + pub fn alloc_many(&self, count: u32) -> AllocEntitiesIterator<'_> { + let current_len = self.free_len.fetch_sub(count as usize, Ordering::Relaxed); + let current_len = if current_len < self.free.len() { + current_len } else { - // We need to allocate some new Entity IDs outside of the range of self.meta. - // - // `range_start` covers some negative territory, e.g. `-3..6`. - // Since the nonnegative values `0..6` are handled by the freelist, that - // means we need to handle the negative range here. - // - // In this example, we truncate the end to 0, leaving us with `-3..0`. - // Then we negate these values to indicate how far beyond the end of `meta.end()` - // to go, yielding `meta.len()+0 .. meta.len()+3`. - let base = self.meta.len() as IdCursor; - - let new_id_end = u32::try_from(base - range_start).expect("too many entities"); - - // `new_id_end` is in range, so no need to check `start`. - let new_id_start = (base - range_end.min(0)) as u32; - - (new_id_start, new_id_end) + 0 }; - - ReserveEntitiesIterator { - meta: &self.meta[..], - freelist_indices: self.pending[freelist_range].iter(), - new_indices: new_id_start..new_id_end, - } - } - - /// Reserve one entity ID concurrently. - /// - /// Equivalent to `self.reserve_entities(1).next().unwrap()`, but more efficient. - pub fn reserve_entity(&self) -> Entity { - let n = self.free_cursor.fetch_sub(1, Ordering::Relaxed); - if n > 0 { - // Allocate from the freelist. - let row = self.pending[(n - 1) as usize]; - Entity::from_row_and_generation(row, self.meta[row.index() as usize].generation) + let start = current_len.saturating_sub(count as usize); + let reuse = start..current_len; + let still_need = (count as usize - reuse.len()) as u32; + let new = if still_need > 0 { + let start_new = self.next_row.fetch_add(still_need, Ordering::Relaxed); + let end_new = start_new + .checked_add(still_need) + .expect("too many entities"); + start_new..end_new } else { - // Grab a new ID, outside the range of `meta.len()`. `flush()` must - // eventually be called to make it valid. - // - // As `self.free_cursor` goes more and more negative, we return IDs farther - // and farther beyond `meta.len()`. - let raw = self.meta.len() as IdCursor - n; - if raw == IdCursor::MAX { - panic!("number of entities can't exceed {}", IdCursor::MAX); - } - // SAFETY: We just checked the bounds - let row = unsafe { EntityRow::new(NonMaxU32::new_unchecked(raw as u32)) }; - Entity::from_row(row) + 0..0 + }; + AllocEntitiesIterator { + reuse: self.free[reuse].iter(), + new, } } +} - /// Check that we do not have pending work requiring `flush()` to be called. - fn verify_flushed(&mut self) { - debug_assert!( - !self.needs_flush(), - "flush() needs to be called before this operation is legal" - ); - } - - /// Allocate an entity ID directly. - pub fn alloc(&mut self) -> Entity { - self.verify_flushed(); - if let Some(row) = self.pending.pop() { - let new_free_cursor = self.pending.len() as IdCursor; - *self.free_cursor.get_mut() = new_free_cursor; - Entity::from_row_and_generation(row, self.meta[row.index() as usize].generation) - } else { - let index = u32::try_from(self.meta.len()) - .ok() - .and_then(NonMaxU32::new) - .expect("too many entities"); - self.meta.push(EntityMeta::EMPTY); - Entity::from_row(EntityRow::new(index)) - } - } +/// An [`Iterator`] returning a sequence of unique [`Entity`] values from [`Entities`]. +/// Dropping this will still retain the entities as allocated; this is effectively a leak. +/// To prevent this, ensure the iterator is exhausted before dropping it. +pub struct AllocEntitiesIterator<'a> { + reuse: core::slice::Iter<'a, Entity>, + new: core::ops::Range, +} - /// Destroy an entity, allowing it to be reused. - /// - /// Returns the `Option` of the entity or `None` if the `entity` was not present. - /// - /// Must not be called while reserved entities are awaiting `flush()`. - pub fn free(&mut self, entity: Entity) -> Option { - self.verify_flushed(); +impl<'a> Iterator for AllocEntitiesIterator<'a> { + type Item = Entity; - let meta = &mut self.meta[entity.index() as usize]; - if meta.generation != entity.generation { - return None; - } + fn next(&mut self) -> Option { + self.reuse.next().copied().or_else(|| { + self.new.next().map(|index| { + // SAFETY: This came from an exclusive range so the max can't be hit. + let row = unsafe { EntityRow::new(NonMaxU32::new_unchecked(index)) }; + Entity::from_row(row) + }) + }) + } - let (new_generation, aliased) = meta.generation.after_versions_and_could_alias(1); - meta.generation = new_generation; - if aliased { - warn!( - "Entity({}) generation wrapped on Entities::free, aliasing may occur", - entity.row() - ); - } + fn size_hint(&self) -> (usize, Option) { + let len = self.reuse.len() + self.new.len(); + (len, Some(len)) + } +} - let loc = mem::replace(&mut meta.location, EntityMeta::EMPTY.location); +impl<'a> ExactSizeIterator for AllocEntitiesIterator<'a> {} - self.pending.push(entity.row()); +impl<'a> core::iter::FusedIterator for AllocEntitiesIterator<'a> {} - let new_free_cursor = self.pending.len() as IdCursor; - *self.free_cursor.get_mut() = new_free_cursor; - Some(loc) - } +// SAFETY: Newly allocated entity values are unique. +unsafe impl EntitySetIterator for AllocEntitiesIterator<'_> {} - /// Ensure at least `n` allocations can succeed without reallocating. - #[expect( - clippy::allow_attributes, - reason = "`clippy::unnecessary_fallible_conversions` may not always lint." - )] - #[allow( - clippy::unnecessary_fallible_conversions, - reason = "`IdCursor::try_from` may fail on 32-bit platforms." - )] - pub fn reserve(&mut self, additional: u32) { - self.verify_flushed(); - - let freelist_size = *self.free_cursor.get_mut(); - let shortfall = IdCursor::try_from(additional) - .expect("64-bit atomic operations are not supported on this platform.") - - freelist_size; - if shortfall > 0 { - self.meta.reserve(shortfall as usize); - } - } +/// [`Entities`] tracks all known [`EntityRow`]s and their metadata. +/// This is like a base table of information all entities have. +#[derive(Debug, Clone)] +pub struct Entities { + meta: Vec, +} - /// Returns true if the [`Entities`] contains [`entity`](Entity). - // This will return false for entities which have been freed, even if - // not reallocated since the generation is incremented in `free` - pub fn contains(&self, entity: Entity) -> bool { - self.resolve_from_id(entity.row()) - .is_some_and(|e| e.generation() == entity.generation()) +impl Entities { + pub(crate) const fn new() -> Self { + Self { meta: Vec::new() } } - /// Clears all [`Entity`] from the World. + /// Clears all entity information pub fn clear(&mut self) { self.meta.clear(); - self.pending.clear(); - *self.free_cursor.get_mut() = 0; } - /// 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`. + /// Returns the [`EntityLocation`] of an [`Entity`] if it is valid and spawned. + /// This can error if the [`EntityGeneration`] of this entity has passed or if the [`EntityRow`] is not spawned. + /// + /// See the module [docs](crate::entity) for a full explanation of these ids, entity life cycles, and the meaning of this result. #[inline] - pub fn get(&self, entity: Entity) -> Option { - self.get_id_location(entity).flatten() + pub fn get_spawned(&self, entity: Entity) -> Result { + let meta = self.meta.get(entity.index() as usize); + let meta = meta.unwrap_or(&EntityMeta::FRESH); + if entity.generation() != meta.generation { + return Err(EntityNotSpawnedError::Invalid(InvalidEntityError { + entity, + current_generation: meta.generation, + })); + }; + meta.location.ok_or(EntityNotSpawnedError::RowNotSpawned( + EntityRowNotSpawnedError { + entity, + location: meta.spawned_or_despawned.by, + }, + )) } - /// Returns the [`EntityIdLocation`] of an [`Entity`]. - /// Note: for pending entities, returns `None`. + /// Returns the [`EntityRowLocation`] of an [`Entity`] if it exists. + /// This can fail if the id's [`EntityGeneration`] has passed. + /// + /// See the module [docs](crate::entity) for a full explanation of these ids, entity life cycles, and the meaning of this result. #[inline] - pub fn get_id_location(&self, entity: Entity) -> Option { - self.meta - .get(entity.index() as usize) - .filter(|meta| meta.generation == entity.generation) - .map(|meta| meta.location) + pub fn get(&self, entity: Entity) -> Result { + match self.get_spawned(entity) { + Ok(location) => Ok(Some(location)), + Err(EntityNotSpawnedError::RowNotSpawned { .. }) => Ok(None), + Err(EntityNotSpawnedError::Invalid(err)) => Err(err), + } } - /// Updates the location of an [`Entity`]. - /// This must be called when moving the components of the existing entity around in storage. + /// Get the [`Entity`] for the given [`EntityRow`]. + /// Note that this entity may not be spawned yet. /// - /// # Safety - /// - `index` must be a valid entity index. - /// - `location` must be valid for the entity at `index` or immediately made valid afterwards - /// before handing control to unknown code. + /// See the module [docs](crate::entity) for a full explanation of these ids, entity life cycles, and the meaning of this result. #[inline] - pub(crate) unsafe fn set(&mut self, index: u32, location: EntityIdLocation) { - // SAFETY: Caller guarantees that `index` a valid entity index - let meta = unsafe { self.meta.get_unchecked_mut(index as usize) }; - meta.location = location; + pub fn resolve_from_row(&self, row: EntityRow) -> Entity { + self.meta + .get(row.index() as usize) + .map(|meta| Entity::from_row_and_generation(row, meta.generation)) + .unwrap_or(Entity::from_row(row)) } - /// Mark an [`Entity`] as spawned or despawned in the given tick. + /// Returns whether the entity at this `row` is spawned or not. /// - /// # Safety - /// - `index` must be a valid entity index. + /// See the module [docs](crate::entity) for a full explanation of these ids, entity life cycles, and the meaning of this result. #[inline] - pub(crate) unsafe fn mark_spawn_despawn(&mut self, index: u32, by: MaybeLocation, tick: Tick) { - // SAFETY: Caller guarantees that `index` a valid entity index - let meta = unsafe { self.meta.get_unchecked_mut(index as usize) }; - meta.spawned_or_despawned = SpawnedOrDespawned { by, tick }; + pub fn is_row_spawned(&self, row: EntityRow) -> bool { + self.meta + .get(row.index() as usize) + .is_some_and(|meta| meta.location.is_some()) } - /// Increments the `generation` of a freed [`Entity`]. The next entity ID allocated with this - /// `index` will count `generation` starting from the prior `generation` + the specified - /// value + 1. + /// Returns true if the entity is valid. + /// This will return true for entities that are valid but have not been spawned. /// - /// Does nothing if no entity with this `index` has been allocated yet. - pub(crate) fn reserve_generations(&mut self, index: u32, generations: u32) -> bool { - if (index as usize) >= self.meta.len() { - return false; - } + /// See the module [docs](crate::entity) for a full explanation of these ids, entity life cycles, and the meaning of this result. + pub fn contains(&self, entity: Entity) -> bool { + self.resolve_from_row(entity.row()).generation() == entity.generation() + } - let meta = &mut self.meta[index as usize]; - if meta.location.is_none() { - meta.generation = meta.generation.after_versions(generations); - true - } else { - false - } + /// Returns true if the entity is valid and is spawned. + /// + /// See the module [docs](crate::entity) for a full explanation of these ids, entity life cycles, and the meaning of this result. + pub fn contains_spawned(&self, entity: Entity) -> bool { + self.get_spawned(entity).is_ok() } - /// Get the [`Entity`] with a given id, if it exists in this [`Entities`] collection - /// Returns `None` if this [`Entity`] is outside of the range of currently reserved Entities + /// Provides information regarding if `entity` may be safely spawned. + /// This can error if the entity is invalid or is already spawned. /// - /// Note: This method may return [`Entities`](Entity) which are currently free - /// Note that [`contains`](Entities::contains) will correctly return false for freed - /// entities, since it checks the generation - pub fn resolve_from_id(&self, row: EntityRow) -> Option { - let idu = row.index() as usize; - if let Some(&EntityMeta { generation, .. }) = self.meta.get(idu) { - Some(Entity::from_row_and_generation(row, generation)) - } else { - // `id` is outside of the meta list - check whether it is reserved but not yet flushed. - let free_cursor = self.free_cursor.load(Ordering::Relaxed); - // If this entity was manually created, then free_cursor might be positive - // Returning None handles that case correctly - let num_pending = usize::try_from(-free_cursor).ok()?; - (idu < self.meta.len() + num_pending).then_some(Entity::from_row(row)) + /// See the module [docs](crate::entity) for a full explanation of these ids, entity life cycles, and the meaning of this result. + #[inline] + pub fn check_can_spawn_at(&self, entity: Entity) -> Result<(), SpawnError> { + match self.get(entity) { + Ok(Some(_)) => Err(SpawnError::AlreadySpawned), + Ok(None) => Ok(()), + Err(err) => Err(SpawnError::Invalid(err)), } } - fn needs_flush(&mut self) -> bool { - *self.free_cursor.get_mut() != self.pending.len() as IdCursor + /// Updates the location of an [`EntityRow`]. + /// This must be called when moving the components of the existing entity around in storage. + /// Returns the previous location of the row. + /// + /// # Safety + /// - The current location of the `row` must already be set. If not, use [`set_location`](Self::set_location). + /// - `location` must be valid for the entity at `row` or immediately made valid afterwards + /// before handing control to unknown code. + #[inline] + pub(crate) unsafe fn update_existing_location( + &mut self, + row: EntityRow, + location: EntityRowLocation, + ) -> EntityRowLocation { + // SAFETY: Caller guarantees that `row` already had a location, so `declare` must have made the index valid already. + let meta = unsafe { self.meta.get_unchecked_mut(row.index() as usize) }; + mem::replace(&mut meta.location, location) } - /// Allocates space for entities previously reserved with [`reserve_entity`](Entities::reserve_entity) or - /// [`reserve_entities`](Entities::reserve_entities), then initializes each one using the supplied function. - /// - /// See [`EntityLocation`] for details on its meaning and how to set it. + /// Declares the location of an [`EntityRow`]. + /// This must be called when spawning entities, but when possible, prefer [`update_existing_location`](Self::update_existing_location). + /// Returns the previous location of the row. /// /// # Safety - /// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`] - /// each time init is called. This _can_ be [`ArchetypeId::INVALID`], provided the [`Entity`] - /// has not been assigned to an [`Archetype`][crate::archetype::Archetype]. - /// - /// Note: freshly-allocated entities (ones which don't come from the pending list) are guaranteed - /// to be initialized with the invalid archetype. - pub unsafe fn flush( + /// - `location` must be valid for the entity at `row` or immediately made valid afterwards + /// before handing control to unknown code. + #[inline] + pub(crate) unsafe fn set_location( &mut self, - mut init: impl FnMut(Entity, &mut EntityIdLocation), - by: MaybeLocation, - tick: Tick, - ) { - let free_cursor = self.free_cursor.get_mut(); - let current_free_cursor = *free_cursor; - - let new_free_cursor = if current_free_cursor >= 0 { - current_free_cursor as usize - } else { - let old_meta_len = self.meta.len(); - let new_meta_len = old_meta_len + -current_free_cursor as usize; - self.meta.resize(new_meta_len, EntityMeta::EMPTY); - for (index, meta) in self.meta.iter_mut().enumerate().skip(old_meta_len) { - // SAFETY: the index is less than the meta length, which can not exceeded u32::MAX - let row = EntityRow::new(unsafe { NonMaxU32::new_unchecked(index as u32) }); - init( - Entity::from_row_and_generation(row, meta.generation), - &mut meta.location, - ); - meta.spawned_or_despawned = SpawnedOrDespawned { by, tick }; - } - - *free_cursor = 0; - 0 - }; + row: EntityRow, + location: EntityRowLocation, + ) -> EntityRowLocation { + self.ensure_row_index_is_valid(row); + // SAFETY: We just did `ensure_row` + self.update_existing_location(row, location) + } - for row in self.pending.drain(new_free_cursor..) { - let meta = &mut self.meta[row.index() as usize]; - init( - Entity::from_row_and_generation(row, meta.generation), - &mut meta.location, - ); - meta.spawned_or_despawned = SpawnedOrDespawned { by, tick }; + /// Ensures the row is within the bounds of [`Self::meta`], expanding it if necessary. + #[inline] + fn ensure_row_index_is_valid(&mut self, row: EntityRow) { + #[cold] // to help with branch prediction + fn expand(meta: &mut Vec, len: usize) { + meta.resize(len, EntityMeta::FRESH); + // Set these up too while we're here. + meta.resize(meta.capacity(), EntityMeta::FRESH); } - } - /// Flushes all reserved entities to an "invalid" state. Attempting to retrieve them will return `None` - /// unless they are later populated with a valid archetype. - pub fn flush_as_invalid(&mut self, by: MaybeLocation, tick: Tick) { - // SAFETY: as per `flush` safety docs, the archetype id can be set to [`ArchetypeId::INVALID`] if - // the [`Entity`] has not been assigned to an [`Archetype`][crate::archetype::Archetype], which is the case here - unsafe { - self.flush( - |_entity, location| { - *location = None; - }, - by, - tick, - ); + let index = row.index() as usize; + if self.meta.len() <= index { + // TODO: hint unlikely once stable. + expand(&mut self.meta, index + 1); } } - /// The count of all entities in the [`World`] that have ever been allocated - /// including the entities that are currently freed. + /// Marks the `row` as free, returning the [`Entity`] to reuse that [`EntityRow`]. /// - /// This does not include entities that have been reserved but have never been - /// allocated yet. - /// - /// [`World`]: crate::world::World - #[inline] - pub fn total_count(&self) -> usize { - self.meta.len() - } - - /// The count of all entities in the [`World`] that are used, - /// including both those allocated and those reserved, but not those freed. + /// # Safety /// - /// [`World`]: crate::world::World - #[inline] - pub fn used_count(&self) -> usize { - (self.meta.len() as isize - self.free_cursor.load(Ordering::Relaxed) as isize) as usize - } + /// - `row` must be despawned (have no location) already. + 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 spawned. + self.ensure_row_index_is_valid(row); + // SAFETY: We just did `ensure_row` + let meta = unsafe { self.meta.get_unchecked_mut(row.index() as usize) }; - /// The count of all entities in the [`World`] that have ever been allocated or reserved, including those that are freed. - /// This is the value that [`Self::total_count()`] would return if [`Self::flush()`] were called right now. - /// - /// [`World`]: crate::world::World - #[inline] - pub fn total_prospective_count(&self) -> usize { - self.meta.len() + (-self.free_cursor.load(Ordering::Relaxed)).min(0) as usize - } + let (new_generation, aliased) = meta.generation.after_versions_and_could_alias(generations); + meta.generation = new_generation; + if aliased { + warn!("EntityRow({row}) generation wrapped on Entities::free, aliasing may occur",); + } - /// The count of currently allocated entities. - #[inline] - pub fn len(&self) -> u32 { - // `pending`, by definition, can't be bigger than `meta`. - (self.meta.len() - self.pending.len()) as u32 + Entity::from_row_and_generation(row, meta.generation) } - /// Checks if any entity is currently active. + /// Mark an [`EntityRow`] as spawned or despawned in the given tick. + /// + /// # Safety + /// - `row` must have been spawned at least once, ensuring its row is valid. #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 + pub(crate) unsafe fn mark_spawned_or_despawned( + &mut self, + row: EntityRow, + by: MaybeLocation, + tick: Tick, + ) { + // SAFETY: Caller guarantees that `row` already had a location, so `declare` must have made the index valid already. + let meta = unsafe { self.meta.get_unchecked_mut(row.index() as usize) }; + meta.spawned_or_despawned = SpawnedOrDespawned { by, tick }; } - /// Try to get the source code location from which this entity has last been - /// spawned, despawned or flushed. + /// Try to get the source code location from which this entity has last been spawned or despawned. /// - /// Returns `None` if its index has been reused by another entity - /// or if this entity has never existed. + /// Returns `None` if the entity does not exist or has never been construced/despawned. pub fn entity_get_spawned_or_despawned_by( &self, entity: Entity, @@ -1151,21 +1149,17 @@ impl Entities { }) } - /// Try to get the [`Tick`] at which this entity has last been - /// spawned, despawned or flushed. + /// Try to get the [`Tick`] at which this entity has last been spawned or despawned. /// - /// Returns `None` if its index has been reused by another entity or if this entity - /// has never been spawned. - pub fn entity_get_spawn_or_despawn_tick(&self, entity: Entity) -> Option { + /// Returns `None` if the entity does not exist or has never been construced/despawned. + pub fn entity_get_spawned_or_despawned_at(&self, entity: Entity) -> Option { self.entity_get_spawned_or_despawned(entity) .map(|spawned_or_despawned| spawned_or_despawned.tick) } - /// Try to get the [`SpawnedOrDespawned`] related to the entity's last spawn, - /// despawn or flush. + /// Try to get the [`SpawnedOrDespawned`] related to the entity's last spawning or despawning. /// - /// Returns `None` if its index has been reused by another entity or if - /// this entity has never been spawned. + /// Returns `None` if the entity does not exist or has never been construced/despawned. #[inline] fn entity_get_spawned_or_despawned(&self, entity: Entity) -> Option { self.meta @@ -1173,8 +1167,7 @@ impl Entities { .filter(|meta| // Generation is incremented immediately upon despawn (meta.generation == entity.generation) - || meta.location.is_none() - && (meta.generation == entity.generation.after_versions(1))) + || (meta.location.is_none() && meta.generation == entity.generation.after_versions(1))) .map(|meta| meta.spawned_or_despawned) } @@ -1202,66 +1195,112 @@ impl Entities { } } - /// Constructs a message explaining why an entity does not exist, if known. - pub(crate) fn entity_does_not_exist_error_details( - &self, - entity: Entity, - ) -> EntityDoesNotExistDetails { - EntityDoesNotExistDetails { - location: self.entity_get_spawned_or_despawned_by(entity), - } + /// The count of currently allocated entity rows. + /// For information on active entities, see [`Self::count_spawned`]. + #[inline] + pub fn len(&self) -> u32 { + self.meta.len() as u32 + } + + /// Checks if any entity has been declared. + /// For information on active entities, see [`Self::any_spawned`]. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Counts the number of entity rows currently spawned. + /// See the module docs for a more precise explanation of what spawning means. + /// Be aware that this is O(n) and is intended only to be used as a diagnostic for tests. + pub fn count_spawned(&self) -> u32 { + self.meta + .iter() + .filter(|meta| meta.location.is_some()) + .count() as u32 + } + + /// Returns true if there are any entity rows currently spawned. + /// See the module docs for a more precise explanation of what spawning means. + pub fn any_spawned(&self) -> bool { + self.meta.iter().any(|meta| meta.location.is_some()) } } -/// An error that occurs when a specified [`Entity`] does not exist. +/// An error that occurs when a specified [`Entity`] can not be spawned. #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] -#[error("The entity with ID {entity} {details}")] -pub struct EntityDoesNotExistError { - /// The entity's ID. - pub entity: Entity, - /// Details on why the entity does not exist, if available. - pub details: EntityDoesNotExistDetails, +pub enum SpawnError { + /// The [`Entity`] to spawn was invalid. + /// It probably had the wrong generation or was created erroneously. + #[error("Invalid id: {0}")] + Invalid(InvalidEntityError), + /// The [`Entity`] to spawn was already spawned. + #[error("The entity can not be spawned as it already has a location.")] + AlreadySpawned, } -impl EntityDoesNotExistError { - pub(crate) fn new(entity: Entity, entities: &Entities) -> Self { - Self { - entity, - details: entities.entity_does_not_exist_error_details(entity), - } - } +/// An error that occurs when a specified [`Entity`] does not exist in the entity id space. +/// See [module](crate::entity) docs for more about entity validity. +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] +#[error("The entity with ID {entity} is invalid; its row now has generation {current_generation}.")] +pub struct InvalidEntityError { + /// The entity's ID. + pub entity: Entity, + /// The generation of the [`EntityRow`], which did not match the requested entity. + pub current_generation: EntityGeneration, } -/// Helper struct that, when printed, will write the appropriate details -/// regarding an entity that did not exist. -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct EntityDoesNotExistDetails { - location: MaybeLocation>>, +/// An error that occurs when a specified [`EntityRow`] is expected to be spawned but is not. +/// This includes when an [`Entity`] that is known to be valid happens to not be spawned. +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] +pub struct EntityRowNotSpawnedError { + /// The entity's ID. + pub entity: Entity, + /// The location of what last despawned the entity. + pub location: MaybeLocation<&'static Location<'static>>, } -impl fmt::Display for EntityDoesNotExistDetails { +impl fmt::Display for EntityRowNotSpawnedError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let entity = self.entity; match self.location.into_option() { - Some(Some(location)) => write!(f, "was despawned by {location}"), - Some(None) => write!( - f, - "does not exist (index has been reused or was never spawned)" - ), + Some(location) => write!(f, "The entity with ID {entity} is not spawned; its row was last despawned by {location}."), None => write!( f, - "does not exist (enable `track_location` feature for more details)" + "The entity with ID {entity} is not spawned; enable `track_location` feature for more details." ), } } } +/// An error that occurs when a specified [`Entity`] is expected to be valid and spawned but is not. +/// Represents an error of either [`InvalidEntityError`] (when the entity is invalid) or [`EntityRowNotSpawnedError`] (when the [`EntityGeneration`] is correct but the [`EntityRow`] is not spawned). +#[derive(thiserror::Error, Copy, Clone, Debug, Eq, PartialEq)] +pub enum EntityNotSpawnedError { + /// The entity was invalid. + #[error("{0}")] + Invalid(#[from] InvalidEntityError), + /// The entity was valid but was not spawned. + #[error("{0}")] + RowNotSpawned(#[from] EntityRowNotSpawnedError), +} + +impl EntityNotSpawnedError { + /// The entity that did not exist or was not spawned. + pub fn entity(&self) -> Entity { + match self { + EntityNotSpawnedError::Invalid(err) => err.entity, + EntityNotSpawnedError::RowNotSpawned(err) => err.entity, + } + } +} + #[derive(Copy, Clone, Debug)] struct EntityMeta { /// The current [`EntityGeneration`] of the [`EntityRow`]. generation: EntityGeneration, /// The current location of the [`EntityRow`]. - location: EntityIdLocation, - /// Location and tick of the last spawn, despawn or flush of this entity. + location: EntityRowLocation, + /// Location and tick of the last spawn/despawn spawned_or_despawned: SpawnedOrDespawned, } @@ -1272,8 +1311,8 @@ struct SpawnedOrDespawned { } impl EntityMeta { - /// meta for **pending entity** - const EMPTY: EntityMeta = EntityMeta { + /// The metadata for a fresh entity: Never spawned/despawned, no location, etc. + const FRESH: EntityMeta = EntityMeta { generation: EntityGeneration::FIRST, location: None, spawned_or_despawned: SpawnedOrDespawned { @@ -1307,15 +1346,16 @@ pub struct EntityLocation { pub table_row: TableRow, } -/// An [`Entity`] id may or may not correspond to a valid conceptual entity. -/// If it does, the conceptual entity may or may not have a location. -/// If it has no location, the [`EntityLocation`] will be `None`. +/// An [`EntityRow`] id may or may not currently be spawned. +/// If it is not spawned, the [`EntityLocation`] will be `None`. /// An location of `None` means the entity effectively does not exist; it has an id, but is not participating in the ECS. /// This is different from a location in the empty archetype, which is participating (queryable, etc) but just happens to have no components. +/// For more information about what a `None` location means, see the module [docs](crate::entity). /// /// Setting a location to `None` is often helpful when you want to destruct an entity or yank it from the ECS without allowing another system to reuse the id for something else. /// It is also useful for reserving an id; commands will often allocate an `Entity` but not provide it a location until the command is applied. -pub type EntityIdLocation = Option; +/// For more information about these more complex entity life cycles, see the module [docs](crate::entity). +pub type EntityRowLocation = Option; #[cfg(test)] mod tests { @@ -1339,37 +1379,6 @@ mod tests { assert_eq!(Entity::from_bits(e.to_bits()), e); } - #[test] - fn reserve_entity_len() { - let mut e = Entities::new(); - e.reserve_entity(); - // SAFETY: entity_location is left invalid - unsafe { e.flush(|_, _| {}, MaybeLocation::caller(), Tick::default()) }; - assert_eq!(e.len(), 1); - } - - #[test] - fn get_reserved_and_invalid() { - let mut entities = Entities::new(); - let e = entities.reserve_entity(); - assert!(entities.contains(e)); - assert!(entities.get(e).is_none()); - - // SAFETY: entity_location is left invalid - unsafe { - entities.flush( - |_entity, _location| { - // do nothing ... leaving entity location invalid - }, - MaybeLocation::caller(), - Tick::default(), - ); - }; - - assert!(entities.contains(e)); - assert!(entities.get(e).is_none()); - } - #[test] fn entity_const() { const C1: Entity = Entity::from_row(EntityRow::from_raw_u32(42).unwrap()); @@ -1389,34 +1398,6 @@ mod tests { assert_eq!(0x00dd_00ff, C4); } - #[test] - fn reserve_generations() { - let mut entities = Entities::new(); - let entity = entities.alloc(); - entities.free(entity); - - assert!(entities.reserve_generations(entity.index(), 1)); - } - - #[test] - fn reserve_generations_and_alloc() { - const GENERATIONS: u32 = 10; - - let mut entities = Entities::new(); - let entity = entities.alloc(); - entities.free(entity); - - assert!(entities.reserve_generations(entity.index(), GENERATIONS)); - - // The very next entity allocated should be a further generation on the same index - let next_entity = entities.alloc(); - assert_eq!(next_entity.index(), entity.index()); - assert!(next_entity - .generation() - .cmp_approx(&entity.generation().after_versions(GENERATIONS)) - .is_gt()); - } - #[test] #[expect( clippy::nonminimal_bool, @@ -1634,4 +1615,28 @@ mod tests { let string = format!("{entity}"); assert_eq!(string, "PLACEHOLDER"); } + + #[test] + fn allocator() { + let mut allocator = EntitiesAllocator::default(); + let mut entities = allocator.alloc_many(2048).collect::>(); + for _ in 0..2048 { + entities.push(allocator.alloc()); + } + + let pre_len = entities.len(); + entities.sort(); + entities.dedup(); + assert_eq!(pre_len, entities.len()); + + for e in entities.drain(..) { + allocator.free(e); + } + + entities.extend(allocator.alloc_many(5000)); + let pre_len = entities.len(); + entities.sort(); + entities.dedup(); + assert_eq!(pre_len, entities.len()); + } } diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index 2460d72ab64d2..bede34597118e 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -72,7 +72,7 @@ //! // within this scope, we can query like no components are disabled. //! assert_eq!(world.query::<&Disabled>().query(&world).count(), 1); //! assert_eq!(world.query::<&CustomDisabled>().query(&world).count(), 1); -//! assert_eq!(world.query::<()>().query(&world).count(), world.entities().len() as usize); +//! assert_eq!(world.query::<()>().query(&world).count(), world.entities().count_spawned() as usize); //! }) //! ``` //! diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index d2f59ac29f83d..e08e7e6015665 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -161,12 +161,12 @@ mod tests { bundle::Bundle, change_detection::Ref, component::Component, - entity::{Entity, EntityMapper}, + entity::{Entity, EntityMapper, EntityNotSpawnedError}, entity_disabling::DefaultQueryFilters, prelude::Or, query::{Added, Changed, FilteredAccess, QueryFilter, With, Without}, resource::Resource, - world::{EntityMut, EntityRef, Mut, World}, + world::{error::EntityDespawnError, EntityMut, EntityRef, Mut, World}, }; use alloc::{string::String, sync::Arc, vec, vec::Vec}; use bevy_platform::collections::HashSet; @@ -382,6 +382,35 @@ mod tests { ); } + #[test] + fn spawning_with_manual_entity_allocation() { + let mut world = World::new(); + let e1 = world.entities_allocator_mut().alloc(); + world.spawn_at(e1, (TableStored("abc"), A(123))).unwrap(); + + let e2 = world.entities_allocator_mut().alloc(); + assert!(matches!( + world.try_despawn_no_free(e2), + Err(EntityDespawnError(EntityNotSpawnedError::RowNotSpawned(_))) + )); + assert!(world.despawn(e2)); + + let e3 = world.entities_allocator_mut().alloc(); + let e3 = world + .spawn_at(e3, (TableStored("junk"), A(0))) + .unwrap() + .despawn_no_free(); + world.spawn_at(e3, (TableStored("def"), A(456))).unwrap(); + + assert_eq!(world.entities.count_spawned(), 2); + assert!(world.despawn(e1)); + assert_eq!(world.entities.count_spawned(), 1); + assert!(world.get::(e1).is_none()); + assert!(world.get::(e1).is_none()); + assert_eq!(world.get::(e3).unwrap().0, "def"); + assert_eq!(world.get::(e3).unwrap().0, 456); + } + #[test] fn despawn_table_storage() { let mut world = World::new(); @@ -1208,16 +1237,6 @@ mod tests { assert_eq!(e_mut.get::().unwrap(), &A(0)); } - #[test] - fn reserve_and_spawn() { - let mut world = World::default(); - let e = world.entities().reserve_entity(); - world.flush_entities(); - let mut e_mut = world.entity_mut(e); - e_mut.insert(A(0)); - assert_eq!(e_mut.get::().unwrap(), &A(0)); - } - #[test] fn changed_query() { let mut world = World::default(); diff --git a/crates/bevy_ecs/src/query/error.rs b/crates/bevy_ecs/src/query/error.rs index fd431f4be1dc1..f76ac26767b92 100644 --- a/crates/bevy_ecs/src/query/error.rs +++ b/crates/bevy_ecs/src/query/error.rs @@ -1,57 +1,32 @@ use bevy_utils::prelude::DebugName; -use thiserror::Error; use crate::{ archetype::ArchetypeId, - entity::{Entity, EntityDoesNotExistError}, + entity::{Entity, EntityNotSpawnedError}, }; /// An error that occurs when retrieving a specific [`Entity`]'s query result from [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState). // TODO: return the type_name as part of this error -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(thiserror::Error, Clone, Copy, Debug, PartialEq, Eq)] pub enum QueryEntityError { /// The given [`Entity`]'s components do not match the query. /// /// Either it does not have a requested component, or it has a component which the query filters out. + #[error("The query does not match entity {0}")] QueryDoesNotMatch(Entity, ArchetypeId), - /// The given [`Entity`] does not exist. - EntityDoesNotExist(EntityDoesNotExistError), + /// The given [`Entity`] is not spawned. + #[error("{0}")] + NotSpawned(#[from] EntityNotSpawnedError), /// The [`Entity`] was requested mutably more than once. /// /// See [`Query::get_many_mut`](crate::system::Query::get_many_mut) for an example. + #[error("The entity with ID {0} was requested mutably more than once")] AliasedMutability(Entity), } -impl From for QueryEntityError { - fn from(error: EntityDoesNotExistError) -> Self { - QueryEntityError::EntityDoesNotExist(error) - } -} - -impl core::error::Error for QueryEntityError {} - -impl core::fmt::Display for QueryEntityError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match *self { - Self::QueryDoesNotMatch(entity, _) => { - write!(f, "The query does not match entity {entity}") - } - Self::EntityDoesNotExist(error) => { - write!(f, "{error}") - } - Self::AliasedMutability(entity) => { - write!( - f, - "The entity with ID {entity} was requested mutably more than once" - ) - } - } - } -} - /// An error that occurs when evaluating a [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState) as a single expected result via /// [`single`](crate::system::Query::single) or [`single_mut`](crate::system::Query::single_mut). -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum QuerySingleError { /// No entity fits the query. #[error("No entities fit the query {0}")] diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 4e987bbc62feb..49d96f5c06b54 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -517,7 +517,7 @@ unsafe impl QueryData for EntityLocation { _table_row: TableRow, ) -> Self::Item<'w, 's> { // SAFETY: `fetch` must be called with an entity that exists in the world - unsafe { fetch.get(entity).debug_checked_unwrap() } + unsafe { fetch.get_spawned(entity).debug_checked_unwrap() } } } @@ -1451,7 +1451,7 @@ unsafe impl QueryData for &Archetype { ) -> Self::Item<'w, 's> { let (entities, archetypes) = *fetch; // SAFETY: `fetch` must be called with an entity that exists in the world - let location = unsafe { entities.get(entity).debug_checked_unwrap() }; + let location = unsafe { entities.get_spawned(entity).debug_checked_unwrap() }; // SAFETY: The assigned archetype for a living entity must always be valid. unsafe { archetypes.get(location.archetype_id).debug_checked_unwrap() } } diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 13e69ce3cb969..a03b362ef1814 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1046,7 +1046,7 @@ where // `tables` and `archetypes` belong to the same world that the [`QueryIter`] // was initialized for. unsafe { - location = self.entities.get(entity).debug_checked_unwrap(); + location = self.entities.get_spawned(entity).debug_checked_unwrap(); archetype = self .archetypes .get(location.archetype_id) @@ -1210,7 +1210,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> ) -> Option> { for entity_borrow in entity_iter { let entity = entity_borrow.entity(); - let Some(location) = entities.get(entity) else { + let Ok(location) = entities.get_spawned(entity) else { continue; }; @@ -2006,7 +2006,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> // `tables` and `archetypes` belong to the same world that the [`QueryIter`] // was initialized for. unsafe { - location = self.entities.get(entity).debug_checked_unwrap(); + location = self.entities.get_spawned(entity).debug_checked_unwrap(); archetype = self .archetypes .get(location.archetype_id) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 91a979fa2e5e7..55a5537700d8f 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -891,7 +891,7 @@ impl QueryState { /// /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); /// - /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); + /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::NotSpawned(error) => error.entity(), _ => panic!()}, wrong_entity); /// ``` #[inline] pub fn get_many<'w, const N: usize>( @@ -929,7 +929,7 @@ impl QueryState { /// /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); /// - /// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); + /// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::NotSpawned(error) => error.entity(), _ => panic!()}, wrong_entity); /// ``` #[inline] pub fn get_many_unique<'w, const N: usize>( @@ -986,7 +986,7 @@ impl QueryState { /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// let invalid_entity = world.spawn_empty().id(); /// - /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); + /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::NotSpawned(error) => error.entity(), _ => panic!()}, wrong_entity); /// assert_eq!(match query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err() {QueryEntityError::QueryDoesNotMatch(entity, _) => entity, _ => panic!()}, invalid_entity); /// assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); /// ``` @@ -1032,7 +1032,7 @@ impl QueryState { /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// let invalid_entity = world.spawn_empty().id(); /// - /// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); + /// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::NotSpawned(error) => error.entity(), _ => panic!()}, wrong_entity); /// assert_eq!(match query_state.get_many_unique_mut(&mut world, UniqueEntityArray::from([invalid_entity])).unwrap_err() {QueryEntityError::QueryDoesNotMatch(entity, _) => entity, _ => panic!()}, invalid_entity); /// ``` #[inline] @@ -1366,7 +1366,7 @@ impl QueryState { /// # let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// # let invalid_entity = world.spawn_empty().id(); /// - /// # assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); + /// # assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::NotSpawned(error) => error.entity(), _ => panic!()}, wrong_entity); /// assert_eq!(match query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err() {QueryEntityError::QueryDoesNotMatch(entity, _) => entity, _ => panic!()}, invalid_entity); /// # assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); /// ``` diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 6da22b1328b5e..081970ca675c1 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -19,7 +19,10 @@ use crate::{ bundle::{Bundle, InsertMode, NoBundleEffect}, change_detection::{MaybeLocation, Mut}, component::{Component, ComponentId, Mutable}, - entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError, OptIn, OptOut}, + entity::{ + Entities, EntitiesAllocator, Entity, EntityClonerBuilder, EntityNotSpawnedError, + InvalidEntityError, OptIn, OptOut, + }, error::{warn, BevyError, CommandWithEntity, ErrorContext, HandleError}, event::{EntityEvent, Event}, message::Message, @@ -102,6 +105,7 @@ use crate::{ pub struct Commands<'w, 's> { queue: InternalQueue<'s>, entities: &'w Entities, + allocator: &'w EntitiesAllocator, } // SAFETY: All commands [`Command`] implement [`Send`] @@ -111,7 +115,11 @@ unsafe impl Send for Commands<'_, '_> {} unsafe impl Sync for Commands<'_, '_> {} const _: () = { - type __StructFieldsAlias<'w, 's> = (Deferred<'s, CommandQueue>, &'w Entities); + type __StructFieldsAlias<'w, 's> = ( + Deferred<'s, CommandQueue>, + &'w EntitiesAllocator, + &'w Entities, + ); #[doc(hidden)] pub struct FetchState { state: <__StructFieldsAlias<'static, 'static> as bevy_ecs::system::SystemParam>::State, @@ -174,7 +182,7 @@ const _: () = { system_meta: &bevy_ecs::system::SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - <(Deferred, &Entities) as bevy_ecs::system::SystemParam>::validate_param( + <__StructFieldsAlias as bevy_ecs::system::SystemParam>::validate_param( &mut state.state, system_meta, world, @@ -188,10 +196,16 @@ const _: () = { world: UnsafeWorldCell<'w>, change_tick: bevy_ecs::change_detection::Tick, ) -> Self::Item<'w, 's> { - let(f0, f1) = <(Deferred<'s, CommandQueue>, &'w Entities) as bevy_ecs::system::SystemParam>::get_param(&mut state.state, system_meta, world, change_tick); + let params = <__StructFieldsAlias as bevy_ecs::system::SystemParam>::get_param( + &mut state.state, + system_meta, + world, + change_tick, + ); Commands { - queue: InternalQueue::CommandQueue(f0), - entities: f1, + queue: InternalQueue::CommandQueue(params.0), + allocator: params.1, + entities: params.2, } } } @@ -212,13 +226,18 @@ enum InternalQueue<'s> { impl<'w, 's> Commands<'w, 's> { /// Returns a new `Commands` instance from a [`CommandQueue`] and a [`World`]. pub fn new(queue: &'s mut CommandQueue, world: &'w World) -> Self { - Self::new_from_entities(queue, &world.entities) + Self::new_from_entities(queue, &world.allocator, &world.entities) } /// Returns a new `Commands` instance from a [`CommandQueue`] and an [`Entities`] reference. - pub fn new_from_entities(queue: &'s mut CommandQueue, entities: &'w Entities) -> Self { + pub fn new_from_entities( + queue: &'s mut CommandQueue, + allocator: &'w EntitiesAllocator, + entities: &'w Entities, + ) -> Self { Self { queue: InternalQueue::CommandQueue(Deferred(queue)), + allocator, entities, } } @@ -232,10 +251,12 @@ impl<'w, 's> Commands<'w, 's> { /// * Caller ensures that `queue` must outlive `'w` pub(crate) unsafe fn new_raw_from_entities( queue: RawCommandQueue, + allocator: &'w EntitiesAllocator, entities: &'w Entities, ) -> Self { Self { queue: InternalQueue::RawCommandQueue(queue), + allocator, entities, } } @@ -267,6 +288,7 @@ impl<'w, 's> Commands<'w, 's> { InternalQueue::RawCommandQueue(queue.clone()) } }, + allocator: self.allocator, entities: self.entities, } } @@ -316,22 +338,12 @@ impl<'w, 's> Commands<'w, 's> { /// with the same combination of components. #[track_caller] pub fn spawn_empty(&mut self) -> EntityCommands<'_> { - let entity = self.entities.reserve_entity(); - let mut entity_commands = EntityCommands { - entity, - commands: self.reborrow(), - }; + let entity = self.allocator.alloc(); let caller = MaybeLocation::caller(); - entity_commands.queue(move |entity: EntityWorldMut| { - let index = entity.id().index(); - let world = entity.into_world_mut(); - let tick = world.change_tick(); - // SAFETY: Entity has been flushed - unsafe { - world.entities_mut().mark_spawn_despawn(index, caller, tick); - } + self.queue(move |world: &mut World| { + world.spawn_at_empty_with_caller(entity, caller).map(|_| ()) }); - entity_commands + self.entity(entity) } /// Spawns a new [`Entity`] with the given components @@ -378,36 +390,15 @@ impl<'w, 's> Commands<'w, 's> { /// with the same combination of components. #[track_caller] pub fn spawn(&mut self, bundle: T) -> EntityCommands<'_> { - let entity = self.entities.reserve_entity(); - let mut entity_commands = EntityCommands { - entity, - commands: self.reborrow(), - }; + let entity = self.allocator.alloc(); let caller = MaybeLocation::caller(); - - entity_commands.queue(move |mut entity: EntityWorldMut| { - // Store metadata about the spawn operation. - // This is the same as in `spawn_empty`, but merged into - // the same command for better performance. - let index = entity.id().index(); - entity.world_scope(|world| { - let tick = world.change_tick(); - // SAFETY: Entity has been flushed - unsafe { - world.entities_mut().mark_spawn_despawn(index, caller, tick); - } - }); - + self.queue(move |world: &mut World| { move_as_ptr!(bundle); - entity.insert_with_caller( - bundle, - InsertMode::Replace, - caller, - crate::relationship::RelationshipHookMode::Run, - ); + world + .spawn_at_with_caller(entity, bundle, caller) + .map(|_| ()) }); - // entity_command::insert(bundle, InsertMode::Replace) - entity_commands + self.entity(entity) } /// Returns the [`EntityCommands`] for the given [`Entity`]. @@ -446,14 +437,17 @@ impl<'w, 's> Commands<'w, 's> { } } - /// Returns the [`EntityCommands`] for the requested [`Entity`] if it exists. - /// + /// Returns the [`EntityCommands`] for the requested [`Entity`] if it is valid. /// This method does not guarantee that commands queued by the returned `EntityCommands` /// will be successful, since the entity could be despawned before they are executed. + /// This also does not error when the entity has not been spawned. + /// For that behavior, see [`get_spawned_entity`](Self::get_spawned_entity), + /// which should be preferred for accessing entities you expect to already be spawned, like those found from a query. + /// For details on entity spawning vs validity, see [`entity`](crate::entity) module docs. /// /// # Errors /// - /// Returns [`EntityDoesNotExistError`] if the requested entity does not exist. + /// Returns [`InvalidEntityError`] if the requested entity does not exist. /// /// # Example /// @@ -487,18 +481,69 @@ impl<'w, 's> Commands<'w, 's> { /// - [`entity`](Self::entity) for the infallible version. #[inline] #[track_caller] - pub fn get_entity( + pub fn get_entity(&mut self, entity: Entity) -> Result, InvalidEntityError> { + let _location = self.entities.get(entity)?; + Ok(EntityCommands { + entity, + commands: self.reborrow(), + }) + } + + /// Returns the [`EntityCommands`] for the requested [`Entity`] if it spawned in the world *now*. + /// Note that for entities that have not been spawned *yet*, like ones from [`spawn`](Self::spawn), this will error. + /// If that is not desired, try [`get_entity`](Self::get_entity). + /// This should be used over [`get_entity`](Self::get_entity) when you expect the entity to already be spawned in the world. + /// If the entity is valid but not yet spawned, this will error that information, where [`get_entity`](Self::get_entity) would succeed, leading to potentially surprising results. + /// For details on entity spawning vs validity, see [`entity`](crate::entity) module docs. + /// + /// This method does not guarantee that commands queued by the returned `EntityCommands` + /// will be successful, since the entity could be despawned before they are executed. + /// + /// # Errors + /// + /// Returns [`EntityNotSpawnedError`] if the requested entity does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// #[derive(Resource)] + /// struct PlayerEntity { + /// entity: Entity + /// } + /// + /// #[derive(Component)] + /// struct Label(&'static str); + /// + /// fn example_system(mut commands: Commands, player: Res) -> Result { + /// // Get the entity if it still exists and store the `EntityCommands`. + /// // If it doesn't exist, the `?` operator will propagate the returned error + /// // to the system, and the system will pass it to an error handler. + /// let mut entity_commands = commands.get_spawned_entity(player.entity)?; + /// + /// // Add a component to the entity. + /// entity_commands.insert(Label("hello world")); + /// + /// // Return from the system successfully. + /// Ok(()) + /// } + /// # bevy_ecs::system::assert_is_system::<(), (), _>(example_system); + /// ``` + /// + /// # See also + /// + /// - [`entity`](Self::entity) for the infallible version. + #[inline] + #[track_caller] + pub fn get_spawned_entity( &mut self, entity: Entity, - ) -> Result, EntityDoesNotExistError> { - if self.entities.contains(entity) { - Ok(EntityCommands { - entity, - commands: self.reborrow(), - }) - } else { - Err(EntityDoesNotExistError::new(entity, self.entities)) - } + ) -> Result, EntityNotSpawnedError> { + let _location = self.entities.get_spawned(entity)?; + Ok(EntityCommands { + entity, + commands: self.reborrow(), + }) } /// Spawns multiple entities with the same combination of components, @@ -2857,7 +2902,7 @@ mod tests { world.flush(); assert_eq!( Some(expected), - world.entities().entity_get_spawn_or_despawn_tick(id) + world.entities().entity_get_spawned_or_despawned_at(id) ); } } diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index bee491017d500..34e4aea1c3803 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -1,7 +1,7 @@ use bevy_utils::Parallel; use crate::{ - entity::Entities, + entity::{Entities, EntitiesAllocator}, prelude::World, system::{Deferred, SystemBuffer, SystemMeta, SystemParam}, }; @@ -51,6 +51,7 @@ struct ParallelCommandQueue { #[derive(SystemParam)] pub struct ParallelCommands<'w, 's> { state: Deferred<'s, ParallelCommandQueue>, + allocator: &'w EntitiesAllocator, entities: &'w Entities, } @@ -71,7 +72,7 @@ impl<'w, 's> ParallelCommands<'w, 's> { /// For an example, see the type-level documentation for [`ParallelCommands`]. pub fn command_scope(&self, f: impl FnOnce(Commands) -> R) -> R { self.state.thread_queues.scope(|queue| { - let commands = Commands::new_from_entities(queue, self.entities); + let commands = Commands::new_from_entities(queue, self.allocator, self.entities); f(commands) }) } diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 2b9fe4319b973..79b72cba6f937 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1138,7 +1138,7 @@ mod tests { ) { assert_eq!(query.iter().count(), 1, "entity exists"); for entity in &query { - let location = entities.get(entity).unwrap(); + let location = entities.get_spawned(entity).unwrap(); let archetype = archetypes.get(location.archetype_id).unwrap(); let archetype_components = archetype.components(); let bundle_id = bundles diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 36a1ca3dc7d69..c3b93b5da6452 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -3,7 +3,7 @@ use bevy_utils::prelude::DebugName; use crate::{ batching::BatchingStrategy, change_detection::Tick, - entity::{Entity, EntityDoesNotExistError, EntityEquivalent, EntitySet, UniqueEntityArray}, + entity::{Entity, EntityEquivalent, EntitySet, UniqueEntityArray}, query::{ DebugCheckedUnwrap, NopWorldQuery, QueryCombinationIter, QueryData, QueryEntityError, QueryFilter, QueryIter, QueryManyIter, QueryManyUniqueIter, QueryParIter, QueryParManyIter, @@ -1421,7 +1421,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// assert_eq!( /// match query.get_many([wrong_entity]).unwrap_err() { - /// QueryEntityError::EntityDoesNotExist(error) => error.entity, + /// QueryEntityError::NotSpawned(error) => error.entity(), /// _ => panic!(), /// }, /// wrong_entity @@ -1472,7 +1472,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// assert_eq!( /// match query.get_many_unique(UniqueEntityArray::from([wrong_entity])).unwrap_err() { - /// QueryEntityError::EntityDoesNotExist(error) => error.entity, + /// QueryEntityError::NotSpawned(error) => error.entity(), /// _ => panic!(), /// }, /// wrong_entity @@ -1540,11 +1540,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { - let location = self - .world - .entities() - .get(entity) - .ok_or(EntityDoesNotExistError::new(entity, self.world.entities()))?; + let location = self.world.entities().get_spawned(entity)?; if !self .state .matched_archetypes @@ -1645,7 +1641,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// .get_many_mut([wrong_entity]) /// .unwrap_err() /// { - /// QueryEntityError::EntityDoesNotExist(error) => error.entity, + /// QueryEntityError::NotSpawned(error) => error.entity(), /// _ => panic!(), /// }, /// wrong_entity @@ -1719,7 +1715,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// .get_many_unique_mut(UniqueEntityArray::from([wrong_entity])) /// .unwrap_err() /// { - /// QueryEntityError::EntityDoesNotExist(error) => error.entity, + /// QueryEntityError::NotSpawned(error) => error.entity(), /// _ => panic!(), /// }, /// wrong_entity diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index a7b5a5dc1a011..a69a1355a66e1 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -501,9 +501,9 @@ mod tests { #[test] fn command_processing() { let mut world = World::new(); - assert_eq!(world.query::<&A>().query(&world).count(), 0); + assert_eq!(world.entities.count_spawned(), 0); world.run_system_once(spawn_entity).unwrap(); - assert_eq!(world.query::<&A>().query(&world).count(), 1); + assert_eq!(world.entities.count_spawned(), 1); } #[test] diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 909e434c19242..75739803b6f0f 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -4,7 +4,7 @@ use crate::{ bundle::Bundles, change_detection::{ComponentTicksMut, ComponentTicksRef, Tick}, component::{ComponentId, Components}, - entity::Entities, + entity::{Entities, EntitiesAllocator}, query::{ Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError, QueryState, ReadOnlyQueryData, @@ -1578,6 +1578,35 @@ unsafe impl<'a> SystemParam for &'a Entities { } } +// SAFETY: Only reads World entities +unsafe impl<'a> ReadOnlySystemParam for &'a EntitiesAllocator {} + +// SAFETY: no component value access +unsafe impl<'a> SystemParam for &'a EntitiesAllocator { + type State = (); + type Item<'w, 's> = &'w EntitiesAllocator; + + fn init_state(_world: &mut World) -> Self::State {} + + fn init_access( + _state: &Self::State, + _system_meta: &mut SystemMeta, + _component_access_set: &mut FilteredAccessSet, + _world: &mut World, + ) { + } + + #[inline] + unsafe fn get_param<'w, 's>( + _state: &'s mut Self::State, + _system_meta: &SystemMeta, + world: UnsafeWorldCell<'w>, + _change_tick: Tick, + ) -> Self::Item<'w, 's> { + world.entities_allocator() + } +} + // SAFETY: Only reads World bundles unsafe impl<'a> ReadOnlySystemParam for &'a Bundles {} diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 2c8b003064147..2488dbcfd0b47 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -738,9 +738,9 @@ mod tests { let exclusive_system_id = world.register_system(|world: &mut World| { world.spawn_empty(); }); - let entity_count = world.entities.len(); + let entity_count = world.entities.count_spawned(); let _ = world.run_system(exclusive_system_id); - assert_eq!(world.entities.len(), entity_count + 1); + assert_eq!(world.entities.count_spawned(), entity_count + 1); } #[test] diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index 6babe7b380f50..610c781eb9e0a 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -98,9 +98,6 @@ impl CommandQueue { /// This clears the queue. #[inline] pub fn apply(&mut self, world: &mut World) { - // flush the previously queued entities - world.flush_entities(); - // flush the world's internal queue world.flush_commands(); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 099d58a6935bb..017cc3eaff5ad 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -72,7 +72,13 @@ impl<'w> DeferredWorld<'w> { // SAFETY: &mut self ensure that there are no outstanding accesses to the queue let command_queue = unsafe { self.world.get_raw_command_queue() }; // SAFETY: command_queue is stored on world and always valid while the world exists - unsafe { Commands::new_raw_from_entities(command_queue, self.world.entities()) } + unsafe { + Commands::new_raw_from_entities( + command_queue, + self.world.entities_allocator(), + self.world.entities(), + ) + } } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -242,7 +248,7 @@ impl<'w> DeferredWorld<'w> { /// /// # Errors /// - /// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if any of the given `entities` do not exist in the world. + /// - Returns [`EntityMutableFetchError::NotSpawned`] if any of the given `entities` do not exist in the world. /// - Only the first entity found to be missing will be returned. /// - Returns [`EntityMutableFetchError::AliasedMutability`] if the same entity is requested multiple times. /// @@ -433,7 +439,9 @@ impl<'w> DeferredWorld<'w> { // - Command queue access does not conflict with entity access. let raw_queue = unsafe { cell.get_raw_command_queue() }; // SAFETY: `&mut self` ensures the commands does not outlive the world. - let commands = unsafe { Commands::new_raw_from_entities(raw_queue, cell.entities()) }; + let commands = unsafe { + Commands::new_raw_from_entities(raw_queue, cell.entities_allocator(), cell.entities()) + }; (fetcher, commands) } diff --git a/crates/bevy_ecs/src/world/entity_access/mod.rs b/crates/bevy_ecs/src/world/entity_access/mod.rs index aa852d0106260..9596d44a02ffc 100644 --- a/crates/bevy_ecs/src/world/entity_access/mod.rs +++ b/crates/bevy_ecs/src/world/entity_access/mod.rs @@ -1094,7 +1094,7 @@ mod tests { unsafe { a.world_mut().trigger(TestEvent(entity)) } a.observe(|_: On| {}); // this flushes commands implicitly by spawning let location = a.location(); - assert_eq!(world.entities().get(entity), Some(location)); + assert_eq!(world.entities().get(entity).unwrap(), Some(location)); } #[test] @@ -1127,11 +1127,15 @@ mod tests { world.add_observer(|_: On, mut commands: Commands| { commands.queue(count_flush); }); + + // Spawning an empty should not flush. world.commands().queue(count_flush); let entity = world.spawn_empty().id(); - assert_eq!(world.resource::().0, 1); + assert_eq!(world.resource::().0, 0); + world.commands().queue(count_flush); world.flush_commands(); + let mut a = world.entity_mut(entity); assert_eq!(a.world().resource::().0, 2); a.insert(TestComponent(0)); @@ -1383,7 +1387,7 @@ mod tests { .map(|l| l.unwrap()); let at = world .entities - .entity_get_spawn_or_despawn_tick(entity) + .entity_get_spawned_or_despawned_at(entity) .unwrap(); (by, at) }); @@ -1423,7 +1427,7 @@ mod tests { despawn_tick, world .entities() - .entity_get_spawn_or_despawn_tick(entity) + .entity_get_spawned_or_despawned_at(entity) .unwrap() ); } diff --git a/crates/bevy_ecs/src/world/entity_access/world_mut.rs b/crates/bevy_ecs/src/world/entity_access/world_mut.rs index f11ff26e16d23..038e0fb5aad8b 100644 --- a/crates/bevy_ecs/src/world/entity_access/world_mut.rs +++ b/crates/bevy_ecs/src/world/entity_access/world_mut.rs @@ -6,7 +6,7 @@ use crate::{ change_detection::{ComponentTicks, MaybeLocation, MutUntyped, Tick}, component::{Component, ComponentId, Components, Mutable, StorageType}, entity::{ - Entity, EntityCloner, EntityClonerBuilder, EntityIdLocation, EntityLocation, OptIn, OptOut, + Entity, EntityCloner, EntityClonerBuilder, EntityLocation, EntityRowLocation, OptIn, OptOut, }, event::{EntityComponentsTrigger, EntityEvent}, lifecycle::{Despawn, Remove, Replace, DESPAWN, REMOVE, REPLACE}, @@ -41,7 +41,7 @@ use core::{any::TypeId, marker::PhantomData, mem::MaybeUninit}; pub struct EntityWorldMut<'w> { world: &'w mut World, entity: Entity, - location: EntityIdLocation, + location: EntityRowLocation, } impl<'w> EntityWorldMut<'w> { @@ -52,9 +52,7 @@ impl<'w> EntityWorldMut<'w> { panic!( "Entity {} {}", self.entity, - self.world - .entities() - .entity_does_not_exist_error_details(self.entity) + self.world.entities().get_spawned(self.entity).unwrap_err() ); } @@ -118,10 +116,10 @@ impl<'w> EntityWorldMut<'w> { pub(crate) unsafe fn new( world: &'w mut World, entity: Entity, - location: Option, + location: EntityRowLocation, ) -> Self { debug_assert!(world.entities().contains(entity)); - debug_assert_eq!(world.entities().get(entity), location); + debug_assert_eq!(world.entities().get(entity).unwrap(), location); EntityWorldMut { world, @@ -161,6 +159,25 @@ impl<'w> EntityWorldMut<'w> { self.entity } + /// Gets metadata indicating the location where the current entity is stored. + #[inline] + pub fn try_location(&self) -> EntityRowLocation { + self.location + } + + /// Returns if the entity is spawned or not. + #[inline] + pub fn is_spawned(&self) -> bool { + self.try_location().is_some() + } + + /// Returns the archetype that the current entity belongs to. + #[inline] + pub fn try_archetype(&self) -> Option<&Archetype> { + self.try_location() + .map(|location| &self.world.archetypes[location.archetype_id]) + } + /// Gets metadata indicating the location where the current entity is stored. /// /// # Panics @@ -168,8 +185,8 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn location(&self) -> EntityLocation { - match self.location { - Some(loc) => loc, + match self.try_location() { + Some(a) => a, None => self.panic_despawned(), } } @@ -181,8 +198,10 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn archetype(&self) -> &Archetype { - let location = self.location(); - &self.world.archetypes[location.archetype_id] + match self.try_archetype() { + Some(a) => a, + None => self.panic_despawned(), + } } /// Returns `true` if the current entity has a component of type `T`. @@ -1409,32 +1428,31 @@ 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. + /// Despawns the entity without freeing it to the allocator. + /// This returns the new [`Entity`], which you must manage. + /// Note that this still increases the generation to differentiate different spawns of the same row. /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. + /// This may be later [`spawn_at`](World::spawn_at). + /// See [`World::despawn_no_free`] for details and usage examples. #[track_caller] - pub fn despawn(self) { - self.despawn_with_caller(MaybeLocation::caller()); + pub fn despawn_no_free(mut self) -> Entity { + self.despawn_no_free_with_caller(MaybeLocation::caller()); + self.entity } - pub(crate) fn despawn_with_caller(self, caller: MaybeLocation) { - let location = self.location(); - let world = self.world; - let archetype = &world.archetypes[location.archetype_id]; + /// This despawns this entity if it is currently spawned, storing the new [`EntityGeneration`](crate::entity::EntityGeneration) in [`Self::entity`] but not freeing it. + pub(crate) fn despawn_no_free_with_caller(&mut self, caller: MaybeLocation) { + // setup + let Some(location) = self.location else { + // If there is no location, we are already despawned + return; + }; + 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()) }; @@ -1500,33 +1518,37 @@ impl<'w> EntityWorldMut<'w> { ); } - for component_id in archetype.iter_components() { - world.removed_components.write(component_id, self.entity); + // do the despawn + let change_tick = self.world.change_tick(); + for component_id in archetype.components() { + self.world + .removed_components + .write(*component_id, self.entity); + } + // SAFETY: Since we had a location, and it was valid, this is safe. + unsafe { + let was_at = self + .world + .entities + .update_existing_location(self.entity.row(), None); + debug_assert_eq!(was_at, Some(location)); + self.world + .entities + .mark_spawned_or_despawned(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_spawned(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_existing_location( + swapped_entity.row(), Some(EntityLocation { archetype_id: swapped_location.archetype_id, archetype_row: location.archetype_row, @@ -1540,22 +1562,28 @@ impl<'w> EntityWorldMut<'w> { 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_spawned(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_existing_location( + moved_entity.row(), Some(EntityLocation { archetype_id: moved_location.archetype_id, archetype_row: moved_location.archetype_row, @@ -1564,18 +1592,38 @@ impl<'w> EntityWorldMut<'w> { }), ); } - world.archetypes[moved_location.archetype_id] + self.world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } - // SAFETY: `self.entity` is a valid entity index - unsafe { - world - .entities - .mark_spawn_despawn(self.entity.index(), caller, change_tick); + // finish + // SAFETY: We just despawned it. + self.entity = unsafe { self.world.entities.mark_free(self.entity.row(), 1) }; + self.world.flush(); + } + + /// 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. + #[track_caller] + pub fn despawn(self) { + self.despawn_with_caller(MaybeLocation::caller()); + } + + pub(crate) fn despawn_with_caller(mut self, caller: MaybeLocation) { + self.despawn_no_free_with_caller(caller); + if let Ok(None) = self.world.entities.get(self.entity) { + self.world.allocator.free(self.entity); } - world.flush(); + // Otherwise: + // A command must have reconstructed it (had a location); don't free + // A command must have already despawned it (err) or otherwise made the free unneeded (ex by spawning and despawning in commands); don't free } /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush`] @@ -1658,7 +1706,7 @@ impl<'w> EntityWorldMut<'w> { /// This is *only* required when using the unsafe function [`EntityWorldMut::world_mut`], /// which enables the location to change. pub fn update_location(&mut self) { - self.location = self.world.entities().get(self.entity); + self.location = self.world.entities().get(self.entity).expect("Commands should not be queued which despawn an entity while an active `EntityWorldMut` is available to do so."); } /// Returns if the entity has been despawned. @@ -1889,9 +1937,7 @@ impl<'w> EntityWorldMut<'w> { config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> Entity { self.assert_not_despawned(); - - let entity_clone = self.world.entities.reserve_entity(); - self.world.flush(); + let entity_clone = self.world.spawn_empty().id(); let mut builder = EntityCloner::build_opt_out(self.world); config(&mut builder); @@ -1937,9 +1983,7 @@ impl<'w> EntityWorldMut<'w> { config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> Entity { self.assert_not_despawned(); - - let entity_clone = self.world.entities.reserve_entity(); - self.world.flush(); + let entity_clone = self.world.spawn_empty().id(); let mut builder = EntityCloner::build_opt_in(self.world); config(&mut builder); diff --git a/crates/bevy_ecs/src/world/entity_fetch.rs b/crates/bevy_ecs/src/world/entity_fetch.rs index 4aa8baf9e8074..1c2e2d2fcf705 100644 --- a/crates/bevy_ecs/src/world/entity_fetch.rs +++ b/crates/bevy_ecs/src/world/entity_fetch.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use core::mem::MaybeUninit; use crate::{ - entity::{Entity, EntityDoesNotExistError, EntityHashMap, EntityHashSet}, + entity::{Entity, EntityHashMap, EntityHashSet, EntityNotSpawnedError}, error::Result, world::{ error::EntityMutableFetchError, unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, @@ -45,7 +45,7 @@ impl<'w> EntityFetcher<'w> { /// # Errors /// /// If any of the given `entities` do not exist in the world, the first - /// [`Entity`] found to be missing will return an [`EntityDoesNotExistError`]. + /// [`Entity`] found to be missing will return an [`EntityNotSpawnedError`]. /// /// # Examples /// @@ -56,7 +56,7 @@ impl<'w> EntityFetcher<'w> { pub fn get( &self, entities: F, - ) -> Result, EntityDoesNotExistError> { + ) -> Result, EntityNotSpawnedError> { // SAFETY: `&self` gives read access to all entities, and prevents mutable access. unsafe { entities.fetch_ref(self.cell) } } @@ -75,7 +75,7 @@ impl<'w> EntityFetcher<'w> { /// [`EntityHashMap`](crate::entity::EntityHashMap). /// # Errors /// - /// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if any of the given `entities` do not exist in the world. + /// - Returns [`EntityMutableFetchError::NotSpawned`] if any of the given `entities` do not exist in the world. /// - Only the first entity found to be missing will be returned. /// - Returns [`EntityMutableFetchError::AliasedMutability`] if the same entity is requested multiple times. /// @@ -142,11 +142,11 @@ pub unsafe trait WorldEntityFetch { /// /// # Errors /// - /// - Returns [`EntityDoesNotExistError`] if the entity does not exist. + /// - Returns [`EntityNotSpawnedError`] if the entity does not exist. unsafe fn fetch_ref( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityDoesNotExistError>; + ) -> Result, EntityNotSpawnedError>; /// Returns mutable reference(s) to the entities with the given [`Entity`] /// IDs, as determined by `self`. @@ -159,7 +159,7 @@ pub unsafe trait WorldEntityFetch { /// /// # Errors /// - /// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if the entity does not exist. + /// - Returns [`EntityMutableFetchError::NotSpawned`] if the entity does not exist. /// - Returns [`EntityMutableFetchError::AliasedMutability`] if the entity was /// requested mutably more than once. unsafe fn fetch_mut( @@ -182,7 +182,7 @@ pub unsafe trait WorldEntityFetch { /// /// # Errors /// - /// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if the entity does not exist. + /// - Returns [`EntityMutableFetchError::NotSpawned`] if the entity does not exist. /// - Returns [`EntityMutableFetchError::AliasedMutability`] if the entity was /// requested mutably more than once. unsafe fn fetch_deferred_mut( @@ -204,7 +204,7 @@ unsafe impl WorldEntityFetch for Entity { unsafe fn fetch_ref( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityDoesNotExistError> { + ) -> Result, EntityNotSpawnedError> { let ecell = cell.get_entity(self)?; // SAFETY: caller ensures that the world cell has read-only access to the entity. Ok(unsafe { EntityRef::new(ecell) }) @@ -215,10 +215,7 @@ unsafe impl WorldEntityFetch for Entity { self, cell: UnsafeWorldCell<'_>, ) -> Result, EntityMutableFetchError> { - let location = cell - .entities() - .get(self) - .ok_or(EntityDoesNotExistError::new(self, cell.entities()))?; + let location = cell.entities().get_spawned(self)?; // SAFETY: caller ensures that the world cell has mutable access to the entity. let world = unsafe { cell.world_mut() }; // SAFETY: location was fetched from the same world's `Entities`. @@ -249,7 +246,7 @@ unsafe impl WorldEntityFetch for [Entity; N] { unsafe fn fetch_ref( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityDoesNotExistError> { + ) -> Result, EntityNotSpawnedError> { <&Self>::fetch_ref(&self, cell) } @@ -283,7 +280,7 @@ unsafe impl WorldEntityFetch for &'_ [Entity; N] { unsafe fn fetch_ref( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityDoesNotExistError> { + ) -> Result, EntityNotSpawnedError> { let mut refs = [MaybeUninit::uninit(); N]; for (r, &id) in core::iter::zip(&mut refs, self) { let ecell = cell.get_entity(id)?; @@ -348,7 +345,7 @@ unsafe impl WorldEntityFetch for &'_ [Entity] { unsafe fn fetch_ref( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityDoesNotExistError> { + ) -> Result, EntityNotSpawnedError> { let mut refs = Vec::with_capacity(self.len()); for &id in self { let ecell = cell.get_entity(id)?; @@ -407,7 +404,7 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet { unsafe fn fetch_ref( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityDoesNotExistError> { + ) -> Result, EntityNotSpawnedError> { let mut refs = EntityHashMap::with_capacity(self.len()); for &id in self { let ecell = cell.get_entity(id)?; diff --git a/crates/bevy_ecs/src/world/error.rs b/crates/bevy_ecs/src/world/error.rs index 6378123903a09..b992823b8c5e7 100644 --- a/crates/bevy_ecs/src/world/error.rs +++ b/crates/bevy_ecs/src/world/error.rs @@ -5,7 +5,7 @@ use bevy_utils::prelude::DebugName; use crate::{ component::ComponentId, - entity::{Entity, EntityDoesNotExistError}, + entity::{Entity, EntityNotSpawnedError}, schedule::InternedScheduleLabel, }; @@ -33,7 +33,7 @@ pub struct TryInsertBatchError { /// An error that occurs when a specified [`Entity`] could not be despawned. #[derive(thiserror::Error, Debug, Clone, Copy)] #[error("Could not despawn entity: {0}")] -pub struct EntityDespawnError(#[from] pub EntityMutableFetchError); +pub struct EntityDespawnError(#[from] pub EntityNotSpawnedError); /// An error that occurs when dynamically retrieving components from an entity. #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] @@ -49,13 +49,13 @@ pub enum EntityComponentError { /// An error that occurs when fetching entities mutably from a world. #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] pub enum EntityMutableFetchError { - /// The entity with the given ID does not exist. + /// The entity is not spawned. #[error( "{0}\n If you were attempting to apply a command to this entity, and want to handle this error gracefully, consider using `EntityCommands::queue_handled` or `queue_silenced`." )] - EntityDoesNotExist(#[from] EntityDoesNotExistError), + NotSpawned(#[from] EntityNotSpawnedError), /// The entity with the given ID was requested mutably more than once. #[error("The entity with ID {0} was requested mutably more than once")] AliasedMutability(Entity), diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 8f013b52ec7db..182921b8548a8 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -43,7 +43,9 @@ use crate::{ ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable, RequiredComponents, RequiredComponentsError, }, - entity::{Entities, Entity, EntityDoesNotExistError}, + entity::{ + Entities, EntitiesAllocator, Entity, EntityNotSpawnedError, InvalidEntityError, SpawnError, + }, entity_disabling::DefaultQueryFilters, error::{DefaultErrorHandler, ErrorHandler}, lifecycle::{ComponentHooks, RemovedComponentMessages, ADD, DESPAWN, INSERT, REMOVE, REPLACE}, @@ -92,6 +94,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, @@ -111,6 +114,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(), @@ -204,6 +208,18 @@ impl World { &self.entities } + /// Retrieves this world's [`EntitiesAllocator`] collection. + #[inline] + pub fn entities_allocator(&self) -> &EntitiesAllocator { + &self.allocator + } + + /// Retrieves this world's [`EntitiesAllocator`] collection mutably. + #[inline] + pub fn entities_allocator_mut(&mut self) -> &mut EntitiesAllocator { + &mut self.allocator + } + /// Retrieves this world's [`Entities`] collection mutably. /// /// # Safety @@ -214,6 +230,14 @@ impl World { &mut self.entities } + /// Retrieves the number of [`Entities`] in the world. + /// + /// This is helpful as a diagnostic, but it can also be used effectively in tests. + #[inline] + pub fn entity_count(&self) -> u32 { + self.entities.count_spawned() + } + /// Retrieves this world's [`Archetypes`] collection. #[inline] pub fn archetypes(&self) -> &Archetypes { @@ -271,7 +295,13 @@ impl World { #[inline] pub fn commands(&mut self) -> Commands<'_, '_> { // SAFETY: command_queue is stored on world and always valid while the world exists - unsafe { Commands::new_raw_from_entities(self.command_queue.clone(), &self.entities) } + unsafe { + Commands::new_raw_from_entities( + self.command_queue.clone(), + &self.allocator, + &self.entities, + ) + } } /// Registers a new [`Component`] type and returns the [`ComponentId`] created for it. @@ -720,19 +750,9 @@ impl World { #[inline] #[track_caller] pub fn entity(&self, entities: F) -> F::Ref<'_> { - #[inline(never)] - #[cold] - #[track_caller] - fn panic_no_entity(world: &World, entity: Entity) -> ! { - panic!( - "Entity {entity} {}", - world.entities.entity_does_not_exist_error_details(entity) - ); - } - match self.get_entity(entities) { - Ok(fetched) => fetched, - Err(error) => panic_no_entity(self, error.entity), + Ok(res) => res, + Err(err) => panic!("{err}"), } } @@ -874,11 +894,8 @@ impl World { pub fn inspect_entity( &self, entity: Entity, - ) -> Result, EntityDoesNotExistError> { - let entity_location = self - .entities() - .get(entity) - .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; + ) -> Result, EntityNotSpawnedError> { + let entity_location = self.entities().get_spawned(entity)?; let archetype = self .archetypes() @@ -905,7 +922,7 @@ impl World { /// # Errors /// /// If any of the given `entities` do not exist in the world, the first - /// [`Entity`] found to be missing will return an [`EntityDoesNotExistError`]. + /// [`Entity`] found to be missing will return an [`EntityNotSpawnedError`]. /// /// # Examples /// @@ -916,7 +933,7 @@ impl World { pub fn get_entity( &self, entities: F, - ) -> Result, EntityDoesNotExistError> { + ) -> Result, EntityNotSpawnedError> { let cell = self.as_unsafe_world_cell_readonly(); // SAFETY: `&self` gives read access to the entire world, and prevents mutable access. unsafe { entities.fetch_ref(cell) } @@ -944,7 +961,7 @@ impl World { /// /// # Errors /// - /// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if any of the given `entities` do not exist in the world. + /// - Returns [`EntityMutableFetchError::NotSpawned`] if any of the given `entities` do not exist in the world. /// - Only the first entity found to be missing will be returned. /// - Returns [`EntityMutableFetchError::AliasedMutability`] if the same entity is requested multiple times. /// @@ -1054,42 +1071,142 @@ impl World { // - Command queue access does not conflict with entity access. let raw_queue = unsafe { cell.get_raw_command_queue() }; // SAFETY: `&mut self` ensures the commands does not outlive the world. - let commands = unsafe { Commands::new_raw_from_entities(raw_queue, cell.entities()) }; + let commands = unsafe { + Commands::new_raw_from_entities(raw_queue, cell.entities_allocator(), cell.entities()) + }; (fetcher, commands) } - /// Spawns a new [`Entity`] and returns a corresponding [`EntityWorldMut`], which can be used - /// to add components to the entity or retrieve its id. + /// Spawns the bundle on the valid but not spawned entity. + /// If the entity can not be spawned for any reason, returns an error. /// - /// ``` - /// use bevy_ecs::{component::Component, world::World}; + /// If it succeeds, this declares the entity to have this bundle. /// - /// #[derive(Component)] - /// struct Position { - /// x: f32, - /// y: f32, - /// } - /// #[derive(Component)] - /// struct Label(&'static str); - /// #[derive(Component)] - /// struct Num(u32); + /// In general, you should prefer [`spawn`](Self::spawn). + /// Spawn internally calls this method, but it takes care of finding a suitable [`Entity`] for you. + /// This is made available for advanced use, which you can see at [`EntitiesAllocator::alloc`]. /// - /// let mut world = World::new(); - /// let entity = world.spawn_empty() - /// .insert(Position { x: 0.0, y: 0.0 }) // add a single component - /// .insert((Num(1), Label("hello"))) // add a bundle of components - /// .id(); + /// # Risk /// - /// let position = world.entity(entity).get::().unwrap(); - /// assert_eq!(position.x, 0.0); - /// ``` + /// It is possible to spawn an `entity` that has not been allocated yet; + /// however, doing so is currently a bad idea as the allocator may hand out this entity row in the future, assuming it to be not spawned. + /// This would cause a panic. + /// + /// Manual spawning is a powerful tool, but must be used carefully. + /// + /// # Example + /// + /// Currently, this is primarily used to spawn entities that come from [`EntitiesAllocator::alloc`]. + /// See that for an example. #[track_caller] - pub fn spawn_empty(&mut self) -> EntityWorldMut<'_> { - self.flush(); - let entity = self.entities.alloc(); - // SAFETY: entity was just allocated - unsafe { self.spawn_at_empty_internal(entity, MaybeLocation::caller()) } + pub fn spawn_at( + &mut self, + entity: Entity, + bundle: B, + ) -> Result, SpawnError> { + move_as_ptr!(bundle); + self.spawn_at_with_caller(entity, bundle, MaybeLocation::caller()) + } + + pub(crate) fn spawn_at_with_caller( + &mut self, + entity: Entity, + bundle: MovingPtr<'_, B>, + caller: MaybeLocation, + ) -> Result, SpawnError> { + self.entities.check_can_spawn_at(entity)?; + Ok(self.spawn_at_unchecked(entity, bundle, caller)) + } + + /// Spawns `bundle` on `entity`. + /// + /// # Panics + /// + /// Panics if the entity row is already constructed + pub(crate) fn spawn_at_unchecked( + &mut self, + entity: Entity, + bundle: MovingPtr<'_, B>, + caller: MaybeLocation, + ) -> EntityWorldMut<'_> { + let change_tick = self.change_tick(); + let mut bundle_spawner = BundleSpawner::new::(self, change_tick); + let (bundle, entity_location) = bundle.partial_move(|bundle| { + // SAFETY: + // - `B` matches `bundle_spawner`'s type + // - `entity` is allocated but non-existent + // - `B::Effect` is unconstrained, and `B::apply_effect` is called exactly once on the bundle after this call. + // - This function ensures that the value pointed to by `bundle` must not be accessed for anything afterwards by consuming + // the `MovingPtr`. The value is otherwise only used to call `apply_effect` within this function, and the safety invariants + // of `DynamicBundle` ensure that only the elements that have not been moved out of by this call are accessed. + unsafe { bundle_spawner.spawn_at::(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) + .expect("For this to fail, a queued command would need to despawn the entity."); + } + + // SAFETY: entity and location are valid, as they were just created above + let mut entity = unsafe { EntityWorldMut::new(self, entity, entity_location) }; + // SAFETY: + // - This is called exactly once after `get_components` has been called in `spawn_non_existent`. + // - `bundle` had it's `get_components` function called exactly once inside `spawn_non_existent`. + unsafe { B::apply_effect(bundle, &mut entity) }; + entity + } + + /// A faster version of [`spawn_at`](Self::spawn_at) for the empty bundle. + #[track_caller] + pub fn spawn_at_empty(&mut self, entity: Entity) -> Result, SpawnError> { + self.spawn_at_empty_with_caller(entity, MaybeLocation::caller()) + } + + pub(crate) fn spawn_at_empty_with_caller( + &mut self, + entity: Entity, + caller: MaybeLocation, + ) -> Result, SpawnError> { + self.entities.check_can_spawn_at(entity)?; + Ok(self.spawn_at_empty_unchecked(entity, caller)) + } + + /// A faster version of [`spawn_at_unchecked`](Self::spawn_at_unchecked) for the empty bundle. + /// + /// # Panics + /// + /// Panics if the entity row is already spawned + pub(crate) fn spawn_at_empty_unchecked( + &mut self, + entity: Entity, + caller: MaybeLocation, + ) -> EntityWorldMut<'_> { + // SAFETY: Locations are immediately made valid + unsafe { + 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 = archetype.allocate(entity, table_row); + let change_tick = self.change_tick(); + let was_at = self.entities.set_location(entity.row(), Some(location)); + assert!( + was_at.is_none(), + "Attempting to construct an empty entity, but it was already constructed." + ); + self.entities + .mark_spawned_or_despawned(entity.row(), caller, change_tick); + + EntityWorldMut::new(self, entity, Some(location)) + } } /// Spawns a new [`Entity`] with a given [`Bundle`] of [components](`Component`) and returns @@ -1163,57 +1280,45 @@ impl World { bundle: MovingPtr<'_, 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); - let (bundle, entity_location) = bundle.partial_move(|bundle| { - // SAFETY: - // - `B` matches `bundle_spawner`'s type - // - `entity` is allocated but non-existent - // - `B::Effect` is unconstrained, and `B::apply_effect` is called exactly once on the bundle after this call. - // - This function ensures that the value pointed to by `bundle` must not be accessed for anything afterwards by consuming - // the `MovingPtr`. The value is otherwise only used to call `apply_effect` within this function, and the safety invariants - // of `DynamicBundle` ensure that only the elements that have not been moved out of by this call are accessed. - 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) }; - // SAFETY: - // - This is called exactly once after `get_components` has been called in `spawn_non_existent`. - // - `bundle` had it's `get_components` function called exactly once inside `spawn_non_existent`. - unsafe { B::apply_effect(bundle, &mut entity) }; - entity + let entity = self.allocator.alloc(); + // This was just spawned from null, so it shouldn't panic. + self.spawn_at_unchecked(entity, bundle, 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.set(entity.index(), Some(location)); - self.entities - .mark_spawn_despawn(entity.index(), caller, change_tick); + /// Spawns a new [`Entity`] and returns a corresponding [`EntityWorldMut`], which can be used + /// to add components to the entity or retrieve its id. + /// + /// ``` + /// use bevy_ecs::{component::Component, world::World}; + /// + /// #[derive(Component)] + /// struct Position { + /// x: f32, + /// y: f32, + /// } + /// #[derive(Component)] + /// struct Label(&'static str); + /// #[derive(Component)] + /// struct Num(u32); + /// + /// let mut world = World::new(); + /// let entity = world.spawn_empty() + /// .insert(Position { x: 0.0, y: 0.0 }) // add a single component + /// .insert((Num(1), Label("hello"))) // add a bundle of components + /// .id(); + /// + /// let position = world.entity(entity).get::().unwrap(); + /// assert_eq!(position.x, 0.0); + /// ``` + #[track_caller] + pub fn spawn_empty(&mut self) -> EntityWorldMut<'_> { + self.spawn_empty_with_caller(MaybeLocation::caller()) + } - EntityWorldMut::new(self, entity, Some(location)) + pub(crate) fn spawn_empty_with_caller(&mut self, caller: MaybeLocation) -> EntityWorldMut<'_> { + let entity = self.allocator.alloc(); + // This was just spawned from null, so it shouldn't panic. + self.spawn_at_empty_unchecked(entity, caller) } /// Spawns a batch of entities with the same component [`Bundle`] type. Takes a given @@ -1377,11 +1482,13 @@ impl World { Ok(result) } - /// Despawns the given [`Entity`], if it exists. This will also remove all of the entity's - /// [`Components`](Component). + /// Despawns the given [`Entity`], if it exists. + /// This will also remove all of the entity's [`Components`](Component). /// /// Returns `true` if the entity is successfully despawned and `false` if /// the entity does not exist. + /// This counts despawning a not constructed entity as a success, and frees it to the allocator. + /// See [entity](crate::entity) module docs for more about construction. /// /// # Note /// @@ -1425,7 +1532,7 @@ impl World { /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). #[track_caller] #[inline] - pub fn try_despawn(&mut self, entity: Entity) -> Result<(), EntityDespawnError> { + pub fn try_despawn(&mut self, entity: Entity) -> Result<(), InvalidEntityError> { self.despawn_with_caller(entity, MaybeLocation::caller()) } @@ -1434,11 +1541,80 @@ impl World { &mut self, entity: Entity, caller: MaybeLocation, - ) -> Result<(), EntityDespawnError> { - self.flush(); - let entity = self.get_entity_mut(entity)?; - entity.despawn_with_caller(caller); - Ok(()) + ) -> Result<(), InvalidEntityError> { + match self.get_entity_mut(entity) { + Ok(entity) => { + entity.despawn_with_caller(caller); + Ok(()) + } + // Only one entity. + Err(EntityMutableFetchError::AliasedMutability(_)) => unreachable!(), + Err(EntityMutableFetchError::NotSpawned(EntityNotSpawnedError::Invalid(err))) => { + Err(err) + } + // The caller wants the entity to be left despawned and in the allocator. In this case, we can just skip the despawning part. + Err(EntityMutableFetchError::NotSpawned(EntityNotSpawnedError::RowNotSpawned(_))) => { + self.allocator.free(entity); + Ok(()) + } + } + } + + /// Performs [`try_despawn_no_free`](Self::try_despawn_no_free), warning on errors. + /// See that method for more information. + #[track_caller] + #[inline] + pub fn despawn_no_free(&mut self, entity: Entity) -> Option { + match self.despawn_no_free_with_caller(entity, MaybeLocation::caller()) { + Ok(entity) => Some(entity), + Err(error) => { + warn!("{error}"); + None + } + } + } + + /// Despawns the given `entity`, if it exists. + /// This will also remove all of the entity's [`Component`]s. + /// + /// The *only* difference between this and [despawning](Self::despawn) an entity is that this does not release the `entity` to be reused. + /// It is up to the caller to either re-spawn or free the `entity`; otherwise, the [`EntityRow`](crate::entity::EntityRow) will not be able to be reused. + /// In general, [`despawn`](Self::despawn) should be used instead, which automatically allows the row to be reused. + /// + /// Returns the new [`Entity`] if of the despawned [`EntityRow`](crate::entity::EntityRow), which should eventually either be re-spawned or freed to the allocator. + /// Returns an [`EntityDespawnError`] if the entity is not spawned. + /// + /// # Note + /// + /// This will also *despawn* the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured + /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). + /// + /// # Example + /// + /// There is no simple example in which this would be practical, but one use for this is a custom entity allocator. + /// Despawning internally calls this and frees the entity id to Bevy's default entity allocator. + /// The same principal can be used to create custom allocators with additional properties. + /// For example, this could be used to make an allocator that yields groups of consecutive [`EntityRow`](crate::entity::EntityRow)s, etc. + /// See [`EntitiesAllocator::alloc`] for more on this. + #[track_caller] + #[inline] + pub fn try_despawn_no_free(&mut self, entity: Entity) -> Result { + self.despawn_no_free_with_caller(entity, MaybeLocation::caller()) + } + + #[inline] + pub(crate) fn despawn_no_free_with_caller( + &mut self, + entity: Entity, + caller: MaybeLocation, + ) -> Result { + let mut entity = self.get_entity_mut(entity).map_err(|err| match err { + EntityMutableFetchError::NotSpawned(err) => err, + // Only one entity. + EntityMutableFetchError::AliasedMutability(_) => unreachable!(), + })?; + entity.despawn_no_free_with_caller(caller); + Ok(entity.id()) } /// Clears the internal component tracker state. @@ -2309,81 +2485,78 @@ impl World { archetype_id: ArchetypeId, } - self.flush(); let change_tick = self.change_tick(); let bundle_id = self.register_bundle_info::(); let mut batch_iter = batch.into_iter(); if let Some((first_entity, first_bundle)) = batch_iter.next() { - if let Some(first_location) = self.entities().get(first_entity) { - let mut cache = InserterArchetypeCache { - // SAFETY: we initialized this bundle_id in `register_info` - inserter: unsafe { - BundleInserter::new_with_id( - self, - first_location.archetype_id, - bundle_id, - change_tick, + match self.entities().get_spawned(first_entity) { + Err(err) => { + panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {first_entity} because: {err}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::()); + } + Ok(first_location) => { + let mut cache = InserterArchetypeCache { + // SAFETY: we initialized this bundle_id in `register_info` + inserter: unsafe { + BundleInserter::new_with_id( + self, + first_location.archetype_id, + bundle_id, + change_tick, + ) + }, + archetype_id: first_location.archetype_id, + }; + move_as_ptr!(first_bundle); + // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter + unsafe { + cache.inserter.insert( + first_entity, + first_location, + first_bundle, + insert_mode, + caller, + RelationshipHookMode::Run, ) - }, - archetype_id: first_location.archetype_id, - }; - - move_as_ptr!(first_bundle); - // SAFETY: - // - `entity` is valid, `location` matches entity, bundle matches inserter - // - `apply_effect` is never called on this bundle. - // - `first_bundle` is not be accessed or dropped after this. - unsafe { - cache.inserter.insert( - first_entity, - first_location, - first_bundle, - insert_mode, - caller, - RelationshipHookMode::Run, - ) - }; + }; - for (entity, bundle) in batch_iter { - if let Some(location) = cache.inserter.entities().get(entity) { - if location.archetype_id != cache.archetype_id { - cache = InserterArchetypeCache { - // SAFETY: we initialized this bundle_id in `register_info` - inserter: unsafe { - BundleInserter::new_with_id( - self, - location.archetype_id, - bundle_id, - change_tick, + for (entity, bundle) in batch_iter { + match cache.inserter.entities().get_spawned(entity) { + Ok(location) => { + if location.archetype_id != cache.archetype_id { + cache = InserterArchetypeCache { + // SAFETY: we initialized this bundle_id in `register_info` + inserter: unsafe { + BundleInserter::new_with_id( + self, + location.archetype_id, + bundle_id, + change_tick, + ) + }, + archetype_id: location.archetype_id, + } + } + move_as_ptr!(bundle); + // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter + unsafe { + cache.inserter.insert( + entity, + location, + bundle, + insert_mode, + caller, + RelationshipHookMode::Run, ) - }, - archetype_id: location.archetype_id, + }; + } + Err(err) => { + panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {entity} because: {err}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::()); } } - - move_as_ptr!(bundle); - // SAFETY: - // - `entity` is valid, `location` matches entity, bundle matches inserter - // - `apply_effect` is never called on this bundle. - // - `bundle` is not be accessed or dropped after this. - unsafe { - cache.inserter.insert( - entity, - location, - bundle, - insert_mode, - caller, - RelationshipHookMode::Run, - ) - }; - } else { - panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {entity}, which {}. See: https://bevy.org/learn/errors/b0003", DebugName::type_name::(), self.entities.entity_does_not_exist_error_details(entity)); } } - } else { - panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {first_entity}, which {}. See: https://bevy.org/learn/errors/b0003", DebugName::type_name::(), self.entities.entity_does_not_exist_error_details(first_entity)); } } } @@ -2459,7 +2632,6 @@ impl World { archetype_id: ArchetypeId, } - self.flush(); let change_tick = self.change_tick(); let bundle_id = self.register_bundle_info::(); @@ -2471,7 +2643,7 @@ impl World { // if the first entity is invalid, whereas this method needs to keep going. let cache = loop { if let Some((first_entity, first_bundle)) = batch_iter.next() { - if let Some(first_location) = self.entities().get(first_entity) { + if let Ok(first_location) = self.entities().get_spawned(first_entity) { let mut cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_bundle_info` inserter: unsafe { @@ -2511,7 +2683,7 @@ impl World { if let Some(mut cache) = cache { for (entity, bundle) in batch_iter { - if let Some(location) = cache.inserter.entities().get(entity) { + if let Ok(location) = cache.inserter.entities().get_spawned(entity) { if location.archetype_id != cache.archetype_id { cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_info` @@ -2786,30 +2958,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. /// @@ -2843,7 +2991,6 @@ impl World { #[inline] #[track_caller] pub fn flush(&mut self) { - self.flush_entities(); self.flush_components(); self.flush_commands(); } @@ -3067,6 +3214,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`]. @@ -3634,7 +3782,7 @@ impl fmt::Debug for World { // Accessing any data stored in the world would be unsound. f.debug_struct("World") .field("id", &self.id) - .field("entity_count", &self.entities.len()) + .field("entity_count", &self.entities.count_spawned()) .field("archetype_count", &self.archetypes.len()) .field("component_count", &self.components.len()) .field("resource_count", &self.storages.resources.len()) @@ -4278,32 +4426,35 @@ mod tests { assert_eq!( Err(e1), - world.get_entity(e1).map(|_| {}).map_err(|e| e.entity) + world.get_entity(e1).map(|_| {}).map_err(|e| e.entity()) ); assert_eq!( Err(e1), - world.get_entity([e1, e2]).map(|_| {}).map_err(|e| e.entity) + world + .get_entity([e1, e2]) + .map(|_| {}) + .map_err(|e| e.entity()) ); assert_eq!( Err(e1), world .get_entity(&[e1, e2] /* this is an array not a slice */) .map(|_| {}) - .map_err(|e| e.entity) + .map_err(|e| e.entity()) ); assert_eq!( Err(e1), world .get_entity(&vec![e1, e2][..]) .map(|_| {}) - .map_err(|e| e.entity) + .map_err(|e| e.entity()) ); assert_eq!( Err(e1), world .get_entity(&EntityHashSet::from_iter([e1, e2])) .map(|_| {}) - .map_err(|e| e.entity) + .map_err(|e| e.entity()) ); } @@ -4344,28 +4495,29 @@ mod tests { .is_ok()); world.entity_mut(e1).despawn(); + assert!(world.get_entity_mut(e2).is_ok()); assert!(matches!( world.get_entity_mut(e1).map(|_| {}), - Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1 + Err(EntityMutableFetchError::NotSpawned(e)) if e.entity() == e1 )); assert!(matches!( world.get_entity_mut([e1, e2]).map(|_| {}), - Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1)); + Err(EntityMutableFetchError::NotSpawned(e)) if e.entity() == e1)); assert!(matches!( world .get_entity_mut(&[e1, e2] /* this is an array not a slice */) .map(|_| {}), - Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1)); + Err(EntityMutableFetchError::NotSpawned(e)) if e.entity() == e1)); assert!(matches!( world.get_entity_mut(&vec![e1, e2][..]).map(|_| {}), - Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1, + Err(EntityMutableFetchError::NotSpawned(e)) if e.entity() == e1, )); assert!(matches!( world .get_entity_mut(&EntityHashSet::from_iter([e1, e2])) .map(|_| {}), - Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1)); + Err(EntityMutableFetchError::NotSpawned(e)) if e.entity() == e1)); } #[test] @@ -4380,7 +4532,7 @@ mod tests { MaybeLocation::new(Some(Location::caller())) ); assert_eq!( - world.entities.entity_get_spawn_or_despawn_tick(entity), + world.entities.entity_get_spawned_or_despawned_at(entity), Some(world.change_tick()) ); world.despawn(entity); @@ -4389,7 +4541,7 @@ mod tests { MaybeLocation::new(Some(Location::caller())) ); assert_eq!( - world.entities.entity_get_spawn_or_despawn_tick(entity), + world.entities.entity_get_spawned_or_despawned_at(entity), Some(world.change_tick()) ); let new = world.spawn_empty().id(); @@ -4399,7 +4551,7 @@ mod tests { MaybeLocation::new(None) ); assert_eq!( - world.entities.entity_get_spawn_or_despawn_tick(entity), + world.entities.entity_get_spawned_or_despawned_at(entity), None ); world.despawn(new); @@ -4408,7 +4560,7 @@ mod tests { MaybeLocation::new(None) ); assert_eq!( - world.entities.entity_get_spawn_or_despawn_tick(entity), + world.entities.entity_get_spawned_or_despawned_at(entity), None ); } diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index 9f9c87ee572b9..14e14bb47d351 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -3,7 +3,7 @@ use bevy_ptr::move_as_ptr; use crate::{ bundle::{Bundle, BundleSpawner, NoBundleEffect}, change_detection::MaybeLocation, - entity::{Entity, EntitySetIterator}, + entity::{AllocEntitiesIterator, Entity, EntitySetIterator}, world::World, }; use core::iter::FusedIterator; @@ -19,6 +19,7 @@ where { inner: I, spawner: BundleSpawner<'w>, + allocator: AllocEntitiesIterator<'w>, caller: MaybeLocation, } @@ -30,21 +31,18 @@ where #[inline] #[track_caller] pub(crate) fn new(world: &'w mut World, iter: I, caller: MaybeLocation) -> Self { - // Ensure all entity allocations are accounted for so `self.entities` can realloc if - // necessary - world.flush(); - let change_tick = world.change_tick(); let (lower, upper) = iter.size_hint(); let length = upper.unwrap_or(lower); - world.entities.reserve(length as u32); let mut spawner = BundleSpawner::new::(world, change_tick); spawner.reserve_storage(length); + let allocator = spawner.allocator().alloc_many(length as u32); Self { inner: iter, + allocator, spawner, caller, } @@ -75,11 +73,16 @@ where fn next(&mut self) -> Option { let bundle = self.inner.next()?; move_as_ptr!(bundle); - // SAFETY: - // - The spawner matches `I::Item`'s type. - // - `I::Item::Effect: NoBundleEffect`, thus [`apply_effect`] does not need to be called. - // - `bundle` is not accessed or dropped after this function call. - unsafe { Some(self.spawner.spawn::(bundle, self.caller)) } + Some(if let Some(bulk) = self.allocator.next() { + // SAFETY: bundle matches spawner type and we just allocated it + unsafe { + self.spawner.spawn_at(bulk, bundle, self.caller); + } + bulk + } else { + // SAFETY: bundle matches spawner type + unsafe { self.spawner.spawn(bundle, self.caller) } + }) } fn size_hint(&self) -> (usize, Option) { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index b3d807d36fb35..6d281ade379b0 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -9,7 +9,9 @@ use crate::{ MutUntyped, Tick, }, component::{ComponentId, Components, Mutable, StorageType}, - entity::{ContainsEntity, Entities, Entity, EntityDoesNotExistError, EntityLocation}, + entity::{ + ContainsEntity, Entities, EntitiesAllocator, Entity, EntityLocation, EntityNotSpawnedError, + }, error::{DefaultErrorHandler, ErrorHandler}, lifecycle::RemovedComponentMessages, observer::Observers, @@ -262,6 +264,14 @@ impl<'w> UnsafeWorldCell<'w> { &unsafe { self.world_metadata() }.entities } + /// Retrieves this world's [`Entities`] collection. + #[inline] + pub fn entities_allocator(self) -> &'w EntitiesAllocator { + // SAFETY: + // - we only access world metadata + &unsafe { self.world_metadata() }.allocator + } + /// Retrieves this world's [`Archetypes`] collection. #[inline] pub fn archetypes(self) -> &'w Archetypes { @@ -361,14 +371,8 @@ impl<'w> UnsafeWorldCell<'w> { /// Retrieves an [`UnsafeEntityCell`] that exposes read and write operations for the given `entity`. /// Similar to the [`UnsafeWorldCell`], you are in charge of making sure that no aliasing rules are violated. #[inline] - pub fn get_entity( - self, - entity: Entity, - ) -> Result, EntityDoesNotExistError> { - let location = self - .entities() - .get(entity) - .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; + pub fn get_entity(self, entity: Entity) -> Result, EntityNotSpawnedError> { + let location = self.entities().get_spawned(entity)?; Ok(UnsafeEntityCell::new( self, entity, @@ -386,11 +390,8 @@ impl<'w> UnsafeWorldCell<'w> { entity: Entity, last_run: Tick, this_run: Tick, - ) -> Result, EntityDoesNotExistError> { - let location = self - .entities() - .get(entity) - .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; + ) -> Result, EntityNotSpawnedError> { + let location = self.entities().get_spawned(entity)?; Ok(UnsafeEntityCell::new( self, entity, location, last_run, this_run, )) diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 6c7cdd02e2894..abe8881731fc0 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -849,8 +849,9 @@ pub fn process_remote_query_request(In(params): In>, world: &mut W let all_optionals = entity_ref .archetype() - .iter_components() - .filter_map(|component_id| { + .components() + .iter() + .filter_map(|&component_id| { let info = world.components().get_info(component_id)?; let type_id = info.type_id()?; // Skip required components (already included) @@ -1257,7 +1258,7 @@ pub fn process_remote_list_components_request( // If `Some`, return all components of the provided entity. if let Some(BrpListComponentsParams { entity }) = params.map(parse).transpose()? { let entity = get_entity(world, entity)?; - for component_id in entity.archetype().iter_components() { + for &component_id in entity.archetype().components().iter() { let Some(component_info) = world.components().get_info(component_id) else { continue; }; @@ -1311,7 +1312,7 @@ pub fn process_remote_list_components_watching_request( let entity_ref = get_entity(world, entity)?; let mut response = BrpListComponentsWatchingResponse::default(); - for component_id in entity_ref.archetype().iter_components() { + for &component_id in entity_ref.archetype().components().iter() { let ticks = entity_ref .get_change_ticks_by_id(component_id) .ok_or(BrpError::internal("Failed to get ticks"))?; diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 39c6a074e6f57..494074df4e43b 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -333,9 +333,7 @@ where let view = match self.view.get_manual(world, view) { Ok(view) => view, Err(err) => match err { - QueryEntityError::EntityDoesNotExist(_) => { - return Err(DrawError::ViewEntityNotFound) - } + QueryEntityError::NotSpawned(_) => return Err(DrawError::ViewEntityNotFound), QueryEntityError::QueryDoesNotMatch(_, _) | QueryEntityError::AliasedMutability(_) => { return Err(DrawError::InvalidViewQuery) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 3acef88a1c8bb..83bf52524dd57 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -283,7 +283,7 @@ impl<'w> DynamicSceneBuilder<'w> { }; let original_entity = self.original_world.entity(entity); - for component_id in original_entity.archetype().iter_components() { + for &component_id in original_entity.archetype().components().iter() { let mut extract_and_push = || { let type_id = self .original_world diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index d13822847fc40..1ab33b4b421bc 100644 --- a/crates/bevy_transform/src/helper.rs +++ b/crates/bevy_transform/src/helper.rs @@ -1,6 +1,7 @@ //! System parameter for computing up-to-date [`GlobalTransform`]s. use bevy_ecs::{ + entity::EntityNotSpawnedError, hierarchy::ChildOf, prelude::Entity, query::QueryEntityError, @@ -52,11 +53,11 @@ fn map_error(err: QueryEntityError, ancestor: bool) -> ComputeGlobalTransformErr use ComputeGlobalTransformError::*; match err { QueryEntityError::QueryDoesNotMatch(entity, _) => MissingTransform(entity), - QueryEntityError::EntityDoesNotExist(error) => { + QueryEntityError::NotSpawned(error) => { if ancestor { - MalformedHierarchy(error.entity) + MalformedHierarchy(error) } else { - NoSuchEntity(error.entity) + NoSuchEntity(error) } } QueryEntityError::AliasedMutability(_) => unreachable!(), @@ -70,12 +71,12 @@ pub enum ComputeGlobalTransformError { #[error("The entity {0:?} or one of its ancestors is missing the `Transform` component")] MissingTransform(Entity), /// The entity does not exist. - #[error("The entity {0:?} does not exist")] - NoSuchEntity(Entity), + #[error("The entity does not exist: {0}")] + NoSuchEntity(EntityNotSpawnedError), /// An ancestor is missing. /// This probably means that your hierarchy has been improperly maintained. - #[error("The ancestor {0:?} is missing")] - MalformedHierarchy(Entity), + #[error("The ancestor is missing: {0}")] + MalformedHierarchy(EntityNotSpawnedError), } #[cfg(test)] diff --git a/release-content/migration-guides/entities_apis.md b/release-content/migration-guides/entities_apis.md new file mode 100644 index 0000000000000..1cf52592d5182 --- /dev/null +++ b/release-content/migration-guides/entities_apis.md @@ -0,0 +1,60 @@ +--- +title: Entities APIs +pull_requests: [19350, 19433, 19451] +--- + +Entities are spawned by allocating their id and then giving that id a location within the world. +In 0.17, this was done in one stroke through `spawn` and `Entities::flush`. +In 0.18, the flushing functionality has been removed in favor of `spawn`ing individual `EntityRow`s instead. +Don't worry, these changes don't affect the common operations like `spawn` and `despawn`, but the did impact the peripheral interfaces and error types. +For a full explanation of the new entity paradigm, errors and terms, see the new `entity` module docs. +If you want more background for the justification of these changes or more information about where these new terms come from, see pr #19451. +This opens up a lot of room for performance improvement but also caused a lot of breaking changes: + +### `Entities` rework + +A lot has changed here. +First, `alloc`, `free`, `reserve`, `reserve_entity`, `reserve_entities`, `flush`, `flush_as_invalid`, `EntityDoesNotExistError`, `total_count`, `used_count`, and `total_prospective_count` have all been removed 😱. + +Allocation has moved to the new `EntitiesAllocator` type, accessible via `World::entities_allocator` and `World::entities_allocator_mut`, which have `alloc`, `free`, and `alloc_many`. + +Reservation and flushing have been completely removed as they are no longer needed. +Instead of reserving an entity and later flushing it, you can `EntitiesAllocator::alloc` (which does not need mutable access), and `World::spawn_at` can be used to "flush" the entity. + +The counting methods have been reworked in the absence of flushing: +`len` and `is_empty` now deal with how many entity rows have been allocated (not necessarily the number that have been spawned), +and the new `count_spawned` and `any_spawned` are similar to the old `len` and `is_empty` behavior but are now O(n). + +In terms of getting information from `Entities`, `get` and `contains` has been reworked to include non-spawned entities. +If you only want spawned entities, `get_spawned` and `contains_spawned` are available. +Additionally, `get` now returns `Result` instead of `Option` for clarity. +`EntityRowLocation` is an alias for `Option`, as entities now may or may not have a location, depending on if it is spawned or not. + +`EntityDoesNotExistError` has been removed and reworked. +See the new entity module docs for more, but: +When an entity's generation is not up to date with its row, `InvalidEntityError` is produced. +When an entity row's `EntityRowLocation` is `None`, `EntityRowNotSpawnedError` is produced. +When an `Entity` is expected to be spawned but is not (either because its generation is outdated or because its row is not spawned), `EntityNotSpawnedError` is produced. +A few other wrapper error types have slightly changed as well, generally moving from "entity does not exist" to "entity is not spawned". + +### Entity Pointers + +When migrating, entity pointers, like `EntityRef`, were changed to assume that the entity they point to is spawned. +This was not necessarily checked before, so the errors for creating an entity pointer is now `EntityNotSpawnedError`. +This probably will not affect you since creating a pointer to an entity that was not spawned is kinda pointless. + +It is still possible to invalidate an `EntityWorldMut` by despawning it from commands. (Ex: The hook for adding a component to an entity actually despawns the entity it was added to.) +If that happens, it may lead to panics, but `EntityWorldMut::is_spawned` has been added to help detect that. + +### Entity Commands + +`Commands::new_from_entities` now also needs `&EntitiesAllocator`, which can be obtained from `UnsafeWorldCell::entities_allocator`. +`Commands::get_entity` does not error for non-spawned entities, making it useful to amend an entity you have queued to spawn through commands. +If you only want spawned entities, use `Commands::get_spawned_entity`. + +### Other entity interactions + +`BundleSpawner::spawn_non_existent` is now `BundleSpawner::construct`. +`World::inspect_entity` now errors with `EntityNotSpawnedError` instead of `EntityDoesNotExistError`. +`QueryEntityError::EntityDoesNotExist` is now `QueryEntityError::NotSpawned`. +`ComputeGlobalTransformError::NoSuchEntity` and `ComputeGlobalTransformError::MalformedHierarchy` now wrap `EntityNotSpawnedError`.