diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 4a2035bfd3..6bfa4d5630 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1190,7 +1190,7 @@ 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() }; + unsafe { entities.get_constructed(swapped_entity).debug_checked_unwrap() }; entities.update( swapped_entity.row(), Some(EntityLocation { @@ -1239,7 +1239,7 @@ 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() }; + unsafe { entities.get_constructed(swapped_entity).debug_checked_unwrap() }; entities.update( swapped_entity.row(), Some(EntityLocation { @@ -1260,7 +1260,7 @@ impl<'w> BundleInserter<'w> { 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_constructed(swapped_entity).debug_checked_unwrap() }; entities.update( swapped_entity.row(), @@ -1569,7 +1569,7 @@ 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_constructed(swapped_entity).unwrap(); world.entities.update( swapped_entity.row(), @@ -1610,7 +1610,7 @@ 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_constructed(swapped_entity).unwrap(); world.entities.update( swapped_entity.row(), diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index ea44052b81..89cc274dc5 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -784,43 +784,107 @@ impl Entities { /// Returns the [`EntityLocation`] of an [`Entity`]. /// Note: for non-constructed entities, returns `None`. #[inline] - pub fn get(&self, entity: Entity) -> Option { - self.get_id_location(entity).flatten() + pub fn get_constructed( + &self, + entity: Entity, + ) -> Result { + match self.meta.get(entity.index() as usize) { + Some(meta) => { + if meta.generation != entity.generation { + Err(ConstructedEntityDoesNotExistError::DidNotExist( + EntityDoesNotExistError { + entity, + current_generation: EntityGeneration::FIRST, + }, + )) + } else { + match meta.location { + Some(location) => Ok(location), + None => Err(ConstructedEntityDoesNotExistError::WasNotConstructed( + EntityNotConstructedError { + entity, + location: meta.spawned_or_despawned.by.map(Some), + }, + )), + } + } + } + None => { + if entity.generation() == EntityGeneration::FIRST { + Err(ConstructedEntityDoesNotExistError::WasNotConstructed( + EntityNotConstructedError { + entity, + location: MaybeLocation::new(None), + }, + )) + } else { + Err(ConstructedEntityDoesNotExistError::DidNotExist( + EntityDoesNotExistError { + entity, + current_generation: EntityGeneration::FIRST, + }, + )) + } + } + } } /// Returns the [`EntityIdLocation`] of an [`Entity`]. #[inline] - pub fn get_id_location(&self, entity: Entity) -> Option { + pub fn get(&self, entity: Entity) -> Result { match self.meta.get(entity.index() as usize) { - Some(meta) => (meta.generation == entity.generation).then_some(meta.location), - None => (entity.generation() == EntityGeneration::FIRST).then_some(None), + Some(meta) => { + if meta.generation == entity.generation { + Ok(meta.location) + } else { + Err(EntityDoesNotExistError { + entity, + current_generation: meta.generation, + }) + } + } + None => { + if entity.generation() == EntityGeneration::FIRST { + Ok(None) + } else { + Err(EntityDoesNotExistError { + entity, + current_generation: EntityGeneration::FIRST, + }) + } + } } } - /// Returns true if the entity exists in the world *now*: - /// It has a location, etc. - /// - /// This will return false if the `entity` is reserved but has not been constructed. + /// Get the [`Entity`] for the given [`EntityRow`]. + /// Note that this entity may not be constructed yet. + #[inline] + pub fn resolve_from_row(&self, row: EntityRow) -> Entity { + self.meta + .get(row.index() as usize) + .map(|meta| Entity::from_raw_and_generation(row, meta.generation)) + .unwrap_or(Entity::from_raw(row)) + } + + /// Returns true if the entity exists. + /// This will return true for entities that exist but have not been constructed. pub fn contains(&self, entity: Entity) -> bool { - self.get(entity).is_some() + self.resolve_from_row(entity.row()).generation() == entity.generation() + } + + /// Returns true if the entity exists in the world *now*. + /// This will return false if the `entity` is not constructed. + pub fn contains_constructed(&self, entity: Entity) -> bool { + self.get_constructed(entity).is_ok() } /// Provides information regarding if `entity` may be constructed. #[inline] pub fn validate_construction(&self, entity: Entity) -> Result<(), ConstructionError> { - if self - .resolve_from_id(entity.row()) - .is_some_and(|found| found.generation() != entity.generation()) - { - Err(ConstructionError::InvalidId) - } else if self - .meta - .get(entity.index() as usize) - .is_some_and(|meta| meta.location.is_some()) - { - Err(ConstructionError::AlreadyConstructed) - } else { - Ok(()) + match self.get(entity) { + Ok(Some(_)) => Err(ConstructionError::AlreadyConstructed), + Ok(None) => Ok(()), + Err(err) => Err(ConstructionError::InvalidId(err)), } } @@ -913,16 +977,6 @@ impl Entities { meta.spawned_or_despawned = SpawnedOrDespawned { by, at }; } - /// 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 declared conceptual entities - /// - /// Note: This method may or may not return `None` for rows that have never had their location declared. - pub fn resolve_from_id(&self, row: EntityRow) -> Option { - self.meta - .get(row.index() as usize) - .map(|meta| Entity::from_raw_and_generation(row, meta.generation)) - } - /// Try to get the source code location from which this entity has last been /// spawned, despawned or flushed. /// @@ -982,16 +1036,6 @@ impl Entities { (meta.spawned_or_despawned.by, meta.spawned_or_despawned.at) } - /// 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), - } - } - #[inline] pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { for meta in &mut self.meta { @@ -1030,10 +1074,8 @@ impl Entities { pub enum ConstructionError { /// The [`Entity`] to construct was invalid. /// It probably had the wrong generation or was created erroneously. - #[error( - "The entity's id was invalid: either erroneously created or with the wrong generation." - )] - InvalidId, + #[error("Invalid id: {0}")] + InvalidId(EntityDoesNotExistError), /// The [`Entity`] to construct was already constructed. #[error("The entity can not be constructed as it already has a location.")] AlreadyConstructed, @@ -1041,46 +1083,53 @@ pub enum ConstructionError { /// An error that occurs when a specified [`Entity`] does not exist. #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] -#[error("The entity with ID {entity} {details}")] +#[error( + "The entity with ID {entity} does not exist; its row now has generation {current_generation}." +)] pub struct EntityDoesNotExistError { /// The entity's ID. pub entity: Entity, - /// Details on why the entity does not exist, if available. - pub details: EntityDoesNotExistDetails, + /// The generation of the [`EntityRow`], which did not match the requested entity. + pub current_generation: EntityGeneration, } -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`] exists but was not constructed when it was expected to be. +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] +pub struct EntityNotConstructedError { + /// The entity's ID. + pub entity: Entity, + /// The location of what last destructed the entity. + pub location: MaybeLocation>>, } -/// 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>>, -} - -impl fmt::Display for EntityDoesNotExistDetails { +impl fmt::Display for EntityNotConstructedError { 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(Some(location)) => write!(f, "The entity with ID {entity} is not constructed; its row was last destructed by {location}."), Some(None) => write!( f, - "does not exist (index has been reused or was never spawned)" + "The entity with ID {entity} is not constructed; its row has never been constructed." ), None => write!( f, - "does not exist (enable `track_location` feature for more details)" + "The entity with ID {entity} is not constructed; enable `track_location` feature for more details." ), } } } +/// Represents an error of either [`EntityDoesNotExistError`] or [`EntityNotConstructedError`]. +#[derive(thiserror::Error, Copy, Clone, Debug, Eq, PartialEq)] +pub enum ConstructedEntityDoesNotExistError { + /// The entity did not exist. + #[error("{0}")] + DidNotExist(#[from] EntityDoesNotExistError), + /// The entity did exist but was not constructed. + #[error("{0}")] + WasNotConstructed(#[from] EntityNotConstructedError), +} + #[derive(Copy, Clone, Debug)] struct EntityMeta { /// The current [`EntityGeneration`] of the [`EntityRow`]. diff --git a/crates/bevy_ecs/src/query/error.rs b/crates/bevy_ecs/src/query/error.rs index 6d0b149b86..f3579351cc 100644 --- a/crates/bevy_ecs/src/query/error.rs +++ b/crates/bevy_ecs/src/query/error.rs @@ -1,56 +1,30 @@ -use thiserror::Error; - use crate::{ archetype::ArchetypeId, - entity::{Entity, EntityDoesNotExistError}, + entity::{ConstructedEntityDoesNotExistError, Entity}, }; /// 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), + #[error("{0}")] + EntityDoesNotExist(#[from] ConstructedEntityDoesNotExistError), /// 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 cffba8cda1..68c059fc45 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -481,7 +481,7 @@ unsafe impl QueryData for EntityLocation { _table_row: TableRow, ) -> Self::Item<'w> { // SAFETY: `fetch` must be called with an entity that exists in the world - unsafe { fetch.get(entity).debug_checked_unwrap() } + unsafe { fetch.get_constructed(entity).debug_checked_unwrap() } } } @@ -1357,7 +1357,7 @@ unsafe impl QueryData for &Archetype { ) -> Self::Item<'w> { 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_constructed(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 baf0d72697..44c2eef525 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1016,7 +1016,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_constructed(entity).debug_checked_unwrap(); archetype = self .archetypes .get(location.archetype_id) @@ -1173,7 +1173,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_constructed(entity) else { continue; }; @@ -1960,7 +1960,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_constructed(entity).debug_checked_unwrap(); archetype = self .archetypes .get(location.archetype_id) diff --git a/crates/bevy_ecs/src/reflect/entity_commands.rs b/crates/bevy_ecs/src/reflect/entity_commands.rs index 15f48e234c..a730013e28 100644 --- a/crates/bevy_ecs/src/reflect/entity_commands.rs +++ b/crates/bevy_ecs/src/reflect/entity_commands.rs @@ -348,9 +348,11 @@ fn insert_reflect_with_registry_ref( .get_represented_type_info() .expect("component should represent a type."); let type_path = type_info.type_path(); - let Ok(mut entity) = world.get_entity_mut(entity) else { - panic!("error[B0003]: Could not insert a reflected component (of type {type_path}) for entity {entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", - world.entities().entity_does_not_exist_error_details(entity)); + let mut entity = match world.get_entity_mut(entity) { + Ok(entity) => entity, + Err(err) => { + panic!("error[B0003]: Could not insert a reflected component (of type {type_path}) for entity {entity}, which {err}. See: https://bevyengine.org/learn/errors/b0003"); + } }; let Some(type_registration) = type_registry.get(type_info.type_id()) else { panic!("`{type_path}` should be registered in type registry via `App::register_type<{type_path}>`"); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 61276f5c1a..56b720b0fb 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -18,7 +18,10 @@ use crate::{ bundle::{Bundle, InsertMode, NoBundleEffect}, change_detection::{MaybeLocation, Mut}, component::{Component, ComponentId, Mutable}, - entity::{Entities, EntitiesAllocator, Entity, EntityClonerBuilder, EntityDoesNotExistError}, + entity::{ + ConstructedEntityDoesNotExistError, Entities, EntitiesAllocator, Entity, + EntityClonerBuilder, EntityDoesNotExistError, + }, error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError}, event::Event, observer::{Observer, TriggerTargets}, @@ -423,11 +426,11 @@ impl<'w, 's> Commands<'w, 's> { } } - /// Returns the [`EntityCommands`] for the requested [`Entity`] if it exists in the world *now*. - /// Note that for entities that have not been constructed, like ones from [`spawn`](Self::spawn), this will error. - /// + /// Returns the [`EntityCommands`] for the requested [`Entity`] if it exists. /// 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. + /// For example, this does not error when the entity has not been constructed. + /// For that behavior, see [`get_constructed_entity`](Self::get_constructed_entity). /// /// # Errors /// @@ -469,14 +472,65 @@ impl<'w, 's> Commands<'w, 's> { &mut self, entity: Entity, ) -> Result { - if self.entities.contains(entity) { - Ok(EntityCommands { - entity, - commands: self.reborrow(), - }) - } else { - Err(EntityDoesNotExistError::new(entity, self.entities)) - } + let _location = self.entities.get(entity)?; + Ok(EntityCommands { + entity, + commands: self.reborrow(), + }) + } + + /// Returns the [`EntityCommands`] for the requested [`Entity`] if it exists in the world *now*. + /// Note that for entities that have not been constructed, like ones from [`spawn`](Self::spawn), this will error. + /// If that is not desired, try [`get_entity`](Self::get_entity). + /// + /// 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 [`EntityDoesNotExistError`] 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_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_constructed_entity( + &mut self, + entity: Entity, + ) -> Result { + let _location = self.entities.get_constructed(entity)?; + Ok(EntityCommands { + entity, + commands: self.reborrow(), + }) } /// Spawns multiple entities with the same combination of components, diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index c3448fb819..a16207a612 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1125,7 +1125,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_constructed(entity).unwrap(); let archetype = archetypes.get(location.archetype_id).unwrap(); let archetype_components = archetype.components().collect::>(); let bundle_id = bundles diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index c67bf7b337..eb5810dee9 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1,7 +1,7 @@ use crate::{ batching::BatchingStrategy, component::Tick, - entity::{Entity, EntityDoesNotExistError, EntityEquivalent, EntitySet, UniqueEntityArray}, + entity::{Entity, EntityEquivalent, EntitySet, UniqueEntityArray}, query::{ DebugCheckedUnwrap, NopWorldQuery, QueryCombinationIter, QueryData, QueryEntityError, QueryFilter, QueryIter, QueryManyIter, QueryManyUniqueIter, QueryParIter, QueryParManyIter, @@ -1538,11 +1538,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_constructed(entity)?; if !self .state .matched_archetypes diff --git a/crates/bevy_ecs/src/world/entity_fetch.rs b/crates/bevy_ecs/src/world/entity_fetch.rs index 3c63193256..07c711d0c4 100644 --- a/crates/bevy_ecs/src/world/entity_fetch.rs +++ b/crates/bevy_ecs/src/world/entity_fetch.rs @@ -213,10 +213,7 @@ unsafe impl WorldEntityFetch for Entity { self, cell: UnsafeWorldCell<'_>, ) -> Result, EntityMutableFetchError> { - let location = cell - .entities() - .get_id_location(self) - .ok_or(EntityDoesNotExistError::new(self, cell.entities()))?; + let location = cell.entities().get(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`. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index d6ffbf04c3..72f5d59113 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1110,7 +1110,8 @@ impl<'w> EntityWorldMut<'w> { self.entity, self.world .entities() - .entity_does_not_exist_error_details(self.entity) + .get_constructed(self.entity) + .unwrap_err() ); } @@ -1168,7 +1169,7 @@ impl<'w> EntityWorldMut<'w> { entity: Entity, location: EntityIdLocation, ) -> Self { - debug_assert_eq!(world.entities().get(entity), location); + debug_assert_eq!(world.entities().get(entity), Ok(location)); EntityWorldMut { world, @@ -2450,7 +2451,7 @@ impl<'w> EntityWorldMut<'w> { 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 = self.world.entities.get(swapped_entity).unwrap(); + let swapped_location = self.world.entities.get_constructed(swapped_entity).unwrap(); // SAFETY: swapped_entity is valid and the swapped entity's components are // moved to the new location immediately after. unsafe { @@ -2485,7 +2486,7 @@ impl<'w> EntityWorldMut<'w> { // Handle displaced entity if let Some(moved_entity) = moved_entity { - let moved_location = self.world.entities.get(moved_entity).unwrap(); + let moved_location = self.world.entities.get_constructed(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 { @@ -2610,7 +2611,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).unwrap_or_default(); } /// Returns if the entity has been despawned. @@ -5781,7 +5782,7 @@ mod tests { a.trigger(TestEvent); // this adds command to change entity archetype a.observe(|_: Trigger| {}); // this flushes commands implicitly by spawning let location = a.location(); - assert_eq!(world.entities().get(entity), Some(location)); + assert_eq!(world.entities().get_constructed(entity), Ok(location)); } #[test] diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 7a7e74c773..f8f553bf2a 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -19,7 +19,7 @@ pub use crate::{ world::command_queue::CommandQueue, }; use crate::{ - entity::{ConstructionError, EntitiesAllocator, EntityRow}, + entity::{ConstructedEntityDoesNotExistError, ConstructionError, EntitiesAllocator, EntityRow}, error::{DefaultErrorHandler, ErrorHandler}, }; pub use bevy_ecs_macros::FromWorld; @@ -720,19 +720,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 +864,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, ConstructedEntityDoesNotExistError> { + let entity_location = self.entities().get_constructed(entity)?; let archetype = self .archetypes() @@ -1137,7 +1124,10 @@ impl World { // SAFETY: command_queue is not referenced anywhere else if !unsafe { self.command_queue.is_empty() } { self.flush(); - entity_location = self.entities().get(entity); + 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 @@ -2456,64 +2446,70 @@ impl World { 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_constructed(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, + }; + // 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, - }; - // 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, - ) - }; + }; - 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_constructed(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, + } + } + // 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::()); } } - // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter - 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://bevyengine.org/learn/errors/b0003", core::any::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://bevyengine.org/learn/errors/b0003", core::any::type_name::(), self.entities.entity_does_not_exist_error_details(first_entity)); } } } @@ -2605,7 +2601,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_constructed(first_entity) { let mut cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_info` inserter: unsafe { @@ -2640,7 +2636,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_constructed(entity) { if location.archetype_id != cache.archetype_id { cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_info` diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index fc384a1bc1..1aa0e0e947 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -373,10 +373,7 @@ impl<'w> UnsafeWorldCell<'w> { self, entity: Entity, ) -> Result, EntityDoesNotExistError> { - let location = self - .entities() - .get_id_location(entity) - .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; + let location = self.entities().get(entity)?; Ok(UnsafeEntityCell::new( self, entity, @@ -395,10 +392,7 @@ impl<'w> UnsafeWorldCell<'w> { last_run: Tick, this_run: Tick, ) -> Result, EntityDoesNotExistError> { - let location = self - .entities() - .get_id_location(entity) - .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; + let location = self.entities().get(entity)?; Ok(UnsafeEntityCell::new( self, entity, location, last_run, this_run, )) diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index d13822847f..50b6d37524 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::ConstructedEntityDoesNotExistError, hierarchy::ChildOf, prelude::Entity, query::QueryEntityError, @@ -54,9 +55,9 @@ fn map_error(err: QueryEntityError, ancestor: bool) -> ComputeGlobalTransformErr QueryEntityError::QueryDoesNotMatch(entity, _) => MissingTransform(entity), QueryEntityError::EntityDoesNotExist(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(ConstructedEntityDoesNotExistError), /// 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(ConstructedEntityDoesNotExistError), } #[cfg(test)]