much, much better error handling

This commit is contained in:
Elliott Pierce 2025-05-31 16:52:41 -04:00
parent 4844dda4cc
commit 7e39f9dda1
14 changed files with 293 additions and 229 deletions

View File

@ -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(),

View File

@ -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<EntityLocation> {
self.get_id_location(entity).flatten()
pub fn get_constructed(
&self,
entity: Entity,
) -> Result<EntityLocation, ConstructedEntityDoesNotExistError> {
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<EntityIdLocation> {
pub fn get(&self, entity: Entity) -> Result<EntityIdLocation, EntityDoesNotExistError> {
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<Entity> {
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<Option<&'static Location<'static>>>,
}
/// 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<Option<&'static Location<'static>>>,
}
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`].

View File

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

View File

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

View File

@ -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<Item: EntityEquivalent>>
) -> Option<D::Item<'w>> {
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<Item = Entity>>
// `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)

View File

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

View File

@ -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<EntityCommands, EntityDoesNotExistError> {
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<PlayerEntity>) -> 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<EntityCommands, ConstructedEntityDoesNotExistError> {
let _location = self.entities.get_constructed(entity)?;
Ok(EntityCommands {
entity,
commands: self.reborrow(),
})
}
/// Spawns multiple entities with the same combination of components,

View File

@ -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::<Vec<_>>();
let bundle_id = bundles

View File

@ -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

View File

@ -213,10 +213,7 @@ unsafe impl WorldEntityFetch for Entity {
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Mut<'_>, 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`.

View File

@ -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<TestEvent>| {}); // 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]

View File

@ -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<F: WorldEntityFetch>(&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<impl Iterator<Item = &ComponentInfo>, EntityDoesNotExistError> {
let entity_location = self
.entities()
.get(entity)
.ok_or(EntityDoesNotExistError::new(entity, self.entities()))?;
) -> Result<impl Iterator<Item = &ComponentInfo>, 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::<B>());
}
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::<B>());
}
}
// 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::<B>(), 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::<B>(), 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`

View File

@ -373,10 +373,7 @@ impl<'w> UnsafeWorldCell<'w> {
self,
entity: Entity,
) -> Result<UnsafeEntityCell<'w>, 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<UnsafeEntityCell<'w>, 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,
))

View File

@ -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)]