Refactored ComponentHook Parameters into HookContext (#17503)
				
					
				
			# Objective
- Make the function signature for `ComponentHook` less verbose
## Solution
- Refactored `Entity`, `ComponentId`, and `Option<&Location>` into a new
`HookContext` struct.
## Testing
- CI
---
## Migration Guide
Update the function signatures for your component hooks to only take 2
arguments, `world` and `context`. Note that because `HookContext` is
plain data with all members public, you can use de-structuring to
simplify migration.
```rust
// Before
fn my_hook(
    mut world: DeferredWorld,
    entity: Entity,
    component_id: ComponentId,
) { ... }
// After
fn my_hook(
    mut world: DeferredWorld,
    HookContext { entity, component_id, caller }: HookContext,
) { ... }
``` 
Likewise, if you were discarding certain parameters, you can use `..` in
the de-structuring:
```rust
// Before
fn my_hook(
    mut world: DeferredWorld,
    entity: Entity,
    _: ComponentId,
) { ... }
// After
fn my_hook(
    mut world: DeferredWorld,
    HookContext { entity, .. }: HookContext,
) { ... }
```
			
			
This commit is contained in:
		
							parent
							
								
									c7ddec571d
								
							
						
					
					
						commit
						41e79ae826
					
				| @ -70,11 +70,11 @@ impl Component for OrderIndependentTransparencySettings { | |||||||
|     type Mutability = Mutable; |     type Mutability = Mutable; | ||||||
| 
 | 
 | ||||||
|     fn register_component_hooks(hooks: &mut ComponentHooks) { |     fn register_component_hooks(hooks: &mut ComponentHooks) { | ||||||
|         hooks.on_add(|world, entity, _, caller| { |         hooks.on_add(|world, context| { | ||||||
|             if let Some(value) = world.get::<OrderIndependentTransparencySettings>(entity) { |             if let Some(value) = world.get::<OrderIndependentTransparencySettings>(context.entity) { | ||||||
|                 if value.layer_count > 32 { |                 if value.layer_count > 32 { | ||||||
|                     warn!("{}OrderIndependentTransparencySettings layer_count set to {} might be too high.", 
 |                     warn!("{}OrderIndependentTransparencySettings layer_count set to {} might be too high.", 
 | ||||||
|                         caller.map(|location|format!("{location}: ")).unwrap_or_default(), |                         context.caller.map(|location|format!("{location}: ")).unwrap_or_default(), | ||||||
|                         value.layer_count |                         value.layer_count | ||||||
|                     ); |                     ); | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -1704,9 +1704,8 @@ fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, remove: &[T]) { | |||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use crate as bevy_ecs; |     use crate as bevy_ecs; | ||||||
|     use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; |     use crate::{component::HookContext, prelude::*, world::DeferredWorld}; | ||||||
|     use alloc::vec; |     use alloc::vec; | ||||||
|     use core::panic::Location; |  | ||||||
| 
 | 
 | ||||||
|     #[derive(Component)] |     #[derive(Component)] | ||||||
|     struct A; |     struct A; | ||||||
| @ -1715,39 +1714,19 @@ mod tests { | |||||||
|     #[component(on_add = a_on_add, on_insert = a_on_insert, on_replace = a_on_replace, on_remove = a_on_remove)] |     #[component(on_add = a_on_add, on_insert = a_on_insert, on_replace = a_on_replace, on_remove = a_on_remove)] | ||||||
|     struct AMacroHooks; |     struct AMacroHooks; | ||||||
| 
 | 
 | ||||||
|     fn a_on_add( |     fn a_on_add(mut world: DeferredWorld, _: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         _: Entity, |  | ||||||
|         _: ComponentId, |  | ||||||
|         _: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world.resource_mut::<R>().assert_order(0); |         world.resource_mut::<R>().assert_order(0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn a_on_insert<T1, T2>( |     fn a_on_insert(mut world: DeferredWorld, _: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         _: T1, |  | ||||||
|         _: T2, |  | ||||||
|         _: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world.resource_mut::<R>().assert_order(1); |         world.resource_mut::<R>().assert_order(1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn a_on_replace<T1, T2>( |     fn a_on_replace(mut world: DeferredWorld, _: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         _: T1, |  | ||||||
|         _: T2, |  | ||||||
|         _: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world.resource_mut::<R>().assert_order(2); |         world.resource_mut::<R>().assert_order(2); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn a_on_remove<T1, T2>( |     fn a_on_remove(mut world: DeferredWorld, _: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         _: T1, |  | ||||||
|         _: T2, |  | ||||||
|         _: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world.resource_mut::<R>().assert_order(3); |         world.resource_mut::<R>().assert_order(3); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -1780,10 +1759,10 @@ mod tests { | |||||||
|         world.init_resource::<R>(); |         world.init_resource::<R>(); | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<A>() |             .register_component_hooks::<A>() | ||||||
|             .on_add(|mut world, _, _, _| world.resource_mut::<R>().assert_order(0)) |             .on_add(|mut world, _| world.resource_mut::<R>().assert_order(0)) | ||||||
|             .on_insert(|mut world, _, _, _| world.resource_mut::<R>().assert_order(1)) |             .on_insert(|mut world, _| world.resource_mut::<R>().assert_order(1)) | ||||||
|             .on_replace(|mut world, _, _, _| world.resource_mut::<R>().assert_order(2)) |             .on_replace(|mut world, _| world.resource_mut::<R>().assert_order(2)) | ||||||
|             .on_remove(|mut world, _, _, _| world.resource_mut::<R>().assert_order(3)); |             .on_remove(|mut world, _| world.resource_mut::<R>().assert_order(3)); | ||||||
| 
 | 
 | ||||||
|         let entity = world.spawn(A).id(); |         let entity = world.spawn(A).id(); | ||||||
|         world.despawn(entity); |         world.despawn(entity); | ||||||
| @ -1807,10 +1786,10 @@ mod tests { | |||||||
|         world.init_resource::<R>(); |         world.init_resource::<R>(); | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<A>() |             .register_component_hooks::<A>() | ||||||
|             .on_add(|mut world, _, _, _| world.resource_mut::<R>().assert_order(0)) |             .on_add(|mut world, _| world.resource_mut::<R>().assert_order(0)) | ||||||
|             .on_insert(|mut world, _, _, _| world.resource_mut::<R>().assert_order(1)) |             .on_insert(|mut world, _| world.resource_mut::<R>().assert_order(1)) | ||||||
|             .on_replace(|mut world, _, _, _| world.resource_mut::<R>().assert_order(2)) |             .on_replace(|mut world, _| world.resource_mut::<R>().assert_order(2)) | ||||||
|             .on_remove(|mut world, _, _, _| world.resource_mut::<R>().assert_order(3)); |             .on_remove(|mut world, _| world.resource_mut::<R>().assert_order(3)); | ||||||
| 
 | 
 | ||||||
|         let mut entity = world.spawn_empty(); |         let mut entity = world.spawn_empty(); | ||||||
|         entity.insert(A); |         entity.insert(A); | ||||||
| @ -1824,8 +1803,8 @@ mod tests { | |||||||
|         let mut world = World::new(); |         let mut world = World::new(); | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<A>() |             .register_component_hooks::<A>() | ||||||
|             .on_replace(|mut world, _, _, _| world.resource_mut::<R>().assert_order(0)) |             .on_replace(|mut world, _| world.resource_mut::<R>().assert_order(0)) | ||||||
|             .on_insert(|mut world, _, _, _| { |             .on_insert(|mut world, _| { | ||||||
|                 if let Some(mut r) = world.get_resource_mut::<R>() { |                 if let Some(mut r) = world.get_resource_mut::<R>() { | ||||||
|                     r.assert_order(1); |                     r.assert_order(1); | ||||||
|                 } |                 } | ||||||
| @ -1846,22 +1825,22 @@ mod tests { | |||||||
|         world.init_resource::<R>(); |         world.init_resource::<R>(); | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<A>() |             .register_component_hooks::<A>() | ||||||
|             .on_add(|mut world, entity, _, _| { |             .on_add(|mut world, context| { | ||||||
|                 world.resource_mut::<R>().assert_order(0); |                 world.resource_mut::<R>().assert_order(0); | ||||||
|                 world.commands().entity(entity).insert(B); |                 world.commands().entity(context.entity).insert(B); | ||||||
|             }) |             }) | ||||||
|             .on_remove(|mut world, entity, _, _| { |             .on_remove(|mut world, context| { | ||||||
|                 world.resource_mut::<R>().assert_order(2); |                 world.resource_mut::<R>().assert_order(2); | ||||||
|                 world.commands().entity(entity).remove::<B>(); |                 world.commands().entity(context.entity).remove::<B>(); | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<B>() |             .register_component_hooks::<B>() | ||||||
|             .on_add(|mut world, entity, _, _| { |             .on_add(|mut world, context| { | ||||||
|                 world.resource_mut::<R>().assert_order(1); |                 world.resource_mut::<R>().assert_order(1); | ||||||
|                 world.commands().entity(entity).remove::<A>(); |                 world.commands().entity(context.entity).remove::<A>(); | ||||||
|             }) |             }) | ||||||
|             .on_remove(|mut world, _, _, _| { |             .on_remove(|mut world, _| { | ||||||
|                 world.resource_mut::<R>().assert_order(3); |                 world.resource_mut::<R>().assert_order(3); | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
| @ -1878,27 +1857,27 @@ mod tests { | |||||||
|         world.init_resource::<R>(); |         world.init_resource::<R>(); | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<A>() |             .register_component_hooks::<A>() | ||||||
|             .on_add(|mut world, entity, _, _| { |             .on_add(|mut world, context| { | ||||||
|                 world.resource_mut::<R>().assert_order(0); |                 world.resource_mut::<R>().assert_order(0); | ||||||
|                 world.commands().entity(entity).insert(B).insert(C); |                 world.commands().entity(context.entity).insert(B).insert(C); | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<B>() |             .register_component_hooks::<B>() | ||||||
|             .on_add(|mut world, entity, _, _| { |             .on_add(|mut world, context| { | ||||||
|                 world.resource_mut::<R>().assert_order(1); |                 world.resource_mut::<R>().assert_order(1); | ||||||
|                 world.commands().entity(entity).insert(D); |                 world.commands().entity(context.entity).insert(D); | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<C>() |             .register_component_hooks::<C>() | ||||||
|             .on_add(|mut world, _, _, _| { |             .on_add(|mut world, _| { | ||||||
|                 world.resource_mut::<R>().assert_order(3); |                 world.resource_mut::<R>().assert_order(3); | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<D>() |             .register_component_hooks::<D>() | ||||||
|             .on_add(|mut world, _, _, _| { |             .on_add(|mut world, _| { | ||||||
|                 world.resource_mut::<R>().assert_order(2); |                 world.resource_mut::<R>().assert_order(2); | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -299,7 +299,7 @@ pub use bevy_ecs_macros::require; | |||||||
| /// - `#[component(on_remove = on_remove_function)]`
 | /// - `#[component(on_remove = on_remove_function)]`
 | ||||||
| ///
 | ///
 | ||||||
| /// ```
 | /// ```
 | ||||||
| /// # use bevy_ecs::component::Component;
 | /// # use bevy_ecs::component::{Component, HookContext};
 | ||||||
| /// # use bevy_ecs::world::DeferredWorld;
 | /// # use bevy_ecs::world::DeferredWorld;
 | ||||||
| /// # use bevy_ecs::entity::Entity;
 | /// # use bevy_ecs::entity::Entity;
 | ||||||
| /// # use bevy_ecs::component::ComponentId;
 | /// # use bevy_ecs::component::ComponentId;
 | ||||||
| @ -315,12 +315,12 @@ pub use bevy_ecs_macros::require; | |||||||
| /// // #[component(on_replace = my_on_replace_hook, on_remove = my_on_remove_hook)]
 | /// // #[component(on_replace = my_on_replace_hook, on_remove = my_on_remove_hook)]
 | ||||||
| /// struct ComponentA;
 | /// struct ComponentA;
 | ||||||
| ///
 | ///
 | ||||||
| /// fn my_on_add_hook(world: DeferredWorld, entity: Entity, id: ComponentId, caller: Option<&Location>) {
 | /// fn my_on_add_hook(world: DeferredWorld, context: HookContext) {
 | ||||||
| ///     // ...
 | ///     // ...
 | ||||||
| /// }
 | /// }
 | ||||||
| ///
 | ///
 | ||||||
| /// // You can also omit writing some types using generics.
 | /// // You can also destructure items directly in the signature
 | ||||||
| /// fn my_on_insert_hook<T1, T2>(world: DeferredWorld, _: T1, _: T2, caller: Option<&Location>) {
 | /// fn my_on_insert_hook(world: DeferredWorld, HookContext { caller, .. }: HookContext) {
 | ||||||
| ///     // ...
 | ///     // ...
 | ||||||
| /// }
 | /// }
 | ||||||
| /// ```
 | /// ```
 | ||||||
| @ -498,9 +498,18 @@ pub enum StorageType { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`.
 | /// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`.
 | ||||||
| /// The caller location is `Some` if the `track_caller` feature is enabled.
 | pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, HookContext); | ||||||
| pub type ComponentHook = | 
 | ||||||
|     for<'w> fn(DeferredWorld<'w>, Entity, ComponentId, Option<&'static Location<'static>>); | /// Context provided to a [`ComponentHook`].
 | ||||||
|  | #[derive(Clone, Copy, Debug)] | ||||||
|  | pub struct HookContext { | ||||||
|  |     /// The [`Entity`] this hook was invoked for.
 | ||||||
|  |     pub entity: Entity, | ||||||
|  |     /// The [`ComponentId`] this hook was invoked for.
 | ||||||
|  |     pub component_id: ComponentId, | ||||||
|  |     /// The caller location is `Some` if the `track_caller` feature is enabled.
 | ||||||
|  |     pub caller: Option<&'static Location<'static>>, | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
 | /// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
 | ||||||
| ///
 | ///
 | ||||||
| @ -537,14 +546,14 @@ pub type ComponentHook = | |||||||
| /// let mut tracked_component_query = world.query::<&MyTrackedComponent>();
 | /// let mut tracked_component_query = world.query::<&MyTrackedComponent>();
 | ||||||
| /// assert!(tracked_component_query.iter(&world).next().is_none());
 | /// assert!(tracked_component_query.iter(&world).next().is_none());
 | ||||||
| ///
 | ///
 | ||||||
| /// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, entity, _component_id, _caller| {
 | /// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, context| {
 | ||||||
| ///    let mut tracked_entities = world.resource_mut::<TrackedEntities>();
 | ///    let mut tracked_entities = world.resource_mut::<TrackedEntities>();
 | ||||||
| ///   tracked_entities.0.insert(entity);
 | ///   tracked_entities.0.insert(context.entity);
 | ||||||
| /// });
 | /// });
 | ||||||
| ///
 | ///
 | ||||||
| /// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, entity, _component_id, _caller| {
 | /// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, context| {
 | ||||||
| ///   let mut tracked_entities = world.resource_mut::<TrackedEntities>();
 | ///   let mut tracked_entities = world.resource_mut::<TrackedEntities>();
 | ||||||
| ///   tracked_entities.0.remove(&entity);
 | ///   tracked_entities.0.remove(&context.entity);
 | ||||||
| /// });
 | /// });
 | ||||||
| ///
 | ///
 | ||||||
| /// let entity = world.spawn(MyTrackedComponent).id();
 | /// let entity = world.spawn(MyTrackedComponent).id();
 | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ use crate::reflect::{ | |||||||
| use crate::{ | use crate::{ | ||||||
|     self as bevy_ecs, |     self as bevy_ecs, | ||||||
|     bundle::Bundle, |     bundle::Bundle, | ||||||
|     component::{Component, ComponentId}, |     component::{Component, HookContext}, | ||||||
|     entity::{Entity, VisitEntities}, |     entity::{Entity, VisitEntities}, | ||||||
|     relationship::{RelatedSpawner, RelatedSpawnerCommands}, |     relationship::{RelatedSpawner, RelatedSpawnerCommands}, | ||||||
|     system::EntityCommands, |     system::EntityCommands, | ||||||
| @ -22,8 +22,8 @@ use crate::{ | |||||||
| }; | }; | ||||||
| use alloc::{format, string::String, vec::Vec}; | use alloc::{format, string::String, vec::Vec}; | ||||||
| use bevy_ecs_macros::VisitEntitiesMut; | use bevy_ecs_macros::VisitEntitiesMut; | ||||||
|  | use core::ops::Deref; | ||||||
| use core::slice; | use core::slice; | ||||||
| use core::{ops::Deref, panic::Location}; |  | ||||||
| use disqualified::ShortName; | use disqualified::ShortName; | ||||||
| use log::warn; | use log::warn; | ||||||
| 
 | 
 | ||||||
| @ -268,9 +268,7 @@ impl<'a> EntityCommands<'a> { | |||||||
| /// contains component `C`. This will print a warning if the parent does not contain `C`.
 | /// contains component `C`. This will print a warning if the parent does not contain `C`.
 | ||||||
| pub fn validate_parent_has_component<C: Component>( | pub fn validate_parent_has_component<C: Component>( | ||||||
|     world: DeferredWorld, |     world: DeferredWorld, | ||||||
|     entity: Entity, |     HookContext { entity, caller, .. }: HookContext, | ||||||
|     _: ComponentId, |  | ||||||
|     caller: Option<&'static Location<'static>>, |  | ||||||
| ) { | ) { | ||||||
|     let entity_ref = world.entity(entity); |     let entity_ref = world.entity(entity); | ||||||
|     let Some(child_of) = entity_ref.get::<ChildOf>() else { |     let Some(child_of) = entity_ref.get::<ChildOf>() else { | ||||||
|  | |||||||
| @ -2030,8 +2030,8 @@ mod tests { | |||||||
|         world.insert_resource(I(0)); |         world.insert_resource(I(0)); | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<Y>() |             .register_component_hooks::<Y>() | ||||||
|             .on_add(|mut world, _, _, _| world.resource_mut::<A>().0 += 1) |             .on_add(|mut world, _| world.resource_mut::<A>().0 += 1) | ||||||
|             .on_insert(|mut world, _, _, _| world.resource_mut::<I>().0 += 1); |             .on_insert(|mut world, _| world.resource_mut::<I>().0 += 1); | ||||||
| 
 | 
 | ||||||
|         // Spawn entity and ensure Y was added
 |         // Spawn entity and ensure Y was added
 | ||||||
|         assert!(world.spawn(X).contains::<Y>()); |         assert!(world.spawn(X).contains::<Y>()); | ||||||
| @ -2060,8 +2060,8 @@ mod tests { | |||||||
|         world.insert_resource(I(0)); |         world.insert_resource(I(0)); | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<Y>() |             .register_component_hooks::<Y>() | ||||||
|             .on_add(|mut world, _, _, _| world.resource_mut::<A>().0 += 1) |             .on_add(|mut world, _| world.resource_mut::<A>().0 += 1) | ||||||
|             .on_insert(|mut world, _, _, _| world.resource_mut::<I>().0 += 1); |             .on_insert(|mut world, _| world.resource_mut::<I>().0 += 1); | ||||||
| 
 | 
 | ||||||
|         // Spawn entity and ensure Y was added
 |         // Spawn entity and ensure Y was added
 | ||||||
|         assert!(world.spawn_empty().insert(X).contains::<Y>()); |         assert!(world.spawn_empty().insert(X).contains::<Y>()); | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| use crate::{ | use crate::{ | ||||||
|     component::{Component, ComponentCloneHandler, ComponentHooks, Mutable, StorageType}, |     component::{ | ||||||
|  |         Component, ComponentCloneHandler, ComponentHooks, HookContext, Mutable, StorageType, | ||||||
|  |     }, | ||||||
|     entity::{ComponentCloneCtx, Entity, EntityCloneBuilder}, |     entity::{ComponentCloneCtx, Entity, EntityCloneBuilder}, | ||||||
|     observer::ObserverState, |     observer::ObserverState, | ||||||
|     world::{DeferredWorld, World}, |     world::{DeferredWorld, World}, | ||||||
| @ -15,7 +17,7 @@ impl Component for ObservedBy { | |||||||
|     type Mutability = Mutable; |     type Mutability = Mutable; | ||||||
| 
 | 
 | ||||||
|     fn register_component_hooks(hooks: &mut ComponentHooks) { |     fn register_component_hooks(hooks: &mut ComponentHooks) { | ||||||
|         hooks.on_remove(|mut world, entity, _, _| { |         hooks.on_remove(|mut world, HookContext { entity, .. }| { | ||||||
|             let observed_by = { |             let observed_by = { | ||||||
|                 let mut component = world.get_mut::<ObservedBy>(entity).unwrap(); |                 let mut component = world.get_mut::<ObservedBy>(entity).unwrap(); | ||||||
|                 core::mem::take(&mut component.0) |                 core::mem::take(&mut component.0) | ||||||
|  | |||||||
| @ -1,9 +1,8 @@ | |||||||
| use alloc::{boxed::Box, vec, vec::Vec}; | use alloc::{boxed::Box, vec, vec::Vec}; | ||||||
| use core::any::Any; | use core::any::Any; | ||||||
| use core::panic::Location; |  | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     component::{ComponentHook, ComponentHooks, ComponentId, Mutable, StorageType}, |     component::{ComponentHook, ComponentHooks, ComponentId, HookContext, Mutable, StorageType}, | ||||||
|     observer::{ObserverDescriptor, ObserverTrigger}, |     observer::{ObserverDescriptor, ObserverTrigger}, | ||||||
|     prelude::*, |     prelude::*, | ||||||
|     query::DebugCheckedUnwrap, |     query::DebugCheckedUnwrap, | ||||||
| @ -67,12 +66,12 @@ impl Component for ObserverState { | |||||||
|     type Mutability = Mutable; |     type Mutability = Mutable; | ||||||
| 
 | 
 | ||||||
|     fn register_component_hooks(hooks: &mut ComponentHooks) { |     fn register_component_hooks(hooks: &mut ComponentHooks) { | ||||||
|         hooks.on_add(|mut world, entity, _, _| { |         hooks.on_add(|mut world, HookContext { entity, .. }| { | ||||||
|             world.commands().queue(move |world: &mut World| { |             world.commands().queue(move |world: &mut World| { | ||||||
|                 world.register_observer(entity); |                 world.register_observer(entity); | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|         hooks.on_remove(|mut world, entity, _, _| { |         hooks.on_remove(|mut world, HookContext { entity, .. }| { | ||||||
|             let descriptor = core::mem::take( |             let descriptor = core::mem::take( | ||||||
|                 &mut world |                 &mut world | ||||||
|                     .entity_mut(entity) |                     .entity_mut(entity) | ||||||
| @ -319,12 +318,12 @@ impl Component for Observer { | |||||||
|     const STORAGE_TYPE: StorageType = StorageType::SparseSet; |     const STORAGE_TYPE: StorageType = StorageType::SparseSet; | ||||||
|     type Mutability = Mutable; |     type Mutability = Mutable; | ||||||
|     fn register_component_hooks(hooks: &mut ComponentHooks) { |     fn register_component_hooks(hooks: &mut ComponentHooks) { | ||||||
|         hooks.on_add(|world, entity, id, caller| { |         hooks.on_add(|world, context| { | ||||||
|             let Some(observe) = world.get::<Self>(entity) else { |             let Some(observe) = world.get::<Self>(context.entity) else { | ||||||
|                 return; |                 return; | ||||||
|             }; |             }; | ||||||
|             let hook = observe.hook_on_add; |             let hook = observe.hook_on_add; | ||||||
|             hook(world, entity, id, caller); |             hook(world, context); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -395,9 +394,7 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>( | |||||||
| /// ensure type parameters match.
 | /// ensure type parameters match.
 | ||||||
| fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>( | fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>( | ||||||
|     mut world: DeferredWorld<'_>, |     mut world: DeferredWorld<'_>, | ||||||
|     entity: Entity, |     HookContext { entity, .. }: HookContext, | ||||||
|     _: ComponentId, |  | ||||||
|     _: Option<&'static Location<'static>>, |  | ||||||
| ) { | ) { | ||||||
|     world.commands().queue(move |world: &mut World| { |     world.commands().queue(move |world: &mut World| { | ||||||
|         let event_id = E::register_component_id(world); |         let event_id = E::register_component_id(world); | ||||||
|  | |||||||
| @ -5,14 +5,13 @@ mod relationship_query; | |||||||
| mod relationship_source_collection; | mod relationship_source_collection; | ||||||
| 
 | 
 | ||||||
| use alloc::format; | use alloc::format; | ||||||
| use core::panic::Location; |  | ||||||
| 
 | 
 | ||||||
| pub use related_methods::*; | pub use related_methods::*; | ||||||
| pub use relationship_query::*; | pub use relationship_query::*; | ||||||
| pub use relationship_source_collection::*; | pub use relationship_source_collection::*; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     component::{Component, ComponentId, Mutable}, |     component::{Component, HookContext, Mutable}, | ||||||
|     entity::Entity, |     entity::Entity, | ||||||
|     system::{ |     system::{ | ||||||
|         command::HandleError, |         command::HandleError, | ||||||
| @ -74,12 +73,7 @@ pub trait Relationship: Component + Sized { | |||||||
|     fn from(entity: Entity) -> Self; |     fn from(entity: Entity) -> Self; | ||||||
| 
 | 
 | ||||||
|     /// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
 |     /// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
 | ||||||
|     fn on_insert( |     fn on_insert(mut world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         entity: Entity, |  | ||||||
|         _: ComponentId, |  | ||||||
|         caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         let target_entity = world.entity(entity).get::<Self>().unwrap().get(); |         let target_entity = world.entity(entity).get::<Self>().unwrap().get(); | ||||||
|         if target_entity == entity { |         if target_entity == entity { | ||||||
|             warn!( |             warn!( | ||||||
| @ -113,12 +107,7 @@ pub trait Relationship: Component + Sized { | |||||||
| 
 | 
 | ||||||
|     /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
 |     /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
 | ||||||
|     // note: think of this as "on_drop"
 |     // note: think of this as "on_drop"
 | ||||||
|     fn on_replace( |     fn on_replace(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         entity: Entity, |  | ||||||
|         _: ComponentId, |  | ||||||
|         _: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         let target_entity = world.entity(entity).get::<Self>().unwrap().get(); |         let target_entity = world.entity(entity).get::<Self>().unwrap().get(); | ||||||
|         if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { |         if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { | ||||||
|             if let Some(mut relationship_target) = |             if let Some(mut relationship_target) = | ||||||
| @ -179,12 +168,7 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized { | |||||||
| 
 | 
 | ||||||
|     /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
 |     /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
 | ||||||
|     // note: think of this as "on_drop"
 |     // note: think of this as "on_drop"
 | ||||||
|     fn on_replace( |     fn on_replace(mut world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         entity: Entity, |  | ||||||
|         _: ComponentId, |  | ||||||
|         caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         // NOTE: this unsafe code is an optimization. We could make this safe, but it would require
 |         // NOTE: this unsafe code is an optimization. We could make this safe, but it would require
 | ||||||
|         // copying the RelationshipTarget collection
 |         // copying the RelationshipTarget collection
 | ||||||
|         // SAFETY: This only reads the Self component and queues Remove commands
 |         // SAFETY: This only reads the Self component and queues Remove commands
 | ||||||
| @ -215,12 +199,7 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized { | |||||||
|     /// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when
 |     /// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when
 | ||||||
|     /// that entity is despawned.
 |     /// that entity is despawned.
 | ||||||
|     // note: think of this as "on_drop"
 |     // note: think of this as "on_drop"
 | ||||||
|     fn on_despawn( |     fn on_despawn(mut world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         entity: Entity, |  | ||||||
|         _: ComponentId, |  | ||||||
|         caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         // NOTE: this unsafe code is an optimization. We could make this safe, but it would require
 |         // NOTE: this unsafe code is an optimization. We could make this safe, but it would require
 | ||||||
|         // copying the RelationshipTarget collection
 |         // copying the RelationshipTarget collection
 | ||||||
|         // SAFETY: This only reads the Self component and queues despawn commands
 |         // SAFETY: This only reads the Self component and queues despawn commands
 | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ use core::panic::Location; | |||||||
| use crate::{ | use crate::{ | ||||||
|     archetype::Archetype, |     archetype::Archetype, | ||||||
|     change_detection::MutUntyped, |     change_detection::MutUntyped, | ||||||
|     component::{ComponentId, Mutable}, |     component::{ComponentId, HookContext, Mutable}, | ||||||
|     entity::Entity, |     entity::Entity, | ||||||
|     event::{Event, EventId, Events, SendBatchIds}, |     event::{Event, EventId, Events, SendBatchIds}, | ||||||
|     observer::{Observers, TriggerTargets}, |     observer::{Observers, TriggerTargets}, | ||||||
| @ -538,12 +538,14 @@ impl<'w> DeferredWorld<'w> { | |||||||
|                 if let Some(hook) = hooks.on_add { |                 if let Some(hook) = hooks.on_add { | ||||||
|                     hook( |                     hook( | ||||||
|                         DeferredWorld { world: self.world }, |                         DeferredWorld { world: self.world }, | ||||||
|  |                         HookContext { | ||||||
|                             entity, |                             entity, | ||||||
|                             component_id, |                             component_id, | ||||||
|                             #[cfg(feature = "track_location")] |                             #[cfg(feature = "track_location")] | ||||||
|                         Some(caller), |                             caller: Some(caller), | ||||||
|                             #[cfg(not(feature = "track_location"))] |                             #[cfg(not(feature = "track_location"))] | ||||||
|                         None, |                             caller: None, | ||||||
|  |                         }, | ||||||
|                     ); |                     ); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -569,12 +571,14 @@ impl<'w> DeferredWorld<'w> { | |||||||
|                 if let Some(hook) = hooks.on_insert { |                 if let Some(hook) = hooks.on_insert { | ||||||
|                     hook( |                     hook( | ||||||
|                         DeferredWorld { world: self.world }, |                         DeferredWorld { world: self.world }, | ||||||
|  |                         HookContext { | ||||||
|                             entity, |                             entity, | ||||||
|                             component_id, |                             component_id, | ||||||
|                             #[cfg(feature = "track_location")] |                             #[cfg(feature = "track_location")] | ||||||
|                         Some(caller), |                             caller: Some(caller), | ||||||
|                             #[cfg(not(feature = "track_location"))] |                             #[cfg(not(feature = "track_location"))] | ||||||
|                         None, |                             caller: None, | ||||||
|  |                         }, | ||||||
|                     ); |                     ); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -600,12 +604,14 @@ impl<'w> DeferredWorld<'w> { | |||||||
|                 if let Some(hook) = hooks.on_replace { |                 if let Some(hook) = hooks.on_replace { | ||||||
|                     hook( |                     hook( | ||||||
|                         DeferredWorld { world: self.world }, |                         DeferredWorld { world: self.world }, | ||||||
|  |                         HookContext { | ||||||
|                             entity, |                             entity, | ||||||
|                             component_id, |                             component_id, | ||||||
|                             #[cfg(feature = "track_location")] |                             #[cfg(feature = "track_location")] | ||||||
|                         Some(caller), |                             caller: Some(caller), | ||||||
|                             #[cfg(not(feature = "track_location"))] |                             #[cfg(not(feature = "track_location"))] | ||||||
|                         None, |                             caller: None, | ||||||
|  |                         }, | ||||||
|                     ); |                     ); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -631,12 +637,14 @@ impl<'w> DeferredWorld<'w> { | |||||||
|                 if let Some(hook) = hooks.on_remove { |                 if let Some(hook) = hooks.on_remove { | ||||||
|                     hook( |                     hook( | ||||||
|                         DeferredWorld { world: self.world }, |                         DeferredWorld { world: self.world }, | ||||||
|  |                         HookContext { | ||||||
|                             entity, |                             entity, | ||||||
|                             component_id, |                             component_id, | ||||||
|                             #[cfg(feature = "track_location")] |                             #[cfg(feature = "track_location")] | ||||||
|                         Some(caller), |                             caller: Some(caller), | ||||||
|                             #[cfg(not(feature = "track_location"))] |                             #[cfg(not(feature = "track_location"))] | ||||||
|                         None, |                             caller: None, | ||||||
|  |                         }, | ||||||
|                     ); |                     ); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -662,12 +670,14 @@ impl<'w> DeferredWorld<'w> { | |||||||
|                 if let Some(hook) = hooks.on_despawn { |                 if let Some(hook) = hooks.on_despawn { | ||||||
|                     hook( |                     hook( | ||||||
|                         DeferredWorld { world: self.world }, |                         DeferredWorld { world: self.world }, | ||||||
|  |                         HookContext { | ||||||
|                             entity, |                             entity, | ||||||
|                             component_id, |                             component_id, | ||||||
|                             #[cfg(feature = "track_location")] |                             #[cfg(feature = "track_location")] | ||||||
|                         Some(caller), |                             caller: Some(caller), | ||||||
|                             #[cfg(not(feature = "track_location"))] |                             #[cfg(not(feature = "track_location"))] | ||||||
|                         None, |                             caller: None, | ||||||
|  |                         }, | ||||||
|                     ); |                     ); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -4401,10 +4401,10 @@ mod tests { | |||||||
|     use bevy_ptr::{OwningPtr, Ptr}; |     use bevy_ptr::{OwningPtr, Ptr}; | ||||||
|     use core::panic::AssertUnwindSafe; |     use core::panic::AssertUnwindSafe; | ||||||
| 
 | 
 | ||||||
|     use core::panic::Location; |  | ||||||
|     #[cfg(feature = "track_location")] |     #[cfg(feature = "track_location")] | ||||||
|     use std::sync::OnceLock; |     use {core::panic::Location, std::sync::OnceLock}; | ||||||
| 
 | 
 | ||||||
|  |     use crate::component::HookContext; | ||||||
|     use crate::{ |     use crate::{ | ||||||
|         self as bevy_ecs, |         self as bevy_ecs, | ||||||
|         change_detection::MutUntyped, |         change_detection::MutUntyped, | ||||||
| @ -5482,22 +5482,12 @@ mod tests { | |||||||
|     #[component(on_add = ord_a_hook_on_add, on_insert = ord_a_hook_on_insert, on_replace = ord_a_hook_on_replace, on_remove = ord_a_hook_on_remove)] |     #[component(on_add = ord_a_hook_on_add, on_insert = ord_a_hook_on_insert, on_replace = ord_a_hook_on_replace, on_remove = ord_a_hook_on_remove)] | ||||||
|     struct OrdA; |     struct OrdA; | ||||||
| 
 | 
 | ||||||
|     fn ord_a_hook_on_add( |     fn ord_a_hook_on_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         entity: Entity, |  | ||||||
|         _id: ComponentId, |  | ||||||
|         _caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world.resource_mut::<TestVec>().0.push("OrdA hook on_add"); |         world.resource_mut::<TestVec>().0.push("OrdA hook on_add"); | ||||||
|         world.commands().entity(entity).insert(OrdB); |         world.commands().entity(entity).insert(OrdB); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn ord_a_hook_on_insert( |     fn ord_a_hook_on_insert(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         entity: Entity, |  | ||||||
|         _id: ComponentId, |  | ||||||
|         _caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world |         world | ||||||
|             .resource_mut::<TestVec>() |             .resource_mut::<TestVec>() | ||||||
|             .0 |             .0 | ||||||
| @ -5506,24 +5496,14 @@ mod tests { | |||||||
|         world.commands().entity(entity).remove::<OrdB>(); |         world.commands().entity(entity).remove::<OrdB>(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn ord_a_hook_on_replace( |     fn ord_a_hook_on_replace(mut world: DeferredWorld, _: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         _entity: Entity, |  | ||||||
|         _id: ComponentId, |  | ||||||
|         _caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world |         world | ||||||
|             .resource_mut::<TestVec>() |             .resource_mut::<TestVec>() | ||||||
|             .0 |             .0 | ||||||
|             .push("OrdA hook on_replace"); |             .push("OrdA hook on_replace"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn ord_a_hook_on_remove( |     fn ord_a_hook_on_remove(mut world: DeferredWorld, _: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         _entity: Entity, |  | ||||||
|         _id: ComponentId, |  | ||||||
|         _caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world |         world | ||||||
|             .resource_mut::<TestVec>() |             .resource_mut::<TestVec>() | ||||||
|             .0 |             .0 | ||||||
| @ -5550,12 +5530,7 @@ mod tests { | |||||||
|     #[component(on_add = ord_b_hook_on_add, on_insert = ord_b_hook_on_insert, on_replace = ord_b_hook_on_replace, on_remove = ord_b_hook_on_remove)] |     #[component(on_add = ord_b_hook_on_add, on_insert = ord_b_hook_on_insert, on_replace = ord_b_hook_on_replace, on_remove = ord_b_hook_on_remove)] | ||||||
|     struct OrdB; |     struct OrdB; | ||||||
| 
 | 
 | ||||||
|     fn ord_b_hook_on_add( |     fn ord_b_hook_on_add(mut world: DeferredWorld, _: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         _entity: Entity, |  | ||||||
|         _id: ComponentId, |  | ||||||
|         _caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world.resource_mut::<TestVec>().0.push("OrdB hook on_add"); |         world.resource_mut::<TestVec>().0.push("OrdB hook on_add"); | ||||||
|         world.commands().queue(|world: &mut World| { |         world.commands().queue(|world: &mut World| { | ||||||
|             world |             world | ||||||
| @ -5565,36 +5540,21 @@ mod tests { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn ord_b_hook_on_insert( |     fn ord_b_hook_on_insert(mut world: DeferredWorld, _: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         _entity: Entity, |  | ||||||
|         _id: ComponentId, |  | ||||||
|         _caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world |         world | ||||||
|             .resource_mut::<TestVec>() |             .resource_mut::<TestVec>() | ||||||
|             .0 |             .0 | ||||||
|             .push("OrdB hook on_insert"); |             .push("OrdB hook on_insert"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn ord_b_hook_on_replace( |     fn ord_b_hook_on_replace(mut world: DeferredWorld, _: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         _entity: Entity, |  | ||||||
|         _id: ComponentId, |  | ||||||
|         _caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world |         world | ||||||
|             .resource_mut::<TestVec>() |             .resource_mut::<TestVec>() | ||||||
|             .0 |             .0 | ||||||
|             .push("OrdB hook on_replace"); |             .push("OrdB hook on_replace"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn ord_b_hook_on_remove( |     fn ord_b_hook_on_remove(mut world: DeferredWorld, _: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         _entity: Entity, |  | ||||||
|         _id: ComponentId, |  | ||||||
|         _caller: Option<&'static Location<'static>>, |  | ||||||
|     ) { |  | ||||||
|         world |         world | ||||||
|             .resource_mut::<TestVec>() |             .resource_mut::<TestVec>() | ||||||
|             .0 |             .0 | ||||||
| @ -5734,7 +5694,7 @@ mod tests { | |||||||
|         struct C; |         struct C; | ||||||
| 
 | 
 | ||||||
|         static TRACKED: OnceLock<&'static Location<'static>> = OnceLock::new(); |         static TRACKED: OnceLock<&'static Location<'static>> = OnceLock::new(); | ||||||
|         fn get_tracked(world: DeferredWorld, entity: Entity, _: ComponentId, _: Option<&Location>) { |         fn get_tracked(world: DeferredWorld, HookContext { entity, .. }: HookContext) { | ||||||
|             TRACKED.get_or_init(|| { |             TRACKED.get_or_init(|| { | ||||||
|                 world |                 world | ||||||
|                     .entities |                     .entities | ||||||
| @ -5795,35 +5755,35 @@ mod tests { | |||||||
|         world.register_component::<Foo>(); |         world.register_component::<Foo>(); | ||||||
|         world |         world | ||||||
|             .register_component_hooks::<Foo>() |             .register_component_hooks::<Foo>() | ||||||
|             .on_add(|world, entity, _, _| { |             .on_add(|world, context| { | ||||||
|                 ADD_COUNT.fetch_add(1, Ordering::Relaxed); |                 ADD_COUNT.fetch_add(1, Ordering::Relaxed); | ||||||
| 
 | 
 | ||||||
|                 assert_eq!( |                 assert_eq!( | ||||||
|                     world.get(entity), |                     world.get(context.entity), | ||||||
|                     Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) |                     Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) | ||||||
|                 ); |                 ); | ||||||
|             }) |             }) | ||||||
|             .on_remove(|world, entity, _, _| { |             .on_remove(|world, context| { | ||||||
|                 REMOVE_COUNT.fetch_add(1, Ordering::Relaxed); |                 REMOVE_COUNT.fetch_add(1, Ordering::Relaxed); | ||||||
| 
 | 
 | ||||||
|                 assert_eq!( |                 assert_eq!( | ||||||
|                     world.get(entity), |                     world.get(context.entity), | ||||||
|                     Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) |                     Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) | ||||||
|                 ); |                 ); | ||||||
|             }) |             }) | ||||||
|             .on_replace(|world, entity, _, _| { |             .on_replace(|world, context| { | ||||||
|                 REPLACE_COUNT.fetch_add(1, Ordering::Relaxed); |                 REPLACE_COUNT.fetch_add(1, Ordering::Relaxed); | ||||||
| 
 | 
 | ||||||
|                 assert_eq!( |                 assert_eq!( | ||||||
|                     world.get(entity), |                     world.get(context.entity), | ||||||
|                     Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) |                     Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) | ||||||
|                 ); |                 ); | ||||||
|             }) |             }) | ||||||
|             .on_insert(|world, entity, _, _| { |             .on_insert(|world, context| { | ||||||
|                 INSERT_COUNT.fetch_add(1, Ordering::Relaxed); |                 INSERT_COUNT.fetch_add(1, Ordering::Relaxed); | ||||||
| 
 | 
 | ||||||
|                 assert_eq!( |                 assert_eq!( | ||||||
|                     world.get(entity), |                     world.get(context.entity), | ||||||
|                     Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) |                     Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) | ||||||
|                 ); |                 ); | ||||||
|             }); |             }); | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| //! Contains the [`AutoFocus`] component and related machinery.
 | //! Contains the [`AutoFocus`] component and related machinery.
 | ||||||
| 
 | 
 | ||||||
| use core::panic::Location; | use bevy_ecs::{component::HookContext, prelude::*, world::DeferredWorld}; | ||||||
| 
 |  | ||||||
| use bevy_ecs::{component::ComponentId, prelude::*, world::DeferredWorld}; |  | ||||||
| 
 | 
 | ||||||
| use crate::InputFocus; | use crate::InputFocus; | ||||||
| 
 | 
 | ||||||
| @ -25,12 +23,7 @@ use bevy_reflect::{prelude::*, Reflect}; | |||||||
| #[component(on_add = on_auto_focus_added)] | #[component(on_add = on_auto_focus_added)] | ||||||
| pub struct AutoFocus; | pub struct AutoFocus; | ||||||
| 
 | 
 | ||||||
| fn on_auto_focus_added( | fn on_auto_focus_added(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) { | ||||||
|     mut world: DeferredWorld, |  | ||||||
|     entity: Entity, |  | ||||||
|     _: ComponentId, |  | ||||||
|     _: Option<&'static Location<'static>>, |  | ||||||
| ) { |  | ||||||
|     if let Some(mut input_focus) = world.get_resource_mut::<InputFocus>() { |     if let Some(mut input_focus) = world.get_resource_mut::<InputFocus>() { | ||||||
|         input_focus.set(entity); |         input_focus.set(entity); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -361,26 +361,20 @@ mod tests { | |||||||
| 
 | 
 | ||||||
|     use alloc::string::String; |     use alloc::string::String; | ||||||
|     use bevy_ecs::{ |     use bevy_ecs::{ | ||||||
|         component::ComponentId, observer::Trigger, system::RunSystemOnce, world::DeferredWorld, |         component::HookContext, observer::Trigger, system::RunSystemOnce, world::DeferredWorld, | ||||||
|     }; |     }; | ||||||
|     use bevy_input::{ |     use bevy_input::{ | ||||||
|         keyboard::{Key, KeyCode}, |         keyboard::{Key, KeyCode}, | ||||||
|         ButtonState, InputPlugin, |         ButtonState, InputPlugin, | ||||||
|     }; |     }; | ||||||
|     use bevy_window::WindowResolution; |     use bevy_window::WindowResolution; | ||||||
|     use core::panic::Location; |  | ||||||
|     use smol_str::SmolStr; |     use smol_str::SmolStr; | ||||||
| 
 | 
 | ||||||
|     #[derive(Component)] |     #[derive(Component)] | ||||||
|     #[component(on_add = set_focus_on_add)] |     #[component(on_add = set_focus_on_add)] | ||||||
|     struct SetFocusOnAdd; |     struct SetFocusOnAdd; | ||||||
| 
 | 
 | ||||||
|     fn set_focus_on_add( |     fn set_focus_on_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) { | ||||||
|         mut world: DeferredWorld, |  | ||||||
|         entity: Entity, |  | ||||||
|         _: ComponentId, |  | ||||||
|         _: Option<&Location>, |  | ||||||
|     ) { |  | ||||||
|         let mut input_focus = world.resource_mut::<InputFocus>(); |         let mut input_focus = world.resource_mut::<InputFocus>(); | ||||||
|         input_focus.set(entity); |         input_focus.set(entity); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; | |||||||
| use bevy_derive::{Deref, DerefMut}; | use bevy_derive::{Deref, DerefMut}; | ||||||
| use bevy_ecs::{ | use bevy_ecs::{ | ||||||
|     change_detection::DetectChanges, |     change_detection::DetectChanges, | ||||||
|     component::{Component, ComponentId}, |     component::{Component, HookContext}, | ||||||
|     entity::{Entity, EntityBorrow}, |     entity::{Entity, EntityBorrow}, | ||||||
|     event::EventReader, |     event::EventReader, | ||||||
|     prelude::{require, With}, |     prelude::{require, With}, | ||||||
| @ -43,7 +43,6 @@ use bevy_window::{ | |||||||
|     WindowScaleFactorChanged, |     WindowScaleFactorChanged, | ||||||
| }; | }; | ||||||
| use core::ops::Range; | use core::ops::Range; | ||||||
| use core::panic::Location; |  | ||||||
| use derive_more::derive::From; | use derive_more::derive::From; | ||||||
| use tracing::warn; | use tracing::warn; | ||||||
| use wgpu::{BlendState, TextureFormat, TextureUsages}; | use wgpu::{BlendState, TextureFormat, TextureUsages}; | ||||||
| @ -333,12 +332,7 @@ pub struct Camera { | |||||||
|     pub sub_camera_view: Option<SubCameraView>, |     pub sub_camera_view: Option<SubCameraView>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn warn_on_no_render_graph( | fn warn_on_no_render_graph(world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) { | ||||||
|     world: DeferredWorld, |  | ||||||
|     entity: Entity, |  | ||||||
|     _: ComponentId, |  | ||||||
|     caller: Option<&'static Location<'static>>, |  | ||||||
| ) { |  | ||||||
|     if !world.entity(entity).contains::<CameraRenderGraph>() { |     if !world.entity(entity).contains::<CameraRenderGraph>() { | ||||||
|         warn!("{}Entity {entity} has a `Camera` component, but it doesn't have a render graph configured. Consider adding a `Camera2d` or `Camera3d` component, or manually adding a `CameraRenderGraph` component if you need a custom render graph.", caller.map(|location|format!("{location}: ")).unwrap_or_default()); |         warn!("{}Entity {entity} has a `Camera` component, but it doesn't have a render graph configured. Consider adding a `Camera2d` or `Camera3d` component, or manually adding a `CameraRenderGraph` component if you need a custom render graph.", caller.map(|location|format!("{location}: ")).unwrap_or_default()); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -32,11 +32,11 @@ impl<C: Component> Plugin for SyncComponentPlugin<C> { | |||||||
|     fn build(&self, app: &mut App) { |     fn build(&self, app: &mut App) { | ||||||
|         app.register_required_components::<C, SyncToRenderWorld>(); |         app.register_required_components::<C, SyncToRenderWorld>(); | ||||||
| 
 | 
 | ||||||
|         app.world_mut().register_component_hooks::<C>().on_remove( |         app.world_mut() | ||||||
|             |mut world, entity, _component_id, _caller| { |             .register_component_hooks::<C>() | ||||||
|  |             .on_remove(|mut world, context| { | ||||||
|                 let mut pending = world.resource_mut::<PendingSyncEntity>(); |                 let mut pending = world.resource_mut::<PendingSyncEntity>(); | ||||||
|                 pending.push(EntityRecord::ComponentRemoved(entity)); |                 pending.push(EntityRecord::ComponentRemoved(context.entity)); | ||||||
|             }, |             }); | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,9 +2,8 @@ mod range; | |||||||
| mod render_layers; | mod render_layers; | ||||||
| 
 | 
 | ||||||
| use core::any::TypeId; | use core::any::TypeId; | ||||||
| use core::panic::Location; |  | ||||||
| 
 | 
 | ||||||
| use bevy_ecs::component::ComponentId; | use bevy_ecs::component::HookContext; | ||||||
| use bevy_ecs::entity::hash_set::EntityHashSet; | use bevy_ecs::entity::hash_set::EntityHashSet; | ||||||
| use bevy_ecs::world::DeferredWorld; | use bevy_ecs::world::DeferredWorld; | ||||||
| use derive_more::derive::{Deref, DerefMut}; | use derive_more::derive::{Deref, DerefMut}; | ||||||
| @ -635,9 +634,7 @@ pub fn check_visibility( | |||||||
| /// ```
 | /// ```
 | ||||||
| pub fn add_visibility_class<C>( | pub fn add_visibility_class<C>( | ||||||
|     mut world: DeferredWorld<'_>, |     mut world: DeferredWorld<'_>, | ||||||
|     entity: Entity, |     HookContext { entity, .. }: HookContext, | ||||||
|     _: ComponentId, |  | ||||||
|     _: Option<&Location>, |  | ||||||
| ) where | ) where | ||||||
|     C: 'static, |     C: 'static, | ||||||
| { | { | ||||||
|  | |||||||
| @ -365,7 +365,7 @@ mod tests { | |||||||
|         let mut dst_world = World::new(); |         let mut dst_world = World::new(); | ||||||
|         dst_world |         dst_world | ||||||
|             .register_component_hooks::<A>() |             .register_component_hooks::<A>() | ||||||
|             .on_add(|mut world, _, _, _| { |             .on_add(|mut world, _| { | ||||||
|                 world.commands().spawn_empty(); |                 world.commands().spawn_empty(); | ||||||
|             }); |             }); | ||||||
|         dst_world.insert_resource(reg.clone()); |         dst_world.insert_resource(reg.clone()); | ||||||
|  | |||||||
| @ -68,12 +68,14 @@ impl Plugin for ScenePlugin { | |||||||
|         // Register component hooks for DynamicSceneRoot
 |         // Register component hooks for DynamicSceneRoot
 | ||||||
|         app.world_mut() |         app.world_mut() | ||||||
|             .register_component_hooks::<DynamicSceneRoot>() |             .register_component_hooks::<DynamicSceneRoot>() | ||||||
|             .on_remove(|mut world, entity, _, _| { |             .on_remove(|mut world, context| { | ||||||
|                 let Some(handle) = world.get::<DynamicSceneRoot>(entity) else { |                 let Some(handle) = world.get::<DynamicSceneRoot>(context.entity) else { | ||||||
|                     return; |                     return; | ||||||
|                 }; |                 }; | ||||||
|                 let id = handle.id(); |                 let id = handle.id(); | ||||||
|                 if let Some(&SceneInstance(scene_instance)) = world.get::<SceneInstance>(entity) { |                 if let Some(&SceneInstance(scene_instance)) = | ||||||
|  |                     world.get::<SceneInstance>(context.entity) | ||||||
|  |                 { | ||||||
|                     let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else { |                     let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else { | ||||||
|                         return; |                         return; | ||||||
|                     }; |                     }; | ||||||
| @ -87,8 +89,10 @@ impl Plugin for ScenePlugin { | |||||||
|         // Register component hooks for SceneRoot
 |         // Register component hooks for SceneRoot
 | ||||||
|         app.world_mut() |         app.world_mut() | ||||||
|             .register_component_hooks::<SceneRoot>() |             .register_component_hooks::<SceneRoot>() | ||||||
|             .on_remove(|mut world, entity, _, _| { |             .on_remove(|mut world, context| { | ||||||
|                 if let Some(&SceneInstance(scene_instance)) = world.get::<SceneInstance>(entity) { |                 if let Some(&SceneInstance(scene_instance)) = | ||||||
|  |                     world.get::<SceneInstance>(context.entity) | ||||||
|  |                 { | ||||||
|                     let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else { |                     let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else { | ||||||
|                         return; |                         return; | ||||||
|                     }; |                     }; | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
| //!     between components (like hierarchies or parent-child links) and need to maintain correctness.
 | //!     between components (like hierarchies or parent-child links) and need to maintain correctness.
 | ||||||
| 
 | 
 | ||||||
| use bevy::{ | use bevy::{ | ||||||
|     ecs::component::{ComponentHooks, Mutable, StorageType}, |     ecs::component::{ComponentHooks, HookContext, Mutable, StorageType}, | ||||||
|     prelude::*, |     prelude::*, | ||||||
| }; | }; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| @ -63,14 +63,21 @@ fn setup(world: &mut World) { | |||||||
|     world |     world | ||||||
|         .register_component_hooks::<MyComponent>() |         .register_component_hooks::<MyComponent>() | ||||||
|         // There are 4 component lifecycle hooks: `on_add`, `on_insert`, `on_replace` and `on_remove`
 |         // There are 4 component lifecycle hooks: `on_add`, `on_insert`, `on_replace` and `on_remove`
 | ||||||
|         // A hook has 4 arguments:
 |         // A hook has 2 arguments:
 | ||||||
|         // - a `DeferredWorld`, this allows access to resource and component data as well as `Commands`
 |         // - a `DeferredWorld`, this allows access to resource and component data as well as `Commands`
 | ||||||
|  |         // - a `HookContext`, this provides access to the following contextual information:
 | ||||||
|         //   - the entity that triggered the hook
 |         //   - the entity that triggered the hook
 | ||||||
|         //   - the component id of the triggering component, this is mostly used for dynamic components
 |         //   - the component id of the triggering component, this is mostly used for dynamic components
 | ||||||
|         //   - the location of the code that caused the hook to trigger
 |         //   - the location of the code that caused the hook to trigger
 | ||||||
|         //
 |         //
 | ||||||
|         // `on_add` will trigger when a component is inserted onto an entity without it
 |         // `on_add` will trigger when a component is inserted onto an entity without it
 | ||||||
|         .on_add(|mut world, entity, component_id, caller| { |         .on_add( | ||||||
|  |             |mut world, | ||||||
|  |              HookContext { | ||||||
|  |                  entity, | ||||||
|  |                  component_id, | ||||||
|  |                  caller, | ||||||
|  |              }| { | ||||||
|                 // You can access component data from within the hook
 |                 // You can access component data from within the hook
 | ||||||
|                 let value = world.get::<MyComponent>(entity).unwrap().0; |                 let value = world.get::<MyComponent>(entity).unwrap().0; | ||||||
|                 println!( |                 println!( | ||||||
| @ -85,22 +92,29 @@ fn setup(world: &mut World) { | |||||||
|                     .insert(value, entity); |                     .insert(value, entity); | ||||||
|                 // Or send events
 |                 // Or send events
 | ||||||
|                 world.send_event(MyEvent); |                 world.send_event(MyEvent); | ||||||
|         }) |             }, | ||||||
|  |         ) | ||||||
|         // `on_insert` will trigger when a component is inserted onto an entity,
 |         // `on_insert` will trigger when a component is inserted onto an entity,
 | ||||||
|         // regardless of whether or not it already had it and after `on_add` if it ran
 |         // regardless of whether or not it already had it and after `on_add` if it ran
 | ||||||
|         .on_insert(|world, _, _, _| { |         .on_insert(|world, _| { | ||||||
|             println!("Current Index: {:?}", world.resource::<MyComponentIndex>()); |             println!("Current Index: {:?}", world.resource::<MyComponentIndex>()); | ||||||
|         }) |         }) | ||||||
|         // `on_replace` will trigger when a component is inserted onto an entity that already had it,
 |         // `on_replace` will trigger when a component is inserted onto an entity that already had it,
 | ||||||
|         // and runs before the value is replaced.
 |         // and runs before the value is replaced.
 | ||||||
|         // Also triggers when a component is removed from an entity, and runs before `on_remove`
 |         // Also triggers when a component is removed from an entity, and runs before `on_remove`
 | ||||||
|         .on_replace(|mut world, entity, _, _| { |         .on_replace(|mut world, context| { | ||||||
|             let value = world.get::<MyComponent>(entity).unwrap().0; |             let value = world.get::<MyComponent>(context.entity).unwrap().0; | ||||||
|             world.resource_mut::<MyComponentIndex>().remove(&value); |             world.resource_mut::<MyComponentIndex>().remove(&value); | ||||||
|         }) |         }) | ||||||
|         // `on_remove` will trigger when a component is removed from an entity,
 |         // `on_remove` will trigger when a component is removed from an entity,
 | ||||||
|         // since it runs before the component is removed you can still access the component data
 |         // since it runs before the component is removed you can still access the component data
 | ||||||
|         .on_remove(|mut world, entity, component_id, caller| { |         .on_remove( | ||||||
|  |             |mut world, | ||||||
|  |              HookContext { | ||||||
|  |                  entity, | ||||||
|  |                  component_id, | ||||||
|  |                  caller, | ||||||
|  |              }| { | ||||||
|                 let value = world.get::<MyComponent>(entity).unwrap().0; |                 let value = world.get::<MyComponent>(entity).unwrap().0; | ||||||
|                 println!( |                 println!( | ||||||
|                     "{component_id:?} removed from {entity} with value {value:?}{}", |                     "{component_id:?} removed from {entity} with value {value:?}{}", | ||||||
| @ -110,7 +124,8 @@ fn setup(world: &mut World) { | |||||||
|                 ); |                 ); | ||||||
|                 // You can also issue commands through `.commands()`
 |                 // You can also issue commands through `.commands()`
 | ||||||
|                 world.commands().entity(entity).despawn(); |                 world.commands().entity(entity).despawn(); | ||||||
|         }); |             }, | ||||||
|  |         ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn trigger_hooks( | fn trigger_hooks( | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| use bevy::{ | use bevy::{ | ||||||
|     ecs::{ |     ecs::{ | ||||||
|         component::{ComponentDescriptor, ComponentId, StorageType}, |         component::{ComponentDescriptor, ComponentId, HookContext, StorageType}, | ||||||
|         world::DeferredWorld, |         world::DeferredWorld, | ||||||
|     }, |     }, | ||||||
|     prelude::*, |     prelude::*, | ||||||
| @ -10,7 +10,6 @@ use bevy::{ | |||||||
|     utils::HashMap, |     utils::HashMap, | ||||||
| }; | }; | ||||||
| use core::alloc::Layout; | use core::alloc::Layout; | ||||||
| use core::panic::Location; |  | ||||||
| 
 | 
 | ||||||
| /// This component is mutable, the default case. This is indicated by components
 | /// This component is mutable, the default case. This is indicated by components
 | ||||||
| /// implementing [`Component`] where [`Component::Mutability`] is [`Mutable`](bevy::ecs::component::Mutable).
 | /// implementing [`Component`] where [`Component::Mutability`] is [`Mutable`](bevy::ecs::component::Mutable).
 | ||||||
| @ -74,12 +73,7 @@ impl NameIndex { | |||||||
| ///
 | ///
 | ||||||
| /// Since all mutations to [`Name`] are captured by hooks, we know it is not currently
 | /// Since all mutations to [`Name`] are captured by hooks, we know it is not currently
 | ||||||
| /// inserted in the index, and its value will not change without triggering a hook.
 | /// inserted in the index, and its value will not change without triggering a hook.
 | ||||||
| fn on_insert_name( | fn on_insert_name(mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext) { | ||||||
|     mut world: DeferredWorld<'_>, |  | ||||||
|     entity: Entity, |  | ||||||
|     _component: ComponentId, |  | ||||||
|     _caller: Option<&'static Location<'static>>, |  | ||||||
| ) { |  | ||||||
|     let Some(&name) = world.entity(entity).get::<Name>() else { |     let Some(&name) = world.entity(entity).get::<Name>() else { | ||||||
|         unreachable!("OnInsert hook guarantees `Name` is available on entity") |         unreachable!("OnInsert hook guarantees `Name` is available on entity") | ||||||
|     }; |     }; | ||||||
| @ -94,12 +88,7 @@ fn on_insert_name( | |||||||
| ///
 | ///
 | ||||||
| /// Since all mutations to [`Name`] are captured by hooks, we know it is currently
 | /// Since all mutations to [`Name`] are captured by hooks, we know it is currently
 | ||||||
| /// inserted in the index.
 | /// inserted in the index.
 | ||||||
| fn on_replace_name( | fn on_replace_name(mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext) { | ||||||
|     mut world: DeferredWorld<'_>, |  | ||||||
|     entity: Entity, |  | ||||||
|     _component: ComponentId, |  | ||||||
|     _caller: Option<&'static Location<'static>>, |  | ||||||
| ) { |  | ||||||
|     let Some(&name) = world.entity(entity).get::<Name>() else { |     let Some(&name) = world.entity(entity).get::<Name>() else { | ||||||
|         unreachable!("OnReplace hook guarantees `Name` is available on entity") |         unreachable!("OnReplace hook guarantees `Name` is available on entity") | ||||||
|     }; |     }; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Zachary Harrold
						Zachary Harrold