Skip to content

Commit

Permalink
Add a trait for commands that run for a given Entity (#7015)
Browse files Browse the repository at this point in the history
# Objective

Resolve #6156.

The most common type of command is one that runs for a single entity. Built-in commands like this can be ergonomically added to the command queue using the `EntityCommands` struct. However, adding custom entity commands to the queue is quite cumbersome. You must first spawn an entity, store its ID in a local, then construct a command using that ID and add it to the queue. This prevents method chaining, which is the main benefit of using `EntityCommands`.

### Example (before)

```rust
struct MyCustomCommand(Entity);

impl Command for MyCustomCommand { ... }

let id = commands.spawn((...)).id();
commmands.add(MyCustomCommand(id));
```

## Solution

Add the `EntityCommand` trait, which allows directly adding per-entity commands to the `EntityCommands` struct.

### Example (after)

```rust
struct MyCustomCommand;

impl EntityCommand for MyCustomCommand { ... }

commands.spawn((...)).add(MyCustomCommand);
```
---

## Changelog

- Added the trait `EntityCommand`. This is a counterpart of `Command` for types that execute code for a single entity.

## Future Work

If we feel its necessary, we can simplify built-in commands (such as `Despawn`) to use this trait.
  • Loading branch information
JoJoJet committed Dec 25, 2022
1 parent 647d276 commit e8564b9
Showing 1 changed file with 109 additions and 0 deletions.
109 changes: 109 additions & 0 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,85 @@ impl<'w, 's> Commands<'w, 's> {
}
}

/// A [`Command`] which gets executed for a given [`Entity`].
///
/// # Examples
///
/// ```
/// # use std::collections::HashSet;
/// # use bevy_ecs::prelude::*;
/// use bevy_ecs::system::EntityCommand;
/// #
/// # #[derive(Component, PartialEq)]
/// # struct Name(String);
/// # impl Name {
/// # fn new(s: String) -> Self { Name(s) }
/// # fn as_str(&self) -> &str { &self.0 }
/// # }
///
/// #[derive(Resource, Default)]
/// struct Counter(i64);
///
/// /// A `Command` which names an entity based on a global counter.
/// struct CountName;
///
/// impl EntityCommand for CountName {
/// fn write(self, id: 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}")));
/// }
/// }
///
/// // App creation boilerplate omitted...
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// #
/// # let mut setup_stage = SystemStage::single_threaded().with_system(setup);
/// # let mut assert_stage = SystemStage::single_threaded().with_system(assert_names);
/// #
/// # setup_stage.run(&mut world);
/// # assert_stage.run(&mut world);
///
/// fn setup(mut commands: Commands) {
/// commands.spawn_empty().add(CountName);
/// commands.spawn_empty().add(CountName);
/// }
///
/// fn assert_names(named: Query<&Name>) {
/// // We use a HashSet because we do not care about the order.
/// let names: HashSet<_> = named.iter().map(Name::as_str).collect();
/// assert_eq!(names, HashSet::from_iter(["Entity #0", "Entity #1"]));
/// }
/// ```
pub trait EntityCommand: Send + 'static {
fn write(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>
where
Self: Sized,
{
WithEntity { cmd: self, id }
}
}

/// Turns an [`EntityCommand`] type into a [`Command`] type.
pub struct WithEntity<C: EntityCommand> {
cmd: C,
id: Entity,
}

impl<C: EntityCommand> Command for WithEntity<C> {
#[inline]
fn write(self, world: &mut World) {
self.cmd.write(self.id, world);
}
}

/// A list of commands that will be run to modify an [entity](crate::entity).
pub struct EntityCommands<'w, 's, 'a> {
entity: Entity,
Expand Down Expand Up @@ -690,6 +769,27 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
});
}

/// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`].
///
/// # Examples
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # fn my_system(mut commands: Commands) {
/// commands
/// .spawn_empty()
/// // Closures with this signature implement `EntityCommand`.
/// .add(|id: Entity, world: &mut World| {
/// println!("Executed an EntityCommand for {id:?}");
/// });
/// # }
/// # bevy_ecs::system::assert_is_system(my_system);
/// ```
pub fn add<C: EntityCommand>(&mut self, command: C) -> &mut Self {
self.commands.add(command.with_entity(self.entity));
self
}

/// Logs the components of the entity at the info level.
///
/// # Panics
Expand All @@ -716,6 +816,15 @@ where
}
}

impl<F> EntityCommand for F
where
F: FnOnce(Entity, &mut World) + Send + 'static,
{
fn write(self, id: Entity, world: &mut World) {
self(id, world);
}
}

#[derive(Debug)]
pub struct Spawn<T> {
pub bundle: T,
Expand Down

0 comments on commit e8564b9

Please sign in to comment.