Restore support for running fn EntityCommands on entities that might be despawned (#11107)

# Objective

In #9604 we removed the ability to define an `EntityCommand` as
`fn(Entity, &mut World)`. However I have since realized that `fn(Entity,
&mut World)` is an incredibly expressive and powerful way to define a
command for an entity that may or may not exist (`fn(EntityWorldMut)`
only works on entities that are alive).

## Solution

Support `EntityCommand`s in the style of `fn(Entity, &mut World)`, as
well as `fn(EntityWorldMut)`. Use a marker generic on the
`EntityCommand` trait to allow multiple impls.

The second commit in this PR replaces all of the internal command
definitions with ones using `fn` definitions. This is mostly just to
show off how expressive this style of command is -- we can revert this
commit if we'd rather avoid breaking changes.

---

## Changelog

Re-added support for expressively defining an `EntityCommand` as a
function that takes `Entity, &mut World`.

## Migration Guide

All `Command` types in `bevy_ecs`, such as `Spawn`, `SpawnBatch`,
`Insert`, etc., have been made private. Use the equivalent methods on
`Commands` or `EntityCommands` instead.
This commit is contained in:
Joseph 2024-01-08 14:32:28 -08:00 committed by GitHub
parent 1260b7bcf1
commit df2ba09989
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -409,7 +409,7 @@ impl<'w, 's> Commands<'w, 's> {
I: IntoIterator + Send + Sync + 'static, I: IntoIterator + Send + Sync + 'static,
I::Item: Bundle, I::Item: Bundle,
{ {
self.queue.push(SpawnBatch { bundles_iter }); self.queue.push(spawn_batch(bundles_iter));
} }
/// Pushes a [`Command`] to the queue for creating entities, if needed, /// Pushes a [`Command`] to the queue for creating entities, if needed,
@ -433,13 +433,12 @@ impl<'w, 's> Commands<'w, 's> {
/// Spawning a specific `entity` value is rarely the right choice. Most apps should use [`Commands::spawn_batch`]. /// Spawning a specific `entity` value is rarely the right choice. Most apps should use [`Commands::spawn_batch`].
/// This method should generally only be used for sharing entities across apps, and only when they have a scheme /// This method should generally only be used for sharing entities across apps, and only when they have a scheme
/// worked out to share an ID space (which doesn't happen by default). /// worked out to share an ID space (which doesn't happen by default).
pub fn insert_or_spawn_batch<I, B>(&mut self, bundles_iter: I) pub fn insert_or_spawn_batch<I, B>(&mut self, bundles: I)
where where
I: IntoIterator + Send + Sync + 'static, I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
I::IntoIter: Iterator<Item = (Entity, B)>,
B: Bundle, B: Bundle,
{ {
self.queue.push(InsertOrSpawnBatch { bundles_iter }); self.queue.push(insert_or_spawn_batch(bundles));
} }
/// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with an inferred value. /// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with an inferred value.
@ -467,7 +466,7 @@ impl<'w, 's> Commands<'w, 's> {
/// # bevy_ecs::system::assert_is_system(initialise_scoreboard); /// # bevy_ecs::system::assert_is_system(initialise_scoreboard);
/// ``` /// ```
pub fn init_resource<R: Resource + FromWorld>(&mut self) { pub fn init_resource<R: Resource + FromWorld>(&mut self) {
self.queue.push(InitResource::<R>::new()); self.queue.push(init_resource::<R>);
} }
/// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with a specific value. /// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with a specific value.
@ -496,7 +495,7 @@ impl<'w, 's> Commands<'w, 's> {
/// # bevy_ecs::system::assert_is_system(system); /// # bevy_ecs::system::assert_is_system(system);
/// ``` /// ```
pub fn insert_resource<R: Resource>(&mut self, resource: R) { pub fn insert_resource<R: Resource>(&mut self, resource: R) {
self.queue.push(InsertResource { resource }); self.queue.push(insert_resource(resource));
} }
/// Pushes a [`Command`] to the queue for removing a [`Resource`] from the [`World`]. /// Pushes a [`Command`] to the queue for removing a [`Resource`] from the [`World`].
@ -520,7 +519,7 @@ impl<'w, 's> Commands<'w, 's> {
/// # bevy_ecs::system::assert_is_system(system); /// # bevy_ecs::system::assert_is_system(system);
/// ``` /// ```
pub fn remove_resource<R: Resource>(&mut self) { pub fn remove_resource<R: Resource>(&mut self) {
self.queue.push(RemoveResource::<R>::new()); self.queue.push(remove_resource::<R>);
} }
/// Runs the system corresponding to the given [`SystemId`]. /// Runs the system corresponding to the given [`SystemId`].
@ -608,18 +607,14 @@ impl<'w, 's> Commands<'w, 's> {
/// struct Counter(i64); /// struct Counter(i64);
/// ///
/// /// A `Command` which names an entity based on a global counter. /// /// A `Command` which names an entity based on a global counter.
/// struct CountName; /// fn count_name(entity: Entity, world: &mut World) {
///
/// impl EntityCommand for CountName {
/// fn apply(self, id: Entity, world: &mut World) {
/// // Get the current value of the counter, and increment it for next time. /// // Get the current value of the counter, and increment it for next time.
/// let mut counter = world.resource_mut::<Counter>(); /// let mut counter = world.resource_mut::<Counter>();
/// let i = counter.0; /// let i = counter.0;
/// counter.0 += 1; /// counter.0 += 1;
/// ///
/// // Name the entity after the value of the counter. /// // Name the entity after the value of the counter.
/// world.entity_mut(id).insert(Name::new(format!("Entity #{i}"))); /// world.entity_mut(entity).insert(Name::new(format!("Entity #{i}")));
/// }
/// } /// }
/// ///
/// // App creation boilerplate omitted... /// // App creation boilerplate omitted...
@ -635,8 +630,8 @@ impl<'w, 's> Commands<'w, 's> {
/// # assert_schedule.run(&mut world); /// # assert_schedule.run(&mut world);
/// ///
/// fn setup(mut commands: Commands) { /// fn setup(mut commands: Commands) {
/// commands.spawn_empty().add(CountName); /// commands.spawn_empty().add(count_name);
/// commands.spawn_empty().add(CountName); /// commands.spawn_empty().add(count_name);
/// } /// }
/// ///
/// fn assert_names(named: Query<&Name>) { /// fn assert_names(named: Query<&Name>) {
@ -645,25 +640,33 @@ impl<'w, 's> Commands<'w, 's> {
/// assert_eq!(names, HashSet::from_iter(["Entity #0", "Entity #1"])); /// assert_eq!(names, HashSet::from_iter(["Entity #0", "Entity #1"]));
/// } /// }
/// ``` /// ```
pub trait EntityCommand: Send + 'static { pub trait EntityCommand<Marker = ()>: Send + 'static {
/// Executes this command for the given [`Entity`]. /// Executes this command for the given [`Entity`].
fn apply(self, id: Entity, world: &mut World); fn apply(self, id: Entity, world: &mut World);
/// Returns a [`Command`] which executes this [`EntityCommand`] for the given [`Entity`]. /// Returns a [`Command`] which executes this [`EntityCommand`] for the given [`Entity`].
fn with_entity(self, id: Entity) -> WithEntity<Self> fn with_entity(self, id: Entity) -> WithEntity<Marker, Self>
where where
Self: Sized, Self: Sized,
{ {
WithEntity { cmd: self, id } WithEntity {
cmd: self,
id,
marker: PhantomData,
}
} }
} }
/// Turns an [`EntityCommand`] type into a [`Command`] type. /// Turns an [`EntityCommand`] type into a [`Command`] type.
pub struct WithEntity<C: EntityCommand> { pub struct WithEntity<Marker, C: EntityCommand<Marker>> {
cmd: C, cmd: C,
id: Entity, id: Entity,
marker: PhantomData<fn() -> Marker>,
} }
impl<C: EntityCommand> Command for WithEntity<C> { impl<M, C: EntityCommand<M>> Command for WithEntity<M, C>
where
M: 'static,
{
#[inline] #[inline]
fn apply(self, world: &mut World) { fn apply(self, world: &mut World) {
self.cmd.apply(self.id, world); self.cmd.apply(self.id, world);
@ -747,11 +750,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
/// # bevy_ecs::system::assert_is_system(add_combat_stats_system); /// # bevy_ecs::system::assert_is_system(add_combat_stats_system);
/// ``` /// ```
pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self { pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self {
self.commands.add(Insert { self.add(insert(bundle))
entity: self.entity,
bundle,
});
self
} }
/// Tries to add a [`Bundle`] of components to the entity. /// Tries to add a [`Bundle`] of components to the entity.
@ -803,11 +802,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
/// # bevy_ecs::system::assert_is_system(add_combat_stats_system); /// # bevy_ecs::system::assert_is_system(add_combat_stats_system);
/// ``` /// ```
pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self { pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self {
self.commands.add(TryInsert { self.add(try_insert(bundle))
entity: self.entity,
bundle,
});
self
} }
/// Removes a [`Bundle`] of components from the entity. /// Removes a [`Bundle`] of components from the entity.
@ -849,8 +844,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
where where
T: Bundle, T: Bundle,
{ {
self.commands.add(Remove::<T>::new(self.entity)); self.add(remove::<T>)
self
} }
/// Despawns the entity. /// Despawns the entity.
@ -884,9 +878,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
/// # bevy_ecs::system::assert_is_system(remove_character_system); /// # bevy_ecs::system::assert_is_system(remove_character_system);
/// ``` /// ```
pub fn despawn(&mut self) { pub fn despawn(&mut self) {
self.commands.add(Despawn { self.add(despawn);
entity: self.entity,
});
} }
/// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`].
@ -905,7 +897,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
/// # } /// # }
/// # bevy_ecs::system::assert_is_system(my_system); /// # bevy_ecs::system::assert_is_system(my_system);
/// ``` /// ```
pub fn add<C: EntityCommand>(&mut self, command: C) -> &mut Self { pub fn add<M: 'static>(&mut self, command: impl EntityCommand<M>) -> &mut Self {
self.commands.add(command.with_entity(self.entity)); self.commands.add(command.with_entity(self.entity));
self self
} }
@ -951,8 +943,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
where where
T: Bundle, T: Bundle,
{ {
self.commands.add(Retain::<T>::new(self.entity)); self.add(retain::<T>)
self
} }
/// Logs the components of the entity at the info level. /// Logs the components of the entity at the info level.
@ -961,9 +952,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
/// ///
/// The command will panic when applied if the associated entity does not exist. /// The command will panic when applied if the associated entity does not exist.
pub fn log_components(&mut self) { pub fn log_components(&mut self) {
self.commands.add(LogComponents { self.add(log_components);
entity: self.entity,
});
} }
/// Returns the underlying [`Commands`]. /// Returns the underlying [`Commands`].
@ -981,7 +970,7 @@ where
} }
} }
impl<F> EntityCommand for F impl<F> EntityCommand<World> for F
where where
F: FnOnce(EntityWorldMut) + Send + 'static, F: FnOnce(EntityWorldMut) + Send + 'static,
{ {
@ -990,41 +979,25 @@ where
} }
} }
/// A [`Command`] that spawns a new entity and adds the components in a [`Bundle`] to it. impl<F> EntityCommand for F
#[derive(Debug)]
pub struct Spawn<T> {
/// The [`Bundle`] of components that will be added to the newly-spawned entity.
pub bundle: T,
}
impl<T> Command for Spawn<T>
where where
T: Bundle, F: FnOnce(Entity, &mut World) + Send + 'static,
{ {
fn apply(self, world: &mut World) { fn apply(self, id: Entity, world: &mut World) {
world.spawn(self.bundle); self(id, world);
} }
} }
/// A [`Command`] that consumes an iterator of [`Bundle`]s to spawn a series of entities. /// A [`Command`] that consumes an iterator of [`Bundle`]s to spawn a series of entities.
/// ///
/// This is more efficient than spawning the entities individually. /// This is more efficient than spawning the entities individually.
pub struct SpawnBatch<I> fn spawn_batch<I, B>(bundles: I) -> impl Command
where where
I: IntoIterator, I: IntoIterator<Item = B> + Send + Sync + 'static,
I::Item: Bundle, B: Bundle,
{ {
/// The iterator that returns the [`Bundle`]s which will be added to each newly-spawned entity. move |world: &mut World| {
pub bundles_iter: I, world.spawn_batch(bundles);
}
impl<I> Command for SpawnBatch<I>
where
I: IntoIterator + Send + Sync + 'static,
I::Item: Bundle,
{
fn apply(self, world: &mut World) {
world.spawn_batch(self.bundles_iter);
} }
} }
@ -1032,24 +1005,13 @@ where
/// If any entities do not already exist in the world, they will be spawned. /// If any entities do not already exist in the world, they will be spawned.
/// ///
/// This is more efficient than inserting the bundles individually. /// This is more efficient than inserting the bundles individually.
pub struct InsertOrSpawnBatch<I, B> fn insert_or_spawn_batch<I, B>(bundles: I) -> impl Command
where where
I: IntoIterator + Send + Sync + 'static, I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle, B: Bundle,
I::IntoIter: Iterator<Item = (Entity, B)>,
{ {
/// The iterator that returns each [entity ID](Entity) and corresponding [`Bundle`]. move |world: &mut World| {
pub bundles_iter: I, if let Err(invalid_entities) = world.insert_or_spawn_batch(bundles) {
}
impl<I, B> Command for InsertOrSpawnBatch<I, B>
where
I: IntoIterator + Send + Sync + 'static,
B: Bundle,
I::IntoIter: Iterator<Item = (Entity, B)>,
{
fn apply(self, world: &mut World) {
if let Err(invalid_entities) = world.insert_or_spawn_batch(self.bundles_iter) {
error!( error!(
"Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}", "Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}",
std::any::type_name::<B>(), std::any::type_name::<B>(),
@ -1066,54 +1028,26 @@ where
/// ///
/// This won't clean up external references to the entity (such as parent-child relationships /// This won't clean up external references to the entity (such as parent-child relationships
/// if you're using `bevy_hierarchy`), which may leave the world in an invalid state. /// if you're using `bevy_hierarchy`), which may leave the world in an invalid state.
#[derive(Debug)] fn despawn(entity: Entity, world: &mut World) {
pub struct Despawn { world.despawn(entity);
/// The entity that will be despawned.
pub entity: Entity,
} }
impl Command for Despawn { /// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity.
fn apply(self, world: &mut World) { fn insert<T: Bundle>(bundle: T) -> impl EntityCommand {
world.despawn(self.entity); move |entity: Entity, world: &mut World| {
} if let Some(mut entity) = world.get_entity_mut(entity) {
} entity.insert(bundle);
/// A [`Command`] that adds the components in a [`Bundle`] to an entity.
pub struct Insert<T> {
/// The entity to which the components will be added.
pub entity: Entity,
/// The [`Bundle`] containing the components that will be added to the entity.
pub bundle: T,
}
impl<T> Command for Insert<T>
where
T: Bundle + 'static,
{
fn apply(self, world: &mut World) {
if let Some(mut entity) = world.get_entity_mut(self.entity) {
entity.insert(self.bundle);
} else { } else {
panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World.", std::any::type_name::<T>(), self.entity); panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World.", std::any::type_name::<T>(), entity);
} }
} }
} }
/// A [`Command`] that attempts to add the components in a [`Bundle`] to an entity. /// An [`EntityCommand`] that attempts to add the components in a [`Bundle`] to an entity.
pub struct TryInsert<T> { fn try_insert(bundle: impl Bundle) -> impl EntityCommand {
/// The entity to which the components will be added. move |entity, world: &mut World| {
pub entity: Entity, if let Some(mut entity) = world.get_entity_mut(entity) {
/// The [`Bundle`] containing the components that will be added to the entity. entity.insert(bundle);
pub bundle: T,
}
impl<T> Command for TryInsert<T>
where
T: Bundle + 'static,
{
fn apply(self, world: &mut World) {
if let Some(mut entity) = world.get_entity_mut(self.entity) {
entity.insert(self.bundle);
} }
} }
} }
@ -1121,132 +1055,47 @@ where
/// A [`Command`] that removes components from an entity. /// A [`Command`] that removes components from an entity.
/// For a [`Bundle`] type `T`, this will remove any components in the bundle. /// For a [`Bundle`] type `T`, this will remove any components in the bundle.
/// Any components in the bundle that aren't found on the entity will be ignored. /// Any components in the bundle that aren't found on the entity will be ignored.
#[derive(Debug)] fn remove<T: Bundle>(entity: Entity, world: &mut World) {
pub struct Remove<T> { if let Some(mut entity_mut) = world.get_entity_mut(entity) {
/// The entity from which the components will be removed.
pub entity: Entity,
_marker: PhantomData<T>,
}
impl<T> Command for Remove<T>
where
T: Bundle,
{
fn apply(self, world: &mut World) {
if let Some(mut entity_mut) = world.get_entity_mut(self.entity) {
entity_mut.remove::<T>(); entity_mut.remove::<T>();
} }
}
}
impl<T> Remove<T> {
/// Creates a [`Command`] which will remove the specified [`Entity`] when applied.
pub const fn new(entity: Entity) -> Self {
Self {
entity,
_marker: PhantomData,
}
}
} }
/// A [`Command`] that removes components from an entity. /// A [`Command`] that removes components from an entity.
/// For a [`Bundle`] type `T`, this will remove all components except those in the bundle. /// For a [`Bundle`] type `T`, this will remove all components except those in the bundle.
/// Any components in the bundle that aren't found on the entity will be ignored. /// Any components in the bundle that aren't found on the entity will be ignored.
#[derive(Debug)] fn retain<T: Bundle>(entity: Entity, world: &mut World) {
pub struct Retain<T> { if let Some(mut entity_mut) = world.get_entity_mut(entity) {
/// The entity from which the components will be removed.
pub entity: Entity,
_marker: PhantomData<T>,
}
impl<T> Command for Retain<T>
where
T: Bundle,
{
fn apply(self, world: &mut World) {
if let Some(mut entity_mut) = world.get_entity_mut(self.entity) {
entity_mut.retain::<T>(); entity_mut.retain::<T>();
} }
}
}
impl<T> Retain<T> {
/// Creates a [`Command`] which will remove all but the specified components when applied.
pub const fn new(entity: Entity) -> Self {
Self {
entity,
_marker: PhantomData,
}
}
} }
/// A [`Command`] that inserts a [`Resource`] into the world using a value /// A [`Command`] that inserts a [`Resource`] into the world using a value
/// created with the [`FromWorld`] trait. /// created with the [`FromWorld`] trait.
pub struct InitResource<R: Resource + FromWorld> { fn init_resource<R: Resource + FromWorld>(world: &mut World) {
_marker: PhantomData<R>,
}
impl<R: Resource + FromWorld> Command for InitResource<R> {
fn apply(self, world: &mut World) {
world.init_resource::<R>(); world.init_resource::<R>();
}
}
impl<R: Resource + FromWorld> InitResource<R> {
/// Creates a [`Command`] which will insert a default created [`Resource`] into the [`World`]
pub const fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
/// A [`Command`] that inserts a [`Resource`] into the world.
pub struct InsertResource<R: Resource> {
/// The resource that will be added to the world.
pub resource: R,
}
impl<R: Resource> Command for InsertResource<R> {
fn apply(self, world: &mut World) {
world.insert_resource(self.resource);
}
} }
/// A [`Command`] that removes the [resource](Resource) `R` from the world. /// A [`Command`] that removes the [resource](Resource) `R` from the world.
pub struct RemoveResource<R: Resource> { fn remove_resource<R: Resource>(world: &mut World) {
_marker: PhantomData<R>,
}
impl<R: Resource> Command for RemoveResource<R> {
fn apply(self, world: &mut World) {
world.remove_resource::<R>(); world.remove_resource::<R>();
}
/// A [`Command`] that inserts a [`Resource`] into the world.
fn insert_resource<R: Resource>(resource: R) -> impl Command {
move |world: &mut World| {
world.insert_resource(resource);
} }
} }
impl<R: Resource> RemoveResource<R> { /// [`EntityCommand`] to log the components of a given entity. See [`EntityCommands::log_components`].
/// Creates a [`Command`] which will remove a [`Resource`] from the [`World`] fn log_components(entity: Entity, world: &mut World) {
pub const fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
/// [`Command`] to log the components of a given entity. See [`EntityCommands::log_components`].
pub struct LogComponents {
entity: Entity,
}
impl Command for LogComponents {
fn apply(self, world: &mut World) {
let debug_infos: Vec<_> = world let debug_infos: Vec<_> = world
.inspect_entity(self.entity) .inspect_entity(entity)
.into_iter() .into_iter()
.map(|component_info| component_info.name()) .map(|component_info| component_info.name())
.collect(); .collect();
info!("Entity {:?}: {:?}", self.entity, debug_infos); info!("Entity {:?}: {:?}", entity, debug_infos);
}
} }
#[cfg(test)] #[cfg(test)]