diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 0c0b92546f..4066dd42df 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -782,6 +782,14 @@ impl Entities { .map(|meta| meta.location) } + /// 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. + pub fn contains(&self, entity: Entity) -> bool { + self.get(entity).is_some() + } + /// Provides information regarding if `entity` may be constructed. #[inline] pub fn validate_construction(&self, entity: Entity) -> Result<(), ConstructionError> { diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 7944d0bedb..57b88bc4f4 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -18,7 +18,7 @@ use crate::{ bundle::{Bundle, InsertMode, NoBundleEffect}, change_detection::{MaybeLocation, Mut}, component::{Component, ComponentId, Mutable}, - entity::{Entities, EntitiesAllocator, Entity, EntityClonerBuilder}, + entity::{Entities, EntitiesAllocator, Entity, EntityClonerBuilder, EntityDoesNotExistError}, error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError}, event::Event, observer::{Observer, TriggerTargets}, @@ -99,7 +99,8 @@ use crate::{ /// [`ApplyDeferred`]: crate::schedule::ApplyDeferred pub struct Commands<'w, 's> { queue: InternalQueue<'s>, - entities: &'w EntitiesAllocator, + entities: &'w Entities, + allocator: &'w EntitiesAllocator, } // SAFETY: All commands [`Command`] implement [`Send`] @@ -109,7 +110,11 @@ unsafe impl Send for Commands<'_, '_> {} unsafe impl Sync for Commands<'_, '_> {} const _: () = { - type __StructFieldsAlias<'w, 's> = (Deferred<'s, CommandQueue>, &'w Entities); + type __StructFieldsAlias<'w, 's> = ( + Deferred<'s, CommandQueue>, + &'w EntitiesAllocator, + &'w Entities, + ); #[doc(hidden)] pub struct FetchState { state: <__StructFieldsAlias<'static, 'static> as bevy_ecs::system::SystemParam>::State, @@ -162,7 +167,7 @@ const _: () = { system_meta: &bevy_ecs::system::SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - <(Deferred, &Entities) as bevy_ecs::system::SystemParam>::validate_param( + <__StructFieldsAlias as bevy_ecs::system::SystemParam>::validate_param( &mut state.state, system_meta, world, @@ -176,10 +181,16 @@ const _: () = { world: UnsafeWorldCell<'w>, change_tick: bevy_ecs::component::Tick, ) -> Self::Item<'w, 's> { - let(f0, f1) = <(Deferred<'s, CommandQueue>, &'w EntitiesAllocator) as bevy_ecs::system::SystemParam>::get_param(&mut state.state, system_meta, world, change_tick); + let params = <__StructFieldsAlias as bevy_ecs::system::SystemParam>::get_param( + &mut state.state, + system_meta, + world, + change_tick, + ); Commands { - queue: InternalQueue::CommandQueue(f0), - entities: f1, + queue: InternalQueue::CommandQueue(params.0), + allocator: params.1, + entities: params.2, } } } @@ -200,13 +211,18 @@ enum InternalQueue<'s> { impl<'w, 's> Commands<'w, 's> { /// Returns a new `Commands` instance from a [`CommandQueue`] and a [`World`]. pub fn new(queue: &'s mut CommandQueue, world: &'w World) -> Self { - Self::new_from_entities(queue, &world.allocator) + Self::new_from_entities(queue, &world.allocator, &world.entities) } /// Returns a new `Commands` instance from a [`CommandQueue`] and an [`Entities`] reference. - pub fn new_from_entities(queue: &'s mut CommandQueue, entities: &'w EntitiesAllocator) -> Self { + pub fn new_from_entities( + queue: &'s mut CommandQueue, + allocator: &'w EntitiesAllocator, + entities: &'w Entities, + ) -> Self { Self { queue: InternalQueue::CommandQueue(Deferred(queue)), + allocator, entities, } } @@ -220,10 +236,12 @@ impl<'w, 's> Commands<'w, 's> { /// * Caller ensures that `queue` must outlive `'w` pub(crate) unsafe fn new_raw_from_entities( queue: RawCommandQueue, - entities: &'w EntitiesAllocator, + allocator: &'w EntitiesAllocator, + entities: &'w Entities, ) -> Self { Self { queue: InternalQueue::RawCommandQueue(queue), + allocator, entities, } } @@ -255,6 +273,7 @@ impl<'w, 's> Commands<'w, 's> { InternalQueue::RawCommandQueue(queue.clone()) } }, + allocator: self.allocator, entities: self.entities, } } @@ -304,7 +323,7 @@ impl<'w, 's> Commands<'w, 's> { /// with the same combination of components. #[track_caller] pub fn spawn_empty(&mut self) -> EntityCommands { - let entity = self.entities.alloc(); + let entity = self.allocator.alloc(); let caller = MaybeLocation::caller(); self.queue(move |world: &mut World| { world @@ -358,7 +377,7 @@ impl<'w, 's> Commands<'w, 's> { /// with the same combination of components. #[track_caller] pub fn spawn(&mut self, bundle: T) -> EntityCommands { - let entity = self.entities.alloc(); + let entity = self.allocator.alloc(); let caller = MaybeLocation::caller(); self.queue(move |world: &mut World| { world @@ -404,6 +423,62 @@ 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. + /// + /// This method does not guarantee that commands queued by the returned `EntityCommands` + /// will be successful, since the entity could be despawned before they are executed. + /// + /// # Errors + /// + /// Returns [`EntityDoesNotExistError`] if the requested entity does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// #[derive(Resource)] + /// struct PlayerEntity { + /// entity: Entity + /// } + /// + /// #[derive(Component)] + /// struct Label(&'static str); + /// + /// fn example_system(mut commands: Commands, player: Res) -> Result { + /// // Get the entity if it still exists and store the `EntityCommands`. + /// // If it doesn't exist, the `?` operator will propagate the returned error + /// // to the system, and the system will pass it to an error handler. + /// let mut entity_commands = commands.get_entity(player.entity)?; + /// + /// // Add a component to the entity. + /// entity_commands.insert(Label("hello world")); + /// + /// // Return from the system successfully. + /// Ok(()) + /// } + /// # bevy_ecs::system::assert_is_system(example_system); + /// ``` + /// + /// # See also + /// + /// - [`entity`](Self::entity) for the infallible version. + #[inline] + #[track_caller] + pub fn get_entity( + &mut self, + entity: Entity, + ) -> Result { + if self.entities.contains(entity) { + Ok(EntityCommands { + entity, + commands: self.reborrow(), + }) + } else { + Err(EntityDoesNotExistError::new(entity, self.entities)) + } + } + /// Spawns multiple entities with the same combination of components, /// based on a batch of [`Bundles`](Bundle). /// diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index fdc5020d72..34e4aea1c3 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -1,7 +1,7 @@ use bevy_utils::Parallel; use crate::{ - entity::EntitiesAllocator, + entity::{Entities, EntitiesAllocator}, prelude::World, system::{Deferred, SystemBuffer, SystemMeta, SystemParam}, }; @@ -51,7 +51,8 @@ struct ParallelCommandQueue { #[derive(SystemParam)] pub struct ParallelCommands<'w, 's> { state: Deferred<'s, ParallelCommandQueue>, - entities: &'w EntitiesAllocator, + allocator: &'w EntitiesAllocator, + entities: &'w Entities, } impl SystemBuffer for ParallelCommandQueue { @@ -71,7 +72,7 @@ impl<'w, 's> ParallelCommands<'w, 's> { /// For an example, see the type-level documentation for [`ParallelCommands`]. pub fn command_scope(&self, f: impl FnOnce(Commands) -> R) -> R { self.state.thread_queues.scope(|queue| { - let commands = Commands::new_from_entities(queue, self.entities); + let commands = Commands::new_from_entities(queue, self.allocator, self.entities); f(commands) }) } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index b7aa959c62..f764c2788e 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -69,7 +69,13 @@ impl<'w> DeferredWorld<'w> { // SAFETY: &mut self ensure that there are no outstanding accesses to the queue let command_queue = unsafe { self.world.get_raw_command_queue() }; // SAFETY: command_queue is stored on world and always valid while the world exists - unsafe { Commands::new_raw_from_entities(command_queue, self.world.entities_allocator()) } + unsafe { + Commands::new_raw_from_entities( + command_queue, + self.world.entities_allocator(), + self.world.entities(), + ) + } } /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. @@ -415,8 +421,9 @@ impl<'w> DeferredWorld<'w> { // - Command queue access does not conflict with entity access. let raw_queue = unsafe { cell.get_raw_command_queue() }; // SAFETY: `&mut self` ensures the commands does not outlive the world. - let commands = - unsafe { Commands::new_raw_from_entities(raw_queue, cell.entities_allocator()) }; + let commands = unsafe { + Commands::new_raw_from_entities(raw_queue, cell.entities_allocator(), cell.entities()) + }; (fetcher, commands) } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 70afda9efe..c3a20db201 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -267,7 +267,13 @@ impl World { #[inline] pub fn commands(&mut self) -> Commands { // SAFETY: command_queue is stored on world and always valid while the world exists - unsafe { Commands::new_raw_from_entities(self.command_queue.clone(), &self.allocator) } + unsafe { + Commands::new_raw_from_entities( + self.command_queue.clone(), + &self.allocator, + &self.entities, + ) + } } /// Registers a new [`Component`] type and returns the [`ComponentId`] created for it. @@ -1046,8 +1052,9 @@ impl World { // - Command queue access does not conflict with entity access. let raw_queue = unsafe { cell.get_raw_command_queue() }; // SAFETY: `&mut self` ensures the commands does not outlive the world. - let commands = - unsafe { Commands::new_raw_from_entities(raw_queue, cell.entities_allocator()) }; + let commands = unsafe { + Commands::new_raw_from_entities(raw_queue, cell.entities_allocator(), cell.entities()) + }; (fetcher, commands) }