diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index a264f0b14bab9..af3003b21e825 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -271,6 +271,16 @@ pub trait BundleEffect { fn apply(self, entity: &mut EntityWorldMut); } +/// BundleEffect can be implemented directly or using the implementation for `FnOnce(&mut EntityWorldMut)`. +impl BundleEffect for E +where + E: FnOnce(&mut EntityWorldMut) + Send + Sync + 'static, +{ + fn apply(self, entity: &mut EntityWorldMut) { + (self)(entity); + } +} + // SAFETY: // - `Bundle::component_ids` calls `ids` for C's component id (and nothing else) // - `Bundle::get_components` is called exactly once for C and passes the component's storage type based on its associated constant. @@ -2118,6 +2128,65 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { }); } +/// [`Patch`] wraps a [`BundleEffect`] into a [`Bundle`] allowing +/// you to leverage the power of a [`BundleEffect`] without having +/// to implement your own unsafe implementation of the Bundle trait. +/// [`BundleEffect`]s get access to [`EntityWorldMut`] on insert allowing for +/// powerful modifications to the world. [`BundleEffect`]s also power other Bevy +/// helper bundles like [`crate::spawn::SpawnIter`], [`crate::spawn::SpawnWith`], and [`crate::observer::Observer::as_bundle`]. +/// You can implement [`BundleEffect`] directly or use the implementation of [`BundleEffect`] +/// for `FnOnce(&mut EntityWorldMut)`. +/// +/// Note: [`crate::component::Component`] Hooks are almost as powerful as +/// [`BundleEffect`]s and should be used instead where possible. [`BundleEffect`]s +/// should only be used where structural ECS changes are required. For more details +/// see [`crate::world::DeferredWorld`] +/// +/// Example, using [`Patch`] to add a [`crate::resource::Resource`] to the world +/// this is not possible with `[crate::component::Component]` Hooks because it is +/// a structural modification +/// +/// ```rust +/// commands.spawn(( +/// Node::default(), +/// Patch(|entity: &mut EntityWorldMut| { +/// // SAFETY: We don't modify the entity's position so this is safe +/// let world = unsafe { entity.world_mut() }; +/// +/// world.insert_resource(SomeResource); +/// }), +/// )) +/// ``` +pub struct Patch(pub E) +where + E: BundleEffect; + +impl DynamicBundle for Patch +where + E: BundleEffect, +{ + type Effect = E; + + fn get_components(self, _func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect { + self.0 + } +} + +unsafe impl Bundle for Patch +where + F: FnOnce(&mut EntityWorldMut) + Send + Sync + 'static, +{ + fn component_ids(_components: &mut ComponentsRegistrator, _ids: &mut impl FnMut(ComponentId)) {} + + fn get_component_ids(_components: &Components, _ids: &mut impl FnMut(Option)) {} + + fn register_required_components( + _components: &mut ComponentsRegistrator, + _required_components: &mut RequiredComponents, + ) { + } +} + #[cfg(test)] mod tests { use crate::{ diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 283f516d51a1c..97cb3799d0e31 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -2,6 +2,7 @@ use alloc::{boxed::Box, vec}; use core::any::Any; use crate::{ + bundle::Patch, component::{ComponentId, Mutable, StorageType}, error::{ErrorContext, ErrorHandler}, lifecycle::{ComponentHook, HookContext}, @@ -296,6 +297,33 @@ impl Observer { pub fn descriptor(&self) -> &ObserverDescriptor { &self.descriptor } + + /// Creates a [`Bundle`] that will spawn a new [`Observer`] (its own Entity) + /// observing this entity it's spawned on. + /// + /// Example + /// ```rust + /// commands.spawn( + /// Node::default(), + /// Text::from("Not clicked"), + /// Observer::as_bundle( + /// |trigger: Trigger>, + /// mut text: Query<&mut Text>| { + /// **text.get_mut(trigger.target).unwrap() = "Clicked!".to_string(); + /// }) + /// ) + /// ``` + pub fn as_bundle(observer: O) -> impl Bundle + where + E: Event, + B: Bundle, + M: Send + Sync + 'static, + O: IntoObserverSystem + Send + Sync + 'static, + { + Patch(move |entity: &mut EntityWorldMut| { + entity.observe(observer); + }) + } } impl Component for Observer {