Add EntityDoesNotExistError, replace cases of Entity as an error, do some easy Resultification (#17855)

## Objective
There's no general error for when an entity doesn't exist, and some
methods are going to need one when they get Resultified. The closest
thing is `EntityFetchError`, but that error has a slightly more specific
purpose.

## Solution
- Added `EntityDoesNotExistError`.
  - Contains `Entity` and `EntityDoesNotExistDetails`.
- Changed `EntityFetchError` and `QueryEntityError`:
- Changed `NoSuchEntity` variant to wrap `EntityDoesNotExistError` and
renamed the variant to `EntityDoesNotExist`.
- Renamed `EntityFetchError` to `EntityMutableFetchError` to make its
purpose clearer.
- Renamed `TryDespawnError` to `EntityDespawnError` to make it more
general.
- Changed `World::inspect_entity` to return `Result<[ok],
EntityDoesNotExistError>` instead of panicking.
- Changed `World::get_entity` and `WorldEntityFetch::fetch_ref` to
return `Result<[ok], EntityDoesNotExistError>` instead of `Result<[ok],
Entity>`.
- Changed `UnsafeWorldCell::get_entity` to return
`Result<UnsafeEntityCell, EntityDoesNotExistError>` instead of
`Option<UnsafeEntityCell>`.

## Migration Guide
- `World::inspect_entity` now returns `Result<impl Iterator<Item =
&ComponentInfo>, EntityDoesNotExistError>` instead of `impl
Iterator<Item = &ComponentInfo>`.
- `World::get_entity` now returns `EntityDoesNotExistError` as an error
instead of `Entity`. You can still access the entity's ID through the
error's `entity` field.
- `UnsafeWorldCell::get_entity` now returns `Result<UnsafeEntityCell,
EntityDoesNotExistError>` instead of `Option<UnsafeEntityCell>`.
This commit is contained in:
JaySpruce 2025-02-16 15:59:46 -06:00 committed by GitHub
parent e186c7ccf4
commit ee44560523
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 212 additions and 207 deletions

View File

@ -1013,13 +1013,32 @@ impl Entities {
.map(Option::flatten)
}
/// Constructs a message explaining why an entity does not exists, if known.
/// Constructs a message explaining why an entity does not exist, if known.
pub(crate) fn entity_does_not_exist_error_details(
&self,
_entity: Entity,
entity: Entity,
) -> EntityDoesNotExistDetails {
EntityDoesNotExistDetails {
location: self.entity_get_spawned_or_despawned_by(_entity),
location: self.entity_get_spawned_or_despawned_by(entity),
}
}
}
/// 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}")]
pub struct EntityDoesNotExistError {
/// The entity's ID.
pub entity: Entity,
/// Details on why the entity does not exist, if available.
pub details: EntityDoesNotExistDetails,
}
impl EntityDoesNotExistError {
pub(crate) fn new(entity: Entity, entities: &Entities) -> Self {
Self {
entity,
details: entities.entity_does_not_exist_error_details(entity),
}
}
}

View File

@ -1,7 +1,7 @@
use thiserror::Error;
use crate::{
entity::{Entity, EntityDoesNotExistDetails},
entity::{Entity, EntityDoesNotExistError},
world::unsafe_world_cell::UnsafeWorldCell,
};
@ -14,13 +14,19 @@ pub enum QueryEntityError<'w> {
/// Either it does not have a requested component, or it has a component which the query filters out.
QueryDoesNotMatch(Entity, UnsafeWorldCell<'w>),
/// The given [`Entity`] does not exist.
NoSuchEntity(Entity, EntityDoesNotExistDetails),
EntityDoesNotExist(EntityDoesNotExistError),
/// The [`Entity`] was requested mutably more than once.
///
/// See [`Query::get_many_mut`](crate::system::Query::get_many_mut) for an example.
AliasedMutability(Entity),
}
impl<'w> From<EntityDoesNotExistError> for QueryEntityError<'w> {
fn from(error: EntityDoesNotExistError) -> Self {
QueryEntityError::EntityDoesNotExist(error)
}
}
impl<'w> core::error::Error for QueryEntityError<'w> {}
impl<'w> core::fmt::Display for QueryEntityError<'w> {
@ -33,8 +39,8 @@ impl<'w> core::fmt::Display for QueryEntityError<'w> {
)?;
format_archetype(f, world, entity)
}
Self::NoSuchEntity(entity, details) => {
write!(f, "The entity with ID {entity} {details}")
Self::EntityDoesNotExist(error) => {
write!(f, "{error}")
}
Self::AliasedMutability(entity) => {
write!(
@ -54,8 +60,8 @@ impl<'w> core::fmt::Debug for QueryEntityError<'w> {
format_archetype(f, world, entity)?;
write!(f, ")")
}
Self::NoSuchEntity(entity, details) => {
write!(f, "NoSuchEntity({entity} {details})")
Self::EntityDoesNotExist(error) => {
write!(f, "EntityDoesNotExist({error})")
}
Self::AliasedMutability(entity) => write!(f, "AliasedMutability({entity})"),
}
@ -88,7 +94,7 @@ impl<'w> PartialEq for QueryEntityError<'w> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::QueryDoesNotMatch(e1, _), Self::QueryDoesNotMatch(e2, _)) if e1 == e2 => true,
(Self::NoSuchEntity(e1, _), Self::NoSuchEntity(e2, _)) if e1 == e2 => true,
(Self::EntityDoesNotExist(e1), Self::EntityDoesNotExist(e2)) if e1 == e2 => true,
(Self::AliasedMutability(e1), Self::AliasedMutability(e2)) if e1 == e2 => true,
_ => false,
}

View File

@ -949,7 +949,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// let wrong_entity = Entity::from_raw(365);
///
/// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::NoSuchEntity(entity, _) => entity, _ => panic!()}, wrong_entity);
/// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity);
/// ```
#[inline]
pub fn get_many<'w, const N: usize>(
@ -1006,7 +1006,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// let wrong_entity = Entity::from_raw(57);
/// let invalid_entity = world.spawn_empty().id();
///
/// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::NoSuchEntity(entity, _) => entity, _ => panic!()}, wrong_entity);
/// 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(&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]));
/// ```
@ -1335,7 +1335,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// # let wrong_entity = Entity::from_raw(57);
/// # let invalid_entity = world.spawn_empty().id();
///
/// # assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::NoSuchEntity(entity, _) => entity, _ => panic!()}, wrong_entity);
/// # 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(&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]));
/// ```

View File

@ -186,7 +186,7 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
let relationship_target = world.get_entity(entity).unwrap().get::<Self>().unwrap();
let mut commands = world.get_raw_command_queue();
for source_entity in relationship_target.iter() {
if world.get_entity(source_entity).is_some() {
if world.get_entity(source_entity).is_ok() {
commands.push(
entity_command::remove::<Self::Relationship>()
.with_entity(source_entity)
@ -217,7 +217,7 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
let relationship_target = world.get_entity(entity).unwrap().get::<Self>().unwrap();
let mut commands = world.get_raw_command_queue();
for source_entity in relationship_target.iter() {
if world.get_entity(source_entity).is_some() {
if world.get_entity(source_entity).is_ok() {
commands.push(
entity_command::despawn()
.with_entity(source_entity)

View File

@ -15,7 +15,7 @@ use crate::{
event::Event,
result::Result,
system::{command::HandleError, Command, IntoObserverSystem},
world::{error::EntityFetchError, EntityWorldMut, FromWorld, World},
world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld, World},
};
use bevy_ptr::OwningPtr;
@ -96,13 +96,13 @@ pub trait CommandWithEntity<Out> {
fn with_entity(self, entity: Entity) -> impl Command<Out> + HandleError<Out>;
}
impl<C: EntityCommand> CommandWithEntity<Result<(), EntityFetchError>> for C {
impl<C: EntityCommand> CommandWithEntity<Result<(), EntityMutableFetchError>> for C {
fn with_entity(
self,
entity: Entity,
) -> impl Command<Result<(), EntityFetchError>> + HandleError<Result<(), EntityFetchError>>
{
move |world: &mut World| -> Result<(), EntityFetchError> {
) -> impl Command<Result<(), EntityMutableFetchError>>
+ HandleError<Result<(), EntityMutableFetchError>> {
move |world: &mut World| -> Result<(), EntityMutableFetchError> {
let entity = world.get_entity_mut(entity)?;
self.apply(entity);
Ok(())
@ -134,7 +134,7 @@ impl<
pub enum EntityCommandError<E> {
/// The entity this [`EntityCommand`] tried to run on could not be fetched.
#[error(transparent)]
EntityFetchError(#[from] EntityFetchError),
EntityFetchError(#[from] EntityMutableFetchError),
/// An error that occurred while running the [`EntityCommand`].
#[error("{0}")]
CommandFailed(E),
@ -300,6 +300,7 @@ pub fn log_components() -> impl EntityCommand {
let debug_infos: Vec<_> = entity
.world()
.inspect_entity(entity.id())
.expect("Entity existence is verified before an EntityCommand is executed")
.map(ComponentInfo::name)
.collect();
info!("Entity {}: {debug_infos:?}", entity.id());

View File

@ -1,7 +1,7 @@
use crate::{
batching::BatchingStrategy,
component::Tick,
entity::{Entity, EntityBorrow, EntitySet},
entity::{Entity, EntityBorrow, EntityDoesNotExistError, EntitySet},
query::{
DebugCheckedUnwrap, NopWorldQuery, QueryCombinationIter, QueryData, QueryEntityError,
QueryFilter, QueryIter, QueryManyIter, QueryManyUniqueIter, QueryParIter, QueryParManyIter,
@ -1310,7 +1310,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
///
/// assert_eq!(
/// match query.get_many([wrong_entity]).unwrap_err() {
/// QueryEntityError::NoSuchEntity(entity, _) => entity,
/// QueryEntityError::EntityDoesNotExist(error) => error.entity,
/// _ => panic!(),
/// },
/// wrong_entity
@ -1430,16 +1430,11 @@ 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(QueryEntityError::NoSuchEntity(
entity,
self.world
.entities()
.entity_does_not_exist_error_details(entity),
))?;
let location = self
.world
.entities()
.get(entity)
.ok_or(EntityDoesNotExistError::new(entity, self.world.entities()))?;
if !self
.state
.matched_archetypes
@ -1524,7 +1519,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// .get_many_mut([wrong_entity])
/// .unwrap_err()
/// {
/// QueryEntityError::NoSuchEntity(entity, _) => entity,
/// QueryEntityError::EntityDoesNotExist(error) => error.entity,
/// _ => panic!(),
/// },
/// wrong_entity

View File

@ -12,7 +12,7 @@ use crate::{
resource::Resource,
system::{Commands, Query},
traversal::Traversal,
world::{error::EntityFetchError, WorldEntityFetch},
world::{error::EntityMutableFetchError, WorldEntityFetch},
};
use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World, ON_INSERT, ON_REPLACE};
@ -94,24 +94,13 @@ impl<'w> DeferredWorld<'w> {
&mut self,
entity: Entity,
f: impl FnOnce(&mut T) -> R,
) -> Result<Option<R>, EntityFetchError> {
) -> Result<Option<R>, EntityMutableFetchError> {
// If the component is not registered, then it doesn't exist on this entity, so no action required.
let Some(component_id) = self.component_id::<T>() else {
return Ok(None);
};
let entity_cell = match self.get_entity_mut(entity) {
Ok(cell) => cell,
Err(EntityFetchError::AliasedMutability(..)) => {
return Err(EntityFetchError::AliasedMutability(entity))
}
Err(EntityFetchError::NoSuchEntity(..)) => {
return Err(EntityFetchError::NoSuchEntity(
entity,
self.entities().entity_does_not_exist_error_details(entity),
))
}
};
let entity_cell = self.get_entity_mut(entity)?;
if !entity_cell.contains::<T>() {
return Ok(None);
@ -201,9 +190,9 @@ impl<'w> DeferredWorld<'w> {
///
/// # Errors
///
/// - Returns [`EntityFetchError::NoSuchEntity`] if any of the given `entities` do not exist in the world.
/// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if any of the given `entities` do not exist in the world.
/// - Only the first entity found to be missing will be returned.
/// - Returns [`EntityFetchError::AliasedMutability`] if the same entity is requested multiple times.
/// - Returns [`EntityMutableFetchError::AliasedMutability`] if the same entity is requested multiple times.
///
/// # Examples
///
@ -217,7 +206,7 @@ impl<'w> DeferredWorld<'w> {
pub fn get_entity_mut<F: WorldEntityFetch>(
&mut self,
entities: F,
) -> Result<F::DeferredMut<'_>, EntityFetchError> {
) -> Result<F::DeferredMut<'_>, EntityMutableFetchError> {
let cell = self.as_unsafe_world_cell();
// SAFETY: `&mut self` gives mutable access to the entire world,
// and prevents any other access to the world.

View File

@ -2,9 +2,9 @@ use alloc::vec::Vec;
use core::mem::MaybeUninit;
use crate::{
entity::{hash_map::EntityHashMap, hash_set::EntityHashSet, Entity},
entity::{hash_map::EntityHashMap, hash_set::EntityHashSet, Entity, EntityDoesNotExistError},
world::{
error::EntityFetchError, unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef,
error::EntityMutableFetchError, unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef,
EntityWorldMut,
},
};
@ -56,8 +56,11 @@ pub unsafe trait WorldEntityFetch {
///
/// # Errors
///
/// - Returns [`Entity`] if the entity does not exist.
unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result<Self::Ref<'_>, Entity>;
/// - Returns [`EntityDoesNotExistError`] if the entity does not exist.
unsafe fn fetch_ref(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Ref<'_>, EntityDoesNotExistError>;
/// Returns mutable reference(s) to the entities with the given [`Entity`]
/// IDs, as determined by `self`.
@ -70,11 +73,13 @@ pub unsafe trait WorldEntityFetch {
///
/// # Errors
///
/// - Returns [`EntityFetchError::NoSuchEntity`] if the entity does not exist.
/// - Returns [`EntityFetchError::AliasedMutability`] if the entity was
/// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if the entity does not exist.
/// - Returns [`EntityMutableFetchError::AliasedMutability`] if the entity was
/// requested mutably more than once.
unsafe fn fetch_mut(self, cell: UnsafeWorldCell<'_>)
-> Result<Self::Mut<'_>, EntityFetchError>;
unsafe fn fetch_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Mut<'_>, EntityMutableFetchError>;
/// Returns mutable reference(s) to the entities with the given [`Entity`]
/// IDs, as determined by `self`, but without structural mutability.
@ -91,13 +96,13 @@ pub unsafe trait WorldEntityFetch {
///
/// # Errors
///
/// - Returns [`EntityFetchError::NoSuchEntity`] if the entity does not exist.
/// - Returns [`EntityFetchError::AliasedMutability`] if the entity was
/// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if the entity does not exist.
/// - Returns [`EntityMutableFetchError::AliasedMutability`] if the entity was
/// requested mutably more than once.
unsafe fn fetch_deferred_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::DeferredMut<'_>, EntityFetchError>;
) -> Result<Self::DeferredMut<'_>, EntityMutableFetchError>;
}
// SAFETY:
@ -109,8 +114,11 @@ unsafe impl WorldEntityFetch for Entity {
type Mut<'w> = EntityWorldMut<'w>;
type DeferredMut<'w> = EntityMut<'w>;
unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result<Self::Ref<'_>, Entity> {
let ecell = cell.get_entity(self).ok_or(self)?;
unsafe fn fetch_ref(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Ref<'_>, EntityDoesNotExistError> {
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) })
}
@ -118,14 +126,11 @@ unsafe impl WorldEntityFetch for Entity {
unsafe fn fetch_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Mut<'_>, EntityFetchError> {
) -> Result<Self::Mut<'_>, EntityMutableFetchError> {
let location = cell
.entities()
.get(self)
.ok_or(EntityFetchError::NoSuchEntity(
self,
cell.entities().entity_does_not_exist_error_details(self),
))?;
.ok_or(EntityDoesNotExistError::new(self, cell.entities()))?;
// 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`.
@ -135,11 +140,8 @@ unsafe impl WorldEntityFetch for Entity {
unsafe fn fetch_deferred_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::DeferredMut<'_>, EntityFetchError> {
let ecell = cell.get_entity(self).ok_or(EntityFetchError::NoSuchEntity(
self,
cell.entities().entity_does_not_exist_error_details(self),
))?;
) -> Result<Self::DeferredMut<'_>, EntityMutableFetchError> {
let ecell = cell.get_entity(self)?;
// SAFETY: caller ensures that the world cell has mutable access to the entity.
Ok(unsafe { EntityMut::new(ecell) })
}
@ -154,21 +156,24 @@ unsafe impl<const N: usize> WorldEntityFetch for [Entity; N] {
type Mut<'w> = [EntityMut<'w>; N];
type DeferredMut<'w> = [EntityMut<'w>; N];
unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result<Self::Ref<'_>, Entity> {
unsafe fn fetch_ref(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Ref<'_>, EntityDoesNotExistError> {
<&Self>::fetch_ref(&self, cell)
}
unsafe fn fetch_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Mut<'_>, EntityFetchError> {
) -> Result<Self::Mut<'_>, EntityMutableFetchError> {
<&Self>::fetch_mut(&self, cell)
}
unsafe fn fetch_deferred_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::DeferredMut<'_>, EntityFetchError> {
) -> Result<Self::DeferredMut<'_>, EntityMutableFetchError> {
<&Self>::fetch_deferred_mut(&self, cell)
}
}
@ -182,10 +187,13 @@ unsafe impl<const N: usize> WorldEntityFetch for &'_ [Entity; N] {
type Mut<'w> = [EntityMut<'w>; N];
type DeferredMut<'w> = [EntityMut<'w>; N];
unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result<Self::Ref<'_>, Entity> {
unsafe fn fetch_ref(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Ref<'_>, EntityDoesNotExistError> {
let mut refs = [MaybeUninit::uninit(); N];
for (r, &id) in core::iter::zip(&mut refs, self) {
let ecell = cell.get_entity(id).ok_or(id)?;
let ecell = cell.get_entity(id)?;
// SAFETY: caller ensures that the world cell has read-only access to the entity.
*r = MaybeUninit::new(unsafe { EntityRef::new(ecell) });
}
@ -199,22 +207,19 @@ unsafe impl<const N: usize> WorldEntityFetch for &'_ [Entity; N] {
unsafe fn fetch_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Mut<'_>, EntityFetchError> {
) -> Result<Self::Mut<'_>, EntityMutableFetchError> {
// Check for duplicate entities.
for i in 0..self.len() {
for j in 0..i {
if self[i] == self[j] {
return Err(EntityFetchError::AliasedMutability(self[i]));
return Err(EntityMutableFetchError::AliasedMutability(self[i]));
}
}
}
let mut refs = [const { MaybeUninit::uninit() }; N];
for (r, &id) in core::iter::zip(&mut refs, self) {
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
id,
cell.entities().entity_does_not_exist_error_details(id),
))?;
let ecell = cell.get_entity(id)?;
// SAFETY: caller ensures that the world cell has mutable access to the entity.
*r = MaybeUninit::new(unsafe { EntityMut::new(ecell) });
}
@ -228,7 +233,7 @@ unsafe impl<const N: usize> WorldEntityFetch for &'_ [Entity; N] {
unsafe fn fetch_deferred_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::DeferredMut<'_>, EntityFetchError> {
) -> Result<Self::DeferredMut<'_>, EntityMutableFetchError> {
// SAFETY: caller ensures that the world cell has mutable access to the entity,
// and `fetch_mut` does not return structurally-mutable references.
unsafe { self.fetch_mut(cell) }
@ -244,10 +249,13 @@ unsafe impl WorldEntityFetch for &'_ [Entity] {
type Mut<'w> = Vec<EntityMut<'w>>;
type DeferredMut<'w> = Vec<EntityMut<'w>>;
unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result<Self::Ref<'_>, Entity> {
unsafe fn fetch_ref(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Ref<'_>, EntityDoesNotExistError> {
let mut refs = Vec::with_capacity(self.len());
for &id in self {
let ecell = cell.get_entity(id).ok_or(id)?;
let ecell = cell.get_entity(id)?;
// SAFETY: caller ensures that the world cell has read-only access to the entity.
refs.push(unsafe { EntityRef::new(ecell) });
}
@ -258,22 +266,19 @@ unsafe impl WorldEntityFetch for &'_ [Entity] {
unsafe fn fetch_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Mut<'_>, EntityFetchError> {
) -> Result<Self::Mut<'_>, EntityMutableFetchError> {
// Check for duplicate entities.
for i in 0..self.len() {
for j in 0..i {
if self[i] == self[j] {
return Err(EntityFetchError::AliasedMutability(self[i]));
return Err(EntityMutableFetchError::AliasedMutability(self[i]));
}
}
}
let mut refs = Vec::with_capacity(self.len());
for &id in self {
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
id,
cell.entities().entity_does_not_exist_error_details(id),
))?;
let ecell = cell.get_entity(id)?;
// SAFETY: caller ensures that the world cell has mutable access to the entity.
refs.push(unsafe { EntityMut::new(ecell) });
}
@ -284,7 +289,7 @@ unsafe impl WorldEntityFetch for &'_ [Entity] {
unsafe fn fetch_deferred_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::DeferredMut<'_>, EntityFetchError> {
) -> Result<Self::DeferredMut<'_>, EntityMutableFetchError> {
// SAFETY: caller ensures that the world cell has mutable access to the entity,
// and `fetch_mut` does not return structurally-mutable references.
unsafe { self.fetch_mut(cell) }
@ -300,10 +305,13 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet {
type Mut<'w> = EntityHashMap<EntityMut<'w>>;
type DeferredMut<'w> = EntityHashMap<EntityMut<'w>>;
unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result<Self::Ref<'_>, Entity> {
unsafe fn fetch_ref(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Ref<'_>, EntityDoesNotExistError> {
let mut refs = EntityHashMap::with_capacity(self.len());
for &id in self {
let ecell = cell.get_entity(id).ok_or(id)?;
let ecell = cell.get_entity(id)?;
// SAFETY: caller ensures that the world cell has read-only access to the entity.
refs.insert(id, unsafe { EntityRef::new(ecell) });
}
@ -313,13 +321,10 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet {
unsafe fn fetch_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::Mut<'_>, EntityFetchError> {
) -> Result<Self::Mut<'_>, EntityMutableFetchError> {
let mut refs = EntityHashMap::with_capacity(self.len());
for &id in self {
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
id,
cell.entities().entity_does_not_exist_error_details(id),
))?;
let ecell = cell.get_entity(id)?;
// SAFETY: caller ensures that the world cell has mutable access to the entity.
refs.insert(id, unsafe { EntityMut::new(ecell) });
}
@ -329,7 +334,7 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet {
unsafe fn fetch_deferred_mut(
self,
cell: UnsafeWorldCell<'_>,
) -> Result<Self::DeferredMut<'_>, EntityFetchError> {
) -> Result<Self::DeferredMut<'_>, EntityMutableFetchError> {
// SAFETY: caller ensures that the world cell has mutable access to the entity,
// and `fetch_mut` does not return structurally-mutable references.
unsafe { self.fetch_mut(cell) }

View File

@ -1,39 +1,26 @@
//! Contains error types returned by bevy's schedule.
use alloc::vec::Vec;
use thiserror::Error;
use crate::{
component::ComponentId,
entity::{Entity, EntityDoesNotExistDetails},
entity::{Entity, EntityDoesNotExistError},
schedule::InternedScheduleLabel,
};
/// The error type returned by [`World::try_run_schedule`] if the provided schedule does not exist.
///
/// [`World::try_run_schedule`]: crate::world::World::try_run_schedule
#[derive(Error, Debug)]
#[derive(thiserror::Error, Debug)]
#[error("The schedule with the label {0:?} was not found.")]
pub struct TryRunScheduleError(pub InternedScheduleLabel);
/// The error type returned by [`World::try_despawn`] if the provided entity does not exist.
///
/// [`World::try_despawn`]: crate::world::World::try_despawn
#[derive(Error, Debug, Clone, Copy)]
#[error("Could not despawn the entity with ID {entity} because it {details}")]
pub struct TryDespawnError {
/// The entity's ID.
pub entity: Entity,
/// Details on why the entity does not exist, if available.
pub details: EntityDoesNotExistDetails,
}
/// The error type returned by [`World::try_insert_batch`] and [`World::try_insert_batch_if_new`]
/// if any of the provided entities do not exist.
///
/// [`World::try_insert_batch`]: crate::world::World::try_insert_batch
/// [`World::try_insert_batch_if_new`]: crate::world::World::try_insert_batch_if_new
#[derive(Error, Debug, Clone)]
#[derive(thiserror::Error, Debug, Clone)]
#[error("Could not insert bundles of type {bundle_type} into the entities with the following IDs because they do not exist: {entities:?}")]
pub struct TryInsertBatchError {
/// The bundles' type name.
@ -42,8 +29,13 @@ pub struct TryInsertBatchError {
pub entities: Vec<Entity>,
}
/// 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);
/// An error that occurs when dynamically retrieving components from an entity.
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityComponentError {
/// The component with the given [`ComponentId`] does not exist on the entity.
#[error("The component with ID {0:?} does not exist on the entity.")]
@ -54,24 +46,12 @@ pub enum EntityComponentError {
}
/// An error that occurs when fetching entities mutably from a world.
#[derive(Error, Debug, Clone, Copy)]
pub enum EntityFetchError {
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityMutableFetchError {
/// The entity with the given ID does not exist.
#[error("The entity with ID {0} {1}")]
NoSuchEntity(Entity, EntityDoesNotExistDetails),
#[error(transparent)]
EntityDoesNotExist(#[from] EntityDoesNotExistError),
/// 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),
}
impl PartialEq for EntityFetchError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::NoSuchEntity(e1, _), Self::NoSuchEntity(e2, _)) if e1 == e2 => true,
(Self::AliasedMutability(e1), Self::AliasedMutability(e2)) if e1 == e2 => true,
_ => false,
}
}
}
impl Eq for EntityFetchError {}

View File

@ -41,7 +41,9 @@ use crate::{
Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentInfo, ComponentTicks,
Components, Mutable, RequiredComponents, RequiredComponentsError, Tick,
},
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation},
entity::{
AllocAtWithoutReplacement, Entities, Entity, EntityDoesNotExistError, EntityLocation,
},
entity_disabling::DefaultQueryFilters,
event::{Event, EventId, Events, SendBatchIds},
observer::Observers,
@ -53,7 +55,9 @@ use crate::{
system::Commands,
world::{
command_queue::RawCommandQueue,
error::{EntityFetchError, TryDespawnError, TryInsertBatchError, TryRunScheduleError},
error::{
EntityDespawnError, EntityMutableFetchError, TryInsertBatchError, TryRunScheduleError,
},
},
};
use alloc::{boxed::Box, vec::Vec};
@ -694,7 +698,7 @@ impl World {
match self.get_entity(entities) {
Ok(fetched) => fetched,
Err(entity) => panic_no_entity(self, entity),
Err(error) => panic_no_entity(self, error.entity),
}
}
@ -821,7 +825,7 @@ impl World {
#[inline(never)]
#[cold]
#[track_caller]
fn panic_on_err(e: EntityFetchError) -> ! {
fn panic_on_err(e: EntityMutableFetchError) -> ! {
panic!("{e}");
}
@ -833,25 +837,23 @@ impl World {
/// Returns the components of an [`Entity`] through [`ComponentInfo`].
#[inline]
pub fn inspect_entity(&self, entity: Entity) -> impl Iterator<Item = &ComponentInfo> {
pub fn inspect_entity(
&self,
entity: Entity,
) -> Result<impl Iterator<Item = &ComponentInfo>, EntityDoesNotExistError> {
let entity_location = self
.entities()
.get(entity)
.unwrap_or_else(|| panic!("Entity {entity} does not exist"));
.ok_or(EntityDoesNotExistError::new(entity, self.entities()))?;
let archetype = self
.archetypes()
.get(entity_location.archetype_id)
.unwrap_or_else(|| {
panic!(
"Archetype {:?} does not exist",
entity_location.archetype_id
)
});
.expect("ArchetypeId was retrieved from an EntityLocation and should correspond to an Archetype");
archetype
Ok(archetype
.components()
.filter_map(|id| self.components().get_info(id))
.filter_map(|id| self.components().get_info(id)))
}
/// Returns [`EntityRef`]s that expose read-only operations for the given
@ -869,7 +871,7 @@ impl World {
/// # Errors
///
/// If any of the given `entities` do not exist in the world, the first
/// [`Entity`] found to be missing will be returned in the [`Err`].
/// [`Entity`] found to be missing will return an [`EntityDoesNotExistError`].
///
/// # Examples
///
@ -877,7 +879,10 @@ impl World {
///
/// [`EntityHashSet`]: crate::entity::hash_set::EntityHashSet
#[inline]
pub fn get_entity<F: WorldEntityFetch>(&self, entities: F) -> Result<F::Ref<'_>, Entity> {
pub fn get_entity<F: WorldEntityFetch>(
&self,
entities: F,
) -> Result<F::Ref<'_>, EntityDoesNotExistError> {
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) }
@ -905,9 +910,9 @@ impl World {
///
/// # Errors
///
/// - Returns [`EntityFetchError::NoSuchEntity`] if any of the given `entities` do not exist in the world.
/// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if any of the given `entities` do not exist in the world.
/// - Only the first entity found to be missing will be returned.
/// - Returns [`EntityFetchError::AliasedMutability`] if the same entity is requested multiple times.
/// - Returns [`EntityMutableFetchError::AliasedMutability`] if the same entity is requested multiple times.
///
/// # Examples
///
@ -918,7 +923,7 @@ impl World {
pub fn get_entity_mut<F: WorldEntityFetch>(
&mut self,
entities: F,
) -> Result<F::Mut<'_>, EntityFetchError> {
) -> Result<F::Mut<'_>, EntityMutableFetchError> {
let cell = self.as_unsafe_world_cell();
// SAFETY: `&mut self` gives mutable access to the entire world,
// and prevents any other access to the world.
@ -1244,21 +1249,10 @@ impl World {
&mut self,
entity: Entity,
f: impl FnOnce(&mut T) -> R,
) -> Result<Option<R>, EntityFetchError> {
) -> Result<Option<R>, EntityMutableFetchError> {
let mut world = DeferredWorld::from(&mut *self);
let result = match world.modify_component(entity, f) {
Ok(result) => result,
Err(EntityFetchError::AliasedMutability(..)) => {
return Err(EntityFetchError::AliasedMutability(entity))
}
Err(EntityFetchError::NoSuchEntity(..)) => {
return Err(EntityFetchError::NoSuchEntity(
entity,
self.entities().entity_does_not_exist_error_details(entity),
))
}
};
let result = world.modify_component(entity, f)?;
self.flush();
Ok(result)
@ -1304,7 +1298,7 @@ impl World {
/// Despawns the given `entity`, if it exists. This will also remove all of the entity's
/// [`Components`](Component).
///
/// Returns a [`TryDespawnError`] if the entity does not exist.
/// Returns an [`EntityDespawnError`] if the entity does not exist.
///
/// # Note
///
@ -1312,7 +1306,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<(), TryDespawnError> {
pub fn try_despawn(&mut self, entity: Entity) -> Result<(), EntityDespawnError> {
self.despawn_with_caller(entity, MaybeLocation::caller())
}
@ -1321,17 +1315,11 @@ impl World {
&mut self,
entity: Entity,
caller: MaybeLocation,
) -> Result<(), TryDespawnError> {
) -> Result<(), EntityDespawnError> {
self.flush();
if let Ok(entity) = self.get_entity_mut(entity) {
entity.despawn_with_caller(caller);
Ok(())
} else {
Err(TryDespawnError {
entity,
details: self.entities().entity_does_not_exist_error_details(entity),
})
}
let entity = self.get_entity_mut(entity)?;
entity.despawn_with_caller(caller);
Ok(())
}
/// Clears the internal component tracker state.
@ -3607,7 +3595,7 @@ mod tests {
entity_disabling::{DefaultQueryFilters, Disabled},
ptr::OwningPtr,
resource::Resource,
world::error::EntityFetchError,
world::error::EntityMutableFetchError,
};
use alloc::{
borrow::ToOwned,
@ -3992,39 +3980,39 @@ mod tests {
let bar_id = TypeId::of::<Bar>();
let baz_id = TypeId::of::<Baz>();
assert_eq!(
to_type_ids(world.inspect_entity(ent0).collect()),
to_type_ids(world.inspect_entity(ent0).unwrap().collect()),
[Some(foo_id), Some(bar_id), Some(baz_id)]
.into_iter()
.collect::<HashSet<_>>()
);
assert_eq!(
to_type_ids(world.inspect_entity(ent1).collect()),
to_type_ids(world.inspect_entity(ent1).unwrap().collect()),
[Some(foo_id), Some(bar_id)]
.into_iter()
.collect::<HashSet<_>>()
);
assert_eq!(
to_type_ids(world.inspect_entity(ent2).collect()),
to_type_ids(world.inspect_entity(ent2).unwrap().collect()),
[Some(bar_id), Some(baz_id)]
.into_iter()
.collect::<HashSet<_>>()
);
assert_eq!(
to_type_ids(world.inspect_entity(ent3).collect()),
to_type_ids(world.inspect_entity(ent3).unwrap().collect()),
[Some(foo_id), Some(baz_id)]
.into_iter()
.collect::<HashSet<_>>()
);
assert_eq!(
to_type_ids(world.inspect_entity(ent4).collect()),
to_type_ids(world.inspect_entity(ent4).unwrap().collect()),
[Some(foo_id)].into_iter().collect::<HashSet<_>>()
);
assert_eq!(
to_type_ids(world.inspect_entity(ent5).collect()),
to_type_ids(world.inspect_entity(ent5).unwrap().collect()),
[Some(bar_id)].into_iter().collect::<HashSet<_>>()
);
assert_eq!(
to_type_ids(world.inspect_entity(ent6).collect()),
to_type_ids(world.inspect_entity(ent6).unwrap().collect()),
[Some(baz_id)].into_iter().collect::<HashSet<_>>()
);
}
@ -4171,20 +4159,34 @@ mod tests {
world.entity_mut(e1).despawn();
assert_eq!(Err(e1), world.get_entity(e1).map(|_| {}));
assert_eq!(Err(e1), world.get_entity([e1, e2]).map(|_| {}));
assert_eq!(
Err(e1),
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)
);
assert_eq!(
Err(e1),
world
.get_entity(&[e1, e2] /* this is an array not a slice */)
.map(|_| {})
.map_err(|e| e.entity)
);
assert_eq!(
Err(e1),
world
.get_entity(&vec![e1, e2][..])
.map(|_| {})
.map_err(|e| e.entity)
);
assert_eq!(Err(e1), world.get_entity(&vec![e1, e2][..]).map(|_| {}));
assert_eq!(
Err(e1),
world
.get_entity(&EntityHashSet::from_iter([e1, e2]))
.map(|_| {})
.map_err(|e| e.entity)
);
}
@ -4206,17 +4208,17 @@ mod tests {
.is_ok());
assert_eq!(
Err(EntityFetchError::AliasedMutability(e1)),
Err(EntityMutableFetchError::AliasedMutability(e1)),
world.get_entity_mut([e1, e2, e1]).map(|_| {})
);
assert_eq!(
Err(EntityFetchError::AliasedMutability(e1)),
Err(EntityMutableFetchError::AliasedMutability(e1)),
world
.get_entity_mut(&[e1, e2, e1] /* this is an array not a slice */)
.map(|_| {})
);
assert_eq!(
Err(EntityFetchError::AliasedMutability(e1)),
Err(EntityMutableFetchError::AliasedMutability(e1)),
world.get_entity_mut(&vec![e1, e2, e1][..]).map(|_| {})
);
// Aliased mutability isn't allowed by HashSets
@ -4228,25 +4230,25 @@ mod tests {
assert!(matches!(
world.get_entity_mut(e1).map(|_| {}),
Err(EntityFetchError::NoSuchEntity(e, ..)) if e == e1
Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1
));
assert!(matches!(
world.get_entity_mut([e1, e2]).map(|_| {}),
Err(EntityFetchError::NoSuchEntity(e,..)) if e == e1));
Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1));
assert!(matches!(
world
.get_entity_mut(&[e1, e2] /* this is an array not a slice */)
.map(|_| {}),
Err(EntityFetchError::NoSuchEntity(e, ..)) if e == e1));
Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1));
assert!(matches!(
world.get_entity_mut(&vec![e1, e2][..]).map(|_| {}),
Err(EntityFetchError::NoSuchEntity(e, ..)) if e == e1,
Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1,
));
assert!(matches!(
world
.get_entity_mut(&EntityHashSet::from_iter([e1, e2]))
.map(|_| {}),
Err(EntityFetchError::NoSuchEntity(e, ..)) if e == e1));
Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1));
}
#[test]

View File

@ -6,7 +6,7 @@ use crate::{
bundle::Bundles,
change_detection::{MaybeLocation, MutUntyped, Ticks, TicksMut},
component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells},
entity::{Entities, Entity, EntityBorrow, EntityLocation},
entity::{Entities, Entity, EntityBorrow, EntityDoesNotExistError, EntityLocation},
observer::Observers,
prelude::Component,
query::{DebugCheckedUnwrap, ReadOnlyQueryData},
@ -351,9 +351,15 @@ 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) -> Option<UnsafeEntityCell<'w>> {
let location = self.entities().get(entity)?;
Some(UnsafeEntityCell::new(self, entity, location))
pub fn get_entity(
self,
entity: Entity,
) -> Result<UnsafeEntityCell<'w>, EntityDoesNotExistError> {
let location = self
.entities()
.get(entity)
.ok_or(EntityDoesNotExistError::new(entity, self.entities()))?;
Ok(UnsafeEntityCell::new(self, entity, location))
}
/// Gets a reference to the resource of the given type if it exists

View File

@ -332,7 +332,9 @@ where
let view = match self.view.get_manual(world, view) {
Ok(view) => view,
Err(err) => match err {
QueryEntityError::NoSuchEntity(_, _) => return Err(DrawError::ViewEntityNotFound),
QueryEntityError::EntityDoesNotExist(_) => {
return Err(DrawError::ViewEntityNotFound)
}
QueryEntityError::QueryDoesNotMatch(_, _)
| QueryEntityError::AliasedMutability(_) => {
return Err(DrawError::InvalidViewQuery)

View File

@ -52,11 +52,11 @@ fn map_error(err: QueryEntityError, ancestor: bool) -> ComputeGlobalTransformErr
use ComputeGlobalTransformError::*;
match err {
QueryEntityError::QueryDoesNotMatch(entity, _) => MissingTransform(entity),
QueryEntityError::NoSuchEntity(entity, _) => {
QueryEntityError::EntityDoesNotExist(error) => {
if ancestor {
MalformedHierarchy(entity)
MalformedHierarchy(error.entity)
} else {
NoSuchEntity(entity)
NoSuchEntity(error.entity)
}
}
QueryEntityError::AliasedMutability(_) => unreachable!(),