Event source location tracking (#16778)
# Objective Fixes #16776 ## Solution - reflect `&'static Location` as an opaque type - I've added this to `impls/std.rs` because other core types are there too. Maybe they should be split out into a `core.rs` in another PR. - add source location to `EventId` (behind the `tracking_change_detection` feature flag) ## Testing --- ## Showcase ```rust fn apply_damage_to_health( mut dmg_events: EventReader<DealDamage>, ) { for (event, event_id) in dmg_events.read_with_id() { info!( "Applying {} damage, triggered by {}", event.amount, event_id.caller ); … ``` ``` 2024-12-12T01:21:50.126827Z INFO event: Applying 9 damage, triggered by examples/ecs/event.rs:47:16 ``` ## Migration Guide - If you manually construct a `SendEvent`, use `SendEvent::new()` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
aa519593ff
commit
b2d3371814
@ -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<E: Event> {
|
||||
/// 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<E>,
|
||||
}
|
||||
|
@ -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<E: Event> Events<E> {
|
||||
/// "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<E> {
|
||||
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<E> {
|
||||
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<E: Event> Events<E> {
|
||||
/// 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<Item = E>) -> SendBatchIds<E> {
|
||||
let last_count = self.event_count;
|
||||
|
||||
@ -152,6 +170,7 @@ impl<E: Event> Events<E> {
|
||||
|
||||
/// 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<E>
|
||||
where
|
||||
E: Default,
|
||||
@ -300,6 +319,7 @@ impl<E: Event> Events<E> {
|
||||
}
|
||||
|
||||
impl<E: Event> Extend<E> for Events<E> {
|
||||
#[track_caller]
|
||||
fn extend<I>(&mut self, iter: I)
|
||||
where
|
||||
I: IntoIterator<Item = E>,
|
||||
@ -309,6 +329,8 @@ impl<E: Event> Extend<E> for Events<E> {
|
||||
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<E: Event> Iterator for SendBatchIds<E> {
|
||||
|
||||
let result = Some(EventId {
|
||||
id: self.last_count,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller: Location::caller(),
|
||||
_marker: PhantomData,
|
||||
});
|
||||
|
||||
|
@ -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<E: Event> {
|
||||
/// 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<E: Event> SendEvent<E> {
|
||||
/// Constructs a new `SendEvent` tracking the caller.
|
||||
pub fn new(event: E) -> Self {
|
||||
Self {
|
||||
event,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller: Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> Command for SendEvent<E> {
|
||||
fn apply(self, world: &mut World) {
|
||||
let mut events = world.resource_mut::<Events<E>>();
|
||||
events.send(self.event);
|
||||
events.send_with_caller(
|
||||
self.event,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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<E> {
|
||||
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<Item = E>) -> SendBatchIds<E> {
|
||||
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<E>
|
||||
where
|
||||
E: Default,
|
||||
|
@ -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<E: Event>(&mut self, event: E) -> &mut Self {
|
||||
self.queue(SendEvent { event });
|
||||
self.queue(SendEvent {
|
||||
event,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller: Location::caller(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -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(<Self as Typed>::type_info())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_partial_reflect(self: Box<Self>) -> Box<dyn PartialReflect> {
|
||||
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<Self>) -> Result<Box<dyn Reflect>, Box<dyn PartialReflect>> {
|
||||
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<Self>) -> ReflectOwned {
|
||||
ReflectOwned::Opaque(self)
|
||||
}
|
||||
|
||||
fn clone_value(&self) -> Box<dyn PartialReflect> {
|
||||
Box::new(*self)
|
||||
}
|
||||
|
||||
fn reflect_hash(&self) -> Option<u64> {
|
||||
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<bool> {
|
||||
if let Some(value) = value.try_downcast_ref::<Self>() {
|
||||
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>() {
|
||||
self.clone_from(value);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ApplyError::MismatchedTypes {
|
||||
from_type: value.reflect_type_path().into(),
|
||||
to_type: <Self as DynamicTypePath>::reflect_type_path(self).into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Reflect for &'static Location<'static> {
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_reflect(self: Box<Self>) -> Box<dyn Reflect> {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_reflect(&self) -> &dyn Reflect {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_reflect_mut(&mut self) -> &mut dyn Reflect {
|
||||
self
|
||||
}
|
||||
|
||||
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
|
||||
*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::<Self>()))
|
||||
}
|
||||
}
|
||||
|
||||
impl GetTypeRegistration for &'static Location<'static> {
|
||||
fn get_type_registration() -> TypeRegistration {
|
||||
let mut registration = TypeRegistration::of::<Self>();
|
||||
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
|
||||
registration
|
||||
}
|
||||
}
|
||||
|
||||
impl FromReflect for &'static Location<'static> {
|
||||
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
|
||||
reflect.try_downcast_ref::<Self>().copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "functions", feature = "std"))]
|
||||
crate::func::macros::impl_function_traits!(&'static Location<'static>);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
|
Loading…
Reference in New Issue
Block a user