Skip to content

Commit 65d3901

Browse files
committed
Add a trait for commands that run for a given Entity (#7015)
# 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.
1 parent 83b602a commit 65d3901

File tree

1 file changed

+109
-0
lines changed
  • crates/bevy_ecs/src/system/commands

1 file changed

+109
-0
lines changed

crates/bevy_ecs/src/system/commands/mod.rs

+109
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,85 @@ impl<'w, 's> Commands<'w, 's> {
528528
}
529529
}
530530

531+
/// A [`Command`] which gets executed for a given [`Entity`].
532+
///
533+
/// # Examples
534+
///
535+
/// ```
536+
/// # use std::collections::HashSet;
537+
/// # use bevy_ecs::prelude::*;
538+
/// use bevy_ecs::system::EntityCommand;
539+
/// #
540+
/// # #[derive(Component, PartialEq)]
541+
/// # struct Name(String);
542+
/// # impl Name {
543+
/// # fn new(s: String) -> Self { Name(s) }
544+
/// # fn as_str(&self) -> &str { &self.0 }
545+
/// # }
546+
///
547+
/// #[derive(Resource, Default)]
548+
/// struct Counter(i64);
549+
///
550+
/// /// A `Command` which names an entity based on a global counter.
551+
/// struct CountName;
552+
///
553+
/// impl EntityCommand for CountName {
554+
/// fn write(self, id: Entity, world: &mut World) {
555+
/// // Get the current value of the counter, and increment it for next time.
556+
/// let mut counter = world.resource_mut::<Counter>();
557+
/// let i = counter.0;
558+
/// counter.0 += 1;
559+
///
560+
/// // Name the entity after the value of the counter.
561+
/// world.entity_mut(id).insert(Name::new(format!("Entity #{i}")));
562+
/// }
563+
/// }
564+
///
565+
/// // App creation boilerplate omitted...
566+
/// # let mut world = World::new();
567+
/// # world.init_resource::<Counter>();
568+
/// #
569+
/// # let mut setup_stage = SystemStage::single_threaded().with_system(setup);
570+
/// # let mut assert_stage = SystemStage::single_threaded().with_system(assert_names);
571+
/// #
572+
/// # setup_stage.run(&mut world);
573+
/// # assert_stage.run(&mut world);
574+
///
575+
/// fn setup(mut commands: Commands) {
576+
/// commands.spawn_empty().add(CountName);
577+
/// commands.spawn_empty().add(CountName);
578+
/// }
579+
///
580+
/// fn assert_names(named: Query<&Name>) {
581+
/// // We use a HashSet because we do not care about the order.
582+
/// let names: HashSet<_> = named.iter().map(Name::as_str).collect();
583+
/// assert_eq!(names, HashSet::from_iter(["Entity #0", "Entity #1"]));
584+
/// }
585+
/// ```
586+
pub trait EntityCommand: Send + 'static {
587+
fn write(self, id: Entity, world: &mut World);
588+
/// Returns a [`Command`] which executes this [`EntityCommand`] for the given [`Entity`].
589+
fn with_entity(self, id: Entity) -> WithEntity<Self>
590+
where
591+
Self: Sized,
592+
{
593+
WithEntity { cmd: self, id }
594+
}
595+
}
596+
597+
/// Turns an [`EntityCommand`] type into a [`Command`] type.
598+
pub struct WithEntity<C: EntityCommand> {
599+
cmd: C,
600+
id: Entity,
601+
}
602+
603+
impl<C: EntityCommand> Command for WithEntity<C> {
604+
#[inline]
605+
fn write(self, world: &mut World) {
606+
self.cmd.write(self.id, world);
607+
}
608+
}
609+
531610
/// A list of commands that will be run to modify an [entity](crate::entity).
532611
pub struct EntityCommands<'w, 's, 'a> {
533612
entity: Entity,
@@ -690,6 +769,27 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
690769
});
691770
}
692771

772+
/// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`].
773+
///
774+
/// # Examples
775+
///
776+
/// ```
777+
/// # use bevy_ecs::prelude::*;
778+
/// # fn my_system(mut commands: Commands) {
779+
/// commands
780+
/// .spawn_empty()
781+
/// // Closures with this signature implement `EntityCommand`.
782+
/// .add(|id: Entity, world: &mut World| {
783+
/// println!("Executed an EntityCommand for {id:?}");
784+
/// });
785+
/// # }
786+
/// # bevy_ecs::system::assert_is_system(my_system);
787+
/// ```
788+
pub fn add<C: EntityCommand>(&mut self, command: C) -> &mut Self {
789+
self.commands.add(command.with_entity(self.entity));
790+
self
791+
}
792+
693793
/// Logs the components of the entity at the info level.
694794
///
695795
/// # Panics
@@ -716,6 +816,15 @@ where
716816
}
717817
}
718818

819+
impl<F> EntityCommand for F
820+
where
821+
F: FnOnce(Entity, &mut World) + Send + 'static,
822+
{
823+
fn write(self, id: Entity, world: &mut World) {
824+
self(id, world);
825+
}
826+
}
827+
719828
#[derive(Debug)]
720829
pub struct Spawn<T> {
721830
pub bundle: T,

0 commit comments

Comments
 (0)