Track callsite for observers & hooks (#15607)

# Objective

Fixes #14708

Also fixes some commands not updating tracked location.


## Solution

`ObserverTrigger` has a new `caller` field with the
`track_change_detection` feature;
hooks take an additional caller parameter (which is `Some(…)` or `None`
depending on the feature).

## Testing

See the new tests in `src/observer/mod.rs`

---

## Showcase

Observers now know from where they were triggered (if
`track_change_detection` is enabled):
```rust
world.observe(move |trigger: Trigger<OnAdd, Foo>| {
    println!("Added Foo from {}", trigger.caller());
});
```

## Migration

- hooks now take an additional `Option<&'static Location>` argument

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
SpecificProtagonist 2025-01-22 21:02:39 +01:00 committed by GitHub
parent 5c43890d49
commit f32a6fb205
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 789 additions and 133 deletions

View File

@ -70,10 +70,13 @@ 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, _| { hooks.on_add(|world, entity, _, caller| {
if let Some(value) = world.get::<OrderIndependentTransparencySettings>(entity) { if let Some(value) = world.get::<OrderIndependentTransparencySettings>(entity) {
if value.layer_count > 32 { if value.layer_count > 32 {
warn!("OrderIndependentTransparencySettings layer_count set to {} might be too high.", value.layer_count); warn!("{}OrderIndependentTransparencySettings layer_count set to {} might be too high.",
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
value.layer_count
);
} }
} }
}); });

View File

@ -1058,12 +1058,16 @@ impl<'w> BundleInserter<'w> {
ON_REPLACE, ON_REPLACE,
entity, entity,
archetype_after_insert.iter_existing(), archetype_after_insert.iter_existing(),
#[cfg(feature = "track_location")]
caller,
); );
} }
deferred_world.trigger_on_replace( deferred_world.trigger_on_replace(
archetype, archetype,
entity, entity,
archetype_after_insert.iter_existing(), archetype_after_insert.iter_existing(),
#[cfg(feature = "track_location")]
caller,
); );
} }
} }
@ -1236,12 +1240,16 @@ impl<'w> BundleInserter<'w> {
new_archetype, new_archetype,
entity, entity,
archetype_after_insert.iter_added(), archetype_after_insert.iter_added(),
#[cfg(feature = "track_location")]
caller,
); );
if new_archetype.has_add_observer() { if new_archetype.has_add_observer() {
deferred_world.trigger_observers( deferred_world.trigger_observers(
ON_ADD, ON_ADD,
entity, entity,
archetype_after_insert.iter_added(), archetype_after_insert.iter_added(),
#[cfg(feature = "track_location")]
caller,
); );
} }
match insert_mode { match insert_mode {
@ -1251,12 +1259,16 @@ impl<'w> BundleInserter<'w> {
new_archetype, new_archetype,
entity, entity,
archetype_after_insert.iter_inserted(), archetype_after_insert.iter_inserted(),
#[cfg(feature = "track_location")]
caller,
); );
if new_archetype.has_insert_observer() { if new_archetype.has_insert_observer() {
deferred_world.trigger_observers( deferred_world.trigger_observers(
ON_INSERT, ON_INSERT,
entity, entity,
archetype_after_insert.iter_inserted(), archetype_after_insert.iter_inserted(),
#[cfg(feature = "track_location")]
caller,
); );
} }
} }
@ -1267,12 +1279,16 @@ impl<'w> BundleInserter<'w> {
new_archetype, new_archetype,
entity, entity,
archetype_after_insert.iter_added(), archetype_after_insert.iter_added(),
#[cfg(feature = "track_location")]
caller,
); );
if new_archetype.has_insert_observer() { if new_archetype.has_insert_observer() {
deferred_world.trigger_observers( deferred_world.trigger_observers(
ON_INSERT, ON_INSERT,
entity, entity,
archetype_after_insert.iter_added(), archetype_after_insert.iter_added(),
#[cfg(feature = "track_location")]
caller,
); );
} }
} }
@ -1348,6 +1364,7 @@ impl<'w> BundleSpawner<'w> {
/// # Safety /// # Safety
/// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type
#[inline] #[inline]
#[track_caller]
pub unsafe fn spawn_non_existent<T: DynamicBundle>( pub unsafe fn spawn_non_existent<T: DynamicBundle>(
&mut self, &mut self,
entity: Entity, entity: Entity,
@ -1395,24 +1412,32 @@ impl<'w> BundleSpawner<'w> {
archetype, archetype,
entity, entity,
bundle_info.iter_contributed_components(), bundle_info.iter_contributed_components(),
#[cfg(feature = "track_location")]
caller,
); );
if archetype.has_add_observer() { if archetype.has_add_observer() {
deferred_world.trigger_observers( deferred_world.trigger_observers(
ON_ADD, ON_ADD,
entity, entity,
bundle_info.iter_contributed_components(), bundle_info.iter_contributed_components(),
#[cfg(feature = "track_location")]
caller,
); );
} }
deferred_world.trigger_on_insert( deferred_world.trigger_on_insert(
archetype, archetype,
entity, entity,
bundle_info.iter_contributed_components(), bundle_info.iter_contributed_components(),
#[cfg(feature = "track_location")]
caller,
); );
if archetype.has_insert_observer() { if archetype.has_insert_observer() {
deferred_world.trigger_observers( deferred_world.trigger_observers(
ON_INSERT, ON_INSERT,
entity, entity,
bundle_info.iter_contributed_components(), bundle_info.iter_contributed_components(),
#[cfg(feature = "track_location")]
caller,
); );
} }
}; };
@ -1681,6 +1706,7 @@ mod tests {
use crate as bevy_ecs; use crate as bevy_ecs;
use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; use crate::{component::ComponentId, prelude::*, world::DeferredWorld};
use alloc::vec; use alloc::vec;
use core::panic::Location;
#[derive(Component)] #[derive(Component)]
struct A; struct A;
@ -1689,19 +1715,39 @@ 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(mut world: DeferredWorld, _: Entity, _: ComponentId) { fn a_on_add(
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>(mut world: DeferredWorld, _: T1, _: T2) { fn a_on_insert<T1, T2>(
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>(mut world: DeferredWorld, _: T1, _: T2) { fn a_on_replace<T1, T2>(
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>(mut world: DeferredWorld, _: T1, _: T2) { fn a_on_remove<T1, T2>(
mut world: DeferredWorld,
_: T1,
_: T2,
_: Option<&'static Location<'static>>,
) {
world.resource_mut::<R>().assert_order(3); world.resource_mut::<R>().assert_order(3);
} }
@ -1734,10 +1780,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);
@ -1761,10 +1807,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);
@ -1778,8 +1824,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);
} }
@ -1800,22 +1846,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, entity, _, _| {
world.resource_mut::<R>().assert_order(0); world.resource_mut::<R>().assert_order(0);
world.commands().entity(entity).insert(B); world.commands().entity(entity).insert(B);
}) })
.on_remove(|mut world, entity, _| { .on_remove(|mut world, entity, _, _| {
world.resource_mut::<R>().assert_order(2); world.resource_mut::<R>().assert_order(2);
world.commands().entity(entity).remove::<B>(); world.commands().entity(entity).remove::<B>();
}); });
world world
.register_component_hooks::<B>() .register_component_hooks::<B>()
.on_add(|mut world, entity, _| { .on_add(|mut world, entity, _, _| {
world.resource_mut::<R>().assert_order(1); world.resource_mut::<R>().assert_order(1);
world.commands().entity(entity).remove::<A>(); world.commands().entity(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);
}); });
@ -1832,27 +1878,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, entity, _, _| {
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(entity).insert(B).insert(C);
}); });
world world
.register_component_hooks::<B>() .register_component_hooks::<B>()
.on_add(|mut world, entity, _| { .on_add(|mut world, entity, _, _| {
world.resource_mut::<R>().assert_order(1); world.resource_mut::<R>().assert_order(1);
world.commands().entity(entity).insert(D); world.commands().entity(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);
}); });

View File

@ -21,8 +21,6 @@ use bevy_ptr::{OwningPtr, UnsafeCellDeref};
#[cfg(feature = "bevy_reflect")] #[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_utils::{HashMap, HashSet, TypeIdMap}; use bevy_utils::{HashMap, HashSet, TypeIdMap};
#[cfg(feature = "track_location")]
use core::panic::Location;
use core::{ use core::{
alloc::Layout, alloc::Layout,
any::{Any, TypeId}, any::{Any, TypeId},
@ -30,6 +28,7 @@ use core::{
fmt::Debug, fmt::Debug,
marker::PhantomData, marker::PhantomData,
mem::needs_drop, mem::needs_drop,
panic::Location,
}; };
use disqualified::ShortName; use disqualified::ShortName;
use thiserror::Error; use thiserror::Error;
@ -304,6 +303,7 @@ pub use bevy_ecs_macros::require;
/// # 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;
/// # use core::panic::Location;
/// # /// #
/// #[derive(Component)] /// #[derive(Component)]
/// #[component(on_add = my_on_add_hook)] /// #[component(on_add = my_on_add_hook)]
@ -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) { /// fn my_on_add_hook(world: DeferredWorld, entity: Entity, id: ComponentId, caller: Option<&Location>) {
/// // ... /// // ...
/// } /// }
/// ///
/// // You can also omit writing some types using generics. /// // You can also omit writing some types using generics.
/// fn my_on_insert_hook<T1, T2>(world: DeferredWorld, _: T1, _: T2) { /// fn my_on_insert_hook<T1, T2>(world: DeferredWorld, _: T1, _: T2, caller: Option<&Location>) {
/// // ... /// // ...
/// } /// }
/// ``` /// ```
@ -497,8 +497,10 @@ pub enum StorageType {
SparseSet, SparseSet,
} }
/// 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`.
pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId); /// The caller location is `Some` if the `track_caller` feature is enabled.
pub type ComponentHook =
for<'w> fn(DeferredWorld<'w>, Entity, ComponentId, 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`].
/// ///
@ -535,12 +537,12 @@ pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId);
/// 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| { /// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, entity, _component_id, _caller| {
/// 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(entity);
/// }); /// });
/// ///
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, entity, _component_id| { /// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, entity, _component_id, _caller| {
/// 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(&entity);
/// }); /// });

View File

@ -274,6 +274,7 @@ pub struct EntityCloner {
impl EntityCloner { impl EntityCloner {
/// Clones and inserts components from the `source` entity into `target` entity using the stored configuration. /// Clones and inserts components from the `source` entity into `target` entity using the stored configuration.
#[track_caller]
pub fn clone_entity(&mut self, world: &mut World) { pub fn clone_entity(&mut self, world: &mut World) {
// SAFETY: // SAFETY:
// - `source_entity` is read-only. // - `source_entity` is read-only.

View File

@ -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;
@ -270,6 +270,7 @@ pub fn validate_parent_has_component<C: Component>(
world: DeferredWorld, world: DeferredWorld,
entity: Entity, entity: Entity,
_: ComponentId, _: 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 {
@ -282,8 +283,9 @@ pub fn validate_parent_has_component<C: Component>(
// TODO: print name here once Name lives in bevy_ecs // TODO: print name here once Name lives in bevy_ecs
let name: Option<String> = None; let name: Option<String> = None;
warn!( warn!(
"warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\ "warning[B0004]: {}{name} with the {ty_name} component has a parent without {ty_name}.\n\
This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/b0004", This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/b0004",
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
ty_name = ShortName::of::<C>(), ty_name = ShortName::of::<C>(),
name = name.map_or_else( name = name.map_or_else(
|| format!("Entity {}", entity), || format!("Entity {}", entity),

View File

@ -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>());

View File

@ -15,7 +15,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, 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)

View File

@ -24,6 +24,9 @@ use core::{
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
#[cfg(feature = "track_location")]
use core::panic::Location;
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the /// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. It also /// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. It also
/// contains event propagation information. See [`Trigger::propagate`] for more information. /// contains event propagation information. See [`Trigger::propagate`] for more information.
@ -138,6 +141,12 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> {
pub fn get_propagate(&self) -> bool { pub fn get_propagate(&self) -> bool {
*self.propagate *self.propagate
} }
/// Returns the source code location that triggered this observer.
#[cfg(feature = "track_location")]
pub fn caller(&self) -> &'static Location<'static> {
self.trigger.caller
}
} }
impl<'w, E: Debug, B: Bundle> Debug for Trigger<'w, E, B> { impl<'w, E: Debug, B: Bundle> Debug for Trigger<'w, E, B> {
@ -311,6 +320,10 @@ pub struct ObserverTrigger {
components: SmallVec<[ComponentId; 2]>, components: SmallVec<[ComponentId; 2]>,
/// The entity the trigger targeted. /// The entity the trigger targeted.
pub target: Entity, pub target: Entity,
/// The location of the source code that triggered the obserer.
#[cfg(feature = "track_location")]
pub caller: &'static Location<'static>,
} }
impl ObserverTrigger { impl ObserverTrigger {
@ -387,6 +400,7 @@ impl Observers {
components: impl Iterator<Item = ComponentId> + Clone, components: impl Iterator<Item = ComponentId> + Clone,
data: &mut T, data: &mut T,
propagate: &mut bool, propagate: &mut bool,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) { ) {
// SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld` // SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld`
let (mut world, observers) = unsafe { let (mut world, observers) = unsafe {
@ -411,6 +425,8 @@ impl Observers {
event_type, event_type,
components: components.clone().collect(), components: components.clone().collect(),
target, target,
#[cfg(feature = "track_location")]
caller,
}, },
data.into(), data.into(),
propagate, propagate,
@ -532,16 +548,38 @@ impl World {
/// While event types commonly implement [`Copy`], /// While event types commonly implement [`Copy`],
/// those that don't will be consumed and will no longer be accessible. /// those that don't will be consumed and will no longer be accessible.
/// If you need to use the event after triggering it, use [`World::trigger_ref`] instead. /// If you need to use the event after triggering it, use [`World::trigger_ref`] instead.
pub fn trigger<E: Event>(&mut self, mut event: E) { #[track_caller]
pub fn trigger<E: Event>(&mut self, event: E) {
self.trigger_with_caller(
event,
#[cfg(feature = "track_location")]
Location::caller(),
);
}
pub(crate) fn trigger_with_caller<E: Event>(
&mut self,
mut event: E,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) {
let event_id = E::register_component_id(self); let event_id = E::register_component_id(self);
// SAFETY: We just registered `event_id` with the type of `event` // SAFETY: We just registered `event_id` with the type of `event`
unsafe { self.trigger_targets_dynamic_ref(event_id, &mut event, ()) }; unsafe {
self.trigger_targets_dynamic_ref_with_caller(
event_id,
&mut event,
(),
#[cfg(feature = "track_location")]
caller,
);
}
} }
/// Triggers the given [`Event`] as a mutable reference, which will run any [`Observer`]s watching for it. /// Triggers the given [`Event`] as a mutable reference, which will run any [`Observer`]s watching for it.
/// ///
/// Compared to [`World::trigger`], this method is most useful when it's necessary to check /// Compared to [`World::trigger`], this method is most useful when it's necessary to check
/// or use the event after it has been modified by observers. /// or use the event after it has been modified by observers.
#[track_caller]
pub fn trigger_ref<E: Event>(&mut self, event: &mut E) { pub fn trigger_ref<E: Event>(&mut self, event: &mut E) {
let event_id = E::register_component_id(self); let event_id = E::register_component_id(self);
// SAFETY: We just registered `event_id` with the type of `event` // SAFETY: We just registered `event_id` with the type of `event`
@ -553,10 +591,33 @@ impl World {
/// While event types commonly implement [`Copy`], /// While event types commonly implement [`Copy`],
/// those that don't will be consumed and will no longer be accessible. /// those that don't will be consumed and will no longer be accessible.
/// If you need to use the event after triggering it, use [`World::trigger_targets_ref`] instead. /// If you need to use the event after triggering it, use [`World::trigger_targets_ref`] instead.
pub fn trigger_targets<E: Event>(&mut self, mut event: E, targets: impl TriggerTargets) { #[track_caller]
pub fn trigger_targets<E: Event>(&mut self, event: E, targets: impl TriggerTargets) {
self.trigger_targets_with_caller(
event,
targets,
#[cfg(feature = "track_location")]
Location::caller(),
);
}
pub(crate) fn trigger_targets_with_caller<E: Event>(
&mut self,
mut event: E,
targets: impl TriggerTargets,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) {
let event_id = E::register_component_id(self); let event_id = E::register_component_id(self);
// SAFETY: We just registered `event_id` with the type of `event` // SAFETY: We just registered `event_id` with the type of `event`
unsafe { self.trigger_targets_dynamic_ref(event_id, &mut event, targets) }; unsafe {
self.trigger_targets_dynamic_ref_with_caller(
event_id,
&mut event,
targets,
#[cfg(feature = "track_location")]
caller,
);
}
} }
/// Triggers the given [`Event`] as a mutable reference for the given `targets`, /// Triggers the given [`Event`] as a mutable reference for the given `targets`,
@ -564,6 +625,7 @@ impl World {
/// ///
/// Compared to [`World::trigger_targets`], this method is most useful when it's necessary to check /// Compared to [`World::trigger_targets`], this method is most useful when it's necessary to check
/// or use the event after it has been modified by observers. /// or use the event after it has been modified by observers.
#[track_caller]
pub fn trigger_targets_ref<E: Event>(&mut self, event: &mut E, targets: impl TriggerTargets) { pub fn trigger_targets_ref<E: Event>(&mut self, event: &mut E, targets: impl TriggerTargets) {
let event_id = E::register_component_id(self); let event_id = E::register_component_id(self);
// SAFETY: We just registered `event_id` with the type of `event` // SAFETY: We just registered `event_id` with the type of `event`
@ -579,6 +641,7 @@ impl World {
/// # Safety /// # Safety
/// ///
/// Caller must ensure that `event_data` is accessible as the type represented by `event_id`. /// Caller must ensure that `event_data` is accessible as the type represented by `event_id`.
#[track_caller]
pub unsafe fn trigger_targets_dynamic<E: Event, Targets: TriggerTargets>( pub unsafe fn trigger_targets_dynamic<E: Event, Targets: TriggerTargets>(
&mut self, &mut self,
event_id: ComponentId, event_id: ComponentId,
@ -600,11 +663,31 @@ impl World {
/// # Safety /// # Safety
/// ///
/// Caller must ensure that `event_data` is accessible as the type represented by `event_id`. /// Caller must ensure that `event_data` is accessible as the type represented by `event_id`.
#[track_caller]
pub unsafe fn trigger_targets_dynamic_ref<E: Event, Targets: TriggerTargets>( pub unsafe fn trigger_targets_dynamic_ref<E: Event, Targets: TriggerTargets>(
&mut self, &mut self,
event_id: ComponentId, event_id: ComponentId,
event_data: &mut E, event_data: &mut E,
targets: Targets, targets: Targets,
) {
self.trigger_targets_dynamic_ref_with_caller(
event_id,
event_data,
targets,
#[cfg(feature = "track_location")]
Location::caller(),
);
}
/// # Safety
///
/// See `trigger_targets_dynamic_ref`
unsafe fn trigger_targets_dynamic_ref_with_caller<E: Event, Targets: TriggerTargets>(
&mut self,
event_id: ComponentId,
event_data: &mut E,
targets: Targets,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) { ) {
let mut world = DeferredWorld::from(self); let mut world = DeferredWorld::from(self);
if targets.entities().is_empty() { if targets.entities().is_empty() {
@ -616,6 +699,8 @@ impl World {
targets.components(), targets.components(),
event_data, event_data,
false, false,
#[cfg(feature = "track_location")]
caller,
); );
}; };
} else { } else {
@ -628,6 +713,8 @@ impl World {
targets.components(), targets.components(),
event_data, event_data,
E::AUTO_PROPAGATE, E::AUTO_PROPAGATE,
#[cfg(feature = "track_location")]
caller,
); );
}; };
} }
@ -756,6 +843,8 @@ impl World {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloc::{vec, vec::Vec}; use alloc::{vec, vec::Vec};
#[cfg(feature = "track_location")]
use core::panic::Location;
use bevy_ptr::OwningPtr; use bevy_ptr::OwningPtr;
use bevy_utils::HashMap; use bevy_utils::HashMap;
@ -1511,6 +1600,40 @@ mod tests {
assert!(world.get_resource::<ResA>().is_some()); assert!(world.get_resource::<ResA>().is_some());
} }
#[test]
#[cfg(feature = "track_location")]
#[track_caller]
fn observer_caller_location_event() {
#[derive(Event)]
struct EventA;
let caller = Location::caller();
let mut world = World::new();
world.add_observer(move |trigger: Trigger<EventA>| {
assert_eq!(trigger.caller(), caller);
});
world.trigger(EventA);
}
#[test]
#[cfg(feature = "track_location")]
#[track_caller]
fn observer_caller_location_command_archetype_move() {
#[derive(Component)]
struct Component;
let caller = Location::caller();
let mut world = World::new();
world.add_observer(move |trigger: Trigger<OnAdd, Component>| {
assert_eq!(trigger.caller(), caller);
});
world.add_observer(move |trigger: Trigger<OnRemove, Component>| {
assert_eq!(trigger.caller(), caller);
});
world.commands().spawn(Component).clear();
world.flush();
}
#[test] #[test]
fn observer_triggered_components() { fn observer_triggered_components() {
#[derive(Resource, Default)] #[derive(Resource, Default)]

View File

@ -1,5 +1,6 @@
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, Mutable, StorageType},
@ -66,12 +67,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, 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, entity, _, _| {
let descriptor = core::mem::take( let descriptor = core::mem::take(
&mut world &mut world
.entity_mut(entity) .entity_mut(entity)
@ -318,12 +319,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| { hooks.on_add(|world, entity, id, caller| {
let Some(observe) = world.get::<Self>(entity) else { let Some(observe) = world.get::<Self>(entity) else {
return; return;
}; };
let hook = observe.hook_on_add; let hook = observe.hook_on_add;
hook(world, entity, _id); hook(world, entity, id, caller);
}); });
} }
} }
@ -396,6 +397,7 @@ fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
mut world: DeferredWorld<'_>, mut world: DeferredWorld<'_>,
entity: Entity, entity: Entity,
_: ComponentId, _: 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);

View File

@ -4,6 +4,9 @@ mod related_methods;
mod relationship_query; mod relationship_query;
mod relationship_source_collection; mod relationship_source_collection;
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::*;
@ -71,11 +74,17 @@ 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(mut world: DeferredWorld, entity: Entity, _: ComponentId) { fn on_insert(
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!(
"The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.", "{}The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.",
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
core::any::type_name::<Self>(), core::any::type_name::<Self>(),
core::any::type_name::<Self>() core::any::type_name::<Self>()
); );
@ -93,7 +102,8 @@ pub trait Relationship: Component + Sized {
} }
} else { } else {
warn!( warn!(
"The {}({target_entity:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.", "{}The {}({target_entity:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.",
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
core::any::type_name::<Self>(), core::any::type_name::<Self>(),
core::any::type_name::<Self>() core::any::type_name::<Self>()
); );
@ -103,7 +113,12 @@ 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(mut world: DeferredWorld, entity: Entity, _: ComponentId) { fn on_replace(
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) =
@ -164,7 +179,12 @@ 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(mut world: DeferredWorld, entity: Entity, _: ComponentId) { fn on_replace(
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
@ -180,7 +200,13 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
.handle_error_with(error_handler::silent()), .handle_error_with(error_handler::silent()),
); );
} else { } else {
warn!("Tried to despawn non-existent entity {}", source_entity); warn!(
"{}Tried to despawn non-existent entity {}",
caller
.map(|location| format!("{location}: "))
.unwrap_or_default(),
source_entity
);
} }
} }
} }
@ -189,7 +215,12 @@ 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(mut world: DeferredWorld, entity: Entity, _: ComponentId) { fn on_despawn(
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
@ -205,7 +236,13 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
.handle_error_with(error_handler::silent()), .handle_error_with(error_handler::silent()),
); );
} else { } else {
warn!("Tried to despawn non-existent entity {}", source_entity); warn!(
"{}Tried to despawn non-existent entity {}",
caller
.map(|location| format!("{location}: "))
.unwrap_or_default(),
source_entity
);
} }
} }
} }

View File

@ -265,9 +265,16 @@ pub fn run_schedule(label: impl ScheduleLabel) -> impl Command<Result> {
} }
/// A [`Command`] that sends a global [`Trigger`](crate::observer::Trigger) without any targets. /// A [`Command`] that sends a global [`Trigger`](crate::observer::Trigger) without any targets.
#[track_caller]
pub fn trigger(event: impl Event) -> impl Command { pub fn trigger(event: impl Event) -> impl Command {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |world: &mut World| { move |world: &mut World| {
world.trigger(event); world.trigger_with_caller(
event,
#[cfg(feature = "track_location")]
caller,
);
} }
} }
@ -276,8 +283,15 @@ pub fn trigger_targets(
event: impl Event, event: impl Event,
targets: impl TriggerTargets + Send + Sync + 'static, targets: impl TriggerTargets + Send + Sync + 'static,
) -> impl Command { ) -> impl Command {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |world: &mut World| { move |world: &mut World| {
world.trigger_targets(event, targets); world.trigger_targets_with_caller(
event,
targets,
#[cfg(feature = "track_location")]
caller,
);
} }
} }

View File

@ -186,12 +186,19 @@ pub fn insert_if_new(bundle: impl Bundle) -> impl EntityCommand {
/// An [`EntityCommand`] that adds a dynamic component to an entity. /// An [`EntityCommand`] that adds a dynamic component to an entity.
#[track_caller] #[track_caller]
pub fn insert_by_id<T: Send + 'static>(component_id: ComponentId, value: T) -> impl EntityCommand { pub fn insert_by_id<T: Send + 'static>(component_id: ComponentId, value: T) -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| { move |mut entity: EntityWorldMut| {
// SAFETY: // SAFETY:
// - `component_id` safety is ensured by the caller // - `component_id` safety is ensured by the caller
// - `ptr` is valid within the `make` block // - `ptr` is valid within the `make` block
OwningPtr::make(value, |ptr| unsafe { OwningPtr::make(value, |ptr| unsafe {
entity.insert_by_id(component_id, ptr); entity.insert_by_id_with_caller(
component_id,
ptr,
#[cfg(feature = "track_location")]
caller,
);
}); });
} }
} }
@ -214,39 +221,70 @@ pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl Ent
} }
/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity. /// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity.
#[track_caller]
pub fn remove<T: Bundle>() -> impl EntityCommand { pub fn remove<T: Bundle>() -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| { move |mut entity: EntityWorldMut| {
entity.remove::<T>(); entity.remove_with_caller::<T>(
#[cfg(feature = "track_location")]
caller,
);
} }
} }
/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity, /// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity,
/// as well as the required components for each component removed. /// as well as the required components for each component removed.
#[track_caller]
pub fn remove_with_requires<T: Bundle>() -> impl EntityCommand { pub fn remove_with_requires<T: Bundle>() -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| { move |mut entity: EntityWorldMut| {
entity.remove_with_requires::<T>(); entity.remove_with_requires_with_caller::<T>(
#[cfg(feature = "track_location")]
caller,
);
} }
} }
/// An [`EntityCommand`] that removes a dynamic component from an entity. /// An [`EntityCommand`] that removes a dynamic component from an entity.
#[track_caller]
pub fn remove_by_id(component_id: ComponentId) -> impl EntityCommand { pub fn remove_by_id(component_id: ComponentId) -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| { move |mut entity: EntityWorldMut| {
entity.remove_by_id(component_id); entity.remove_by_id_with_caller(
component_id,
#[cfg(feature = "track_location")]
caller,
);
} }
} }
/// An [`EntityCommand`] that removes all components from an entity. /// An [`EntityCommand`] that removes all components from an entity.
#[track_caller]
pub fn clear() -> impl EntityCommand { pub fn clear() -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| { move |mut entity: EntityWorldMut| {
entity.clear(); entity.clear_with_caller(
#[cfg(feature = "track_location")]
caller,
);
} }
} }
/// An [`EntityCommand`] that removes all components from an entity, /// An [`EntityCommand`] that removes all components from an entity,
/// except for those in the given [`Bundle`]. /// except for those in the given [`Bundle`].
#[track_caller]
pub fn retain<T: Bundle>() -> impl EntityCommand { pub fn retain<T: Bundle>() -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| { move |mut entity: EntityWorldMut| {
entity.retain::<T>(); entity.retain_with_caller::<T>(
#[cfg(feature = "track_location")]
caller,
);
} }
} }
@ -256,6 +294,7 @@ pub fn retain<T: Bundle>() -> impl EntityCommand {
/// ///
/// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured /// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured
/// to despawn descendants. This results in "recursive despawn" behavior. /// to despawn descendants. This results in "recursive despawn" behavior.
#[track_caller]
pub fn despawn() -> impl EntityCommand { pub fn despawn() -> impl EntityCommand {
#[cfg(feature = "track_location")] #[cfg(feature = "track_location")]
let caller = Location::caller(); let caller = Location::caller();
@ -269,11 +308,18 @@ pub fn despawn() -> impl EntityCommand {
/// An [`EntityCommand`] that creates an [`Observer`](crate::observer::Observer) /// An [`EntityCommand`] that creates an [`Observer`](crate::observer::Observer)
/// listening for events of type `E` targeting an entity /// listening for events of type `E` targeting an entity
#[track_caller]
pub fn observe<E: Event, B: Bundle, M>( pub fn observe<E: Event, B: Bundle, M>(
observer: impl IntoObserverSystem<E, B, M>, observer: impl IntoObserverSystem<E, B, M>,
) -> impl EntityCommand { ) -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| { move |mut entity: EntityWorldMut| {
entity.observe(observer); entity.observe_with_caller(
observer,
#[cfg(feature = "track_location")]
caller,
);
} }
} }

View File

@ -13,10 +13,8 @@ pub use parallel_scope::*;
use alloc::boxed::Box; use alloc::boxed::Box;
use core::marker::PhantomData; use core::marker::PhantomData;
use log::error;
#[cfg(feature = "track_location")]
use core::panic::Location; use core::panic::Location;
use log::error;
use crate::{ use crate::{
self as bevy_ecs, self as bevy_ecs,
@ -685,7 +683,6 @@ impl<'w, 's> Commands<'w, 's> {
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static, I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle, B: Bundle,
{ {
#[cfg(feature = "track_location")]
let caller = Location::caller(); let caller = Location::caller();
self.queue(move |world: &mut World| { self.queue(move |world: &mut World| {
if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller( if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller(
@ -694,7 +691,7 @@ impl<'w, 's> Commands<'w, 's> {
caller, caller,
) { ) {
error!( error!(
"Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}", "{caller}: Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}",
core::any::type_name::<B>(), core::any::type_name::<B>(),
invalid_entities invalid_entities
); );
@ -1044,6 +1041,7 @@ impl<'w, 's> Commands<'w, 's> {
/// isn't scoped to specific targets. /// isn't scoped to specific targets.
/// ///
/// [`Trigger`]: crate::observer::Trigger /// [`Trigger`]: crate::observer::Trigger
#[track_caller]
pub fn trigger(&mut self, event: impl Event) { pub fn trigger(&mut self, event: impl Event) {
self.queue(command::trigger(event)); self.queue(command::trigger(event));
} }
@ -1052,6 +1050,7 @@ impl<'w, 's> Commands<'w, 's> {
/// watches those targets. /// watches those targets.
/// ///
/// [`Trigger`]: crate::observer::Trigger /// [`Trigger`]: crate::observer::Trigger
#[track_caller]
pub fn trigger_targets( pub fn trigger_targets(
&mut self, &mut self,
event: impl Event, event: impl Event,
@ -1602,6 +1601,7 @@ impl<'a> EntityCommands<'a> {
/// } /// }
/// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system);
/// ``` /// ```
#[track_caller]
pub fn remove<T>(&mut self) -> &mut Self pub fn remove<T>(&mut self) -> &mut Self
where where
T: Bundle, T: Bundle,
@ -1679,6 +1679,7 @@ impl<'a> EntityCommands<'a> {
/// } /// }
/// # bevy_ecs::system::assert_is_system(remove_with_requires_system); /// # bevy_ecs::system::assert_is_system(remove_with_requires_system);
/// ``` /// ```
#[track_caller]
pub fn remove_with_requires<T: Bundle>(&mut self) -> &mut Self { pub fn remove_with_requires<T: Bundle>(&mut self) -> &mut Self {
self.queue(entity_command::remove_with_requires::<T>()) self.queue(entity_command::remove_with_requires::<T>())
} }
@ -1688,11 +1689,13 @@ impl<'a> EntityCommands<'a> {
/// # Panics /// # Panics
/// ///
/// Panics if the provided [`ComponentId`] does not exist in the [`World`]. /// Panics if the provided [`ComponentId`] does not exist in the [`World`].
#[track_caller]
pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self { pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self {
self.queue(entity_command::remove_by_id(component_id)) self.queue(entity_command::remove_by_id(component_id))
} }
/// Removes all components associated with the entity. /// Removes all components associated with the entity.
#[track_caller]
pub fn clear(&mut self) -> &mut Self { pub fn clear(&mut self) -> &mut Self {
self.queue(entity_command::clear()) self.queue(entity_command::clear())
} }
@ -1865,6 +1868,7 @@ impl<'a> EntityCommands<'a> {
/// } /// }
/// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system);
/// ``` /// ```
#[track_caller]
pub fn retain<T>(&mut self) -> &mut Self pub fn retain<T>(&mut self) -> &mut Self
where where
T: Bundle, T: Bundle,

View File

@ -1,4 +1,6 @@
use core::ops::Deref; use core::ops::Deref;
#[cfg(feature = "track_location")]
use core::panic::Location;
use crate::{ use crate::{
archetype::Archetype, archetype::Archetype,
@ -126,9 +128,21 @@ impl<'w> DeferredWorld<'w> {
// - ON_REPLACE is able to accept ZST events // - ON_REPLACE is able to accept ZST events
unsafe { unsafe {
let archetype = &*archetype; let archetype = &*archetype;
self.trigger_on_replace(archetype, entity, [component_id].into_iter()); self.trigger_on_replace(
archetype,
entity,
[component_id].into_iter(),
#[cfg(feature = "track_location")]
Location::caller(),
);
if archetype.has_replace_observer() { if archetype.has_replace_observer() {
self.trigger_observers(ON_REPLACE, entity, [component_id].into_iter()); self.trigger_observers(
ON_REPLACE,
entity,
[component_id].into_iter(),
#[cfg(feature = "track_location")]
Location::caller(),
);
} }
} }
@ -155,9 +169,21 @@ impl<'w> DeferredWorld<'w> {
// - ON_REPLACE is able to accept ZST events // - ON_REPLACE is able to accept ZST events
unsafe { unsafe {
let archetype = &*archetype; let archetype = &*archetype;
self.trigger_on_insert(archetype, entity, [component_id].into_iter()); self.trigger_on_insert(
archetype,
entity,
[component_id].into_iter(),
#[cfg(feature = "track_location")]
Location::caller(),
);
if archetype.has_insert_observer() { if archetype.has_insert_observer() {
self.trigger_observers(ON_INSERT, entity, [component_id].into_iter()); self.trigger_observers(
ON_INSERT,
entity,
[component_id].into_iter(),
#[cfg(feature = "track_location")]
Location::caller(),
);
} }
} }
@ -503,13 +529,22 @@ impl<'w> DeferredWorld<'w> {
archetype: &Archetype, archetype: &Archetype,
entity: Entity, entity: Entity,
targets: impl Iterator<Item = ComponentId>, targets: impl Iterator<Item = ComponentId>,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) { ) {
if archetype.has_add_hook() { if archetype.has_add_hook() {
for component_id in targets { for component_id in targets {
// SAFETY: Caller ensures that these components exist // SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_add { if let Some(hook) = hooks.on_add {
hook(DeferredWorld { world: self.world }, entity, component_id); hook(
DeferredWorld { world: self.world },
entity,
component_id,
#[cfg(feature = "track_location")]
Some(caller),
#[cfg(not(feature = "track_location"))]
None,
);
} }
} }
} }
@ -525,13 +560,22 @@ impl<'w> DeferredWorld<'w> {
archetype: &Archetype, archetype: &Archetype,
entity: Entity, entity: Entity,
targets: impl Iterator<Item = ComponentId>, targets: impl Iterator<Item = ComponentId>,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) { ) {
if archetype.has_insert_hook() { if archetype.has_insert_hook() {
for component_id in targets { for component_id in targets {
// SAFETY: Caller ensures that these components exist // SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_insert { if let Some(hook) = hooks.on_insert {
hook(DeferredWorld { world: self.world }, entity, component_id); hook(
DeferredWorld { world: self.world },
entity,
component_id,
#[cfg(feature = "track_location")]
Some(caller),
#[cfg(not(feature = "track_location"))]
None,
);
} }
} }
} }
@ -547,13 +591,22 @@ impl<'w> DeferredWorld<'w> {
archetype: &Archetype, archetype: &Archetype,
entity: Entity, entity: Entity,
targets: impl Iterator<Item = ComponentId>, targets: impl Iterator<Item = ComponentId>,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) { ) {
if archetype.has_replace_hook() { if archetype.has_replace_hook() {
for component_id in targets { for component_id in targets {
// SAFETY: Caller ensures that these components exist // SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_replace { if let Some(hook) = hooks.on_replace {
hook(DeferredWorld { world: self.world }, entity, component_id); hook(
DeferredWorld { world: self.world },
entity,
component_id,
#[cfg(feature = "track_location")]
Some(caller),
#[cfg(not(feature = "track_location"))]
None,
);
} }
} }
} }
@ -569,13 +622,22 @@ impl<'w> DeferredWorld<'w> {
archetype: &Archetype, archetype: &Archetype,
entity: Entity, entity: Entity,
targets: impl Iterator<Item = ComponentId>, targets: impl Iterator<Item = ComponentId>,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) { ) {
if archetype.has_remove_hook() { if archetype.has_remove_hook() {
for component_id in targets { for component_id in targets {
// SAFETY: Caller ensures that these components exist // SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_remove { if let Some(hook) = hooks.on_remove {
hook(DeferredWorld { world: self.world }, entity, component_id); hook(
DeferredWorld { world: self.world },
entity,
component_id,
#[cfg(feature = "track_location")]
Some(caller),
#[cfg(not(feature = "track_location"))]
None,
);
} }
} }
} }
@ -591,13 +653,22 @@ impl<'w> DeferredWorld<'w> {
archetype: &Archetype, archetype: &Archetype,
entity: Entity, entity: Entity,
targets: impl Iterator<Item = ComponentId>, targets: impl Iterator<Item = ComponentId>,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) { ) {
if archetype.has_despawn_hook() { if archetype.has_despawn_hook() {
for component_id in targets { for component_id in targets {
// SAFETY: Caller ensures that these components exist // SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_despawn { if let Some(hook) = hooks.on_despawn {
hook(DeferredWorld { world: self.world }, entity, component_id); hook(
DeferredWorld { world: self.world },
entity,
component_id,
#[cfg(feature = "track_location")]
Some(caller),
#[cfg(not(feature = "track_location"))]
None,
);
} }
} }
} }
@ -613,6 +684,7 @@ impl<'w> DeferredWorld<'w> {
event: ComponentId, event: ComponentId,
target: Entity, target: Entity,
components: impl Iterator<Item = ComponentId> + Clone, components: impl Iterator<Item = ComponentId> + Clone,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) { ) {
Observers::invoke::<_>( Observers::invoke::<_>(
self.reborrow(), self.reborrow(),
@ -621,6 +693,8 @@ impl<'w> DeferredWorld<'w> {
components, components,
&mut (), &mut (),
&mut false, &mut false,
#[cfg(feature = "track_location")]
caller,
); );
} }
@ -636,6 +710,7 @@ impl<'w> DeferredWorld<'w> {
components: &[ComponentId], components: &[ComponentId],
data: &mut E, data: &mut E,
mut propagate: bool, mut propagate: bool,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) where ) where
T: Traversal<E>, T: Traversal<E>,
{ {
@ -647,6 +722,8 @@ impl<'w> DeferredWorld<'w> {
components.iter().copied(), components.iter().copied(),
data, data,
&mut propagate, &mut propagate,
#[cfg(feature = "track_location")]
caller,
); );
if !propagate { if !propagate {
break; break;

View File

@ -1594,6 +1594,23 @@ impl<'w> EntityWorldMut<'w> {
&mut self, &mut self,
component_id: ComponentId, component_id: ComponentId,
component: OwningPtr<'_>, component: OwningPtr<'_>,
) -> &mut Self {
self.insert_by_id_with_caller(
component_id,
component,
#[cfg(feature = "track_location")]
Location::caller(),
)
}
/// # Safety
/// See [`EntityWorldMut::insert_by_id`]
#[inline]
pub(crate) unsafe fn insert_by_id_with_caller(
&mut self,
component_id: ComponentId,
component: OwningPtr<'_>,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) -> &mut Self { ) -> &mut Self {
self.assert_not_despawned(); self.assert_not_despawned();
let change_tick = self.world.change_tick(); let change_tick = self.world.change_tick();
@ -1616,6 +1633,8 @@ impl<'w> EntityWorldMut<'w> {
self.location, self.location,
Some(component).into_iter(), Some(component).into_iter(),
Some(storage_type).iter().cloned(), Some(storage_type).iter().cloned(),
#[cfg(feature = "track_location")]
caller,
); );
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
@ -1665,6 +1684,8 @@ impl<'w> EntityWorldMut<'w> {
self.location, self.location,
iter_components, iter_components,
(*storage_types).iter().cloned(), (*storage_types).iter().cloned(),
#[cfg(feature = "track_location")]
Location::caller(),
); );
*self.world.bundles.get_storages_unchecked(bundle_id) = core::mem::take(&mut storage_types); *self.world.bundles.get_storages_unchecked(bundle_id) = core::mem::take(&mut storage_types);
self.world.flush(); self.world.flush();
@ -1682,6 +1703,7 @@ impl<'w> EntityWorldMut<'w> {
/// If the entity has been despawned while this `EntityWorldMut` is still alive. /// If the entity has been despawned while this `EntityWorldMut` is still alive.
// TODO: BundleRemover? // TODO: BundleRemover?
#[must_use] #[must_use]
#[track_caller]
pub fn take<T: Bundle>(&mut self) -> Option<T> { pub fn take<T: Bundle>(&mut self) -> Option<T> {
self.assert_not_despawned(); self.assert_not_despawned();
let world = &mut self.world; let world = &mut self.world;
@ -1727,6 +1749,8 @@ impl<'w> EntityWorldMut<'w> {
old_archetype, old_archetype,
entity, entity,
bundle_info, bundle_info,
#[cfg(feature = "track_location")]
Location::caller(),
); );
} }
@ -1867,7 +1891,11 @@ impl<'w> EntityWorldMut<'w> {
/// ///
/// # Safety /// # Safety
/// - A `BundleInfo` with the corresponding `BundleId` must have been initialized. /// - A `BundleInfo` with the corresponding `BundleId` must have been initialized.
unsafe fn remove_bundle(&mut self, bundle: BundleId) -> EntityLocation { unsafe fn remove_bundle(
&mut self,
bundle: BundleId,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) -> EntityLocation {
let entity = self.entity; let entity = self.entity;
let world = &mut self.world; let world = &mut self.world;
let location = self.location; let location = self.location;
@ -1910,6 +1938,8 @@ impl<'w> EntityWorldMut<'w> {
old_archetype, old_archetype,
entity, entity,
bundle_info, bundle_info,
#[cfg(feature = "track_location")]
caller,
); );
} }
@ -1956,14 +1986,32 @@ impl<'w> EntityWorldMut<'w> {
/// ///
/// If the entity has been despawned while this `EntityWorldMut` is still alive. /// If the entity has been despawned while this `EntityWorldMut` is still alive.
// TODO: BundleRemover? // TODO: BundleRemover?
#[track_caller]
pub fn remove<T: Bundle>(&mut self) -> &mut Self { pub fn remove<T: Bundle>(&mut self) -> &mut Self {
self.remove_with_caller::<T>(
#[cfg(feature = "track_location")]
Location::caller(),
)
}
#[inline]
pub(crate) fn remove_with_caller<T: Bundle>(
&mut self,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) -> &mut Self {
self.assert_not_despawned(); self.assert_not_despawned();
let storages = &mut self.world.storages; let storages = &mut self.world.storages;
let components = &mut self.world.components; let components = &mut self.world.components;
let bundle_info = self.world.bundles.register_info::<T>(components, storages); let bundle_info = self.world.bundles.register_info::<T>(components, storages);
// SAFETY: the `BundleInfo` is initialized above // SAFETY: the `BundleInfo` is initialized above
self.location = unsafe { self.remove_bundle(bundle_info) }; self.location = unsafe {
self.remove_bundle(
bundle_info,
#[cfg(feature = "track_location")]
caller,
)
};
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -1974,7 +2022,18 @@ impl<'w> EntityWorldMut<'w> {
/// # Panics /// # Panics
/// ///
/// If the entity has been despawned while this `EntityWorldMut` is still alive. /// If the entity has been despawned while this `EntityWorldMut` is still alive.
#[track_caller]
pub fn remove_with_requires<T: Bundle>(&mut self) -> &mut Self { pub fn remove_with_requires<T: Bundle>(&mut self) -> &mut Self {
self.remove_with_requires_with_caller::<T>(
#[cfg(feature = "track_location")]
Location::caller(),
)
}
pub(crate) fn remove_with_requires_with_caller<T: Bundle>(
&mut self,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) -> &mut Self {
self.assert_not_despawned(); self.assert_not_despawned();
let storages = &mut self.world.storages; let storages = &mut self.world.storages;
let components = &mut self.world.components; let components = &mut self.world.components;
@ -1983,7 +2042,13 @@ impl<'w> EntityWorldMut<'w> {
let bundle_id = bundles.register_contributed_bundle_info::<T>(components, storages); let bundle_id = bundles.register_contributed_bundle_info::<T>(components, storages);
// SAFETY: the dynamic `BundleInfo` is initialized above // SAFETY: the dynamic `BundleInfo` is initialized above
self.location = unsafe { self.remove_bundle(bundle_id) }; self.location = unsafe {
self.remove_bundle(
bundle_id,
#[cfg(feature = "track_location")]
caller,
)
};
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -1996,7 +2061,19 @@ impl<'w> EntityWorldMut<'w> {
/// # Panics /// # Panics
/// ///
/// If the entity has been despawned while this `EntityWorldMut` is still alive. /// If the entity has been despawned while this `EntityWorldMut` is still alive.
#[track_caller]
pub fn retain<T: Bundle>(&mut self) -> &mut Self { pub fn retain<T: Bundle>(&mut self) -> &mut Self {
self.retain_with_caller::<T>(
#[cfg(feature = "track_location")]
Location::caller(),
)
}
#[inline]
pub(crate) fn retain_with_caller<T: Bundle>(
&mut self,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) -> &mut Self {
self.assert_not_despawned(); self.assert_not_despawned();
let archetypes = &mut self.world.archetypes; let archetypes = &mut self.world.archetypes;
let storages = &mut self.world.storages; let storages = &mut self.world.storages;
@ -2016,7 +2093,13 @@ impl<'w> EntityWorldMut<'w> {
let remove_bundle = self.world.bundles.init_dynamic_info(components, to_remove); let remove_bundle = self.world.bundles.init_dynamic_info(components, to_remove);
// SAFETY: the `BundleInfo` for the components to remove is initialized above // SAFETY: the `BundleInfo` for the components to remove is initialized above
self.location = unsafe { self.remove_bundle(remove_bundle) }; self.location = unsafe {
self.remove_bundle(
remove_bundle,
#[cfg(feature = "track_location")]
caller,
)
};
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -2030,7 +2113,21 @@ impl<'w> EntityWorldMut<'w> {
/// ///
/// Panics if the provided [`ComponentId`] does not exist in the [`World`] or if the /// Panics if the provided [`ComponentId`] does not exist in the [`World`] or if the
/// entity has been despawned while this `EntityWorldMut` is still alive. /// entity has been despawned while this `EntityWorldMut` is still alive.
#[track_caller]
pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self { pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self {
self.remove_by_id_with_caller(
component_id,
#[cfg(feature = "track_location")]
Location::caller(),
)
}
#[inline]
pub(crate) fn remove_by_id_with_caller(
&mut self,
component_id: ComponentId,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) -> &mut Self {
self.assert_not_despawned(); self.assert_not_despawned();
let components = &mut self.world.components; let components = &mut self.world.components;
@ -2040,7 +2137,13 @@ impl<'w> EntityWorldMut<'w> {
.init_component_info(components, component_id); .init_component_info(components, component_id);
// SAFETY: the `BundleInfo` for this `component_id` is initialized above // SAFETY: the `BundleInfo` for this `component_id` is initialized above
self.location = unsafe { self.remove_bundle(bundle_id) }; self.location = unsafe {
self.remove_bundle(
bundle_id,
#[cfg(feature = "track_location")]
caller,
)
};
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -2054,6 +2157,7 @@ impl<'w> EntityWorldMut<'w> {
/// ///
/// Panics if any of the provided [`ComponentId`]s do not exist in the [`World`] or if the /// Panics if any of the provided [`ComponentId`]s do not exist in the [`World`] or if the
/// entity has been despawned while this `EntityWorldMut` is still alive. /// entity has been despawned while this `EntityWorldMut` is still alive.
#[track_caller]
pub fn remove_by_ids(&mut self, component_ids: &[ComponentId]) -> &mut Self { pub fn remove_by_ids(&mut self, component_ids: &[ComponentId]) -> &mut Self {
self.assert_not_despawned(); self.assert_not_despawned();
let components = &mut self.world.components; let components = &mut self.world.components;
@ -2064,7 +2168,13 @@ impl<'w> EntityWorldMut<'w> {
.init_dynamic_info(components, component_ids); .init_dynamic_info(components, component_ids);
// SAFETY: the `BundleInfo` for this `bundle_id` is initialized above // SAFETY: the `BundleInfo` for this `bundle_id` is initialized above
unsafe { self.remove_bundle(bundle_id) }; unsafe {
self.remove_bundle(
bundle_id,
#[cfg(feature = "track_location")]
Location::caller(),
)
};
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
@ -2076,7 +2186,19 @@ impl<'w> EntityWorldMut<'w> {
/// # Panics /// # Panics
/// ///
/// If the entity has been despawned while this `EntityWorldMut` is still alive. /// If the entity has been despawned while this `EntityWorldMut` is still alive.
#[track_caller]
pub fn clear(&mut self) -> &mut Self { pub fn clear(&mut self) -> &mut Self {
self.clear_with_caller(
#[cfg(feature = "track_location")]
Location::caller(),
)
}
#[inline]
pub(crate) fn clear_with_caller(
&mut self,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) -> &mut Self {
self.assert_not_despawned(); self.assert_not_despawned();
let component_ids: Vec<ComponentId> = self.archetype().components().collect(); let component_ids: Vec<ComponentId> = self.archetype().components().collect();
let components = &mut self.world.components; let components = &mut self.world.components;
@ -2087,7 +2209,13 @@ impl<'w> EntityWorldMut<'w> {
.init_dynamic_info(components, component_ids.as_slice()); .init_dynamic_info(components, component_ids.as_slice());
// SAFETY: the `BundleInfo` for this `component_id` is initialized above // SAFETY: the `BundleInfo` for this `component_id` is initialized above
self.location = unsafe { self.remove_bundle(bundle_id) }; self.location = unsafe {
self.remove_bundle(
bundle_id,
#[cfg(feature = "track_location")]
caller,
)
};
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -2140,17 +2268,53 @@ impl<'w> EntityWorldMut<'w> {
// SAFETY: All components in the archetype exist in world // SAFETY: All components in the archetype exist in world
unsafe { unsafe {
if archetype.has_despawn_observer() { if archetype.has_despawn_observer() {
deferred_world.trigger_observers(ON_DESPAWN, self.entity, archetype.components()); deferred_world.trigger_observers(
ON_DESPAWN,
self.entity,
archetype.components(),
#[cfg(feature = "track_location")]
caller,
);
} }
deferred_world.trigger_on_despawn(archetype, self.entity, archetype.components()); deferred_world.trigger_on_despawn(
archetype,
self.entity,
archetype.components(),
#[cfg(feature = "track_location")]
caller,
);
if archetype.has_replace_observer() { if archetype.has_replace_observer() {
deferred_world.trigger_observers(ON_REPLACE, self.entity, archetype.components()); deferred_world.trigger_observers(
ON_REPLACE,
self.entity,
archetype.components(),
#[cfg(feature = "track_location")]
Location::caller(),
);
} }
deferred_world.trigger_on_replace(archetype, self.entity, archetype.components()); deferred_world.trigger_on_replace(
archetype,
self.entity,
archetype.components(),
#[cfg(feature = "track_location")]
Location::caller(),
);
if archetype.has_remove_observer() { if archetype.has_remove_observer() {
deferred_world.trigger_observers(ON_REMOVE, self.entity, archetype.components()); deferred_world.trigger_observers(
ON_REMOVE,
self.entity,
archetype.components(),
#[cfg(feature = "track_location")]
Location::caller(),
);
} }
deferred_world.trigger_on_remove(archetype, self.entity, archetype.components()); deferred_world.trigger_on_remove(
archetype,
self.entity,
archetype.components(),
#[cfg(feature = "track_location")]
Location::caller(),
);
} }
for component_id in archetype.components() { for component_id in archetype.components() {
@ -2387,13 +2551,29 @@ impl<'w> EntityWorldMut<'w> {
/// # Panics /// # Panics
/// ///
/// If the entity has been despawned while this `EntityWorldMut` is still alive. /// If the entity has been despawned while this `EntityWorldMut` is still alive.
#[track_caller]
pub fn observe<E: Event, B: Bundle, M>( pub fn observe<E: Event, B: Bundle, M>(
&mut self, &mut self,
observer: impl IntoObserverSystem<E, B, M>, observer: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
self.observe_with_caller(
observer,
#[cfg(feature = "track_location")]
Location::caller(),
)
}
pub(crate) fn observe_with_caller<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) -> &mut Self { ) -> &mut Self {
self.assert_not_despawned(); self.assert_not_despawned();
self.world self.world.spawn_with_caller(
.spawn(Observer::new(observer).with_entity(self.entity)); Observer::new(observer).with_entity(self.entity),
#[cfg(feature = "track_location")]
caller,
);
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -2571,19 +2751,40 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers(
archetype: &Archetype, archetype: &Archetype,
entity: Entity, entity: Entity,
bundle_info: &BundleInfo, bundle_info: &BundleInfo,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) { ) {
if archetype.has_replace_observer() { if archetype.has_replace_observer() {
deferred_world.trigger_observers( deferred_world.trigger_observers(
ON_REPLACE, ON_REPLACE,
entity, entity,
bundle_info.iter_explicit_components(), bundle_info.iter_explicit_components(),
#[cfg(feature = "track_location")]
caller,
); );
} }
deferred_world.trigger_on_replace(archetype, entity, bundle_info.iter_explicit_components()); deferred_world.trigger_on_replace(
archetype,
entity,
bundle_info.iter_explicit_components(),
#[cfg(feature = "track_location")]
caller,
);
if archetype.has_remove_observer() { if archetype.has_remove_observer() {
deferred_world.trigger_observers(ON_REMOVE, entity, bundle_info.iter_explicit_components()); deferred_world.trigger_observers(
ON_REMOVE,
entity,
bundle_info.iter_explicit_components(),
#[cfg(feature = "track_location")]
caller,
);
} }
deferred_world.trigger_on_remove(archetype, entity, bundle_info.iter_explicit_components()); deferred_world.trigger_on_remove(
archetype,
entity,
bundle_info.iter_explicit_components(),
#[cfg(feature = "track_location")]
caller,
);
} }
/// A view into a single entity and component in a world, which may either be vacant or occupied. /// A view into a single entity and component in a world, which may either be vacant or occupied.
@ -3860,7 +4061,6 @@ where
/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the /// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the
/// [`BundleInfo`] used to construct [`BundleInserter`] /// [`BundleInfo`] used to construct [`BundleInserter`]
/// - [`Entity`] must correspond to [`EntityLocation`] /// - [`Entity`] must correspond to [`EntityLocation`]
#[track_caller]
unsafe fn insert_dynamic_bundle< unsafe fn insert_dynamic_bundle<
'a, 'a,
I: Iterator<Item = OwningPtr<'a>>, I: Iterator<Item = OwningPtr<'a>>,
@ -3871,6 +4071,7 @@ unsafe fn insert_dynamic_bundle<
location: EntityLocation, location: EntityLocation,
components: I, components: I,
storage_types: S, storage_types: S,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) -> EntityLocation { ) -> EntityLocation {
struct DynamicInsertBundle<'a, I: Iterator<Item = (StorageType, OwningPtr<'a>)>> { struct DynamicInsertBundle<'a, I: Iterator<Item = (StorageType, OwningPtr<'a>)>> {
components: I, components: I,
@ -3896,7 +4097,7 @@ unsafe fn insert_dynamic_bundle<
bundle, bundle,
InsertMode::Replace, InsertMode::Replace,
#[cfg(feature = "track_location")] #[cfg(feature = "track_location")]
Location::caller(), caller,
) )
} }
} }
@ -4200,7 +4401,6 @@ mod tests {
use bevy_ptr::{OwningPtr, Ptr}; use bevy_ptr::{OwningPtr, Ptr};
use core::panic::AssertUnwindSafe; use core::panic::AssertUnwindSafe;
#[cfg(feature = "track_location")]
use core::panic::Location; use core::panic::Location;
#[cfg(feature = "track_location")] #[cfg(feature = "track_location")]
use std::sync::OnceLock; use std::sync::OnceLock;
@ -5282,12 +5482,22 @@ 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(mut world: DeferredWorld, entity: Entity, _id: ComponentId) { fn ord_a_hook_on_add(
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(mut world: DeferredWorld, entity: Entity, _id: ComponentId) { fn ord_a_hook_on_insert(
mut world: DeferredWorld,
entity: Entity,
_id: ComponentId,
_caller: Option<&'static Location<'static>>,
) {
world world
.resource_mut::<TestVec>() .resource_mut::<TestVec>()
.0 .0
@ -5296,14 +5506,24 @@ mod tests {
world.commands().entity(entity).remove::<OrdB>(); world.commands().entity(entity).remove::<OrdB>();
} }
fn ord_a_hook_on_replace(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) { fn ord_a_hook_on_replace(
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(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) { fn ord_a_hook_on_remove(
mut world: DeferredWorld,
_entity: Entity,
_id: ComponentId,
_caller: Option<&'static Location<'static>>,
) {
world world
.resource_mut::<TestVec>() .resource_mut::<TestVec>()
.0 .0
@ -5330,7 +5550,12 @@ 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(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) { fn ord_b_hook_on_add(
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
@ -5340,21 +5565,36 @@ mod tests {
}); });
} }
fn ord_b_hook_on_insert(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) { fn ord_b_hook_on_insert(
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(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) { fn ord_b_hook_on_replace(
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(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) { fn ord_b_hook_on_remove(
mut world: DeferredWorld,
_entity: Entity,
_id: ComponentId,
_caller: Option<&'static Location<'static>>,
) {
world world
.resource_mut::<TestVec>() .resource_mut::<TestVec>()
.0 .0
@ -5494,7 +5734,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) { fn get_tracked(world: DeferredWorld, entity: Entity, _: ComponentId, _: Option<&Location>) {
TRACKED.get_or_init(|| { TRACKED.get_or_init(|| {
world world
.entities .entities
@ -5555,7 +5795,7 @@ 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, entity, _, _| {
ADD_COUNT.fetch_add(1, Ordering::Relaxed); ADD_COUNT.fetch_add(1, Ordering::Relaxed);
assert_eq!( assert_eq!(
@ -5563,7 +5803,7 @@ mod tests {
Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed)))
); );
}) })
.on_remove(|world, entity, _| { .on_remove(|world, entity, _, _| {
REMOVE_COUNT.fetch_add(1, Ordering::Relaxed); REMOVE_COUNT.fetch_add(1, Ordering::Relaxed);
assert_eq!( assert_eq!(
@ -5571,7 +5811,7 @@ mod tests {
Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed)))
); );
}) })
.on_replace(|world, entity, _| { .on_replace(|world, entity, _, _| {
REPLACE_COUNT.fetch_add(1, Ordering::Relaxed); REPLACE_COUNT.fetch_add(1, Ordering::Relaxed);
assert_eq!( assert_eq!(
@ -5579,7 +5819,7 @@ mod tests {
Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed)))
); );
}) })
.on_insert(|world, entity, _| { .on_insert(|world, entity, _, _| {
INSERT_COUNT.fetch_add(1, Ordering::Relaxed); INSERT_COUNT.fetch_add(1, Ordering::Relaxed);
assert_eq!( assert_eq!(

View File

@ -1077,6 +1077,18 @@ impl World {
/// ``` /// ```
#[track_caller] #[track_caller]
pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityWorldMut { pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityWorldMut {
self.spawn_with_caller(
bundle,
#[cfg(feature = "track_location")]
Location::caller(),
)
}
pub(crate) fn spawn_with_caller<B: Bundle>(
&mut self,
bundle: B,
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
) -> EntityWorldMut {
self.flush(); self.flush();
let change_tick = self.change_tick(); let change_tick = self.change_tick();
let entity = self.entities.alloc(); let entity = self.entities.alloc();
@ -1087,7 +1099,7 @@ impl World {
entity, entity,
bundle, bundle,
#[cfg(feature = "track_location")] #[cfg(feature = "track_location")]
Location::caller(), caller,
) )
}; };
@ -1102,7 +1114,7 @@ impl World {
#[cfg(feature = "track_location")] #[cfg(feature = "track_location")]
self.entities self.entities
.set_spawned_or_despawned_by(entity.index(), Location::caller()); .set_spawned_or_despawned_by(entity.index(), caller);
// SAFETY: entity and location are valid, as they were just created above // SAFETY: entity and location are valid, as they were just created above
unsafe { EntityWorldMut::new(self, entity, entity_location) } unsafe { EntityWorldMut::new(self, entity, entity_location) }

View File

@ -1,5 +1,7 @@
//! Contains the [`AutoFocus`] component and related machinery. //! Contains the [`AutoFocus`] component and related machinery.
use core::panic::Location;
use bevy_ecs::{component::ComponentId, prelude::*, world::DeferredWorld}; use bevy_ecs::{component::ComponentId, prelude::*, world::DeferredWorld};
use crate::InputFocus; use crate::InputFocus;
@ -23,7 +25,12 @@ 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(mut world: DeferredWorld, entity: Entity, _: ComponentId) { fn on_auto_focus_added(
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);
} }

View File

@ -368,13 +368,19 @@ mod tests {
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(mut world: DeferredWorld, entity: Entity, _: ComponentId) { fn set_focus_on_add(
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);
} }

View File

@ -23,6 +23,7 @@
//! you can use the [`TabNavigation`] system parameter directly instead. //! you can use the [`TabNavigation`] system parameter directly instead.
//! This object can be injected into your systems, and provides a [`navigate`](`TabNavigation::navigate`) method which can be //! This object can be injected into your systems, and provides a [`navigate`](`TabNavigation::navigate`) method which can be
//! used to navigate between focusable entities. //! used to navigate between focusable entities.
use alloc::vec::Vec; use alloc::vec::Vec;
use bevy_app::{App, Plugin, Startup}; use bevy_app::{App, Plugin, Startup};
use bevy_ecs::{ use bevy_ecs::{

View File

@ -43,6 +43,7 @@ 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};
@ -332,9 +333,14 @@ pub struct Camera {
pub sub_camera_view: Option<SubCameraView>, pub sub_camera_view: Option<SubCameraView>,
} }
fn warn_on_no_render_graph(world: DeferredWorld, entity: Entity, _: ComponentId) { fn warn_on_no_render_graph(
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."); 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());
} }
} }

View File

@ -33,7 +33,7 @@ impl<C: Component> Plugin for SyncComponentPlugin<C> {
app.register_required_components::<C, SyncToRenderWorld>(); app.register_required_components::<C, SyncToRenderWorld>();
app.world_mut().register_component_hooks::<C>().on_remove( app.world_mut().register_component_hooks::<C>().on_remove(
|mut world, entity, _component_id| { |mut world, entity, _component_id, _caller| {
let mut pending = world.resource_mut::<PendingSyncEntity>(); let mut pending = world.resource_mut::<PendingSyncEntity>();
pending.push(EntityRecord::ComponentRemoved(entity)); pending.push(EntityRecord::ComponentRemoved(entity));
}, },

View File

@ -2,6 +2,7 @@ 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::ComponentId;
use bevy_ecs::entity::hash_set::EntityHashSet; use bevy_ecs::entity::hash_set::EntityHashSet;
@ -632,8 +633,12 @@ pub fn check_visibility(
/// ... /// ...
/// } /// }
/// ``` /// ```
pub fn add_visibility_class<C>(mut world: DeferredWorld<'_>, entity: Entity, _: ComponentId) pub fn add_visibility_class<C>(
where mut world: DeferredWorld<'_>,
entity: Entity,
_: ComponentId,
_: Option<&Location>,
) where
C: 'static, C: 'static,
{ {
if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) { if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) {

View File

@ -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());

View File

@ -68,7 +68,7 @@ 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, entity, _, _| {
let Some(handle) = world.get::<DynamicSceneRoot>(entity) else { let Some(handle) = world.get::<DynamicSceneRoot>(entity) else {
return; return;
}; };
@ -87,7 +87,7 @@ 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, entity, _, _| {
if let Some(&SceneInstance(scene_instance)) = world.get::<SceneInstance>(entity) { if let Some(&SceneInstance(scene_instance)) = world.get::<SceneInstance>(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;

View File

@ -63,16 +63,22 @@ 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 3 arguments: // A hook has 4 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 // - 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
// //
// `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| { .on_add(|mut world, 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!("Component: {component_id:?} added to: {entity} with value {value:?}"); println!(
"{component_id:?} added to {entity} with value {value:?}{}",
caller
.map(|location| format!("due to {location}"))
.unwrap_or_default()
);
// Or access resources // Or access resources
world world
.resource_mut::<MyComponentIndex>() .resource_mut::<MyComponentIndex>()
@ -82,21 +88,26 @@ fn setup(world: &mut World) {
}) })
// `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, entity, _, _| {
let value = world.get::<MyComponent>(entity).unwrap().0; let value = world.get::<MyComponent>(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| { .on_remove(|mut world, entity, component_id, caller| {
let value = world.get::<MyComponent>(entity).unwrap().0; let value = world.get::<MyComponent>(entity).unwrap().0;
println!("Component: {component_id:?} removed from: {entity} with value {value:?}"); println!(
"{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()` // You can also issue commands through `.commands()`
world.commands().entity(entity).despawn(); world.commands().entity(entity).despawn();
}); });

View File

@ -10,6 +10,7 @@ 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).
@ -73,7 +74,12 @@ 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(mut world: DeferredWorld<'_>, entity: Entity, _component: ComponentId) { fn on_insert_name(
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")
}; };
@ -88,7 +94,12 @@ fn on_insert_name(mut world: DeferredWorld<'_>, entity: Entity, _component: Comp
/// ///
/// 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(mut world: DeferredWorld<'_>, entity: Entity, _component: ComponentId) { fn on_replace_name(
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")
}; };