diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 7d3f541dab930..91de11838dcc5 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -548,6 +548,95 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { self } + /// Moves a [`Bundle`] to a target entity. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # #[derive(Component)] + /// # struct Dummy; + /// # + /// # #[derive(Bundle)] + /// # struct Cargo { + /// # _dummy: Dummy + /// # } + /// # + /// # #[derive(Component)] + /// # struct NextDestination { entity: Entity } + /// # + /// fn transport(mut commands: Commands, query: Query<(Entity, &NextDestination)>) { + /// for (source, NextDestination { entity: target }) in query.iter() { + /// commands.entity(source).move_bundle_to::(*target); + /// } + /// } + /// ``` + pub fn move_bundle_to(&mut self, target: Entity) -> &mut Self { + assert!( + self.commands.entities.contains(target), + "Attempting to move bundle {:?} from entity {:?} to entity {:?}, which doesn't exist.", + std::any::type_name::(), + self.entity, + target + ); + self.commands.add(MoveBundle:: { + source: self.entity, + target, + _bundle: PhantomData, + }); + self + } + + /// Moves a [`Component`] to a target entity. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # #[derive(Component)] + /// # struct Charge; + /// # + /// # #[derive(Component)] + /// # struct ConnectedTo { entity: Entity } + /// # + /// fn conduction(mut commands: Commands, query: Query<(Entity, &ConnectedTo), With>) { + /// for (source, ConnectedTo { entity: target }) in query.iter() { + /// commands.entity(source).move_to::(*target); + /// } + /// } + /// ``` + pub fn move_to(&mut self, target: Entity) -> &mut Self { + self.move_bundle_to::<(C,)>(target) + } + + /// Moves a [`Bundle`] from a source entity. + /// + /// See [`move_bundle_to`](EntityCommands::move_bundle_to). + pub fn move_bundle_from(&mut self, source: Entity) -> &mut Self { + assert!( + self.commands.entities.contains(source), + "Attempting to move bundle {:?} from entity {:?} to entity {:?}, which doesn't exist.", + std::any::type_name::(), + source, + self.entity + ); + self.commands.add(MoveBundle:: { + source, + target: self.entity, + _bundle: PhantomData, + }); + self + } + + /// Moves a [`Component`] from a source entity. + /// + /// See [`move_to`](EntityCommands::move_to). + pub fn move_from(&mut self, source: Entity) -> &mut Self { + self.move_bundle_from::<(C,)>(source) + } + /// Despawns the entity. /// /// See [`World::despawn`] for more details. @@ -672,6 +761,19 @@ impl Command for Despawn { } } +/// Utility method for formatting common command panic messages. +fn panic_fmt( + verb: &'static str, + trait_name: &'static str, + preposition: &'static str, + entity: Entity, +) -> ! { + panic!("Could not {} a {} (of type `{}`) {} entity {:?} because it doesn't exist in this World.\n\ + If this command was added to a newly spawned entity, ensure that you have not despawned that entity within the same stage.\n\ + This may have occurred due to system order ambiguity, or if the spawning system has multiple command buffers", + verb, trait_name, std::any::type_name::(), preposition, entity); +} + pub struct InsertBundle { pub entity: Entity, pub bundle: T, @@ -685,9 +787,49 @@ where if let Some(mut entity) = world.get_entity_mut(self.entity) { entity.insert_bundle(self.bundle); } else { - panic!("Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World.\n\ - If this command was added to a newly spawned entity, ensure that you have not despawned that entity within the same stage.\n\ - This may have occurred due to system order ambiguity, or if the spawning system has multiple command buffers", std::any::type_name::(), self.entity); + panic_fmt::("insert", "bundle", "for", self.entity); + } + } +} + +pub struct MoveBundle { + pub source: Entity, + pub target: Entity, + _bundle: PhantomData, +} + +impl MoveBundle { + /// Creates a new [`MoveBundle`] with given source and target IDs. + pub fn new(source: Entity, target: Entity) -> Self { + Self { + source, + target, + _bundle: PhantomData, + } + } +} + +impl Command for MoveBundle +where + T: Bundle + 'static, +{ + fn write(self, world: &mut World) { + let bundle_opt = if let Some(mut source) = world.get_entity_mut(self.source) { + source.remove_bundle::() + } else { + panic_fmt::("move", "bundle", "from", self.source); + }; + + let bundle = if let Some(some) = bundle_opt { + some + } else { + return; + }; + + if let Some(mut target) = world.get_entity_mut(self.target) { + target.insert_bundle(bundle); + } else { + panic_fmt::("move", "bundle", "to", self.target); } } } @@ -706,9 +848,7 @@ where if let Some(mut entity) = world.get_entity_mut(self.entity) { entity.insert(self.component); } else { - panic!("Could not add a component (of type `{}`) to entity {:?} because it doesn't exist in this World.\n\ - If this command was added to a newly spawned entity, ensure that you have not despawned that entity within the same stage.\n\ - This may have occurred due to system order ambiguity, or if the spawning system has multiple command buffers", std::any::type_name::(), self.entity); + panic_fmt::("add", "component", "to", self.entity); } } }