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 },
|
||||||
entity,
|
HookContext {
|
||||||
component_id,
|
entity,
|
||||||
#[cfg(feature = "track_location")]
|
component_id,
|
||||||
Some(caller),
|
#[cfg(feature = "track_location")]
|
||||||
#[cfg(not(feature = "track_location"))]
|
caller: Some(caller),
|
||||||
None,
|
#[cfg(not(feature = "track_location"))]
|
||||||
|
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 },
|
||||||
entity,
|
HookContext {
|
||||||
component_id,
|
entity,
|
||||||
#[cfg(feature = "track_location")]
|
component_id,
|
||||||
Some(caller),
|
#[cfg(feature = "track_location")]
|
||||||
#[cfg(not(feature = "track_location"))]
|
caller: Some(caller),
|
||||||
None,
|
#[cfg(not(feature = "track_location"))]
|
||||||
|
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 },
|
||||||
entity,
|
HookContext {
|
||||||
component_id,
|
entity,
|
||||||
#[cfg(feature = "track_location")]
|
component_id,
|
||||||
Some(caller),
|
#[cfg(feature = "track_location")]
|
||||||
#[cfg(not(feature = "track_location"))]
|
caller: Some(caller),
|
||||||
None,
|
#[cfg(not(feature = "track_location"))]
|
||||||
|
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 },
|
||||||
entity,
|
HookContext {
|
||||||
component_id,
|
entity,
|
||||||
#[cfg(feature = "track_location")]
|
component_id,
|
||||||
Some(caller),
|
#[cfg(feature = "track_location")]
|
||||||
#[cfg(not(feature = "track_location"))]
|
caller: Some(caller),
|
||||||
None,
|
#[cfg(not(feature = "track_location"))]
|
||||||
|
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 },
|
||||||
entity,
|
HookContext {
|
||||||
component_id,
|
entity,
|
||||||
#[cfg(feature = "track_location")]
|
component_id,
|
||||||
Some(caller),
|
#[cfg(feature = "track_location")]
|
||||||
#[cfg(not(feature = "track_location"))]
|
caller: Some(caller),
|
||||||
None,
|
#[cfg(not(feature = "track_location"))]
|
||||||
|
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,54 +63,69 @@ 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`
|
||||||
// - the entity that triggered the hook
|
// - a `HookContext`, this provides access to the following contextual information:
|
||||||
// - the component id of the triggering component, this is mostly used for dynamic components
|
// - the entity that triggered the hook
|
||||||
// - the location of the code that caused the hook to trigger
|
// - 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
|
||||||
//
|
//
|
||||||
// `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(
|
||||||
// You can access component data from within the hook
|
|mut world,
|
||||||
let value = world.get::<MyComponent>(entity).unwrap().0;
|
HookContext {
|
||||||
println!(
|
entity,
|
||||||
"{component_id:?} added to {entity} with value {value:?}{}",
|
component_id,
|
||||||
caller
|
caller,
|
||||||
.map(|location| format!("due to {location}"))
|
}| {
|
||||||
.unwrap_or_default()
|
// You can access component data from within the hook
|
||||||
);
|
let value = world.get::<MyComponent>(entity).unwrap().0;
|
||||||
// Or access resources
|
println!(
|
||||||
world
|
"{component_id:?} added to {entity} with value {value:?}{}",
|
||||||
.resource_mut::<MyComponentIndex>()
|
caller
|
||||||
.insert(value, entity);
|
.map(|location| format!("due to {location}"))
|
||||||
// Or send events
|
.unwrap_or_default()
|
||||||
world.send_event(MyEvent);
|
);
|
||||||
})
|
// Or access resources
|
||||||
|
world
|
||||||
|
.resource_mut::<MyComponentIndex>()
|
||||||
|
.insert(value, entity);
|
||||||
|
// Or send events
|
||||||
|
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(
|
||||||
let value = world.get::<MyComponent>(entity).unwrap().0;
|
|mut world,
|
||||||
println!(
|
HookContext {
|
||||||
"{component_id:?} removed from {entity} with value {value:?}{}",
|
entity,
|
||||||
caller
|
component_id,
|
||||||
.map(|location| format!("due to {location}"))
|
caller,
|
||||||
.unwrap_or_default()
|
}| {
|
||||||
);
|
let value = world.get::<MyComponent>(entity).unwrap().0;
|
||||||
// You can also issue commands through `.commands()`
|
println!(
|
||||||
world.commands().entity(entity).despawn();
|
"{component_id:?} removed from {entity} with value {value:?}{}",
|
||||||
});
|
caller
|
||||||
|
.map(|location| format!("due to {location}"))
|
||||||
|
.unwrap_or_default()
|
||||||
|
);
|
||||||
|
// You can also issue commands through `.commands()`
|
||||||
|
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