much, much better error handling
This commit is contained in:
parent
4844dda4cc
commit
7e39f9dda1
@ -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(),
|
||||
|
||||
@ -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`].
|
||||
|
||||
@ -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}")]
|
||||
|
||||
@ -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() }
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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}>`");
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`.
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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,
|
||||
))
|
||||
|
||||
@ -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)]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user