From 6dc972af36d9416b97cfcfb459a8b9360064fa3d Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 3 Oct 2024 01:54:23 +0200 Subject: [PATCH 01/12] track callsite for observer --- crates/bevy_ecs/src/bundle.rs | 21 ++- crates/bevy_ecs/src/observer/mod.rs | 71 ++++++++++- crates/bevy_ecs/src/observer/trigger_event.rs | 29 ++++- crates/bevy_ecs/src/system/commands/mod.rs | 93 +++++++------- crates/bevy_ecs/src/world/deferred_world.rs | 5 + crates/bevy_ecs/src/world/entity_ref.rs | 120 +++++++++++++----- crates/bevy_ecs/src/world/mod.rs | 44 +------ crates/bevy_ecs/src/world/spawn_batch.rs | 17 +-- 8 files changed, 251 insertions(+), 149 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 446eb30921225..41980133f775e 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -22,7 +22,6 @@ use crate::{ }; use bevy_ptr::{ConstNonNull, OwningPtr}; use bevy_utils::{all_tuples, HashMap, HashSet, TypeIdMap}; -#[cfg(feature = "track_change_detection")] use core::panic::Location; use core::{any::TypeId, ptr::NonNull}; @@ -893,7 +892,7 @@ impl<'w> BundleInserter<'w> { location: EntityLocation, bundle: T, insert_mode: InsertMode, - #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, + caller: &'static Location<'static>, ) -> EntityLocation { let bundle_info = self.bundle_info.as_ref(); let add_bundle = self.add_bundle.as_ref(); @@ -913,6 +912,7 @@ impl<'w> BundleInserter<'w> { ON_REPLACE, entity, add_bundle.iter_existing(), + caller, ); } } @@ -1082,7 +1082,7 @@ impl<'w> BundleInserter<'w> { unsafe { deferred_world.trigger_on_add(new_archetype, entity, add_bundle.iter_added()); if new_archetype.has_add_observer() { - deferred_world.trigger_observers(ON_ADD, entity, add_bundle.iter_added()); + deferred_world.trigger_observers(ON_ADD, entity, add_bundle.iter_added(), caller); } match insert_mode { InsertMode::Replace => { @@ -1097,6 +1097,7 @@ impl<'w> BundleInserter<'w> { ON_INSERT, entity, add_bundle.iter_inserted(), + caller, ); } } @@ -1113,6 +1114,7 @@ impl<'w> BundleInserter<'w> { ON_INSERT, entity, add_bundle.iter_added(), + caller, ); } } @@ -1192,7 +1194,7 @@ impl<'w> BundleSpawner<'w> { &mut self, entity: Entity, bundle: T, - #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, + caller: &'static Location<'static>, ) -> EntityLocation { // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid let bundle_info = self.bundle_info.as_ref(); @@ -1241,6 +1243,7 @@ impl<'w> BundleSpawner<'w> { ON_ADD, entity, bundle_info.iter_contributed_components(), + caller, ); } deferred_world.trigger_on_insert( @@ -1253,6 +1256,7 @@ impl<'w> BundleSpawner<'w> { ON_INSERT, entity, bundle_info.iter_contributed_components(), + caller, ); } }; @@ -1266,17 +1270,12 @@ impl<'w> BundleSpawner<'w> { pub unsafe fn spawn( &mut self, bundle: T, - #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, + caller: &'static Location<'static>, ) -> Entity { let entity = self.entities().alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type unsafe { - self.spawn_non_existent( - entity, - bundle, - #[cfg(feature = "track_change_detection")] - caller, - ); + self.spawn_non_existent(entity, bundle, caller); } entity } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index c74d78ae0318f..8f9b2c161d7d0 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -18,6 +18,7 @@ use crate::{ }; use bevy_ptr::Ptr; use bevy_utils::HashMap; +use core::panic::Location; use core::{ fmt::Debug, marker::PhantomData, @@ -199,6 +200,9 @@ pub struct ObserverTrigger { /// The entity the trigger targeted. pub entity: Entity, + + /// The location of the source code that triggered the obserer. + pub caller: &'static Location<'static>, } // Map between an observer entity and its runner @@ -265,6 +269,7 @@ impl Observers { components: impl Iterator, data: &mut T, propagate: &mut bool, + caller: &'static Location<'static>, ) { // SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld` let (mut world, observers) = unsafe { @@ -286,6 +291,7 @@ impl Observers { observer, event_type, entity, + caller, }, data.into(), propagate, @@ -377,16 +383,28 @@ 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. + #[track_caller] pub fn trigger(&mut self, event: impl Event) { - TriggerEvent { event, targets: () }.trigger(self); + TriggerEvent { + event, + targets: (), + caller: Location::caller(), + } + .trigger(self); } /// 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 impl Event) { - TriggerEvent { event, targets: () }.trigger_ref(self); + TriggerEvent { + event, + targets: (), + caller: Location::caller(), + } + .trigger_ref(self); } /// Triggers the given [`Event`] for the given `targets`, which will run any [`Observer`]s watching for it. @@ -394,8 +412,14 @@ 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. + #[track_caller] pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) { - TriggerEvent { event, targets }.trigger(self); + TriggerEvent { + event, + targets, + caller: Location::caller(), + } + .trigger(self); } /// Triggers the given [`Event`] as a mutable reference for the given `targets`, @@ -403,8 +427,14 @@ 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 impl Event, targets: impl TriggerTargets) { - TriggerEvent { event, targets }.trigger_ref(self); + TriggerEvent { + event, + targets, + caller: Location::caller(), + } + .trigger_ref(self); } /// Register an observer to the cache, called when an observer is created @@ -529,6 +559,7 @@ impl World { #[cfg(test)] mod tests { use alloc::vec; + use core::panic::Location; use bevy_ptr::OwningPtr; @@ -1234,4 +1265,36 @@ mod tests { assert!(world.get_resource::().is_some()); } + + #[test] + #[track_caller] + fn observer_caller_location_event() { + #[derive(Event)] + struct EventA; + + let caller = Location::caller(); + let mut world = World::new(); + world.observe(move |trigger: Trigger| { + assert_eq!(trigger.trigger.caller, caller); + }); + world.trigger(EventA); + } + + #[test] + #[track_caller] + fn observer_caller_location_command_archetype_move() { + #[derive(Component)] + struct Component; + + let caller = Location::caller(); + let mut world = World::new(); + world.observe(move |trigger: Trigger| { + assert_eq!(trigger.trigger.caller, caller); + }); + world.observe(move |trigger: Trigger| { + assert_eq!(trigger.trigger.caller, caller); + }); + world.commands().spawn(Component).clear(); + world.flush_commands(); + } } diff --git a/crates/bevy_ecs/src/observer/trigger_event.rs b/crates/bevy_ecs/src/observer/trigger_event.rs index 5221aff070e2e..843658a3647b4 100644 --- a/crates/bevy_ecs/src/observer/trigger_event.rs +++ b/crates/bevy_ecs/src/observer/trigger_event.rs @@ -1,3 +1,5 @@ +use core::panic::Location; + use crate::{ component::ComponentId, entity::Entity, @@ -12,19 +14,28 @@ pub struct TriggerEvent { /// The targets to trigger the event for. pub targets: Targets, + + /// The source code that emitted this command. + pub caller: &'static Location<'static>, } impl TriggerEvent { pub(super) fn trigger(mut self, world: &mut World) { let event_type = world.register_component::(); - trigger_event(world, event_type, &mut self.event, self.targets); + trigger_event( + world, + event_type, + &mut self.event, + self.targets, + self.caller, + ); } } impl TriggerEvent<&mut E, Targets> { pub(super) fn trigger_ref(self, world: &mut World) { let event_type = world.register_component::(); - trigger_event(world, event_type, self.event, self.targets); + trigger_event(world, event_type, self.event, self.targets, self.caller); } } @@ -41,17 +52,20 @@ pub struct EmitDynamicTrigger { event_type: ComponentId, event_data: T, targets: Targets, + caller: &'static Location<'static>, } impl EmitDynamicTrigger { /// Sets the event type of the resulting trigger, used for dynamic triggers /// # Safety /// Caller must ensure that the component associated with `event_type` is accessible as E + #[track_caller] pub unsafe fn new_with_id(event_type: ComponentId, event_data: E, targets: Targets) -> Self { Self { event_type, event_data, targets, + caller: Location::caller(), } } } @@ -60,7 +74,13 @@ impl Command for EmitDynamicTrigger { fn apply(mut self, world: &mut World) { - trigger_event(world, self.event_type, &mut self.event_data, self.targets); + trigger_event( + world, + self.event_type, + &mut self.event_data, + self.targets, + self.caller, + ); } } @@ -70,6 +90,7 @@ fn trigger_event( event_type: ComponentId, event_data: &mut E, targets: Targets, + caller: &'static Location<'static>, ) { let mut world = DeferredWorld::from(world); if targets.entities().is_empty() { @@ -81,6 +102,7 @@ fn trigger_event( targets.components(), event_data, false, + caller, ); }; } else { @@ -93,6 +115,7 @@ fn trigger_event( targets.components(), event_data, E::AUTO_PROPAGATE, + caller, ); }; } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 4f941c07a6721..763d1abe240e7 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -814,20 +814,30 @@ 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(TriggerEvent { event, targets: () }); + self.queue(TriggerEvent { + event, + targets: (), + caller: Location::caller(), + }); } /// Sends a [`Trigger`] for the given targets. This will run any [`Observer`] of the `event` that /// watches those targets. /// /// [`Trigger`]: crate::observer::Trigger + #[track_caller] pub fn trigger_targets( &mut self, event: impl Event, targets: impl TriggerTargets + Send + Sync + 'static, ) { - self.queue(TriggerEvent { event, targets }); + self.queue(TriggerEvent { + event, + targets, + caller: Location::caller(), + }); } /// Spawns an [`Observer`] and returns the [`EntityCommands`] associated with the entity that stores the observer. @@ -1303,6 +1313,7 @@ impl EntityCommands<'_> { /// } /// # bevy_ecs::system::assert_is_system(add_health_system); /// ``` + #[track_caller] pub fn try_insert_if_new_and(&mut self, bundle: impl Bundle, condition: F) -> &mut Self where F: FnOnce() -> bool, @@ -1323,6 +1334,7 @@ impl EntityCommands<'_> { /// # Note /// /// Unlike [`Self::insert_if_new`], this will not panic if the associated entity does not exist. + #[track_caller] pub fn try_insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { self.queue(try_insert(bundle, InsertMode::Keep)) } @@ -1362,19 +1374,22 @@ impl EntityCommands<'_> { /// } /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` + #[track_caller] pub fn remove(&mut self) -> &mut Self where T: Bundle, { - self.queue(remove::) + self.queue(remove::()) } /// Removes a component from the entity. + #[track_caller] pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self { self.queue(remove_by_id(component_id)) } /// Removes all components associated with the entity. + #[track_caller] pub fn clear(&mut self) -> &mut Self { self.queue(clear()) } @@ -1473,7 +1488,7 @@ impl EntityCommands<'_> { where T: Bundle, { - self.queue(retain::) + self.queue(retain::()) } /// Logs the components of the entity at the info level. @@ -1647,15 +1662,9 @@ where I: IntoIterator + Send + Sync + 'static, B: Bundle, { - #[cfg(feature = "track_change_detection")] let caller = Location::caller(); move |world: &mut World| { - SpawnBatchIter::new( - world, - bundles_iter.into_iter(), - #[cfg(feature = "track_change_detection")] - caller, - ); + SpawnBatchIter::new(world, bundles_iter.into_iter(), caller); } } @@ -1669,14 +1678,10 @@ where I: IntoIterator + Send + Sync + 'static, B: Bundle, { - #[cfg(feature = "track_change_detection")] let caller = Location::caller(); move |world: &mut World| { - if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller( - bundles_iter, - #[cfg(feature = "track_change_detection")] - caller, - ) { + if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller(bundles_iter, caller) + { error!( "Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}", core::any::type_name::(), @@ -1707,12 +1712,7 @@ fn insert(bundle: T, mode: InsertMode) -> impl EntityCommand { let caller = Location::caller(); move |entity: Entity, world: &mut World| { if let Some(mut entity) = world.get_entity_mut(entity) { - entity.insert_with_caller( - bundle, - mode, - #[cfg(feature = "track_change_detection")] - caller, - ); + entity.insert_with_caller(bundle, mode, caller); } else { panic!("error[B0003]: {caller}: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), entity); } @@ -1726,12 +1726,7 @@ fn insert_from_world(mode: InsertMode) -> impl EntityC move |entity: Entity, world: &mut World| { let value = T::from_world(world); if let Some(mut entity) = world.get_entity_mut(entity) { - entity.insert_with_caller( - value, - mode, - #[cfg(feature = "track_change_detection")] - caller, - ); + entity.insert_with_caller(value, mode, caller); } else { panic!("error[B0003]: {caller}: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), entity); } @@ -1742,16 +1737,10 @@ fn insert_from_world(mode: InsertMode) -> impl EntityC /// Does nothing if the entity does not exist. #[track_caller] fn try_insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand { - #[cfg(feature = "track_change_detection")] let caller = Location::caller(); move |entity, world: &mut World| { if let Some(mut entity) = world.get_entity_mut(entity) { - entity.insert_with_caller( - bundle, - mode, - #[cfg(feature = "track_change_detection")] - caller, - ); + entity.insert_with_caller(bundle, mode, caller); } } } @@ -1762,18 +1751,20 @@ fn try_insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand { /// /// - The returned `EntityCommand` must be queued for the world where `component_id` was created. /// - `T` must be the type represented by `component_id`. +#[track_caller] unsafe fn insert_by_id( component_id: ComponentId, value: T, on_none_entity: impl FnOnce(Entity) + Send + 'static, ) -> impl EntityCommand { + let caller = Location::caller(); move |entity, world: &mut World| { if let Some(mut entity) = world.get_entity_mut(entity) { // 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, caller); }); } else { on_none_entity(entity); @@ -1785,9 +1776,13 @@ unsafe fn insert_by_id( /// /// For a [`Bundle`] type `T`, this will remove any components in the bundle. /// Any components in the bundle that aren't found on the entity will be ignored. -fn remove(entity: Entity, world: &mut World) { - if let Some(mut entity) = world.get_entity_mut(entity) { - entity.remove::(); +#[track_caller] +fn remove() -> impl EntityCommand { + let caller = Location::caller(); + move |entity, world: &mut World| { + if let Some(mut entity) = world.get_entity_mut(entity) { + entity.remove_with_caller::(caller); + } } } @@ -1795,19 +1790,23 @@ fn remove(entity: Entity, world: &mut World) { /// # Panics /// /// Panics if the provided [`ComponentId`] does not exist in the [`World`]. +#[track_caller] fn remove_by_id(component_id: ComponentId) -> impl EntityCommand { + let caller = Location::caller(); move |entity: Entity, world: &mut World| { if let Some(mut entity) = world.get_entity_mut(entity) { - entity.remove_by_id(component_id); + entity.remove_by_id_with_caller(component_id, caller); } } } /// An [`EntityCommand`] that removes all components associated with a provided entity. +#[track_caller] fn clear() -> impl EntityCommand { + let caller = Location::caller(); move |entity: Entity, world: &mut World| { if let Some(mut entity) = world.get_entity_mut(entity) { - entity.clear(); + entity.clear_with_caller(caller); } } } @@ -1816,9 +1815,13 @@ fn clear() -> impl EntityCommand { /// /// For a [`Bundle`] type `T`, this will remove all components except those in the bundle. /// Any components in the bundle that aren't found on the entity will be ignored. -fn retain(entity: Entity, world: &mut World) { - if let Some(mut entity_mut) = world.get_entity_mut(entity) { - entity_mut.retain::(); +#[track_caller] +fn retain() -> impl EntityCommand { + let caller = Location::caller(); + move |entity: Entity, world: &mut World| { + if let Some(mut entity_mut) = world.get_entity_mut(entity) { + entity_mut.retain_with_caller::(caller); + } } } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 8b3b046859137..9f64fc7a928fa 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -1,4 +1,5 @@ use core::ops::Deref; +use core::panic::Location; use crate::{ archetype::Archetype, @@ -371,6 +372,7 @@ impl<'w> DeferredWorld<'w> { event: ComponentId, entity: Entity, components: impl Iterator, + caller: &'static Location<'static>, ) { Observers::invoke::<_>( self.reborrow(), @@ -379,6 +381,7 @@ impl<'w> DeferredWorld<'w> { components, &mut (), &mut false, + caller, ); } @@ -394,6 +397,7 @@ impl<'w> DeferredWorld<'w> { components: &[ComponentId], data: &mut E, mut propagate: bool, + caller: &'static Location<'static>, ) where T: Traversal, { @@ -405,6 +409,7 @@ impl<'w> DeferredWorld<'w> { components.iter().copied(), data, &mut propagate, + 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 023db78f5dbdd..30caece40cde2 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -14,6 +14,7 @@ use crate::{ }; use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::{HashMap, HashSet}; +use core::panic::Location; use core::{any::TypeId, marker::PhantomData, mem::MaybeUninit}; use thiserror::Error; @@ -1149,12 +1150,7 @@ impl<'w> EntityWorldMut<'w> { /// This will overwrite any previous value(s) of the same component type. #[track_caller] pub fn insert(&mut self, bundle: T) -> &mut Self { - self.insert_with_caller( - bundle, - InsertMode::Replace, - #[cfg(feature = "track_change_detection")] - core::panic::Location::caller(), - ) + self.insert_with_caller(bundle, InsertMode::Replace, Location::caller()) } /// Adds a [`Bundle`] of components to the entity without overwriting. @@ -1163,12 +1159,7 @@ impl<'w> EntityWorldMut<'w> { /// unchanged. #[track_caller] pub fn insert_if_new(&mut self, bundle: T) -> &mut Self { - self.insert_with_caller( - bundle, - InsertMode::Keep, - #[cfg(feature = "track_change_detection")] - core::panic::Location::caller(), - ) + self.insert_with_caller(bundle, InsertMode::Keep, Location::caller()) } /// Split into a new function so we can pass the calling location into the function when using @@ -1178,7 +1169,7 @@ impl<'w> EntityWorldMut<'w> { &mut self, bundle: T, mode: InsertMode, - #[cfg(feature = "track_change_detection")] caller: &'static core::panic::Location, + caller: &'static Location, ) -> &mut Self { let change_tick = self.world.change_tick(); let mut bundle_inserter = @@ -1186,7 +1177,7 @@ impl<'w> EntityWorldMut<'w> { self.location = // SAFETY: location matches current entity. `T` matches `bundle_info` unsafe { - bundle_inserter.insert(self.entity, self.location, bundle, mode, #[cfg(feature = "track_change_detection")] caller) + bundle_inserter.insert(self.entity, self.location, bundle, mode, caller) }; self } @@ -1206,6 +1197,18 @@ impl<'w> EntityWorldMut<'w> { &mut self, component_id: ComponentId, component: OwningPtr<'_>, + ) -> &mut Self { + self.insert_by_id_with_caller(component_id, component, Location::caller()) + } + + /// # Safety + /// See [`inser_by_id`] + #[inline] + pub(crate) unsafe fn insert_by_id_with_caller( + &mut self, + component_id: ComponentId, + component: OwningPtr<'_>, + caller: &'static Location<'static>, ) -> &mut Self { let change_tick = self.world.change_tick(); let bundle_id = self @@ -1227,6 +1230,7 @@ impl<'w> EntityWorldMut<'w> { self.location, Some(component).into_iter(), Some(storage_type).iter().cloned(), + caller, ); self } @@ -1269,6 +1273,7 @@ impl<'w> EntityWorldMut<'w> { self.location, iter_components, (*storage_types).iter().cloned(), + Location::caller(), ); *self.world.bundles.get_storages_unchecked(bundle_id) = core::mem::take(&mut storage_types); self @@ -1280,6 +1285,7 @@ impl<'w> EntityWorldMut<'w> { /// remove any of them. // TODO: BundleRemover? #[must_use] + #[track_caller] pub fn take(&mut self) -> Option { let world = &mut self.world; let storages = &mut world.storages; @@ -1325,6 +1331,7 @@ impl<'w> EntityWorldMut<'w> { old_archetype, entity, bundle_info, + Location::caller(), ); } @@ -1462,7 +1469,11 @@ impl<'w> EntityWorldMut<'w> { /// # Safety /// - A `BundleInfo` with the corresponding `BundleId` must have been initialized. #[allow(clippy::too_many_arguments)] - unsafe fn remove_bundle(&mut self, bundle: BundleId) -> EntityLocation { + unsafe fn remove_bundle( + &mut self, + bundle: BundleId, + caller: &'static Location<'static>, + ) -> EntityLocation { let entity = self.entity; let world = &mut self.world; let location = self.location; @@ -1505,6 +1516,7 @@ impl<'w> EntityWorldMut<'w> { old_archetype, entity, bundle_info, + caller, ); } @@ -1548,12 +1560,20 @@ impl<'w> EntityWorldMut<'w> { /// See [`EntityCommands::remove`](crate::system::EntityCommands::remove) for more details. // TODO: BundleRemover? pub fn remove(&mut self) -> &mut Self { + self.remove_with_caller::(Location::caller()) + } + + #[inline] + pub(crate) fn remove_with_caller( + &mut self, + caller: &'static Location<'static>, + ) -> &mut Self { 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, caller) }; self } @@ -1561,7 +1581,16 @@ impl<'w> EntityWorldMut<'w> { /// Removes any components except those in the [`Bundle`] (and its Required Components) from the entity. /// /// See [`EntityCommands::retain`](crate::system::EntityCommands::retain) for more details. + #[track_caller] pub fn retain(&mut self) -> &mut Self { + self.retain_with_caller::(Location::caller()) + } + + #[inline] + pub(crate) fn retain_with_caller( + &mut self, + caller: &'static Location<'static>, + ) -> &mut Self { let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; @@ -1580,7 +1609,7 @@ 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, caller) }; self } @@ -1591,7 +1620,17 @@ impl<'w> EntityWorldMut<'w> { /// # 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.remove_by_id_with_caller(component_id, Location::caller()) + } + + #[inline] + pub(crate) fn remove_by_id_with_caller( + &mut self, + component_id: ComponentId, + caller: &'static Location<'static>, + ) -> &mut Self { let components = &mut self.world.components; let bundle_id = self @@ -1600,13 +1639,19 @@ 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, caller) }; self } /// Removes all components associated with the entity. + #[track_caller] pub fn clear(&mut self) -> &mut Self { + self.clear_with_caller(Location::caller()) + } + + #[inline] + pub(crate) fn clear_with_caller(&mut self, caller: &'static Location<'static>) -> &mut Self { let component_ids: Vec = self.archetype().components().collect(); let components = &mut self.world.components; @@ -1616,7 +1661,7 @@ 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, caller) }; self } @@ -1624,6 +1669,7 @@ impl<'w> EntityWorldMut<'w> { /// Despawns the current entity. /// /// See [`World::despawn`] for more details. + #[track_caller] pub fn despawn(self) { let world = self.world; let archetype = &world.archetypes[self.location.archetype_id]; @@ -1639,11 +1685,21 @@ impl<'w> EntityWorldMut<'w> { unsafe { deferred_world.trigger_on_replace(archetype, self.entity, archetype.components()); 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(), + Location::caller(), + ); } deferred_world.trigger_on_remove(archetype, self.entity, archetype.components()); 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(), + Location::caller(), + ); } } @@ -1857,6 +1913,7 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( archetype: &Archetype, entity: Entity, bundle_info: &BundleInfo, + caller: &'static Location<'static>, ) { deferred_world.trigger_on_replace(archetype, entity, bundle_info.iter_explicit_components()); if archetype.has_replace_observer() { @@ -1864,11 +1921,17 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( ON_REPLACE, entity, bundle_info.iter_explicit_components(), + caller, ); } deferred_world.trigger_on_remove(archetype, entity, bundle_info.iter_explicit_components()); 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(), + caller, + ); } } @@ -2968,7 +3031,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>, @@ -2979,6 +3041,7 @@ unsafe fn insert_dynamic_bundle< location: EntityLocation, components: I, storage_types: S, + caller: &'static Location<'static>, ) -> EntityLocation { struct DynamicInsertBundle<'a, I: Iterator)>> { components: I, @@ -2997,16 +3060,7 @@ unsafe fn insert_dynamic_bundle< }; // SAFETY: location matches current entity. - unsafe { - bundle_inserter.insert( - entity, - location, - bundle, - InsertMode::Replace, - #[cfg(feature = "track_change_detection")] - core::panic::Location::caller(), - ) - } + unsafe { bundle_inserter.insert(entity, location, bundle, InsertMode::Replace, caller) } } /// Removes a bundle from the given archetype and returns the resulting archetype (or None if the diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 6f1774e264833..0085cc0f4ad34 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1246,14 +1246,7 @@ impl World { let entity_location = { let mut bundle_spawner = BundleSpawner::new::(self, change_tick); // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent - unsafe { - bundle_spawner.spawn_non_existent( - entity, - bundle, - #[cfg(feature = "track_change_detection")] - Location::caller(), - ) - } + unsafe { bundle_spawner.spawn_non_existent(entity, bundle, Location::caller()) } }; // SAFETY: entity and location are valid, as they were just created above @@ -1304,12 +1297,7 @@ impl World { I: IntoIterator, I::Item: Bundle, { - SpawnBatchIter::new( - self, - iter.into_iter(), - #[cfg(feature = "track_change_detection")] - Location::caller(), - ) + SpawnBatchIter::new(self, iter.into_iter(), Location::caller()) } /// Retrieves a reference to the given `entity`'s [`Component`] of the given type. @@ -2112,11 +2100,7 @@ impl World { I::IntoIter: Iterator, B: Bundle, { - self.insert_or_spawn_batch_with_caller( - iter, - #[cfg(feature = "track_change_detection")] - Location::caller(), - ) + self.insert_or_spawn_batch_with_caller(iter, Location::caller()) } /// Split into a new function so we can pass the calling location into the function when using @@ -2125,7 +2109,7 @@ impl World { pub(crate) fn insert_or_spawn_batch_with_caller( &mut self, iter: I, - #[cfg(feature = "track_change_detection")] caller: &'static Location, + caller: &'static Location, ) -> Result<(), Vec> where I: IntoIterator, @@ -2175,7 +2159,6 @@ impl World { location, bundle, InsertMode::Replace, - #[cfg(feature = "track_change_detection")] caller, ) }; @@ -2197,7 +2180,6 @@ impl World { location, bundle, InsertMode::Replace, - #[cfg(feature = "track_change_detection")] caller, ) }; @@ -2209,27 +2191,13 @@ impl World { AllocAtWithoutReplacement::DidNotExist => { if let SpawnOrInsert::Spawn(ref mut spawner) = spawn_or_insert { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter - unsafe { - spawner.spawn_non_existent( - entity, - bundle, - #[cfg(feature = "track_change_detection")] - caller, - ) - }; + unsafe { spawner.spawn_non_existent(entity, bundle, caller) }; } else { // SAFETY: we initialized this bundle_id in `init_info` let mut spawner = unsafe { BundleSpawner::new_with_id(self, bundle_id, change_tick) }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter - unsafe { - spawner.spawn_non_existent( - entity, - bundle, - #[cfg(feature = "track_change_detection")] - caller, - ) - }; + unsafe { spawner.spawn_non_existent(entity, bundle, caller) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); } } diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index dbdbc1cfac31b..bd389ba7d88b4 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -4,7 +4,6 @@ use crate::{ world::World, }; use core::iter::FusedIterator; -#[cfg(feature = "track_change_detection")] use core::panic::Location; /// An iterator that spawns a series of entities and returns the [ID](Entity) of @@ -18,7 +17,6 @@ where { inner: I, spawner: BundleSpawner<'w>, - #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, } @@ -29,11 +27,7 @@ where { #[inline] #[track_caller] - pub(crate) fn new( - world: &'w mut World, - iter: I, - #[cfg(feature = "track_change_detection")] caller: &'static Location, - ) -> Self { + pub(crate) fn new(world: &'w mut World, iter: I, caller: &'static Location) -> Self { // Ensure all entity allocations are accounted for so `self.entities` can realloc if // necessary world.flush(); @@ -50,7 +44,6 @@ where Self { inner: iter, spawner, - #[cfg(feature = "track_change_detection")] caller, } } @@ -80,13 +73,7 @@ where fn next(&mut self) -> Option { let bundle = self.inner.next()?; // SAFETY: bundle matches spawner type - unsafe { - Some(self.spawner.spawn( - bundle, - #[cfg(feature = "track_change_detection")] - self.caller, - )) - } + unsafe { Some(self.spawner.spawn(bundle, self.caller)) } } fn size_hint(&self) -> (usize, Option) { From 6472a40a20715990b2b3e253f98285cb35ec99e3 Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 3 Oct 2024 02:02:08 +0200 Subject: [PATCH 02/12] fix typo --- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 30caece40cde2..2a12f702f2d5e 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1202,7 +1202,7 @@ impl<'w> EntityWorldMut<'w> { } /// # Safety - /// See [`inser_by_id`] + /// See [`insert_by_id`] #[inline] pub(crate) unsafe fn insert_by_id_with_caller( &mut self, From 0f55896a6c7eb272a7685968b8a27d589d3c967d Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 3 Oct 2024 02:16:58 +0200 Subject: [PATCH 03/12] fix doc link --- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 2a12f702f2d5e..d417848080d47 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1202,7 +1202,7 @@ impl<'w> EntityWorldMut<'w> { } /// # Safety - /// See [`insert_by_id`] + /// See [`EntityWorldMut::insert_by_id`] #[inline] pub(crate) unsafe fn insert_by_id_with_caller( &mut self, From 2da3dc62e9fd4cbfbf55915f038f5a4f163bb399 Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 3 Oct 2024 03:18:56 +0200 Subject: [PATCH 04/12] hooks --- crates/bevy_ecs/src/bundle.rs | 63 ++++++++++++------- crates/bevy_ecs/src/component.rs | 4 +- crates/bevy_ecs/src/lib.rs | 8 +-- .../bevy_ecs/src/observer/entity_observer.rs | 2 +- crates/bevy_ecs/src/observer/runner.rs | 10 +-- crates/bevy_ecs/src/world/deferred_world.rs | 32 ++++++++-- crates/bevy_ecs/src/world/entity_ref.rs | 28 +++++++-- crates/bevy_scene/src/dynamic_scene.rs | 2 +- crates/bevy_scene/src/lib.rs | 4 +- examples/ecs/component_hooks.rs | 17 ++--- 10 files changed, 118 insertions(+), 52 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 41980133f775e..c90eb124d3718 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -906,7 +906,12 @@ impl<'w> BundleInserter<'w> { let mut deferred_world = self.world.into_deferred(); if insert_mode == InsertMode::Replace { - deferred_world.trigger_on_replace(archetype, entity, add_bundle.iter_existing()); + deferred_world.trigger_on_replace( + archetype, + entity, + add_bundle.iter_existing(), + caller, + ); if archetype.has_replace_observer() { deferred_world.trigger_observers( ON_REPLACE, @@ -1080,7 +1085,7 @@ impl<'w> BundleInserter<'w> { // SAFETY: All components in the bundle are guaranteed to exist in the World // as they must be initialized before creating the BundleInfo. unsafe { - deferred_world.trigger_on_add(new_archetype, entity, add_bundle.iter_added()); + deferred_world.trigger_on_add(new_archetype, entity, add_bundle.iter_added(), caller); if new_archetype.has_add_observer() { deferred_world.trigger_observers(ON_ADD, entity, add_bundle.iter_added(), caller); } @@ -1091,6 +1096,7 @@ impl<'w> BundleInserter<'w> { new_archetype, entity, add_bundle.iter_inserted(), + caller, ); if new_archetype.has_insert_observer() { deferred_world.trigger_observers( @@ -1108,6 +1114,7 @@ impl<'w> BundleInserter<'w> { new_archetype, entity, add_bundle.iter_added(), + caller, ); if new_archetype.has_insert_observer() { deferred_world.trigger_observers( @@ -1237,6 +1244,7 @@ impl<'w> BundleSpawner<'w> { archetype, entity, bundle_info.iter_contributed_components(), + caller, ); if archetype.has_add_observer() { deferred_world.trigger_observers( @@ -1250,6 +1258,7 @@ impl<'w> BundleSpawner<'w> { archetype, entity, bundle_info.iter_contributed_components(), + caller, ); if archetype.has_insert_observer() { deferred_world.trigger_observers( @@ -1454,6 +1463,7 @@ fn initialize_dynamic_bundle( mod tests { use crate as bevy_ecs; use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; + use core::panic::Location; #[derive(Component)] struct A; @@ -1462,19 +1472,24 @@ 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, + _: &'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, _: &'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, _: &'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, _: &'static Location<'static>) { world.resource_mut::().assert_order(3); } @@ -1507,10 +1522,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); @@ -1534,10 +1549,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); @@ -1551,8 +1566,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); } @@ -1573,22 +1588,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); }); @@ -1605,27 +1620,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 121d49a291985..0c0c9d6ab5c93 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -26,6 +26,7 @@ use core::{ fmt::Debug, marker::PhantomData, mem::needs_drop, + panic::Location, }; use thiserror::Error; @@ -414,7 +415,8 @@ pub enum StorageType { } /// 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); +pub type ComponentHook = + for<'w> fn(DeferredWorld<'w>, Entity, ComponentId, &'static Location<'static>); /// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`]. /// diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 4dda0d4ea9e38..c86ecb78e3242 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1912,8 +1912,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::()); @@ -1942,8 +1942,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 0f9baba760a85..8223443f276a6 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -12,7 +12,7 @@ impl Component for ObservedBy { const STORAGE_TYPE: StorageType = StorageType::SparseSet; 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/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index f85725d392525..b38d2f3a11910 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -1,4 +1,5 @@ use core::any::Any; +use core::panic::Location; use crate::{ component::{ComponentHook, ComponentHooks, ComponentId, StorageType}, @@ -64,12 +65,12 @@ impl Component for ObserverState { const STORAGE_TYPE: StorageType = StorageType::SparseSet; 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) @@ -315,12 +316,12 @@ impl Observer { impl Component for Observer { const STORAGE_TYPE: StorageType = StorageType::SparseSet; 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); }); } } @@ -393,6 +394,7 @@ fn hook_on_add>( mut world: DeferredWorld<'_>, entity: Entity, _: ComponentId, + _: &'static Location<'static>, ) { world.commands().queue(move |world: &mut World| { let event_type = world.register_component::(); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 9f64fc7a928fa..cc371fc7106ff 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -284,13 +284,19 @@ impl<'w> DeferredWorld<'w> { archetype: &Archetype, entity: Entity, targets: impl Iterator, + 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, + caller, + ); } } } @@ -306,13 +312,19 @@ impl<'w> DeferredWorld<'w> { archetype: &Archetype, entity: Entity, targets: impl Iterator, + 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, + caller, + ); } } } @@ -328,13 +340,19 @@ impl<'w> DeferredWorld<'w> { archetype: &Archetype, entity: Entity, targets: impl Iterator, + 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, + caller, + ); } } } @@ -350,13 +368,19 @@ impl<'w> DeferredWorld<'w> { archetype: &Archetype, entity: Entity, targets: impl Iterator, + 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, + caller, + ); } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index d417848080d47..760a4a723082d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1683,7 +1683,12 @@ impl<'w> EntityWorldMut<'w> { // SAFETY: All components in the archetype exist in world unsafe { - deferred_world.trigger_on_replace(archetype, self.entity, archetype.components()); + deferred_world.trigger_on_replace( + archetype, + self.entity, + archetype.components(), + Location::caller(), + ); if archetype.has_replace_observer() { deferred_world.trigger_observers( ON_REPLACE, @@ -1692,7 +1697,12 @@ impl<'w> EntityWorldMut<'w> { Location::caller(), ); } - deferred_world.trigger_on_remove(archetype, self.entity, archetype.components()); + deferred_world.trigger_on_remove( + archetype, + self.entity, + archetype.components(), + Location::caller(), + ); if archetype.has_remove_observer() { deferred_world.trigger_observers( ON_REMOVE, @@ -1915,7 +1925,12 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( bundle_info: &BundleInfo, caller: &'static Location<'static>, ) { - deferred_world.trigger_on_replace(archetype, entity, bundle_info.iter_explicit_components()); + deferred_world.trigger_on_replace( + archetype, + entity, + bundle_info.iter_explicit_components(), + caller, + ); if archetype.has_replace_observer() { deferred_world.trigger_observers( ON_REPLACE, @@ -1924,7 +1939,12 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( 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(), + caller, + ); if archetype.has_remove_observer() { deferred_world.trigger_observers( ON_REMOVE, diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 123ebf93aecb2..d24b2db1c50c2 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -368,7 +368,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 8a21b2040d78e..ea85ffc3c2707 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -71,7 +71,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; }; @@ -90,7 +90,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 7598ad90ca161..7581a5a30359e 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -62,16 +62,17 @@ 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:?} due to {caller}"); // Or access resources world .resource_mut::() @@ -81,21 +82,23 @@ 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:?} due to {caller}" + ); // You can also issue commands through `.commands()` world.commands().entity(entity).despawn(); }); From 603c91880aff785506a93a2ece1060bb2cd99474 Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 3 Oct 2024 03:23:51 +0200 Subject: [PATCH 05/12] fix import --- crates/bevy_ecs/src/component.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 0c0c9d6ab5c93..5fa684ddd4a1f 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -17,8 +17,6 @@ use bevy_ptr::{OwningPtr, UnsafeCellDeref}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; use bevy_utils::{HashMap, HashSet, TypeIdMap}; -#[cfg(feature = "track_change_detection")] -use core::panic::Location; use core::{ alloc::Layout, any::{Any, TypeId}, From 19e4ec6a4c7ad909eea93fd9e0b105d496876a69 Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 3 Oct 2024 03:33:54 +0200 Subject: [PATCH 06/12] fix doctest --- crates/bevy_ecs/src/component.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 5fa684ddd4a1f..f2b88b8b1516a 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -290,6 +290,7 @@ use thiserror::Error; /// # 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)] @@ -301,12 +302,12 @@ use thiserror::Error; /// // #[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: &'static Location<'static>) { /// // ... /// } /// /// // 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: &'static Location<'static>) { /// // ... /// } /// ``` @@ -451,12 +452,12 @@ pub type ComponentHook = /// 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); /// }); From 468791f5768f5ed72e08e47cdcaac89eb2227967 Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 12 Dec 2024 15:58:11 +0100 Subject: [PATCH 07/12] add comment & fix errors --- crates/bevy_ecs/src/bundle.rs | 15 +-------------- crates/bevy_ecs/src/component.rs | 3 ++- crates/bevy_ecs/src/observer/mod.rs | 1 + crates/bevy_ecs/src/world/entity_ref.rs | 5 ++--- crates/bevy_ecs/src/world/mod.rs | 6 +++--- 5 files changed, 9 insertions(+), 21 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 496d1cbd712d2..02de5308e7ad3 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1217,19 +1217,6 @@ impl<'w> BundleSpawner<'w> { &mut self, entity: Entity, bundle: T, - ) -> EntityLocation { - self.spawn_non_existent_with_caller( - entity, - bundle, - #[cfg(feature = "track_change_detection")] - Location::caller(), - ) - } - - pub(crate) unsafe fn spawn_non_existent_with_caller( - &mut self, - entity: Entity, - bundle: T, #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, ) -> EntityLocation { // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid @@ -1317,7 +1304,7 @@ impl<'w> BundleSpawner<'w> { let entity = self.entities().alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type unsafe { - self.spawn_non_existent_with_caller( + self.spawn_non_existent( entity, bundle, #[cfg(feature = "track_change_detection")] diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 415869fac5424..4ea2c3d7da1b3 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -493,7 +493,8 @@ pub enum StorageType { 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`. +/// The caller location is `Some` if the `track_change_detection` feature is enabled. pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId, Option<&'static Location<'static>>); diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index f37200154b4e5..d52b58f84b15b 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -1408,6 +1408,7 @@ mod tests { world.flush_commands(); } + #[test] fn observer_triggered_components() { #[derive(Resource, Default)] struct Counter(HashMap); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 8f0867f757d1c..8f4ef3f994df2 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1813,7 +1813,7 @@ impl<'w> EntityWorldMut<'w> { ) } - pub fn remove_with_requires_with_caller( + pub(crate) fn remove_with_requires_with_caller( &mut self, #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, ) -> &mut Self { @@ -3864,8 +3864,7 @@ unsafe impl DynamicComponentFetch for &'_ HashSet { #[cfg(test)] mod tests { use bevy_ptr::{OwningPtr, Ptr}; - use core::panic::AssertUnwindSafe; - use std::panic::Location; + use core::panic::{AssertUnwindSafe, Location}; use crate::{ self as bevy_ecs, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index e873ce4362381..c2a5199f86281 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1367,7 +1367,7 @@ impl World { let mut bundle_spawner = BundleSpawner::new::(self, change_tick); // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent unsafe { - bundle_spawner.spawn_non_existent_with_caller( + bundle_spawner.spawn_non_existent( entity, bundle, #[cfg(feature = "track_change_detection")] @@ -2511,7 +2511,7 @@ impl World { if let SpawnOrInsert::Spawn(ref mut spawner) = spawn_or_insert { // SAFETY: `entity` is allocated (but non existent), bundle matches inserter unsafe { - spawner.spawn_non_existent_with_caller( + spawner.spawn_non_existent( entity, bundle, #[cfg(feature = "track_change_detection")] @@ -2524,7 +2524,7 @@ impl World { unsafe { BundleSpawner::new_with_id(self, bundle_id, change_tick) }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { - spawner.spawn_non_existent_with_caller( + spawner.spawn_non_existent( entity, bundle, #[cfg(feature = "track_change_detection")] From 15c9af52b040516ba1ab0e3d44e2de60f7096c83 Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 12 Dec 2024 16:08:18 +0100 Subject: [PATCH 08/12] better error message & fix errors --- crates/bevy_render/src/camera/camera.rs | 10 ++++++++-- crates/bevy_render/src/sync_component.rs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 3c375d935cd72..149d821bcd8c9 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -38,6 +38,7 @@ use bevy_window::{ WindowScaleFactorChanged, }; use core::ops::Range; +use core::panic::Location; use derive_more::derive::From; use wgpu::{BlendState, TextureFormat, TextureUsages}; @@ -326,9 +327,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)); }, From 2baa5c770b565d2433bbfc666d1e12bad148ebde Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 12 Dec 2024 16:23:20 +0100 Subject: [PATCH 09/12] core pipeline --- crates/bevy_core_pipeline/src/oit/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 14e8b8d4e36e3..3ad33094bd523 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -71,10 +71,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 + ); } } }); From 6245c93fabe86aaa14b19a6047887692d0f5438e Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 12 Dec 2024 16:27:12 +0100 Subject: [PATCH 10/12] fix test --- crates/bevy_ecs/src/observer/mod.rs | 2 ++ crates/bevy_ecs/src/world/entity_ref.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index d52b58f84b15b..022ce3d632317 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -1377,6 +1377,7 @@ mod tests { } #[test] + #[cfg(feature = "track_change_detection")] #[track_caller] fn observer_caller_location_event() { #[derive(Event)] @@ -1391,6 +1392,7 @@ mod tests { } #[test] + #[cfg(feature = "track_change_detection")] #[track_caller] fn observer_caller_location_command_archetype_move() { #[derive(Component)] diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 8f4ef3f994df2..7058ecc45d53f 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -14,6 +14,7 @@ use crate::{ }; use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::{HashMap, HashSet}; +#[cfg(feature = "track_change_detection")] use core::panic::Location; use core::{any::TypeId, marker::PhantomData, mem::MaybeUninit}; use thiserror::Error; From c985e7ff53d7179f7f22a401af569730c87912ec Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 12 Dec 2024 16:31:11 +0100 Subject: [PATCH 11/12] fix example --- examples/ecs/component_hooks.rs | 8 ++++++-- examples/ecs/immutable_components.rs | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 8e04d02364b13..a2d33b8c1b12a 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -75,7 +75,9 @@ fn setup(world: &mut World) { let value = world.get::(entity).unwrap().0; println!( "{component_id:?} added to {entity:?} with value {value:?}{}", - caller.map(|location| format!("due to {location}").unwrap_or_default()) + caller + .map(|location| format!("due to {location}")) + .unwrap_or_default() ); // Or access resources world @@ -102,7 +104,9 @@ fn setup(world: &mut World) { let value = world.get::(entity).unwrap().0; println!( "{component_id:?} removed from {entity:?} with value {value:?}{}", - caller.map(|location| format!("due to {location}").unwrap_or_default()) + 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 2e9b54522cd3d..fc480a0e894cb 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") }; From 00f0623bc025f71489a7ac559b7500f27613de92 Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Thu, 12 Dec 2024 16:49:41 +0100 Subject: [PATCH 12/12] warning --- crates/bevy_ecs/src/observer/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 022ce3d632317..b3d5b76611b0d 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -619,6 +619,7 @@ impl World { #[cfg(test)] mod tests { use alloc::vec; + #[cfg(feature = "track_change_detection")] use core::panic::Location; use bevy_ptr::OwningPtr;