fixed commands

This commit is contained in:
Elliott Pierce 2025-05-30 22:17:59 -04:00
parent 4eab25cb85
commit 8556b190f3
6 changed files with 131 additions and 147 deletions

View File

@ -978,8 +978,14 @@ impl Entities {
} }
} }
/// An error that occurs when a specified [`Entity`] can not be constructed.
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstructionError { pub enum ConstructionError {
#[error(
"The entity's id was invalid: either erroneously created or with the wrong generation."
)]
InvalidId, InvalidId,
#[error("The entity can not be constructed as it already has a location.")]
AlreadyConstructed, AlreadyConstructed,
} }

View File

@ -18,7 +18,7 @@ use crate::{
bundle::{Bundle, InsertMode, NoBundleEffect}, bundle::{Bundle, InsertMode, NoBundleEffect},
change_detection::{MaybeLocation, Mut}, change_detection::{MaybeLocation, Mut},
component::{Component, ComponentId, Mutable}, component::{Component, ComponentId, Mutable},
entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError}, entity::{Entities, EntitiesAllocator, Entity, EntityClonerBuilder},
error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError}, error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError},
event::Event, event::Event,
observer::{Observer, TriggerTargets}, observer::{Observer, TriggerTargets},
@ -99,7 +99,7 @@ use crate::{
/// [`ApplyDeferred`]: crate::schedule::ApplyDeferred /// [`ApplyDeferred`]: crate::schedule::ApplyDeferred
pub struct Commands<'w, 's> { pub struct Commands<'w, 's> {
queue: InternalQueue<'s>, queue: InternalQueue<'s>,
entities: &'w Entities, entities: &'w EntitiesAllocator,
} }
// SAFETY: All commands [`Command`] implement [`Send`] // SAFETY: All commands [`Command`] implement [`Send`]
@ -176,7 +176,7 @@ const _: () = {
world: UnsafeWorldCell<'w>, world: UnsafeWorldCell<'w>,
change_tick: bevy_ecs::component::Tick, change_tick: bevy_ecs::component::Tick,
) -> Self::Item<'w, 's> { ) -> Self::Item<'w, 's> {
let(f0, f1) = <(Deferred<'s, CommandQueue>, &'w Entities) as bevy_ecs::system::SystemParam>::get_param(&mut state.state, system_meta, world, change_tick); let(f0, f1) = <(Deferred<'s, CommandQueue>, &'w EntitiesAllocator) as bevy_ecs::system::SystemParam>::get_param(&mut state.state, system_meta, world, change_tick);
Commands { Commands {
queue: InternalQueue::CommandQueue(f0), queue: InternalQueue::CommandQueue(f0),
entities: f1, entities: f1,
@ -200,11 +200,11 @@ enum InternalQueue<'s> {
impl<'w, 's> Commands<'w, 's> { impl<'w, 's> Commands<'w, 's> {
/// Returns a new `Commands` instance from a [`CommandQueue`] and a [`World`]. /// Returns a new `Commands` instance from a [`CommandQueue`] and a [`World`].
pub fn new(queue: &'s mut CommandQueue, world: &'w World) -> Self { pub fn new(queue: &'s mut CommandQueue, world: &'w World) -> Self {
Self::new_from_entities(queue, &world.entities) Self::new_from_entities(queue, &world.allocator)
} }
/// Returns a new `Commands` instance from a [`CommandQueue`] and an [`Entities`] reference. /// Returns a new `Commands` instance from a [`CommandQueue`] and an [`Entities`] reference.
pub fn new_from_entities(queue: &'s mut CommandQueue, entities: &'w Entities) -> Self { pub fn new_from_entities(queue: &'s mut CommandQueue, entities: &'w EntitiesAllocator) -> Self {
Self { Self {
queue: InternalQueue::CommandQueue(Deferred(queue)), queue: InternalQueue::CommandQueue(Deferred(queue)),
entities, entities,
@ -220,7 +220,7 @@ impl<'w, 's> Commands<'w, 's> {
/// * Caller ensures that `queue` must outlive `'w` /// * Caller ensures that `queue` must outlive `'w`
pub(crate) unsafe fn new_raw_from_entities( pub(crate) unsafe fn new_raw_from_entities(
queue: RawCommandQueue, queue: RawCommandQueue,
entities: &'w Entities, entities: &'w EntitiesAllocator,
) -> Self { ) -> Self {
Self { Self {
queue: InternalQueue::RawCommandQueue(queue), queue: InternalQueue::RawCommandQueue(queue),
@ -304,24 +304,14 @@ impl<'w, 's> Commands<'w, 's> {
/// with the same combination of components. /// with the same combination of components.
#[track_caller] #[track_caller]
pub fn spawn_empty(&mut self) -> EntityCommands { pub fn spawn_empty(&mut self) -> EntityCommands {
let entity = self.entities.reserve_entity(); let entity = self.entities.alloc();
let mut entity_commands = EntityCommands {
entity,
commands: self.reborrow(),
};
let caller = MaybeLocation::caller(); let caller = MaybeLocation::caller();
entity_commands.queue(move |entity: EntityWorldMut| { self.queue(move |world: &mut World| {
let index = entity.id().index(); world
let world = entity.into_world_mut(); .construct_empty_with_caller(entity, caller)
let tick = world.change_tick(); .map(|_| ())
// SAFETY: Entity has been flushed
unsafe {
world
.entities_mut()
.mark_construct_or_destruct(index, caller, tick);
}
}); });
entity_commands self.entity(entity)
} }
/// Spawns a new [`Entity`] with the given components /// Spawns a new [`Entity`] with the given components
@ -368,37 +358,14 @@ impl<'w, 's> Commands<'w, 's> {
/// with the same combination of components. /// with the same combination of components.
#[track_caller] #[track_caller]
pub fn spawn<T: Bundle>(&mut self, bundle: T) -> EntityCommands { pub fn spawn<T: Bundle>(&mut self, bundle: T) -> EntityCommands {
let entity = self.entities.reserve_entity(); let entity = self.entities.alloc();
let mut entity_commands = EntityCommands {
entity,
commands: self.reborrow(),
};
let caller = MaybeLocation::caller(); let caller = MaybeLocation::caller();
self.queue(move |world: &mut World| {
entity_commands.queue(move |mut entity: EntityWorldMut| { world
// Store metadata about the spawn operation. .construct_with_caller(entity, bundle, caller)
// This is the same as in `spawn_empty`, but merged into .map(|_| ())
// the same command for better performance.
let index = entity.id().index();
entity.world_scope(|world| {
let tick = world.change_tick();
// SAFETY: Entity has been flushed
unsafe {
world
.entities_mut()
.mark_construct_or_destruct(index, caller, tick);
}
});
entity.insert_with_caller(
bundle,
InsertMode::Replace,
caller,
crate::relationship::RelationshipHookMode::Run,
);
}); });
// entity_command::insert(bundle, InsertMode::Replace) self.entity(entity)
entity_commands
} }
/// Returns the [`EntityCommands`] for the given [`Entity`]. /// Returns the [`EntityCommands`] for the given [`Entity`].
@ -437,61 +404,6 @@ impl<'w, 's> Commands<'w, 's> {
} }
} }
/// 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.
///
/// # 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_entity(
&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))
}
}
/// Spawns multiple entities with the same combination of components, /// Spawns multiple entities with the same combination of components,
/// based on a batch of [`Bundles`](Bundle). /// based on a batch of [`Bundles`](Bundle).
/// ///

View File

@ -1,7 +1,7 @@
use bevy_utils::Parallel; use bevy_utils::Parallel;
use crate::{ use crate::{
entity::Entities, entity::EntitiesAllocator,
prelude::World, prelude::World,
system::{Deferred, SystemBuffer, SystemMeta, SystemParam}, system::{Deferred, SystemBuffer, SystemMeta, SystemParam},
}; };
@ -51,7 +51,7 @@ struct ParallelCommandQueue {
#[derive(SystemParam)] #[derive(SystemParam)]
pub struct ParallelCommands<'w, 's> { pub struct ParallelCommands<'w, 's> {
state: Deferred<'s, ParallelCommandQueue>, state: Deferred<'s, ParallelCommandQueue>,
entities: &'w Entities, entities: &'w EntitiesAllocator,
} }
impl SystemBuffer for ParallelCommandQueue { impl SystemBuffer for ParallelCommandQueue {

View File

@ -4,7 +4,7 @@ use crate::{
bundle::Bundles, bundle::Bundles,
change_detection::{MaybeLocation, Ticks, TicksMut}, change_detection::{MaybeLocation, Ticks, TicksMut},
component::{ComponentId, ComponentTicks, Components, Tick}, component::{ComponentId, ComponentTicks, Components, Tick},
entity::Entities, entity::{Entities, EntitiesAllocator},
query::{ query::{
Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError, Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError,
QueryState, ReadOnlyQueryData, QueryState, ReadOnlyQueryData,
@ -1552,6 +1552,27 @@ unsafe impl<'a> SystemParam for &'a Entities {
} }
} }
// SAFETY: Only reads World entities
unsafe impl<'a> ReadOnlySystemParam for &'a EntitiesAllocator {}
// SAFETY: no component value access
unsafe impl<'a> SystemParam for &'a EntitiesAllocator {
type State = ();
type Item<'w, 's> = &'w EntitiesAllocator;
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
#[inline]
unsafe fn get_param<'w, 's>(
_state: &'s mut Self::State,
_system_meta: &SystemMeta,
world: UnsafeWorldCell<'w>,
_change_tick: Tick,
) -> Self::Item<'w, 's> {
world.entities_allocator()
}
}
// SAFETY: Only reads World bundles // SAFETY: Only reads World bundles
unsafe impl<'a> ReadOnlySystemParam for &'a Bundles {} unsafe impl<'a> ReadOnlySystemParam for &'a Bundles {}

View File

@ -267,7 +267,7 @@ impl World {
#[inline] #[inline]
pub fn commands(&mut self) -> Commands { pub fn commands(&mut self) -> Commands {
// SAFETY: command_queue is stored on world and always valid while the world exists // 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.entities) } unsafe { Commands::new_raw_from_entities(self.command_queue.clone(), &self.allocator) }
} }
/// Registers a new [`Component`] type and returns the [`ComponentId`] created for it. /// Registers a new [`Component`] type and returns the [`ComponentId`] created for it.
@ -1046,7 +1046,8 @@ impl World {
// - Command queue access does not conflict with entity access. // - Command queue access does not conflict with entity access.
let raw_queue = unsafe { cell.get_raw_command_queue() }; let raw_queue = unsafe { cell.get_raw_command_queue() };
// SAFETY: `&mut self` ensures the commands does not outlive the world. // SAFETY: `&mut self` ensures the commands does not outlive the world.
let commands = unsafe { Commands::new_raw_from_entities(raw_queue, cell.entities()) }; let commands =
unsafe { Commands::new_raw_from_entities(raw_queue, cell.entities_allocator()) };
(fetcher, commands) (fetcher, commands)
} }
@ -1089,10 +1090,19 @@ impl World {
&mut self, &mut self,
entity: Entity, entity: Entity,
bundle: B, bundle: B,
) -> Result<EntityWorldMut<'_>, ConstructionError> {
self.construct_with_caller(entity, bundle, MaybeLocation::caller())
}
pub(crate) fn construct_with_caller<B: Bundle>(
&mut self,
entity: Entity,
bundle: B,
caller: MaybeLocation,
) -> Result<EntityWorldMut<'_>, ConstructionError> { ) -> Result<EntityWorldMut<'_>, ConstructionError> {
self.entities.validate_construction(entity)?; self.entities.validate_construction(entity)?;
// SAFETY: We just ensured it was valid. // SAFETY: We just ensured it was valid.
Ok(unsafe { self.construct_unchecked(entity, bundle, MaybeLocation::caller()) }) Ok(unsafe { self.construct_unchecked(entity, bundle, caller) })
} }
/// Constructs `bundle` on `entity`. /// Constructs `bundle` on `entity`.
@ -1127,46 +1137,34 @@ impl World {
entity entity
} }
/// Spawns a new [`Entity`] and returns a corresponding [`EntityWorldMut`], which can be used
/// to add components to the entity or retrieve its id.
///
/// ```
/// use bevy_ecs::{component::Component, world::World};
///
/// #[derive(Component)]
/// struct Position {
/// x: f32,
/// y: f32,
/// }
/// #[derive(Component)]
/// struct Label(&'static str);
/// #[derive(Component)]
/// struct Num(u32);
///
/// let mut world = World::new();
/// let entity = world.spawn_empty()
/// .insert(Position { x: 0.0, y: 0.0 }) // add a single component
/// .insert((Num(1), Label("hello"))) // add a bundle of components
/// .id();
///
/// let position = world.entity(entity).get::<Position>().unwrap();
/// assert_eq!(position.x, 0.0);
/// ```
#[track_caller] #[track_caller]
pub fn spawn_empty(&mut self) -> EntityWorldMut { pub fn construct_empty(
self.flush(); &mut self,
let entity = self.allocator.alloc(); entity: Entity,
// SAFETY: entity was just allocated ) -> Result<EntityWorldMut<'_>, ConstructionError> {
unsafe { self.spawn_at_empty_internal(entity, MaybeLocation::caller()) } self.construct_empty_with_caller(entity, MaybeLocation::caller())
} }
/// # Safety pub(crate) fn construct_empty_with_caller(
/// must be called on an entity that was just allocated
unsafe fn spawn_at_empty_internal(
&mut self, &mut self,
entity: Entity, entity: Entity,
caller: MaybeLocation, caller: MaybeLocation,
) -> EntityWorldMut { ) -> Result<EntityWorldMut<'_>, ConstructionError> {
self.entities.validate_construction(entity)?;
// SAFETY: We just ensured it was valid.
Ok(unsafe { self.construct_empty_unchecked(entity, caller) })
}
/// Constructs `bundle` on `entity`.
///
/// # Safety
///
/// `entity` must be valid and have no location.
pub(crate) unsafe fn construct_empty_unchecked(
&mut self,
entity: Entity,
caller: MaybeLocation,
) -> EntityWorldMut<'_> {
let archetype = self.archetypes.empty_mut(); let archetype = self.archetypes.empty_mut();
// PERF: consider avoiding allocating entities in the empty archetype unless needed // PERF: consider avoiding allocating entities in the empty archetype unless needed
let table_row = self.storages.tables[archetype.table_id()].allocate(entity); let table_row = self.storages.tables[archetype.table_id()].allocate(entity);
@ -1256,6 +1254,42 @@ impl World {
unsafe { self.construct_unchecked(entity, bundle, caller) } unsafe { self.construct_unchecked(entity, bundle, caller) }
} }
/// Spawns a new [`Entity`] and returns a corresponding [`EntityWorldMut`], which can be used
/// to add components to the entity or retrieve its id.
///
/// ```
/// use bevy_ecs::{component::Component, world::World};
///
/// #[derive(Component)]
/// struct Position {
/// x: f32,
/// y: f32,
/// }
/// #[derive(Component)]
/// struct Label(&'static str);
/// #[derive(Component)]
/// struct Num(u32);
///
/// let mut world = World::new();
/// let entity = world.spawn_empty()
/// .insert(Position { x: 0.0, y: 0.0 }) // add a single component
/// .insert((Num(1), Label("hello"))) // add a bundle of components
/// .id();
///
/// let position = world.entity(entity).get::<Position>().unwrap();
/// assert_eq!(position.x, 0.0);
/// ```
#[track_caller]
pub fn spawn_empty(&mut self) -> EntityWorldMut {
self.spawn_empty_with_caller(MaybeLocation::caller())
}
pub(crate) fn spawn_empty_with_caller(&mut self, caller: MaybeLocation) -> EntityWorldMut {
let entity = self.allocator.alloc();
// SAFETY: entity was just allocated
unsafe { self.construct_empty_unchecked(entity, caller) }
}
/// Spawns a batch of entities with the same component [`Bundle`] type. Takes a given /// Spawns a batch of entities with the same component [`Bundle`] type. Takes a given
/// [`Bundle`] iterator and returns a corresponding [`Entity`] iterator. /// [`Bundle`] iterator and returns a corresponding [`Entity`] iterator.
/// This is more efficient than spawning entities and adding components to them individually /// This is more efficient than spawning entities and adding components to them individually

View File

@ -6,7 +6,10 @@ use crate::{
bundle::Bundles, bundle::Bundles,
change_detection::{MaybeLocation, MutUntyped, Ticks, TicksMut}, change_detection::{MaybeLocation, MutUntyped, Ticks, TicksMut},
component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells}, component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells},
entity::{ContainsEntity, Entities, Entity, EntityDoesNotExistError, EntityLocation}, entity::{
ContainsEntity, Entities, EntitiesAllocator, Entity, EntityDoesNotExistError,
EntityLocation,
},
error::{DefaultErrorHandler, ErrorHandler}, error::{DefaultErrorHandler, ErrorHandler},
observer::Observers, observer::Observers,
prelude::Component, prelude::Component,
@ -259,6 +262,14 @@ impl<'w> UnsafeWorldCell<'w> {
&unsafe { self.world_metadata() }.entities &unsafe { self.world_metadata() }.entities
} }
/// Retrieves this world's [`Entities`] collection.
#[inline]
pub fn entities_allocator(self) -> &'w EntitiesAllocator {
// SAFETY:
// - we only access world metadata
&unsafe { self.world_metadata() }.allocator
}
/// Retrieves this world's [`Archetypes`] collection. /// Retrieves this world's [`Archetypes`] collection.
#[inline] #[inline]
pub fn archetypes(self) -> &'w Archetypes { pub fn archetypes(self) -> &'w Archetypes {