diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 94774ddf52822..0942331bb8190 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -70,10 +70,13 @@ impl Component for OrderIndependentTransparencySettings { type Mutability = Mutable; fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_add(|world, entity, _| { + hooks.on_add(|world, entity, _, caller| { if let Some(value) = world.get::(entity) { 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 + ); } } }); diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index fb7d61cbb959d..2d83cfc6683d9 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1058,12 +1058,16 @@ impl<'w> BundleInserter<'w> { ON_REPLACE, entity, archetype_after_insert.iter_existing(), + #[cfg(feature = "track_location")] + caller, ); } deferred_world.trigger_on_replace( archetype, entity, archetype_after_insert.iter_existing(), + #[cfg(feature = "track_location")] + caller, ); } } @@ -1236,12 +1240,16 @@ impl<'w> BundleInserter<'w> { new_archetype, entity, archetype_after_insert.iter_added(), + #[cfg(feature = "track_location")] + caller, ); if new_archetype.has_add_observer() { deferred_world.trigger_observers( ON_ADD, entity, archetype_after_insert.iter_added(), + #[cfg(feature = "track_location")] + caller, ); } match insert_mode { @@ -1251,12 +1259,16 @@ impl<'w> BundleInserter<'w> { new_archetype, entity, archetype_after_insert.iter_inserted(), + #[cfg(feature = "track_location")] + caller, ); if new_archetype.has_insert_observer() { deferred_world.trigger_observers( ON_INSERT, entity, archetype_after_insert.iter_inserted(), + #[cfg(feature = "track_location")] + caller, ); } } @@ -1267,12 +1279,16 @@ impl<'w> BundleInserter<'w> { new_archetype, entity, archetype_after_insert.iter_added(), + #[cfg(feature = "track_location")] + caller, ); if new_archetype.has_insert_observer() { deferred_world.trigger_observers( ON_INSERT, entity, archetype_after_insert.iter_added(), + #[cfg(feature = "track_location")] + caller, ); } } @@ -1348,6 +1364,7 @@ impl<'w> BundleSpawner<'w> { /// # Safety /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type #[inline] + #[track_caller] pub unsafe fn spawn_non_existent( &mut self, entity: Entity, @@ -1395,24 +1412,32 @@ impl<'w> BundleSpawner<'w> { archetype, entity, bundle_info.iter_contributed_components(), + #[cfg(feature = "track_location")] + caller, ); if archetype.has_add_observer() { deferred_world.trigger_observers( ON_ADD, entity, bundle_info.iter_contributed_components(), + #[cfg(feature = "track_location")] + caller, ); } deferred_world.trigger_on_insert( archetype, entity, bundle_info.iter_contributed_components(), + #[cfg(feature = "track_location")] + caller, ); if archetype.has_insert_observer() { deferred_world.trigger_observers( ON_INSERT, entity, bundle_info.iter_contributed_components(), + #[cfg(feature = "track_location")] + caller, ); } }; @@ -1681,6 +1706,7 @@ mod tests { use crate as bevy_ecs; use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; use alloc::vec; + use core::panic::Location; #[derive(Component)] 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)] 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::().assert_order(0); } - fn a_on_insert(mut world: DeferredWorld, _: T1, _: T2) { + fn a_on_insert( + mut world: DeferredWorld, + _: T1, + _: T2, + _: Option<&'static Location<'static>>, + ) { world.resource_mut::().assert_order(1); } - fn a_on_replace(mut world: DeferredWorld, _: T1, _: T2) { + fn a_on_replace( + mut world: DeferredWorld, + _: T1, + _: T2, + _: Option<&'static Location<'static>>, + ) { world.resource_mut::().assert_order(2); } - fn a_on_remove(mut world: DeferredWorld, _: T1, _: T2) { + fn a_on_remove( + mut world: DeferredWorld, + _: T1, + _: T2, + _: Option<&'static Location<'static>>, + ) { world.resource_mut::().assert_order(3); } @@ -1734,10 +1780,10 @@ mod tests { world.init_resource::(); world .register_component_hooks::() - .on_add(|mut world, _, _| world.resource_mut::().assert_order(0)) - .on_insert(|mut world, _, _| world.resource_mut::().assert_order(1)) - .on_replace(|mut world, _, _| world.resource_mut::().assert_order(2)) - .on_remove(|mut world, _, _| world.resource_mut::().assert_order(3)); + .on_add(|mut world, _, _, _| world.resource_mut::().assert_order(0)) + .on_insert(|mut world, _, _, _| world.resource_mut::().assert_order(1)) + .on_replace(|mut world, _, _, _| world.resource_mut::().assert_order(2)) + .on_remove(|mut world, _, _, _| world.resource_mut::().assert_order(3)); let entity = world.spawn(A).id(); world.despawn(entity); @@ -1761,10 +1807,10 @@ mod tests { world.init_resource::(); world .register_component_hooks::() - .on_add(|mut world, _, _| world.resource_mut::().assert_order(0)) - .on_insert(|mut world, _, _| world.resource_mut::().assert_order(1)) - .on_replace(|mut world, _, _| world.resource_mut::().assert_order(2)) - .on_remove(|mut world, _, _| world.resource_mut::().assert_order(3)); + .on_add(|mut world, _, _, _| world.resource_mut::().assert_order(0)) + .on_insert(|mut world, _, _, _| world.resource_mut::().assert_order(1)) + .on_replace(|mut world, _, _, _| world.resource_mut::().assert_order(2)) + .on_remove(|mut world, _, _, _| world.resource_mut::().assert_order(3)); let mut entity = world.spawn_empty(); entity.insert(A); @@ -1778,8 +1824,8 @@ mod tests { let mut world = World::new(); world .register_component_hooks::() - .on_replace(|mut world, _, _| world.resource_mut::().assert_order(0)) - .on_insert(|mut world, _, _| { + .on_replace(|mut world, _, _, _| world.resource_mut::().assert_order(0)) + .on_insert(|mut world, _, _, _| { if let Some(mut r) = world.get_resource_mut::() { r.assert_order(1); } @@ -1800,22 +1846,22 @@ mod tests { world.init_resource::(); world .register_component_hooks::() - .on_add(|mut world, entity, _| { + .on_add(|mut world, entity, _, _| { world.resource_mut::().assert_order(0); world.commands().entity(entity).insert(B); }) - .on_remove(|mut world, entity, _| { + .on_remove(|mut world, entity, _, _| { world.resource_mut::().assert_order(2); world.commands().entity(entity).remove::(); }); world .register_component_hooks::() - .on_add(|mut world, entity, _| { + .on_add(|mut world, entity, _, _| { world.resource_mut::().assert_order(1); world.commands().entity(entity).remove::(); }) - .on_remove(|mut world, _, _| { + .on_remove(|mut world, _, _, _| { world.resource_mut::().assert_order(3); }); @@ -1832,27 +1878,27 @@ mod tests { world.init_resource::(); world .register_component_hooks::() - .on_add(|mut world, entity, _| { + .on_add(|mut world, entity, _, _| { world.resource_mut::().assert_order(0); world.commands().entity(entity).insert(B).insert(C); }); world .register_component_hooks::() - .on_add(|mut world, entity, _| { + .on_add(|mut world, entity, _, _| { world.resource_mut::().assert_order(1); world.commands().entity(entity).insert(D); }); world .register_component_hooks::() - .on_add(|mut world, _, _| { + .on_add(|mut world, _, _, _| { world.resource_mut::().assert_order(3); }); world .register_component_hooks::() - .on_add(|mut world, _, _| { + .on_add(|mut world, _, _, _| { world.resource_mut::().assert_order(2); }); diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index f17bbb106c8cb..a6817bf6ef523 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -21,8 +21,6 @@ use bevy_ptr::{OwningPtr, UnsafeCellDeref}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; use bevy_utils::{HashMap, HashSet, TypeIdMap}; -#[cfg(feature = "track_location")] -use core::panic::Location; use core::{ alloc::Layout, any::{Any, TypeId}, @@ -30,6 +28,7 @@ use core::{ fmt::Debug, marker::PhantomData, mem::needs_drop, + panic::Location, }; use disqualified::ShortName; use thiserror::Error; @@ -304,6 +303,7 @@ pub use bevy_ecs_macros::require; /// # use bevy_ecs::world::DeferredWorld; /// # use bevy_ecs::entity::Entity; /// # use bevy_ecs::component::ComponentId; +/// # use core::panic::Location; /// # /// #[derive(Component)] /// #[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)] /// 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. -/// fn my_on_insert_hook(world: DeferredWorld, _: T1, _: T2) { +/// fn my_on_insert_hook(world: DeferredWorld, _: T1, _: T2, caller: Option<&Location>) { /// // ... /// } /// ``` @@ -497,8 +497,10 @@ pub enum StorageType { SparseSet, } -/// 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 type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`. +/// The caller location is `Some` if the `track_caller` feature is enabled. +pub type ComponentHook = + for<'w> fn(DeferredWorld<'w>, Entity, ComponentId, Option<&'static Location<'static>>); /// [`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>(); /// assert!(tracked_component_query.iter(&world).next().is_none()); /// -/// world.register_component_hooks::().on_add(|mut world, entity, _component_id| { +/// world.register_component_hooks::().on_add(|mut world, entity, _component_id, _caller| { /// let mut tracked_entities = world.resource_mut::(); /// tracked_entities.0.insert(entity); /// }); /// -/// world.register_component_hooks::().on_remove(|mut world, entity, _component_id| { +/// world.register_component_hooks::().on_remove(|mut world, entity, _component_id, _caller| { /// let mut tracked_entities = world.resource_mut::(); /// tracked_entities.0.remove(&entity); /// }); diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index c985edba2c8f4..a57a3e6f6b54f 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -274,6 +274,7 @@ pub struct EntityCloner { impl EntityCloner { /// 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) { // SAFETY: // - `source_entity` is read-only. diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 9963feb58a40c..bd77d3f721b62 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -22,8 +22,8 @@ use crate::{ }; use alloc::{format, string::String, vec::Vec}; use bevy_ecs_macros::VisitEntitiesMut; -use core::ops::Deref; use core::slice; +use core::{ops::Deref, panic::Location}; use disqualified::ShortName; use log::warn; @@ -270,6 +270,7 @@ pub fn validate_parent_has_component( world: DeferredWorld, entity: Entity, _: ComponentId, + caller: Option<&'static Location<'static>>, ) { let entity_ref = world.entity(entity); let Some(child_of) = entity_ref.get::() else { @@ -282,8 +283,9 @@ pub fn validate_parent_has_component( // TODO: print name here once Name lives in bevy_ecs let name: Option = None; 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", + caller.map(|c| format!("{c}: ")).unwrap_or_default(), ty_name = ShortName::of::(), name = name.map_or_else( || format!("Entity {}", entity), diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index f99f73c1220b6..c59c5515b1b62 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -2030,8 +2030,8 @@ mod tests { world.insert_resource(I(0)); world .register_component_hooks::() - .on_add(|mut world, _, _| world.resource_mut::().0 += 1) - .on_insert(|mut world, _, _| world.resource_mut::().0 += 1); + .on_add(|mut world, _, _, _| world.resource_mut::().0 += 1) + .on_insert(|mut world, _, _, _| world.resource_mut::().0 += 1); // Spawn entity and ensure Y was added assert!(world.spawn(X).contains::()); @@ -2060,8 +2060,8 @@ mod tests { world.insert_resource(I(0)); world .register_component_hooks::() - .on_add(|mut world, _, _| world.resource_mut::().0 += 1) - .on_insert(|mut world, _, _| world.resource_mut::().0 += 1); + .on_add(|mut world, _, _, _| world.resource_mut::().0 += 1) + .on_insert(|mut world, _, _, _| world.resource_mut::().0 += 1); // Spawn entity and ensure Y was added assert!(world.spawn_empty().insert(X).contains::()); diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index ab2f4b33a2d62..8edc6d2874b3a 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -15,7 +15,7 @@ impl Component for ObservedBy { type Mutability = Mutable; fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_remove(|mut world, entity, _| { + hooks.on_remove(|mut world, entity, _, _| { let observed_by = { let mut component = world.get_mut::(entity).unwrap(); core::mem::take(&mut component.0) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index de1b61d0434dd..c97e560d174de 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -24,6 +24,9 @@ use core::{ }; 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 /// [`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. @@ -138,6 +141,12 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> { pub fn get_propagate(&self) -> bool { *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> { @@ -311,6 +320,10 @@ pub struct ObserverTrigger { components: SmallVec<[ComponentId; 2]>, /// The entity the trigger targeted. pub target: Entity, + + /// The location of the source code that triggered the obserer. + #[cfg(feature = "track_location")] + pub caller: &'static Location<'static>, } impl ObserverTrigger { @@ -387,6 +400,7 @@ impl Observers { components: impl Iterator + Clone, data: &mut T, propagate: &mut bool, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) { // SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld` let (mut world, observers) = unsafe { @@ -411,6 +425,8 @@ impl Observers { event_type, components: components.clone().collect(), target, + #[cfg(feature = "track_location")] + caller, }, data.into(), propagate, @@ -532,16 +548,38 @@ impl World { /// While event types commonly implement [`Copy`], /// 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. - pub fn trigger(&mut self, mut event: E) { + #[track_caller] + pub fn trigger(&mut self, event: E) { + self.trigger_with_caller( + event, + #[cfg(feature = "track_location")] + Location::caller(), + ); + } + + pub(crate) fn trigger_with_caller( + &mut self, + mut event: E, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, + ) { let event_id = E::register_component_id(self); // 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. /// /// 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. + #[track_caller] pub fn trigger_ref(&mut self, event: &mut E) { let event_id = E::register_component_id(self); // SAFETY: We just registered `event_id` with the type of `event` @@ -553,10 +591,33 @@ impl World { /// While event types commonly implement [`Copy`], /// 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. - pub fn trigger_targets(&mut self, mut event: E, targets: impl TriggerTargets) { + #[track_caller] + pub fn trigger_targets(&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( + &mut self, + mut event: E, + targets: impl TriggerTargets, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, + ) { let event_id = E::register_component_id(self); // 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`, @@ -564,6 +625,7 @@ impl World { /// /// 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. + #[track_caller] pub fn trigger_targets_ref(&mut self, event: &mut E, targets: impl TriggerTargets) { let event_id = E::register_component_id(self); // SAFETY: We just registered `event_id` with the type of `event` @@ -579,6 +641,7 @@ impl World { /// # Safety /// /// Caller must ensure that `event_data` is accessible as the type represented by `event_id`. + #[track_caller] pub unsafe fn trigger_targets_dynamic( &mut self, event_id: ComponentId, @@ -600,11 +663,31 @@ impl World { /// # Safety /// /// Caller must ensure that `event_data` is accessible as the type represented by `event_id`. + #[track_caller] pub unsafe fn trigger_targets_dynamic_ref( &mut self, event_id: ComponentId, event_data: &mut E, 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( + &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); if targets.entities().is_empty() { @@ -616,6 +699,8 @@ impl World { targets.components(), event_data, false, + #[cfg(feature = "track_location")] + caller, ); }; } else { @@ -628,6 +713,8 @@ impl World { targets.components(), event_data, E::AUTO_PROPAGATE, + #[cfg(feature = "track_location")] + caller, ); }; } @@ -756,6 +843,8 @@ impl World { #[cfg(test)] mod tests { use alloc::{vec, vec::Vec}; + #[cfg(feature = "track_location")] + use core::panic::Location; use bevy_ptr::OwningPtr; use bevy_utils::HashMap; @@ -1511,6 +1600,40 @@ mod tests { assert!(world.get_resource::().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| { + 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| { + assert_eq!(trigger.caller(), caller); + }); + world.add_observer(move |trigger: Trigger| { + assert_eq!(trigger.caller(), caller); + }); + world.commands().spawn(Component).clear(); + world.flush(); + } + #[test] fn observer_triggered_components() { #[derive(Resource, Default)] diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 0814e4462686b..96b5838c19f92 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -1,5 +1,6 @@ use alloc::{boxed::Box, vec, vec::Vec}; use core::any::Any; +use core::panic::Location; use crate::{ component::{ComponentHook, ComponentHooks, ComponentId, Mutable, StorageType}, @@ -66,12 +67,12 @@ impl Component for ObserverState { type Mutability = Mutable; 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.register_observer(entity); }); }); - hooks.on_remove(|mut world, entity, _| { + hooks.on_remove(|mut world, entity, _, _| { let descriptor = core::mem::take( &mut world .entity_mut(entity) @@ -318,12 +319,12 @@ impl Component for Observer { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; 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::(entity) else { return; }; let hook = observe.hook_on_add; - hook(world, entity, _id); + hook(world, entity, id, caller); }); } } @@ -396,6 +397,7 @@ fn hook_on_add>( mut world: DeferredWorld<'_>, entity: Entity, _: ComponentId, + _: Option<&'static Location<'static>>, ) { world.commands().queue(move |world: &mut World| { let event_id = E::register_component_id(world); diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 7d2c645a42ba8..10c8bc302c35f 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -4,6 +4,9 @@ mod related_methods; mod relationship_query; mod relationship_source_collection; +use alloc::format; +use core::panic::Location; + pub use related_methods::*; pub use relationship_query::*; pub use relationship_source_collection::*; @@ -71,11 +74,17 @@ pub trait Relationship: Component + Sized { fn from(entity: Entity) -> Self; /// 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::().unwrap().get(); if target_entity == entity { 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::(), core::any::type_name::() ); @@ -93,7 +102,8 @@ pub trait Relationship: Component + Sized { } } else { 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::(), core::any::type_name::() ); @@ -103,7 +113,12 @@ pub trait Relationship: Component + Sized { /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. // 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::().unwrap().get(); if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { if let Some(mut relationship_target) = @@ -164,7 +179,12 @@ pub trait RelationshipTarget: Component + Sized { /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. // 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 // copying the RelationshipTarget collection // SAFETY: This only reads the Self component and queues Remove commands @@ -180,7 +200,13 @@ pub trait RelationshipTarget: Component + Sized { .handle_error_with(error_handler::silent()), ); } 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 + Sized { /// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when /// that entity is despawned. // 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 // copying the RelationshipTarget collection // SAFETY: This only reads the Self component and queues despawn commands @@ -205,7 +236,13 @@ pub trait RelationshipTarget: Component + Sized { .handle_error_with(error_handler::silent()), ); } 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 + ); } } } diff --git a/crates/bevy_ecs/src/system/commands/command.rs b/crates/bevy_ecs/src/system/commands/command.rs index b29535367bf5e..13a45e6622fd5 100644 --- a/crates/bevy_ecs/src/system/commands/command.rs +++ b/crates/bevy_ecs/src/system/commands/command.rs @@ -265,9 +265,16 @@ pub fn run_schedule(label: impl ScheduleLabel) -> impl Command { } /// A [`Command`] that sends a global [`Trigger`](crate::observer::Trigger) without any targets. +#[track_caller] pub fn trigger(event: impl Event) -> impl Command { + #[cfg(feature = "track_location")] + let caller = Location::caller(); 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, targets: impl TriggerTargets + Send + Sync + 'static, ) -> impl Command { + #[cfg(feature = "track_location")] + let caller = Location::caller(); move |world: &mut World| { - world.trigger_targets(event, targets); + world.trigger_targets_with_caller( + event, + targets, + #[cfg(feature = "track_location")] + caller, + ); } } diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index eea85ba4e48c5..e5a1097abcf82 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -186,12 +186,19 @@ pub fn insert_if_new(bundle: impl Bundle) -> impl EntityCommand { /// An [`EntityCommand`] that adds a dynamic component to an entity. #[track_caller] pub fn insert_by_id(component_id: ComponentId, value: T) -> impl EntityCommand { + #[cfg(feature = "track_location")] + let caller = Location::caller(); move |mut entity: EntityWorldMut| { // SAFETY: // - `component_id` safety is ensured by the caller // - `ptr` is valid within the `make` block OwningPtr::make(value, |ptr| unsafe { - entity.insert_by_id(component_id, ptr); + entity.insert_by_id_with_caller( + component_id, + ptr, + #[cfg(feature = "track_location")] + caller, + ); }); } } @@ -214,39 +221,70 @@ pub fn insert_from_world(mode: InsertMode) -> impl Ent } /// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity. +#[track_caller] pub fn remove() -> impl EntityCommand { + #[cfg(feature = "track_location")] + let caller = Location::caller(); move |mut entity: EntityWorldMut| { - entity.remove::(); + entity.remove_with_caller::( + #[cfg(feature = "track_location")] + caller, + ); } } /// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity, /// as well as the required components for each component removed. +#[track_caller] pub fn remove_with_requires() -> impl EntityCommand { + #[cfg(feature = "track_location")] + let caller = Location::caller(); move |mut entity: EntityWorldMut| { - entity.remove_with_requires::(); + entity.remove_with_requires_with_caller::( + #[cfg(feature = "track_location")] + caller, + ); } } /// An [`EntityCommand`] that removes a dynamic component from an entity. +#[track_caller] pub fn remove_by_id(component_id: ComponentId) -> impl EntityCommand { + #[cfg(feature = "track_location")] + let caller = Location::caller(); 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. +#[track_caller] pub fn clear() -> impl EntityCommand { + #[cfg(feature = "track_location")] + let caller = Location::caller(); move |mut entity: EntityWorldMut| { - entity.clear(); + entity.clear_with_caller( + #[cfg(feature = "track_location")] + caller, + ); } } /// An [`EntityCommand`] that removes all components from an entity, /// except for those in the given [`Bundle`]. +#[track_caller] pub fn retain() -> impl EntityCommand { + #[cfg(feature = "track_location")] + let caller = Location::caller(); move |mut entity: EntityWorldMut| { - entity.retain::(); + entity.retain_with_caller::( + #[cfg(feature = "track_location")] + caller, + ); } } @@ -256,6 +294,7 @@ pub fn retain() -> impl EntityCommand { /// /// 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. +#[track_caller] pub fn despawn() -> impl EntityCommand { #[cfg(feature = "track_location")] let caller = Location::caller(); @@ -269,11 +308,18 @@ pub fn despawn() -> impl EntityCommand { /// An [`EntityCommand`] that creates an [`Observer`](crate::observer::Observer) /// listening for events of type `E` targeting an entity +#[track_caller] pub fn observe( observer: impl IntoObserverSystem, ) -> impl EntityCommand { + #[cfg(feature = "track_location")] + let caller = Location::caller(); move |mut entity: EntityWorldMut| { - entity.observe(observer); + entity.observe_with_caller( + observer, + #[cfg(feature = "track_location")] + caller, + ); } } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 59cb74ed8f544..3748c5c1fb4a3 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -13,10 +13,8 @@ pub use parallel_scope::*; use alloc::boxed::Box; use core::marker::PhantomData; -use log::error; - -#[cfg(feature = "track_location")] use core::panic::Location; +use log::error; use crate::{ self as bevy_ecs, @@ -685,7 +683,6 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, B: Bundle, { - #[cfg(feature = "track_location")] let caller = Location::caller(); self.queue(move |world: &mut World| { if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller( @@ -694,7 +691,7 @@ impl<'w, 's> Commands<'w, 's> { caller, ) { 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::(), invalid_entities ); @@ -1044,6 +1041,7 @@ impl<'w, 's> Commands<'w, 's> { /// isn't scoped to specific targets. /// /// [`Trigger`]: crate::observer::Trigger + #[track_caller] pub fn trigger(&mut self, event: impl Event) { self.queue(command::trigger(event)); } @@ -1052,6 +1050,7 @@ impl<'w, 's> Commands<'w, 's> { /// watches those targets. /// /// [`Trigger`]: crate::observer::Trigger + #[track_caller] pub fn trigger_targets( &mut self, event: impl Event, @@ -1602,6 +1601,7 @@ impl<'a> EntityCommands<'a> { /// } /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` + #[track_caller] pub fn remove(&mut self) -> &mut Self where T: Bundle, @@ -1679,6 +1679,7 @@ impl<'a> EntityCommands<'a> { /// } /// # bevy_ecs::system::assert_is_system(remove_with_requires_system); /// ``` + #[track_caller] pub fn remove_with_requires(&mut self) -> &mut Self { self.queue(entity_command::remove_with_requires::()) } @@ -1688,11 +1689,13 @@ impl<'a> EntityCommands<'a> { /// # Panics /// /// 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 { self.queue(entity_command::remove_by_id(component_id)) } /// Removes all components associated with the entity. + #[track_caller] pub fn clear(&mut self) -> &mut Self { self.queue(entity_command::clear()) } @@ -1865,6 +1868,7 @@ impl<'a> EntityCommands<'a> { /// } /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` + #[track_caller] pub fn retain(&mut self) -> &mut Self where T: Bundle, diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 8d77503f8c73a..6cdead4ce5a74 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -1,4 +1,6 @@ use core::ops::Deref; +#[cfg(feature = "track_location")] +use core::panic::Location; use crate::{ archetype::Archetype, @@ -126,9 +128,21 @@ impl<'w> DeferredWorld<'w> { // - ON_REPLACE is able to accept ZST events unsafe { 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() { - 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 unsafe { 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() { - 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, entity: Entity, targets: impl Iterator, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) { if archetype.has_add_hook() { for component_id in targets { // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); 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, entity: Entity, targets: impl Iterator, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) { if archetype.has_insert_hook() { for component_id in targets { // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); 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, entity: Entity, targets: impl Iterator, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) { if archetype.has_replace_hook() { for component_id in targets { // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); 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, entity: Entity, targets: impl Iterator, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) { if archetype.has_remove_hook() { for component_id in targets { // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); 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, entity: Entity, targets: impl Iterator, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) { if archetype.has_despawn_hook() { for component_id in targets { // SAFETY: Caller ensures that these components exist let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); 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, target: Entity, components: impl Iterator + Clone, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) { Observers::invoke::<_>( self.reborrow(), @@ -621,6 +693,8 @@ impl<'w> DeferredWorld<'w> { components, &mut (), &mut false, + #[cfg(feature = "track_location")] + caller, ); } @@ -636,6 +710,7 @@ impl<'w> DeferredWorld<'w> { components: &[ComponentId], data: &mut E, mut propagate: bool, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) where T: Traversal, { @@ -647,6 +722,8 @@ impl<'w> DeferredWorld<'w> { components.iter().copied(), data, &mut propagate, + #[cfg(feature = "track_location")] + caller, ); if !propagate { break; diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index d4ebad7efe7a5..00ed616c5b65f 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1594,6 +1594,23 @@ impl<'w> EntityWorldMut<'w> { &mut self, component_id: ComponentId, 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 { self.assert_not_despawned(); let change_tick = self.world.change_tick(); @@ -1616,6 +1633,8 @@ impl<'w> EntityWorldMut<'w> { self.location, Some(component).into_iter(), Some(storage_type).iter().cloned(), + #[cfg(feature = "track_location")] + caller, ); self.world.flush(); self.update_location(); @@ -1665,6 +1684,8 @@ impl<'w> EntityWorldMut<'w> { self.location, iter_components, (*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.flush(); @@ -1682,6 +1703,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. // TODO: BundleRemover? #[must_use] + #[track_caller] pub fn take(&mut self) -> Option { self.assert_not_despawned(); let world = &mut self.world; @@ -1727,6 +1749,8 @@ impl<'w> EntityWorldMut<'w> { old_archetype, entity, bundle_info, + #[cfg(feature = "track_location")] + Location::caller(), ); } @@ -1867,7 +1891,11 @@ impl<'w> EntityWorldMut<'w> { /// /// # Safety /// - 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 world = &mut self.world; let location = self.location; @@ -1910,6 +1938,8 @@ impl<'w> EntityWorldMut<'w> { old_archetype, entity, 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. // TODO: BundleRemover? + #[track_caller] pub fn remove(&mut self) -> &mut Self { + self.remove_with_caller::( + #[cfg(feature = "track_location")] + Location::caller(), + ) + } + + #[inline] + pub(crate) fn remove_with_caller( + &mut self, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, + ) -> &mut Self { self.assert_not_despawned(); let storages = &mut self.world.storages; let components = &mut self.world.components; let bundle_info = self.world.bundles.register_info::(components, storages); // 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.update_location(); self @@ -1974,7 +2022,18 @@ impl<'w> EntityWorldMut<'w> { /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. + #[track_caller] pub fn remove_with_requires(&mut self) -> &mut Self { + self.remove_with_requires_with_caller::( + #[cfg(feature = "track_location")] + Location::caller(), + ) + } + + pub(crate) fn remove_with_requires_with_caller( + &mut self, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, + ) -> &mut Self { self.assert_not_despawned(); let storages = &mut self.world.storages; let components = &mut self.world.components; @@ -1983,7 +2042,13 @@ impl<'w> EntityWorldMut<'w> { let bundle_id = bundles.register_contributed_bundle_info::(components, storages); // 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.update_location(); self @@ -1996,7 +2061,19 @@ impl<'w> EntityWorldMut<'w> { /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. + #[track_caller] pub fn retain(&mut self) -> &mut Self { + self.retain_with_caller::( + #[cfg(feature = "track_location")] + Location::caller(), + ) + } + + #[inline] + pub(crate) fn retain_with_caller( + &mut self, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, + ) -> &mut Self { self.assert_not_despawned(); let archetypes = &mut self.world.archetypes; 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); // 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.update_location(); self @@ -2030,7 +2113,21 @@ impl<'w> EntityWorldMut<'w> { /// /// Panics if the provided [`ComponentId`] does not exist in the [`World`] or if the /// entity has been despawned while this `EntityWorldMut` is still alive. + #[track_caller] 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(); let components = &mut self.world.components; @@ -2040,7 +2137,13 @@ impl<'w> EntityWorldMut<'w> { .init_component_info(components, component_id); // 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.update_location(); 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 /// entity has been despawned while this `EntityWorldMut` is still alive. + #[track_caller] pub fn remove_by_ids(&mut self, component_ids: &[ComponentId]) -> &mut Self { self.assert_not_despawned(); let components = &mut self.world.components; @@ -2064,7 +2168,13 @@ impl<'w> EntityWorldMut<'w> { .init_dynamic_info(components, component_ids); // 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.update_location(); @@ -2076,7 +2186,19 @@ impl<'w> EntityWorldMut<'w> { /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. + #[track_caller] 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(); let component_ids: Vec = self.archetype().components().collect(); let components = &mut self.world.components; @@ -2087,7 +2209,13 @@ impl<'w> EntityWorldMut<'w> { .init_dynamic_info(components, component_ids.as_slice()); // 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.update_location(); self @@ -2140,17 +2268,53 @@ impl<'w> EntityWorldMut<'w> { // SAFETY: All components in the archetype exist in world unsafe { 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() { - 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() { - 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() { @@ -2387,13 +2551,29 @@ impl<'w> EntityWorldMut<'w> { /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. + #[track_caller] pub fn observe( &mut self, observer: impl IntoObserverSystem, + ) -> &mut Self { + self.observe_with_caller( + observer, + #[cfg(feature = "track_location")] + Location::caller(), + ) + } + + pub(crate) fn observe_with_caller( + &mut self, + observer: impl IntoObserverSystem, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) -> &mut Self { self.assert_not_despawned(); - self.world - .spawn(Observer::new(observer).with_entity(self.entity)); + self.world.spawn_with_caller( + Observer::new(observer).with_entity(self.entity), + #[cfg(feature = "track_location")] + caller, + ); self.world.flush(); self.update_location(); self @@ -2571,19 +2751,40 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( archetype: &Archetype, entity: Entity, bundle_info: &BundleInfo, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) { if archetype.has_replace_observer() { deferred_world.trigger_observers( ON_REPLACE, entity, 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() { - 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. @@ -3860,7 +4061,6 @@ where /// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the /// [`BundleInfo`] used to construct [`BundleInserter`] /// - [`Entity`] must correspond to [`EntityLocation`] -#[track_caller] unsafe fn insert_dynamic_bundle< 'a, I: Iterator>, @@ -3871,6 +4071,7 @@ unsafe fn insert_dynamic_bundle< location: EntityLocation, components: I, storage_types: S, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, ) -> EntityLocation { struct DynamicInsertBundle<'a, I: Iterator)>> { components: I, @@ -3896,7 +4097,7 @@ unsafe fn insert_dynamic_bundle< bundle, InsertMode::Replace, #[cfg(feature = "track_location")] - Location::caller(), + caller, ) } } @@ -4200,7 +4401,6 @@ mod tests { use bevy_ptr::{OwningPtr, Ptr}; use core::panic::AssertUnwindSafe; - #[cfg(feature = "track_location")] use core::panic::Location; #[cfg(feature = "track_location")] 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)] 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::().0.push("OrdA hook on_add"); 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 .resource_mut::() .0 @@ -5296,14 +5506,24 @@ mod tests { world.commands().entity(entity).remove::(); } - 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 .resource_mut::() .0 .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 .resource_mut::() .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)] 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::().0.push("OrdB hook on_add"); world.commands().queue(|world: &mut 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 .resource_mut::() .0 .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 .resource_mut::() .0 .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 .resource_mut::() .0 @@ -5494,7 +5734,7 @@ mod tests { struct C; 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(|| { world .entities @@ -5555,7 +5795,7 @@ mod tests { world.register_component::(); world .register_component_hooks::() - .on_add(|world, entity, _| { + .on_add(|world, entity, _, _| { ADD_COUNT.fetch_add(1, Ordering::Relaxed); assert_eq!( @@ -5563,7 +5803,7 @@ mod tests { Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) ); }) - .on_remove(|world, entity, _| { + .on_remove(|world, entity, _, _| { REMOVE_COUNT.fetch_add(1, Ordering::Relaxed); assert_eq!( @@ -5571,7 +5811,7 @@ mod tests { Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) ); }) - .on_replace(|world, entity, _| { + .on_replace(|world, entity, _, _| { REPLACE_COUNT.fetch_add(1, Ordering::Relaxed); assert_eq!( @@ -5579,7 +5819,7 @@ mod tests { Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) ); }) - .on_insert(|world, entity, _| { + .on_insert(|world, entity, _, _| { INSERT_COUNT.fetch_add(1, Ordering::Relaxed); assert_eq!( diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 5c619d706464c..7d2eb08a09c32 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1077,6 +1077,18 @@ impl World { /// ``` #[track_caller] pub fn spawn(&mut self, bundle: B) -> EntityWorldMut { + self.spawn_with_caller( + bundle, + #[cfg(feature = "track_location")] + Location::caller(), + ) + } + + pub(crate) fn spawn_with_caller( + &mut self, + bundle: B, + #[cfg(feature = "track_location")] caller: &'static Location<'static>, + ) -> EntityWorldMut { self.flush(); let change_tick = self.change_tick(); let entity = self.entities.alloc(); @@ -1087,7 +1099,7 @@ impl World { entity, bundle, #[cfg(feature = "track_location")] - Location::caller(), + caller, ) }; @@ -1102,7 +1114,7 @@ impl World { #[cfg(feature = "track_location")] 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 unsafe { EntityWorldMut::new(self, entity, entity_location) } diff --git a/crates/bevy_input_focus/src/autofocus.rs b/crates/bevy_input_focus/src/autofocus.rs index 57e53c571f3a3..e91833adb1fc7 100644 --- a/crates/bevy_input_focus/src/autofocus.rs +++ b/crates/bevy_input_focus/src/autofocus.rs @@ -1,5 +1,7 @@ //! Contains the [`AutoFocus`] component and related machinery. +use core::panic::Location; + use bevy_ecs::{component::ComponentId, prelude::*, world::DeferredWorld}; use crate::InputFocus; @@ -23,7 +25,12 @@ use bevy_reflect::{prelude::*, Reflect}; #[component(on_add = on_auto_focus_added)] 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::() { input_focus.set(entity); } diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 54823b14b6983..28640d20deeff 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -368,13 +368,19 @@ mod tests { ButtonState, InputPlugin, }; use bevy_window::WindowResolution; + use core::panic::Location; use smol_str::SmolStr; #[derive(Component)] #[component(on_add = set_focus_on_add)] 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::(); input_focus.set(entity); } diff --git a/crates/bevy_input_focus/src/tab_navigation.rs b/crates/bevy_input_focus/src/tab_navigation.rs index dedb191d5a239..683e5d12d5fe8 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -23,6 +23,7 @@ //! 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 //! used to navigate between focusable entities. + use alloc::vec::Vec; use bevy_app::{App, Plugin, Startup}; use bevy_ecs::{ diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index f7ef9e09a8830..5b48dc4fec98b 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -43,6 +43,7 @@ use bevy_window::{ WindowScaleFactorChanged, }; use core::ops::Range; +use core::panic::Location; use derive_more::derive::From; use tracing::warn; use wgpu::{BlendState, TextureFormat, TextureUsages}; @@ -332,9 +333,14 @@ pub struct Camera { pub sub_camera_view: Option, } -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::() { - 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()); } } diff --git a/crates/bevy_render/src/sync_component.rs b/crates/bevy_render/src/sync_component.rs index 0fac10b409a17..f4f0f9a5e40fc 100644 --- a/crates/bevy_render/src/sync_component.rs +++ b/crates/bevy_render/src/sync_component.rs @@ -33,7 +33,7 @@ impl Plugin for SyncComponentPlugin { app.register_required_components::(); app.world_mut().register_component_hooks::().on_remove( - |mut world, entity, _component_id| { + |mut world, entity, _component_id, _caller| { let mut pending = world.resource_mut::(); pending.push(EntityRecord::ComponentRemoved(entity)); }, diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 7a7a89d30f1fe..6660e6628071e 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -2,6 +2,7 @@ mod range; mod render_layers; use core::any::TypeId; +use core::panic::Location; use bevy_ecs::component::ComponentId; use bevy_ecs::entity::hash_set::EntityHashSet; @@ -632,8 +633,12 @@ pub fn check_visibility( /// ... /// } /// ``` -pub fn add_visibility_class(mut world: DeferredWorld<'_>, entity: Entity, _: ComponentId) -where +pub fn add_visibility_class( + mut world: DeferredWorld<'_>, + entity: Entity, + _: ComponentId, + _: Option<&Location>, +) where C: 'static, { if let Some(mut visibility_class) = world.get_mut::(entity) { diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 88d0105035deb..312826663e86b 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -365,7 +365,7 @@ mod tests { let mut dst_world = World::new(); dst_world .register_component_hooks::() - .on_add(|mut world, _, _| { + .on_add(|mut world, _, _, _| { world.commands().spawn_empty(); }); dst_world.insert_resource(reg.clone()); diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 76673219c5e7d..a68d63b302004 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -68,7 +68,7 @@ impl Plugin for ScenePlugin { // Register component hooks for DynamicSceneRoot app.world_mut() .register_component_hooks::() - .on_remove(|mut world, entity, _| { + .on_remove(|mut world, entity, _, _| { let Some(handle) = world.get::(entity) else { return; }; @@ -87,7 +87,7 @@ impl Plugin for ScenePlugin { // Register component hooks for SceneRoot app.world_mut() .register_component_hooks::() - .on_remove(|mut world, entity, _| { + .on_remove(|mut world, entity, _, _| { if let Some(&SceneInstance(scene_instance)) = world.get::(entity) { let Some(mut scene_spawner) = world.get_resource_mut::() else { return; diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 6596bdf3c259d..26136965d7f37 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -63,16 +63,22 @@ fn setup(world: &mut World) { world .register_component_hooks::() // 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` // - the entity that triggered the hook // - 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(|mut world, entity, component_id| { + .on_add(|mut world, entity, component_id, caller| { // You can access component data from within the hook let value = world.get::(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 world .resource_mut::() @@ -82,21 +88,26 @@ fn setup(world: &mut World) { }) // `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 - .on_insert(|world, _, _| { + .on_insert(|world, _, _, _| { println!("Current Index: {:?}", world.resource::()); }) // `on_replace` will trigger when a component is inserted onto an entity that already had it, // and runs before the value is replaced. // 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::(entity).unwrap().0; world.resource_mut::().remove(&value); }) // `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 - .on_remove(|mut world, entity, component_id| { + .on_remove(|mut world, entity, component_id, caller| { let value = world.get::(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()` world.commands().entity(entity).despawn(); }); diff --git a/examples/ecs/immutable_components.rs b/examples/ecs/immutable_components.rs index 9b385a466b01e..ca5682f36246e 100644 --- a/examples/ecs/immutable_components.rs +++ b/examples/ecs/immutable_components.rs @@ -10,6 +10,7 @@ use bevy::{ utils::HashMap, }; use core::alloc::Layout; +use core::panic::Location; /// This component is mutable, the default case. This is indicated by components /// 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 /// 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::() else { 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 /// 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::() else { unreachable!("OnReplace hook guarantees `Name` is available on entity") };