diff --git a/crates/bevy_ecs/src/event/base.rs b/crates/bevy_ecs/src/event/base.rs index dad87382b4dec..3106009d6be3b 100644 --- a/crates/bevy_ecs/src/event/base.rs +++ b/crates/bevy_ecs/src/event/base.rs @@ -1,6 +1,8 @@ use crate::{component::Component, traversal::Traversal}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; +#[cfg(feature = "track_change_detection")] +use core::panic::Location; use core::{ cmp::Ordering, fmt, @@ -59,6 +61,9 @@ pub struct EventId { /// Uniquely identifies the event associated with this ID. // This value corresponds to the order in which each event was added to the world. pub id: usize, + /// The source code location that triggered this event. + #[cfg(feature = "track_change_detection")] + pub caller: &'static Location<'static>, #[cfg_attr(feature = "bevy_reflect", reflect(ignore))] pub(super) _marker: PhantomData, } diff --git a/crates/bevy_ecs/src/event/collections.rs b/crates/bevy_ecs/src/event/collections.rs index c04512ba62bba..1354509107777 100644 --- a/crates/bevy_ecs/src/event/collections.rs +++ b/crates/bevy_ecs/src/event/collections.rs @@ -4,6 +4,8 @@ use bevy_ecs::{ system::Resource, }; use bevy_utils::detailed_trace; +#[cfg(feature = "track_change_detection")] +use core::panic::Location; use core::{ marker::PhantomData, ops::{Deref, DerefMut}, @@ -120,9 +122,24 @@ impl Events { /// "Sends" an `event` by writing it to the current event buffer. /// [`EventReader`](super::EventReader)s can then read the event. /// This method returns the [ID](`EventId`) of the sent `event`. + #[track_caller] pub fn send(&mut self, event: E) -> EventId { + self.send_with_caller( + event, + #[cfg(feature = "track_change_detection")] + Location::caller(), + ) + } + + pub(crate) fn send_with_caller( + &mut self, + event: E, + #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, + ) -> EventId { let event_id = EventId { id: self.event_count, + #[cfg(feature = "track_change_detection")] + caller, _marker: PhantomData, }; detailed_trace!("Events::send() -> id: {}", event_id); @@ -138,6 +155,7 @@ impl Events { /// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s. /// This is more efficient than sending each event individually. /// This method returns the [IDs](`EventId`) of the sent `events`. + #[track_caller] pub fn send_batch(&mut self, events: impl IntoIterator) -> SendBatchIds { let last_count = self.event_count; @@ -152,6 +170,7 @@ impl Events { /// Sends the default value of the event. Useful when the event is an empty struct. /// This method returns the [ID](`EventId`) of the sent `event`. + #[track_caller] pub fn send_default(&mut self) -> EventId where E: Default, @@ -300,6 +319,7 @@ impl Events { } impl Extend for Events { + #[track_caller] fn extend(&mut self, iter: I) where I: IntoIterator, @@ -309,6 +329,8 @@ impl Extend for Events { let events = iter.into_iter().map(|event| { let event_id = EventId { id: event_count, + #[cfg(feature = "track_change_detection")] + caller: Location::caller(), _marker: PhantomData, }; event_count += 1; @@ -377,6 +399,8 @@ impl Iterator for SendBatchIds { let result = Some(EventId { id: self.last_count, + #[cfg(feature = "track_change_detection")] + caller: Location::caller(), _marker: PhantomData, }); diff --git a/crates/bevy_ecs/src/event/send_event.rs b/crates/bevy_ecs/src/event/send_event.rs index 2640357bf024c..0d5f61cadcc4f 100644 --- a/crates/bevy_ecs/src/event/send_event.rs +++ b/crates/bevy_ecs/src/event/send_event.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "track_change_detection")] +use core::panic::Location; + use super::{Event, Events}; use crate::world::{Command, World}; @@ -5,11 +8,30 @@ use crate::world::{Command, World}; pub struct SendEvent { /// The event to send. pub event: E, + /// The source code location that triggered this command. + #[cfg(feature = "track_change_detection")] + pub caller: &'static Location<'static>, +} + +// This does not use `From`, as the resulting `Into` is not track_caller +impl SendEvent { + /// Constructs a new `SendEvent` tracking the caller. + pub fn new(event: E) -> Self { + Self { + event, + #[cfg(feature = "track_change_detection")] + caller: Location::caller(), + } + } } impl Command for SendEvent { fn apply(self, world: &mut World) { let mut events = world.resource_mut::>(); - events.send(self.event); + events.send_with_caller( + self.event, + #[cfg(feature = "track_change_detection")] + self.caller, + ); } } diff --git a/crates/bevy_ecs/src/event/writer.rs b/crates/bevy_ecs/src/event/writer.rs index 11ad9712447de..f391e7c3449da 100644 --- a/crates/bevy_ecs/src/event/writer.rs +++ b/crates/bevy_ecs/src/event/writer.rs @@ -69,6 +69,7 @@ impl<'w, E: Event> EventWriter<'w, E> { /// This method returns the [ID](`EventId`) of the sent `event`. /// /// See [`Events`] for details. + #[track_caller] pub fn send(&mut self, event: E) -> EventId { self.events.send(event) } @@ -78,6 +79,7 @@ impl<'w, E: Event> EventWriter<'w, E> { /// This method returns the [IDs](`EventId`) of the sent `events`. /// /// See [`Events`] for details. + #[track_caller] pub fn send_batch(&mut self, events: impl IntoIterator) -> SendBatchIds { self.events.send_batch(events) } @@ -86,6 +88,7 @@ impl<'w, E: Event> EventWriter<'w, E> { /// This method returns the [ID](`EventId`) of the sent `event`. /// /// See [`Events`] for details. + #[track_caller] pub fn send_default(&mut self) -> EventId where E: Default, diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 53731b6cfae86..015152d61502b 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -986,8 +986,13 @@ impl<'w, 's> Commands<'w, 's> { /// sent, consider using a typed [`EventWriter`] instead. /// /// [`EventWriter`]: crate::event::EventWriter + #[track_caller] pub fn send_event(&mut self, event: E) -> &mut Self { - self.queue(SendEvent { event }); + self.queue(SendEvent { + event, + #[cfg(feature = "track_change_detection")] + caller: Location::caller(), + }); self } diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 19fa4eb2b4c18..6679fa63151fe 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -25,6 +25,7 @@ use core::{ any::Any, fmt, hash::{BuildHasher, Hash, Hasher}, + panic::Location, }; #[cfg(feature = "std")] @@ -2278,6 +2279,149 @@ impl GetTypeRegistration for Cow<'static, Path> { #[cfg(all(feature = "functions", feature = "std"))] crate::func::macros::impl_function_traits!(Cow<'static, Path>); +impl TypePath for &'static Location<'static> { + fn type_path() -> &'static str { + "core::panic::Location" + } + + fn short_type_path() -> &'static str { + "Location" + } +} + +impl PartialReflect for &'static Location<'static> { + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + + #[inline] + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Opaque + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) + } + + fn clone_value(&self) -> Box { + Box::new(*self) + } + + fn reflect_hash(&self) -> Option { + let mut hasher = reflect_hasher(); + Hash::hash(&Any::type_id(self), &mut hasher); + Hash::hash(self, &mut hasher); + Some(hasher.finish()) + } + + fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { + if let Some(value) = value.try_downcast_ref::() { + Some(PartialEq::eq(self, value)) + } else { + Some(false) + } + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + if let Some(value) = value.try_downcast_ref::() { + self.clone_from(value); + Ok(()) + } else { + Err(ApplyError::MismatchedTypes { + from_type: value.reflect_type_path().into(), + to_type: ::reflect_type_path(self).into(), + }) + } + } +} + +impl Reflect for &'static Location<'static> { + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self + } + + fn as_reflect(&self) -> &dyn Reflect { + self + } + + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } +} + +impl Typed for &'static Location<'static> { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) + } +} + +impl GetTypeRegistration for &'static Location<'static> { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration + } +} + +impl FromReflect for &'static Location<'static> { + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + reflect.try_downcast_ref::().copied() + } +} + +#[cfg(all(feature = "functions", feature = "std"))] +crate::func::macros::impl_function_traits!(&'static Location<'static>); + #[cfg(test)] mod tests { use crate::{