Add Trigger::components, which lists the component targets that were triggered (#15811)

# Objective

- Closes #14774 

## Solution

Added:

```rust
impl<'w, E, B: Bundle> Trigger<'w, E, B> {
    pub fn components(&self) -> &[ComponentId];
}
```

I went with storing it in the trigger as a `SmallVec<[Component; 1]>`
because a singular target component will be the most common case, and it
remains the same size as `Vec<ComponentId>`.

## Testing

Added a test.
This commit is contained in:
Christian Hughes 2024-10-14 19:17:03 -07:00 committed by GitHub
parent 9f5f5d3d41
commit 345f935b1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 65 additions and 20 deletions

View file

@ -41,7 +41,7 @@ derive_more = { version = "1", default-features = false, features = [
] }
nonmax = "0.5"
arrayvec = { version = "0.7.4", optional = true }
smallvec = "1"
smallvec = { version = "1", features = ["union"] }
[dev-dependencies]
rand = "0.8"

View file

@ -135,15 +135,15 @@ pub(crate) struct AddBundle {
}
impl AddBundle {
pub(crate) fn iter_inserted(&self) -> impl Iterator<Item = ComponentId> + '_ {
pub(crate) fn iter_inserted(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.added.iter().chain(self.existing.iter()).copied()
}
pub(crate) fn iter_added(&self) -> impl Iterator<Item = ComponentId> + '_ {
pub(crate) fn iter_added(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.added.iter().copied()
}
pub(crate) fn iter_existing(&self) -> impl Iterator<Item = ComponentId> + '_ {
pub(crate) fn iter_existing(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.existing.iter().copied()
}
}
@ -489,7 +489,7 @@ impl Archetype {
///
/// All of the IDs are unique.
#[inline]
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
pub fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.components.indices()
}

View file

@ -463,7 +463,7 @@ impl BundleInfo {
/// Returns an iterator over the [ID](ComponentId) of each component explicitly defined in this bundle (ex: this excludes Required Components).
/// To iterate all components contributed by this bundle (including Required Components), see [`BundleInfo::iter_contributed_components`]
#[inline]
pub fn iter_explicit_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
pub fn iter_explicit_components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.explicit_components().iter().copied()
}
@ -471,7 +471,7 @@ impl BundleInfo {
///
/// To iterate only components explicitly defined in this bundle, see [`BundleInfo::iter_explicit_components`]
#[inline]
pub fn iter_contributed_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
pub fn iter_contributed_components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.component_ids.iter().copied()
}

View file

@ -23,6 +23,7 @@ use core::{
marker::PhantomData,
ops::{Deref, DerefMut},
};
use smallvec::SmallVec;
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. It also
@ -70,6 +71,13 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> {
self.trigger.entity
}
/// Returns the components that triggered the observer, out of the
/// components defined in `B`. Does not necessarily include all of them as
/// `B` acts like an `OR` filter rather than an `AND` filter.
pub fn components(&self) -> &[ComponentId] {
&self.trigger.components
}
/// Returns the [`Entity`] that observed the triggered event.
/// This allows you to despawn the observer, ceasing observation.
///
@ -193,14 +201,21 @@ impl ObserverDescriptor {
pub struct ObserverTrigger {
/// The [`Entity`] of the observer handling the trigger.
pub observer: Entity,
/// The [`ComponentId`] the trigger targeted.
/// The [`Event`] the trigger targeted.
pub event_type: ComponentId,
/// The [`ComponentId`]s the trigger targeted.
components: SmallVec<[ComponentId; 2]>,
/// The entity the trigger targeted.
pub entity: Entity,
}
impl ObserverTrigger {
/// Returns the components that the trigger targeted.
pub fn components(&self) -> &[ComponentId] {
&self.components
}
}
// Map between an observer entity and its runner
type ObserverMap = EntityHashMap<ObserverRunner>;
@ -262,7 +277,7 @@ impl Observers {
mut world: DeferredWorld,
event_type: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
components: impl Iterator<Item = ComponentId> + Clone,
data: &mut T,
propagate: &mut bool,
) {
@ -279,12 +294,15 @@ impl Observers {
(world.into_deferred(), observers)
};
let trigger_for_components = components.clone();
let mut trigger_observer = |(&observer, runner): (&Entity, &ObserverRunner)| {
(runner)(
world.reborrow(),
ObserverTrigger {
observer,
event_type,
components: components.clone().collect(),
entity,
},
data.into(),
@ -302,7 +320,7 @@ impl Observers {
}
// Trigger observers listening to this trigger targeting a specific component
components.for_each(|id| {
trigger_for_components.for_each(|id| {
if let Some(component_observers) = observers.component_observers.get(&id) {
component_observers
.map
@ -552,8 +570,10 @@ mod tests {
use alloc::vec;
use bevy_ptr::OwningPtr;
use bevy_utils::HashMap;
use crate as bevy_ecs;
use crate::component::ComponentId;
use crate::{
observer::{EmitDynamicTrigger, Observer, ObserverDescriptor, ObserverState, OnReplace},
prelude::*,
@ -1268,9 +1288,6 @@ mod tests {
#[test]
fn observer_invalid_params() {
#[derive(Event)]
struct EventA;
#[derive(Resource)]
struct ResA;
@ -1289,9 +1306,6 @@ mod tests {
#[test]
fn observer_apply_deferred_from_param_set() {
#[derive(Event)]
struct EventA;
#[derive(Resource)]
struct ResA;
@ -1309,4 +1323,35 @@ mod tests {
assert!(world.get_resource::<ResA>().is_some());
}
#[test]
fn observer_triggered_components() {
#[derive(Resource, Default)]
struct Counter(HashMap<ComponentId, usize>);
let mut world = World::new();
world.init_resource::<Counter>();
let a_id = world.register_component::<A>();
let b_id = world.register_component::<B>();
world.add_observer(
|trigger: Trigger<EventA, (A, B)>, mut counter: ResMut<Counter>| {
for &component in trigger.components() {
*counter.0.entry(component).or_default() += 1;
}
},
);
world.flush();
world.trigger_targets(EventA, [a_id, b_id]);
world.trigger_targets(EventA, a_id);
world.trigger_targets(EventA, b_id);
world.trigger_targets(EventA, [a_id, b_id]);
world.trigger_targets(EventA, a_id);
world.flush();
let counter = world.resource::<Counter>();
assert_eq!(4, *counter.0.get(&a_id).unwrap());
assert_eq!(3, *counter.0.get(&b_id).unwrap());
}
}

View file

@ -423,7 +423,7 @@ macro_rules! impl_sparse_set {
}
/// Returns an iterator visiting all keys (indices) in arbitrary order.
pub fn indices(&self) -> impl Iterator<Item = I> + '_ {
pub fn indices(&self) -> impl Iterator<Item = I> + Clone + '_ {
self.indices.iter().cloned()
}

View file

@ -501,7 +501,7 @@ impl<'w> DeferredWorld<'w> {
&mut self,
event: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
components: impl Iterator<Item = ComponentId> + Clone,
) {
Observers::invoke::<_>(
self.reborrow(),