Skip to main content

bevy_ecs/system/commands/
entity_command.rs

1//! Contains the definition of the [`EntityCommand`] trait,
2//! as well as the blanket implementation of the trait for closures.
3//!
4//! It also contains functions that return closures for use with
5//! [`EntityCommands`](crate::system::EntityCommands).
6
7use alloc::{string::ToString, vec::Vec};
8#[cfg(not(feature = "trace"))]
9use log::info;
10#[cfg(feature = "trace")]
11use tracing::info;
12
13use crate::{
14    bundle::{Bundle, InsertMode},
15    change_detection::MaybeLocation,
16    component::{Component, ComponentId},
17    entity::{Entity, EntityClonerBuilder, OptIn, OptOut},
18    error::EntityCommandOutput,
19    name::Name,
20    observer::IntoEntityObserver,
21    relationship::RelationshipHookMode,
22    system::Command,
23    world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld, World},
24};
25use bevy_ptr::{move_as_ptr, OwningPtr};
26
27use bevy_platform::sync::Arc;
28
29/// A command which gets executed for a given [`Entity`].
30///
31/// Should be used with [`EntityCommands::queue`](crate::system::EntityCommands::queue).
32///
33/// The `Out` generic parameter is the returned "output" of the command.
34///
35/// # Examples
36///
37/// ```
38/// # use std::collections::HashSet;
39/// # use bevy_ecs::prelude::*;
40/// use bevy_ecs::system::EntityCommand;
41/// #
42/// # #[derive(Component, PartialEq)]
43/// # struct Name(String);
44/// # impl Name {
45/// #   fn new(s: String) -> Self { Name(s) }
46/// #   fn as_str(&self) -> &str { &self.0 }
47/// # }
48///
49/// #[derive(Resource, Default)]
50/// struct Counter(i64);
51///
52/// /// A `Command` which names an entity based on a global counter.
53/// fn count_name(mut entity: EntityWorldMut) {
54///     // Get the current value of the counter, and increment it for next time.
55///     let i = {
56///         let mut counter = entity.resource_mut::<Counter>();
57///         let i = counter.0;
58///         counter.0 += 1;
59///         i
60///     };
61///     // Name the entity after the value of the counter.
62///     entity.insert(Name::new(format!("Entity #{i}")));
63/// }
64///
65/// // App creation boilerplate omitted...
66/// # let mut world = World::new();
67/// # world.init_resource::<Counter>();
68/// #
69/// # let mut setup_schedule = Schedule::default();
70/// # setup_schedule.add_systems(setup);
71/// # let mut assert_schedule = Schedule::default();
72/// # assert_schedule.add_systems(assert_names);
73/// #
74/// # setup_schedule.run(&mut world);
75/// # assert_schedule.run(&mut world);
76///
77/// fn setup(mut commands: Commands) {
78///     commands.spawn_empty().queue(count_name);
79///     commands.spawn_empty().queue(count_name);
80/// }
81///
82/// fn assert_names(named: Query<&Name>) {
83///     // We use a HashSet because we do not care about the order.
84///     let names: HashSet<_> = named.iter().map(Name::as_str).collect();
85///     assert_eq!(names, HashSet::from_iter(["Entity #0", "Entity #1"]));
86/// }
87/// ```
88pub trait EntityCommand: Send + 'static {
89    /// The return type of [`apply`](EntityCommand::apply).
90    type Out: EntityCommandOutput;
91
92    /// Executes this command for the given [`Entity`].
93    fn apply(self, entity: EntityWorldMut) -> Self::Out;
94
95    /// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that
96    /// internally runs the [`EntityCommand`] on that entity.
97    #[inline]
98    fn with_entity(self, entity: Entity) -> impl Command
99    where
100        Self: Sized,
101    {
102        move |world: &mut World| {
103            let entity = world.get_entity_mut(entity)?;
104            self.apply(entity).into_result()
105        }
106    }
107}
108
109/// An error that occurs when running an [`EntityCommand`] on a specific entity.
110#[derive(#[allow(unused_qualifications)]
#[automatically_derived]
impl<E> ::thiserror::__private21::Error for EntityCommandError<E> where
    Self: ::core::fmt::Debug + ::core::fmt::Display {
    fn source(&self)
        ->
            ::core::option::Option<&(dyn ::thiserror::__private21::Error +
            'static)> {
        use ::thiserror::__private21::AsDynError as _;

        #[allow(deprecated)]
        match self {
            EntityCommandError::EntityFetchError { 0: transparent } =>
                ::thiserror::__private21::Error::source(transparent.as_dyn_error()),
            EntityCommandError::CommandFailed { .. } =>
                ::core::option::Option::None,
        }
    }
}
#[allow(unused_qualifications)]
#[automatically_derived]
impl<E> ::core::fmt::Display for EntityCommandError<E> where
    E: ::core::fmt::Display {
    fn fmt(&self, __formatter: &mut ::core::fmt::Formatter)
        -> ::core::fmt::Result {
        use ::thiserror::__private21::AsDisplay as _;

        #[allow(unused_variables, deprecated, clippy ::
        used_underscore_binding)]
        match self {
            EntityCommandError::EntityFetchError(_0) =>
                ::core::fmt::Display::fmt(_0, __formatter),
            EntityCommandError::CommandFailed(_0) =>
                match (_0.as_display(),) {
                    (__display0,) =>
                        __formatter.write_fmt(format_args!("{0}", __display0)),
                },
        }
    }
}
EntityCommandError<E>
#[allow(clippy :: redundant_field_names)]
fn from(source: EntityMutableFetchError) -> Self {
    EntityCommandError::EntityFetchError { 0: source }
}thiserror::Error, #[automatically_derived]
impl<E: ::core::fmt::Debug> ::core::fmt::Debug for EntityCommandError<E> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::EntityFetchError(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "EntityFetchError", &__self_0),
            Self::CommandFailed(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "CommandFailed", &__self_0),
        }
    }
}Debug)]
111pub enum EntityCommandError<E> {
112    /// The entity this [`EntityCommand`] tried to run on could not be fetched.
113    #[error(transparent)]
114    EntityFetchError(#[from] EntityMutableFetchError),
115    /// An error that occurred while running the [`EntityCommand`].
116    #[error("{0}")]
117    CommandFailed(E),
118}
119
120impl<Out, F> EntityCommand for F
121where
122    F: FnOnce(EntityWorldMut) -> Out + Send + 'static,
123    Out: EntityCommandOutput,
124{
125    type Out = Out;
126
127    fn apply(self, entity: EntityWorldMut) -> Self::Out {
128        self(entity)
129    }
130}
131
132impl<Out, F> EntityCommand for Arc<F>
133where
134    F: Fn(EntityWorldMut) -> Out + Send + Sync + ?Sized + 'static,
135    Out: EntityCommandOutput + 'static,
136{
137    type Out = Out;
138
139    fn apply(self, entity: EntityWorldMut) -> Self::Out {
140        self(entity)
141    }
142}
143
144/// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity.
145#[track_caller]
146pub fn insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand {
147    let caller = MaybeLocation::caller();
148    move |mut entity: EntityWorldMut| {
149        let mut bundle = ::core::mem::MaybeUninit::new(bundle);
let bundle = unsafe { ::bevy_ptr::MovingPtr::from_value(&mut bundle) };move_as_ptr!(bundle);
150        entity.insert_with_caller(bundle, mode, caller, RelationshipHookMode::Run);
151    }
152}
153
154/// An [`EntityCommand`] that adds a dynamic component to an entity.
155///
156/// # Safety
157///
158/// - [`ComponentId`] must be from the same world as the target entity.
159/// - `T` must have the same layout as the one passed during `component_id` creation.
160#[track_caller]
161pub unsafe fn insert_by_id<T: Send + 'static>(
162    component_id: ComponentId,
163    value: T,
164    mode: InsertMode,
165) -> impl EntityCommand {
166    let caller = MaybeLocation::caller();
167    move |mut entity: EntityWorldMut| {
168        // SAFETY:
169        // - `component_id` safety is ensured by the caller
170        // - `ptr` is valid within the `make` block
171        OwningPtr::make(value, |ptr| unsafe {
172            entity.insert_by_id_with_caller(
173                component_id,
174                ptr,
175                mode,
176                caller,
177                RelationshipHookMode::Run,
178            );
179        });
180    }
181}
182
183/// An [`EntityCommand`] that adds a component to an entity using
184/// the component's [`FromWorld`] implementation.
185///
186/// `T::from_world` will only be invoked if the component will actually be inserted.
187/// In other words, `T::from_world` will *not* be invoked if `mode` is [`InsertMode::Keep`]
188/// and the entity already has the component.
189#[track_caller]
190pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
191    let caller = MaybeLocation::caller();
192    move |mut entity: EntityWorldMut| {
193        if !(mode == InsertMode::Keep && entity.contains::<T>()) {
194            let value = entity.world_scope(|world| T::from_world(world));
195            let mut value = ::core::mem::MaybeUninit::new(value);
let value = unsafe { ::bevy_ptr::MovingPtr::from_value(&mut value) };move_as_ptr!(value);
196            entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
197        }
198    }
199}
200
201/// An [`EntityCommand`] that adds a component to an entity using
202/// some function that returns the component.
203///
204/// The function will only be invoked if the component will actually be inserted.
205/// In other words, the function will *not* be invoked if `mode` is [`InsertMode::Keep`]
206/// and the entity already has the component.
207#[track_caller]
208pub fn insert_with<T: Component, F>(component_fn: F, mode: InsertMode) -> impl EntityCommand
209where
210    F: FnOnce() -> T + Send + 'static,
211{
212    let caller = MaybeLocation::caller();
213    move |mut entity: EntityWorldMut| {
214        if !(mode == InsertMode::Keep && entity.contains::<T>()) {
215            let bundle = component_fn();
216            let mut bundle = ::core::mem::MaybeUninit::new(bundle);
let bundle = unsafe { ::bevy_ptr::MovingPtr::from_value(&mut bundle) };move_as_ptr!(bundle);
217            entity.insert_with_caller(bundle, mode, caller, RelationshipHookMode::Run);
218        }
219    }
220}
221
222/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity.
223#[track_caller]
224pub fn remove<T: Bundle>() -> impl EntityCommand {
225    let caller = MaybeLocation::caller();
226    move |mut entity: EntityWorldMut| {
227        entity.remove_with_caller::<T>(caller);
228    }
229}
230
231/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity,
232/// as well as the required components for each component removed.
233#[track_caller]
234pub fn remove_with_requires<T: Bundle>() -> impl EntityCommand {
235    let caller = MaybeLocation::caller();
236    move |mut entity: EntityWorldMut| {
237        entity.remove_with_requires_with_caller::<T>(caller);
238    }
239}
240
241/// An [`EntityCommand`] that removes a dynamic component from an entity.
242#[track_caller]
243pub fn remove_by_id(component_id: ComponentId) -> impl EntityCommand {
244    let caller = MaybeLocation::caller();
245    move |mut entity: EntityWorldMut| {
246        entity.remove_by_id_with_caller(component_id, caller);
247    }
248}
249
250/// An [`EntityCommand`] that removes all components from an entity.
251#[track_caller]
252pub fn clear() -> impl EntityCommand {
253    let caller = MaybeLocation::caller();
254    move |mut entity: EntityWorldMut| {
255        entity.clear_with_caller(caller);
256    }
257}
258
259/// An [`EntityCommand`] that removes all components from an entity,
260/// except for those in the given [`Bundle`].
261#[track_caller]
262pub fn retain<T: Bundle>() -> impl EntityCommand {
263    let caller = MaybeLocation::caller();
264    move |mut entity: EntityWorldMut| {
265        entity.retain_with_caller::<T>(caller);
266    }
267}
268
269/// An [`EntityCommand`] that despawns an entity.
270///
271/// # Note
272///
273/// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget)
274/// that is configured to despawn descendants.
275///
276/// For example, this will recursively despawn [`Children`](crate::hierarchy::Children).
277#[track_caller]
278pub fn despawn() -> impl EntityCommand {
279    let caller = MaybeLocation::caller();
280    move |entity: EntityWorldMut| {
281        entity.despawn_with_caller(caller);
282    }
283}
284
285/// An [`EntityCommand`] that creates an [`Observer`](crate::observer::Observer)
286/// watching for an [`EntityEvent`](crate::event::EntityEvent) of type `E` whose
287/// [`event_target`](crate::event::EntityEvent::event_target) targets this entity.
288///
289/// Accepts any type that implements [`IntoEntityObserver`], including:
290/// - Observer systems (closures or functions implementing [`IntoObserverSystem`](crate::system::IntoObserverSystem))
291/// - Observer systems with run conditions (via `.run_if()`)
292#[track_caller]
293pub fn observe<M>(observer: impl IntoEntityObserver<M>) -> impl EntityCommand {
294    let caller = MaybeLocation::caller();
295    move |mut entity: EntityWorldMut| {
296        entity.observe_with_caller(observer, caller);
297    }
298}
299
300/// An [`EntityCommand`] that clones parts of an entity onto another entity,
301/// configured through [`EntityClonerBuilder`].
302///
303/// This builder tries to clone every component from the source entity except
304/// for components that were explicitly denied, for example by using the
305/// [`deny`](EntityClonerBuilder<OptOut>::deny) method.
306///
307/// Required components are not considered by denied components and must be
308/// explicitly denied as well if desired.
309pub fn clone_with_opt_out(
310    target: Entity,
311    config: impl FnOnce(&mut EntityClonerBuilder<OptOut>) + Send + Sync + 'static,
312) -> impl EntityCommand {
313    move |mut entity: EntityWorldMut| {
314        entity.clone_with_opt_out(target, config);
315    }
316}
317
318/// An [`EntityCommand`] that clones parts of an entity onto another entity,
319/// configured through [`EntityClonerBuilder`].
320///
321/// This builder tries to clone every component that was explicitly allowed
322/// from the source entity, for example by using the
323/// [`allow`](EntityClonerBuilder<OptIn>::allow) method.
324///
325/// Required components are also cloned when the target entity does not contain them.
326pub fn clone_with_opt_in(
327    target: Entity,
328    config: impl FnOnce(&mut EntityClonerBuilder<OptIn>) + Send + Sync + 'static,
329) -> impl EntityCommand {
330    move |mut entity: EntityWorldMut| {
331        entity.clone_with_opt_in(target, config);
332    }
333}
334
335/// An [`EntityCommand`] that clones the specified components of an entity
336/// and inserts them into another entity.
337pub fn clone_components<B: Bundle>(target: Entity) -> impl EntityCommand {
338    move |mut entity: EntityWorldMut| {
339        entity.clone_components::<B>(target);
340    }
341}
342
343/// An [`EntityCommand`] moves the specified components of this entity into another entity.
344///
345/// Components with [`Ignore`] clone behavior will not be moved, while components that
346/// have a [`Custom`] clone behavior will be cloned using it and then removed from the source entity.
347/// All other components will be moved without any other special handling.
348///
349/// Note that this will trigger `on_remove` hooks/observers on this entity and `on_insert`/`on_add` hooks/observers on the target entity.
350///
351/// # Panics
352///
353/// The command will panic when applied if the target entity does not exist.
354///
355/// [`Ignore`]: crate::component::ComponentCloneBehavior::Ignore
356/// [`Custom`]: crate::component::ComponentCloneBehavior::Custom
357pub fn move_components<B: Bundle>(target: Entity) -> impl EntityCommand {
358    move |mut entity: EntityWorldMut| {
359        entity.move_components::<B>(target);
360    }
361}
362
363/// An [`EntityCommand`] that logs the components of an entity.
364pub fn log_components() -> impl EntityCommand {
365    move |entity: EntityWorldMut| {
366        let name = entity.get::<Name>().map(ToString::to_string);
367        let id = entity.id();
368        let mut components: Vec<_> = entity
369            .world()
370            .inspect_entity(id)
371            .expect("Entity existence is verified before an EntityCommand is executed")
372            .map(|(_, info)| info.name().to_string())
373            .collect();
374        components.sort();
375
376        #[cfg(not(feature = "debug"))]
377        {
378            let component_count = components.len();
379            #[cfg(feature = "trace")]
380            {
381                if let Some(name) = name {
382                    info!(id=?id, name=?name, ?component_count, "log_components. Enable the `debug` feature to log component names.");
383                } else {
384                    info!(id=?id, ?component_count, "log_components. Enable the `debug` feature to log component names.");
385                }
386            }
387            #[cfg(not(feature = "trace"))]
388            {
389                let name = name
390                    .map(|name| alloc::format!(" ({name})"))
391                    .unwrap_or_default();
392                info!("Entity {id}{name}: {component_count} components. Enable the `debug` feature to log component names.");
393            }
394        }
395
396        #[cfg(feature = "debug")]
397        {
398            #[cfg(feature = "trace")]
399            {
400                if let Some(name) = name {
401                    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event src/system/commands/entity_command.rs:401",
                        "bevy_ecs::system::commands::entity_command",
                        ::tracing::Level::INFO,
                        ::tracing_core::__macro_support::Option::Some("src/system/commands/entity_command.rs"),
                        ::tracing_core::__macro_support::Option::Some(401u32),
                        ::tracing_core::__macro_support::Option::Some("bevy_ecs::system::commands::entity_command"),
                        ::tracing_core::field::FieldSet::new(&["message",
                                        {
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("id")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("id");
                                            NAME.as_str()
                                        },
                                        {
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("name")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("name");
                                            NAME.as_str()
                                        },
                                        {
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("components")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("components");
                                            NAME.as_str()
                                        }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::INFO <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("log_components")
                                            as &dyn ::tracing::field::Value)),
                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&id)
                                            as &dyn ::tracing::field::Value)),
                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&name)
                                            as &dyn ::tracing::field::Value)),
                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&components)
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};info!(id=?id, name=?name, ?components, "log_components");
402                } else {
403                    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event src/system/commands/entity_command.rs:403",
                        "bevy_ecs::system::commands::entity_command",
                        ::tracing::Level::INFO,
                        ::tracing_core::__macro_support::Option::Some("src/system/commands/entity_command.rs"),
                        ::tracing_core::__macro_support::Option::Some(403u32),
                        ::tracing_core::__macro_support::Option::Some("bevy_ecs::system::commands::entity_command"),
                        ::tracing_core::field::FieldSet::new(&["message",
                                        {
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("id")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("id");
                                            NAME.as_str()
                                        },
                                        {
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("components")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("components");
                                            NAME.as_str()
                                        }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::INFO <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("log_components")
                                            as &dyn ::tracing::field::Value)),
                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&id)
                                            as &dyn ::tracing::field::Value)),
                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&components)
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};info!(id=?id, ?components, "log_components");
404                }
405            }
406            #[cfg(not(feature = "trace"))]
407            {
408                let name = name
409                    .map(|name| alloc::format!(" ({name})"))
410                    .unwrap_or_default();
411                info!("Entity {id}{name}: {components:?}");
412            }
413        }
414    }
415}