mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 06:30:19 +00:00
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:
parent
1260b7bcf1
commit
df2ba09989
1 changed files with 85 additions and 236 deletions
|
@ -409,7 +409,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
I: IntoIterator + Send + Sync + 'static,
|
||||
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,
|
||||
|
@ -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`].
|
||||
/// 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).
|
||||
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
|
||||
I: IntoIterator + Send + Sync + 'static,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
|
||||
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.
|
||||
|
@ -467,7 +466,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// # bevy_ecs::system::assert_is_system(initialise_scoreboard);
|
||||
/// ```
|
||||
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.
|
||||
|
@ -496,7 +495,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// # bevy_ecs::system::assert_is_system(system);
|
||||
/// ```
|
||||
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`].
|
||||
|
@ -520,7 +519,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// # bevy_ecs::system::assert_is_system(system);
|
||||
/// ```
|
||||
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`].
|
||||
|
@ -608,18 +607,14 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// struct Counter(i64);
|
||||
///
|
||||
/// /// A `Command` which names an entity based on a global counter.
|
||||
/// struct CountName;
|
||||
///
|
||||
/// impl EntityCommand for CountName {
|
||||
/// fn apply(self, id: Entity, world: &mut World) {
|
||||
/// fn count_name(entity: Entity, world: &mut World) {
|
||||
/// // Get the current value of the counter, and increment it for next time.
|
||||
/// let mut counter = world.resource_mut::<Counter>();
|
||||
/// let i = counter.0;
|
||||
/// counter.0 += 1;
|
||||
///
|
||||
/// // 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...
|
||||
|
@ -635,8 +630,8 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// # assert_schedule.run(&mut world);
|
||||
///
|
||||
/// fn setup(mut commands: Commands) {
|
||||
/// commands.spawn_empty().add(CountName);
|
||||
/// commands.spawn_empty().add(CountName);
|
||||
/// commands.spawn_empty().add(count_name);
|
||||
/// commands.spawn_empty().add(count_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"]));
|
||||
/// }
|
||||
/// ```
|
||||
pub trait EntityCommand: Send + 'static {
|
||||
pub trait EntityCommand<Marker = ()>: Send + 'static {
|
||||
/// Executes this command for the given [`Entity`].
|
||||
fn apply(self, id: Entity, world: &mut World);
|
||||
/// 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
|
||||
Self: Sized,
|
||||
{
|
||||
WithEntity { cmd: self, id }
|
||||
WithEntity {
|
||||
cmd: self,
|
||||
id,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns an [`EntityCommand`] type into a [`Command`] type.
|
||||
pub struct WithEntity<C: EntityCommand> {
|
||||
pub struct WithEntity<Marker, C: EntityCommand<Marker>> {
|
||||
cmd: C,
|
||||
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]
|
||||
fn apply(self, world: &mut 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);
|
||||
/// ```
|
||||
pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self {
|
||||
self.commands.add(Insert {
|
||||
entity: self.entity,
|
||||
bundle,
|
||||
});
|
||||
self
|
||||
self.add(insert(bundle))
|
||||
}
|
||||
|
||||
/// 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);
|
||||
/// ```
|
||||
pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self {
|
||||
self.commands.add(TryInsert {
|
||||
entity: self.entity,
|
||||
bundle,
|
||||
});
|
||||
self
|
||||
self.add(try_insert(bundle))
|
||||
}
|
||||
|
||||
/// Removes a [`Bundle`] of components from the entity.
|
||||
|
@ -849,8 +844,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
|
|||
where
|
||||
T: Bundle,
|
||||
{
|
||||
self.commands.add(Remove::<T>::new(self.entity));
|
||||
self
|
||||
self.add(remove::<T>)
|
||||
}
|
||||
|
||||
/// Despawns the entity.
|
||||
|
@ -884,9 +878,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
|
|||
/// # bevy_ecs::system::assert_is_system(remove_character_system);
|
||||
/// ```
|
||||
pub fn despawn(&mut self) {
|
||||
self.commands.add(Despawn {
|
||||
entity: self.entity,
|
||||
});
|
||||
self.add(despawn);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
/// ```
|
||||
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
|
||||
}
|
||||
|
@ -951,8 +943,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
|
|||
where
|
||||
T: Bundle,
|
||||
{
|
||||
self.commands.add(Retain::<T>::new(self.entity));
|
||||
self
|
||||
self.add(retain::<T>)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn log_components(&mut self) {
|
||||
self.commands.add(LogComponents {
|
||||
entity: self.entity,
|
||||
});
|
||||
self.add(log_components);
|
||||
}
|
||||
|
||||
/// Returns the underlying [`Commands`].
|
||||
|
@ -981,7 +970,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<F> EntityCommand for F
|
||||
impl<F> EntityCommand<World> for F
|
||||
where
|
||||
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.
|
||||
#[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>
|
||||
impl<F> EntityCommand for F
|
||||
where
|
||||
T: Bundle,
|
||||
F: FnOnce(Entity, &mut World) + Send + 'static,
|
||||
{
|
||||
fn apply(self, world: &mut World) {
|
||||
world.spawn(self.bundle);
|
||||
fn apply(self, id: Entity, world: &mut World) {
|
||||
self(id, world);
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that consumes an iterator of [`Bundle`]s to spawn a series of entities.
|
||||
///
|
||||
/// This is more efficient than spawning the entities individually.
|
||||
pub struct SpawnBatch<I>
|
||||
fn spawn_batch<I, B>(bundles: I) -> impl Command
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Bundle,
|
||||
I: IntoIterator<Item = B> + Send + Sync + 'static,
|
||||
B: Bundle,
|
||||
{
|
||||
/// The iterator that returns the [`Bundle`]s which will be added to each newly-spawned entity.
|
||||
pub bundles_iter: I,
|
||||
}
|
||||
|
||||
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);
|
||||
move |world: &mut World| {
|
||||
world.spawn_batch(bundles);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1032,24 +1005,13 @@ where
|
|||
/// If any entities do not already exist in the world, they will be spawned.
|
||||
///
|
||||
/// 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
|
||||
I: IntoIterator + Send + Sync + 'static,
|
||||
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
|
||||
B: Bundle,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
{
|
||||
/// The iterator that returns each [entity ID](Entity) and corresponding [`Bundle`].
|
||||
pub bundles_iter: I,
|
||||
}
|
||||
|
||||
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) {
|
||||
move |world: &mut World| {
|
||||
if let Err(invalid_entities) = world.insert_or_spawn_batch(bundles) {
|
||||
error!(
|
||||
"Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}",
|
||||
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
|
||||
/// if you're using `bevy_hierarchy`), which may leave the world in an invalid state.
|
||||
#[derive(Debug)]
|
||||
pub struct Despawn {
|
||||
/// The entity that will be despawned.
|
||||
pub entity: Entity,
|
||||
fn despawn(entity: Entity, world: &mut World) {
|
||||
world.despawn(entity);
|
||||
}
|
||||
|
||||
impl Command for Despawn {
|
||||
fn apply(self, world: &mut World) {
|
||||
world.despawn(self.entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
/// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity.
|
||||
fn insert<T: Bundle>(bundle: T) -> impl EntityCommand {
|
||||
move |entity: Entity, world: &mut World| {
|
||||
if let Some(mut entity) = world.get_entity_mut(entity) {
|
||||
entity.insert(bundle);
|
||||
} 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.
|
||||
pub struct TryInsert<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 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);
|
||||
/// An [`EntityCommand`] that attempts to add the components in a [`Bundle`] to an entity.
|
||||
fn try_insert(bundle: impl Bundle) -> impl EntityCommand {
|
||||
move |entity, world: &mut World| {
|
||||
if let Some(mut entity) = world.get_entity_mut(entity) {
|
||||
entity.insert(bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1121,132 +1055,47 @@ where
|
|||
/// A [`Command`] that removes components from an entity.
|
||||
/// 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.
|
||||
#[derive(Debug)]
|
||||
pub struct Remove<T> {
|
||||
/// 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) {
|
||||
fn remove<T: Bundle>(entity: Entity, world: &mut World) {
|
||||
if let Some(mut entity_mut) = world.get_entity_mut(entity) {
|
||||
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.
|
||||
/// 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.
|
||||
#[derive(Debug)]
|
||||
pub struct Retain<T> {
|
||||
/// 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) {
|
||||
fn retain<T: Bundle>(entity: Entity, world: &mut World) {
|
||||
if let Some(mut entity_mut) = world.get_entity_mut(entity) {
|
||||
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
|
||||
/// created with the [`FromWorld`] trait.
|
||||
pub struct InitResource<R: Resource + FromWorld> {
|
||||
_marker: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<R: Resource + FromWorld> Command for InitResource<R> {
|
||||
fn apply(self, world: &mut World) {
|
||||
fn init_resource<R: Resource + FromWorld>(world: &mut World) {
|
||||
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.
|
||||
pub struct RemoveResource<R: Resource> {
|
||||
_marker: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<R: Resource> Command for RemoveResource<R> {
|
||||
fn apply(self, world: &mut World) {
|
||||
fn remove_resource<R: Resource>(world: &mut World) {
|
||||
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> {
|
||||
/// Creates a [`Command`] which will remove a [`Resource`] from the [`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) {
|
||||
/// [`EntityCommand`] to log the components of a given entity. See [`EntityCommands::log_components`].
|
||||
fn log_components(entity: Entity, world: &mut World) {
|
||||
let debug_infos: Vec<_> = world
|
||||
.inspect_entity(self.entity)
|
||||
.inspect_entity(entity)
|
||||
.into_iter()
|
||||
.map(|component_info| component_info.name())
|
||||
.collect();
|
||||
info!("Entity {:?}: {:?}", self.entity, debug_infos);
|
||||
}
|
||||
info!("Entity {:?}: {:?}", entity, debug_infos);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Reference in a new issue