Skip to content

Commit 95e5212

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 329b71f commit 95e5212

File tree

9 files changed

+266
-142
lines changed

9 files changed

+266
-142
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

@@ -671,3 +673,41 @@ impl ComponentTicks {
671673
self.changed.set_changed(change_tick);
672674
}
673675
}
676+
677+
/// Initialize and fetch a [`ComponentId`] for a specific type.
678+
///
679+
/// # Example
680+
/// ```rust
681+
/// # use bevy_ecs::{system::Local, component::{Component, ComponentIdFor}};
682+
/// #[derive(Component)]
683+
/// struct Player;
684+
/// fn my_system(my_component_id: Local<ComponentIdFor<Player>>) {
685+
/// // ...
686+
/// }
687+
/// ```
688+
pub struct ComponentIdFor<T: Component> {
689+
component_id: ComponentId,
690+
phantom: PhantomData<T>,
691+
}
692+
693+
impl<T: Component> FromWorld for ComponentIdFor<T> {
694+
fn from_world(world: &mut World) -> Self {
695+
Self {
696+
component_id: world.init_component::<T>(),
697+
phantom: PhantomData,
698+
}
699+
}
700+
}
701+
702+
impl<T: Component> std::ops::Deref for ComponentIdFor<T> {
703+
type Target = ComponentId;
704+
fn deref(&self) -> &Self::Target {
705+
&self.component_id
706+
}
707+
}
708+
709+
impl<T: Component> From<ComponentIdFor<T>> for ComponentId {
710+
fn from(to_component_id: ComponentIdFor<T>) -> ComponentId {
711+
*to_component_id
712+
}
713+
}

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 storage;
1819
pub mod system;
@@ -33,6 +34,7 @@ pub mod prelude {
3334
entity::Entity,
3435
event::{EventReader, EventWriter, Events},
3536
query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without},
37+
removal_detection::RemovedComponents,
3638
schedule::{
3739
IntoSystemDescriptor, RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel,
3840
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
@@ -41,7 +43,7 @@ pub mod prelude {
4143
adapter as system_adapter,
4244
adapter::{dbg, error, ignore, info, unwrap, warn},
4345
Commands, In, IntoPipeSystem, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands,
44-
ParamSet, Query, RemovedComponents, Res, ResMut, Resource, System, SystemParamFunction,
46+
ParamSet, Query, Res, ResMut, Resource, System, SystemParamFunction,
4547
},
4648
world::{FromWorld, Mut, World},
4749
};
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)
@@ -140,10 +140,11 @@ mod tests {
140140
entity::{Entities, Entity},
141141
prelude::AnyOf,
142142
query::{Added, Changed, Or, With, Without},
143+
removal_detection::RemovedComponents,
143144
schedule::{Schedule, Stage, SystemStage},
144145
system::{
145146
Commands, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, QueryComponentError,
146-
RemovedComponents, Res, ResMut, Resource, System, SystemState,
147+
Res, ResMut, Resource, System, SystemState,
147148
},
148149
world::{FromWorld, World},
149150
};
@@ -603,7 +604,7 @@ mod tests {
603604
world.entity_mut(spurious_entity).despawn();
604605

605606
fn validate_despawn(
606-
removed_i32: RemovedComponents<W<i32>>,
607+
mut removed_i32: RemovedComponents<W<i32>>,
607608
despawned: Res<Despawned>,
608609
mut n_systems: ResMut<NSystems>,
609610
) {
@@ -628,13 +629,16 @@ mod tests {
628629
world.entity_mut(entity_to_remove_w_from).remove::<W<i32>>();
629630

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

0 commit comments

Comments
 (0)