Skip to content

Commit

Permalink
Event source location tracking (#16778)
Browse files Browse the repository at this point in the history
# 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 <[email protected]>
  • Loading branch information
SpecificProtagonist and alice-i-cecile authored Dec 12, 2024
1 parent aa51959 commit b2d3371
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 2 deletions.
5 changes: 5 additions & 0 deletions crates/bevy_ecs/src/event/base.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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>,
}
Expand Down
24 changes: 24 additions & 0 deletions crates/bevy_ecs/src/event/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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);
Expand All @@ -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;

Expand All @@ -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,
Expand Down Expand Up @@ -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>,
Expand All @@ -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;
Expand Down Expand Up @@ -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,
});

Expand Down
24 changes: 23 additions & 1 deletion crates/bevy_ecs/src/event/send_event.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
#[cfg(feature = "track_change_detection")]
use core::panic::Location;

use super::{Event, Events};
use crate::world::{Command, World};

/// A command to send an arbitrary [`Event`], used by [`Commands::send_event`](crate::system::Commands::send_event).
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,
);
}
}
3 changes: 3 additions & 0 deletions crates/bevy_ecs/src/event/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
144 changes: 144 additions & 0 deletions crates/bevy_reflect/src/impls/std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use core::{
any::Any,
fmt,
hash::{BuildHasher, Hash, Hasher},
panic::Location,
};

#[cfg(feature = "std")]
Expand Down Expand Up @@ -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::{
Expand Down

0 comments on commit b2d3371

Please sign in to comment.