diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index fb488f5e7f..d9db569d51 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -12,6 +12,7 @@ use crate::{ world::command_queue::RawCommandQueue, world::{Command, CommandQueue, EntityWorldMut, FromWorld, World}, }; +use bevy_ptr::OwningPtr; use bevy_utils::tracing::{error, info}; pub use parallel_scope::*; @@ -937,6 +938,50 @@ impl EntityCommands<'_> { self.add(insert(bundle)) } + /// Adds a dynamic component to an entity. + /// + /// See [`EntityWorldMut::insert_by_id`] for more information. + /// + /// # Panics + /// + /// The command will panic when applied if the associated entity does not exist. + /// + /// To avoid a panic in this case, use the command [`Self::try_insert_by_id`] instead. + /// + /// # Safety + /// + /// - [`ComponentId`] must be from the same world as `self`. + /// - `T` must have the same layout as the one passed during `component_id` creation. + pub unsafe fn insert_by_id( + &mut self, + component_id: ComponentId, + value: T, + ) -> &mut Self { + // SAFETY: same invariants as parent call + self.add(unsafe {insert_by_id(component_id, value, move |entity| { + panic!("error[B0003]: Could not insert a component {component_id:?} (with type {}) for entity {entity:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/#b0003", std::any::type_name::()); + })}); + self + } + + /// Attempts to add a dynamic component to an entity. + /// + /// See [`EntityWorldMut::insert_by_id`] for more information. + /// + /// # Safety + /// + /// - [`ComponentId`] must be from the same world as `self`. + /// - `T` must have the same layout as the one passed during `component_id` creation. + pub unsafe fn try_insert_by_id( + &mut self, + component_id: ComponentId, + value: T, + ) -> &mut Self { + // SAFETY: same invariants as parent call + self.add(unsafe { insert_by_id(component_id, value, |_| {}) }); + self + } + /// Tries to add a [`Bundle`] of components to the entity. /// /// This will overwrite any previous value(s) of the same component type. @@ -1252,6 +1297,31 @@ fn try_insert(bundle: impl Bundle) -> impl EntityCommand { } } +/// An [`EntityCommand`] that attempts to add the dynamic component to an entity. +/// +/// # Safety +/// +/// - The returned `EntityCommand` must be queued for the world where `component_id` was created. +/// - `T` must be the type represented by `component_id`. +unsafe fn insert_by_id( + component_id: ComponentId, + value: T, + on_none_entity: impl FnOnce(Entity) + Send + 'static, +) -> impl EntityCommand { + move |entity, world: &mut World| { + if let Some(mut entity) = world.get_entity_mut(entity) { + // SAFETY: + // - `component_id` safety is ensured by the caller + // - `ptr` is valid within the `make` block; + OwningPtr::make(value, |ptr| unsafe { + entity.insert_by_id(component_id, ptr); + }); + } else { + on_none_entity(entity); + } + } +} + /// An [`EntityCommand`] 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.