Skip to content

Commit 4d63994

Browse files
committed
Reworked removal detection as events
Formatting Fix some docs Unnecessary imports impl From rather than Into for ToComponentId Try to fix CI errors Fix up test to expect double buffering Fix linnk in docs Simplify component id initializing/fetching Remove from system params Move component id for to component file Move ComponentIdFor to component.rs Deref and don't allow modifying the component id IntoIterator for RemovedComponents Formatting Example for ComponentIdFor Improve documentation MaybeIter inbetween Remove unnecessary type for iterator Improve safety comment, don't box unnecessarily formatting
1 parent ca2d91e commit 4d63994

File tree

10 files changed

+260
-131
lines changed

10 files changed

+260
-131
lines changed

crates/bevy_ecs/src/component.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{
44
change_detection::MAX_CHANGE_AGE,
55
storage::{SparseSetIndex, Storages},
66
system::Resource,
7+
world::{FromWorld, World},
78
};
89
pub use bevy_ecs_macros::Component;
910
use bevy_ptr::{OwningPtr, UnsafeCellDeref};
@@ -12,6 +13,7 @@ use std::{
1213
alloc::Layout,
1314
any::{Any, TypeId},
1415
borrow::Cow,
16+
marker::PhantomData,
1517
mem::needs_drop,
1618
};
1719

@@ -698,3 +700,41 @@ impl ComponentTicks {
698700
self.changed.set_changed(change_tick);
699701
}
700702
}
703+
704+
/// Initialize and fetch a [`ComponentId`] for a specific type.
705+
///
706+
/// # Example
707+
/// ```rust
708+
/// # use bevy_ecs::{system::Local, component::{Component, ComponentIdFor}};
709+
/// #[derive(Component)]
710+
/// struct Player;
711+
/// fn my_system(my_component_id: Local<ComponentIdFor<Player>>) {
712+
/// // ...
713+
/// }
714+
/// ```
715+
pub struct ComponentIdFor<T: Component> {
716+
component_id: ComponentId,
717+
phantom: PhantomData<T>,
718+
}
719+
720+
impl<T: Component> FromWorld for ComponentIdFor<T> {
721+
fn from_world(world: &mut World) -> Self {
722+
Self {
723+
component_id: world.init_component::<T>(),
724+
phantom: PhantomData,
725+
}
726+
}
727+
}
728+
729+
impl<T: Component> std::ops::Deref for ComponentIdFor<T> {
730+
type Target = ComponentId;
731+
fn deref(&self) -> &Self::Target {
732+
&self.component_id
733+
}
734+
}
735+
736+
impl<T: Component> From<ComponentIdFor<T>> for ComponentId {
737+
fn from(to_component_id: ComponentIdFor<T>) -> ComponentId {
738+
*to_component_id
739+
}
740+
}

crates/bevy_ecs/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod event;
1313
pub mod query;
1414
#[cfg(feature = "bevy_reflect")]
1515
pub mod reflect;
16+
pub mod removal_detection;
1617
pub mod schedule;
1718
pub mod schedule_v3;
1819
pub mod storage;
@@ -34,6 +35,7 @@ pub mod prelude {
3435
entity::Entity,
3536
event::{EventReader, EventWriter, Events},
3637
query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without},
38+
removal_detection::RemovedComponents,
3739
schedule::{
3840
IntoSystemDescriptor, RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel,
3941
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
@@ -42,7 +44,7 @@ pub mod prelude {
4244
adapter as system_adapter,
4345
adapter::{dbg, error, ignore, info, unwrap, warn},
4446
Commands, In, IntoPipeSystem, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands,
45-
ParamSet, Query, RemovedComponents, Res, ResMut, Resource, System, SystemParamFunction,
47+
ParamSet, Query, Res, ResMut, Resource, System, SystemParamFunction,
4648
},
4749
world::{FromWorld, World},
4850
};
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
//! Alerting events when a component is removed from an entity.
2+
3+
use crate::{
4+
self as bevy_ecs,
5+
component::{Component, ComponentId, ComponentIdFor},
6+
entity::Entity,
7+
event::{Events, ManualEventIterator, ManualEventReader},
8+
prelude::Local,
9+
storage::SparseSet,
10+
};
11+
use bevy_ecs_macros::SystemParam;
12+
13+
use std::{
14+
fmt::Debug,
15+
iter,
16+
marker::PhantomData,
17+
ops::{Deref, DerefMut},
18+
option,
19+
};
20+
21+
/// Wrapper around a [`ManualEventReader<Entity>`] so that we
22+
/// can differentiate events between components.
23+
#[derive(Debug)]
24+
pub struct RemovedComponentReader<T>
25+
where
26+
T: Component,
27+
{
28+
reader: ManualEventReader<Entity>,
29+
marker: PhantomData<T>,
30+
}
31+
32+
impl<T: Component> Default for RemovedComponentReader<T> {
33+
fn default() -> Self {
34+
Self {
35+
reader: Default::default(),
36+
marker: PhantomData,
37+
}
38+
}
39+
}
40+
41+
impl<T: Component> Deref for RemovedComponentReader<T> {
42+
type Target = ManualEventReader<Entity>;
43+
fn deref(&self) -> &Self::Target {
44+
&self.reader
45+
}
46+
}
47+
48+
impl<T: Component> DerefMut for RemovedComponentReader<T> {
49+
fn deref_mut(&mut self) -> &mut Self::Target {
50+
&mut self.reader
51+
}
52+
}
53+
54+
/// Wrapper around a map of components to [`Events<Entity>`].
55+
/// So that we can find the events without naming the type directly.
56+
#[derive(Default, Debug)]
57+
pub struct RemovedComponentEvents {
58+
event_sets: SparseSet<ComponentId, Events<Entity>>,
59+
}
60+
61+
impl RemovedComponentEvents {
62+
pub fn new() -> Self {
63+
Self::default()
64+
}
65+
66+
pub fn update(&mut self) {
67+
for (_component_id, events) in self.event_sets.iter_mut() {
68+
events.update();
69+
}
70+
}
71+
72+
pub fn get(&self, component_id: impl Into<ComponentId>) -> Option<&Events<Entity>> {
73+
self.event_sets.get(component_id.into())
74+
}
75+
76+
pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
77+
self.event_sets
78+
.get_or_insert_with(component_id.into(), Default::default)
79+
.send(entity);
80+
}
81+
}
82+
83+
/// A [`SystemParam`] that grants access to the entities that had their `T` [`Component`] removed.
84+
///
85+
/// Note that this does not allow you to see which data existed before removal.
86+
/// If you need this, you will need to track the component data value on your own,
87+
/// using a regularly scheduled system that requests `Query<(Entity, &T), Changed<T>>`
88+
/// and stores the data somewhere safe to later cross-reference.
89+
///
90+
/// If you are using `bevy_ecs` as a standalone crate,
91+
/// note that the `RemovedComponents` list will not be automatically cleared for you,
92+
/// and will need to be manually flushed using [`World::clear_trackers`](crate::world::World::clear_trackers)
93+
///
94+
/// For users of `bevy` and `bevy_app`, this is automatically done in `bevy_app::App::update`.
95+
/// For the main world, [`World::clear_trackers`](crate::world::World::clear_trackers) is run after the main schedule is run and after
96+
/// `SubApp`'s have run.
97+
///
98+
/// # Examples
99+
///
100+
/// Basic usage:
101+
///
102+
/// ```
103+
/// # use bevy_ecs::component::Component;
104+
/// # use bevy_ecs::system::IntoSystem;
105+
/// # use bevy_ecs::removal_detection::RemovedComponents;
106+
/// #
107+
/// # #[derive(Component)]
108+
/// # struct MyComponent;
109+
/// fn react_on_removal(mut removed: RemovedComponents<MyComponent>) {
110+
/// removed.iter().for_each(|removed_entity| println!("{:?}", removed_entity));
111+
/// }
112+
/// # bevy_ecs::system::assert_is_system(react_on_removal);
113+
/// ```
114+
#[derive(SystemParam)]
115+
pub struct RemovedComponents<'w, 's, T: Component> {
116+
component_id: Local<'s, ComponentIdFor<T>>,
117+
reader: Local<'s, RemovedComponentReader<T>>,
118+
event_sets: &'w RemovedComponentEvents,
119+
}
120+
121+
type RemovedIter<'a> =
122+
iter::Flatten<option::IntoIter<iter::Cloned<ManualEventIterator<'a, Entity>>>>;
123+
124+
impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> {
125+
pub fn iter(&mut self) -> RemovedIter<'_> {
126+
self.event_sets
127+
.get(**self.component_id)
128+
.map(|events| self.reader.iter(events).cloned())
129+
.into_iter()
130+
.flatten()
131+
}
132+
}
133+
134+
impl<'a, 'w, 's: 'a, T> IntoIterator for &'a mut RemovedComponents<'w, 's, T>
135+
where
136+
T: Component,
137+
{
138+
type Item = Entity;
139+
type IntoIter = RemovedIter<'a>;
140+
fn into_iter(self) -> Self::IntoIter {
141+
self.iter()
142+
}
143+
}

crates/bevy_ecs/src/system/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
//! - [`NonSend`] and `Option<NonSend>`
8585
//! - [`NonSendMut`] and `Option<NonSendMut>`
8686
//! - [`&World`](crate::world::World)
87-
//! - [`RemovedComponents`]
87+
//! - [`RemovedComponents`](crate::removal_detection::RemovedComponents)
8888
//! - [`SystemName`]
8989
//! - [`SystemChangeTick`]
9090
//! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata)
@@ -139,10 +139,11 @@ mod tests {
139139
entity::{Entities, Entity},
140140
prelude::{AnyOf, StageLabel},
141141
query::{Added, Changed, Or, With, Without},
142+
removal_detection::RemovedComponents,
142143
schedule::{Schedule, Stage, SystemStage},
143144
system::{
144145
Commands, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, QueryComponentError,
145-
RemovedComponents, Res, ResMut, Resource, System, SystemState,
146+
Res, ResMut, Resource, System, SystemState,
146147
},
147148
world::{FromWorld, World},
148149
};
@@ -602,7 +603,7 @@ mod tests {
602603
world.entity_mut(spurious_entity).despawn();
603604

604605
fn validate_despawn(
605-
removed_i32: RemovedComponents<W<i32>>,
606+
mut removed_i32: RemovedComponents<W<i32>>,
606607
despawned: Res<Despawned>,
607608
mut n_systems: ResMut<NSystems>,
608609
) {
@@ -627,13 +628,16 @@ mod tests {
627628
world.entity_mut(entity_to_remove_w_from).remove::<W<i32>>();
628629

629630
fn validate_remove(
630-
removed_i32: RemovedComponents<W<i32>>,
631+
mut removed_i32: RemovedComponents<W<i32>>,
632+
despawned: Res<Despawned>,
631633
removed: Res<Removed>,
632634
mut n_systems: ResMut<NSystems>,
633635
) {
636+
// The despawned entity from the previous frame was
637+
// double buffered so we now have it in this system as well.
634638
assert_eq!(
635639
removed_i32.iter().collect::<Vec<_>>(),
636-
&[removed.0],
640+
&[despawned.0, removed.0],
637641
"removing a component causes the correct entity to show up in the 'RemovedComponent' system parameter."
638642
);
639643

0 commit comments

Comments
 (0)