Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Replace RemovedComponents<T> backing with Events<Entity> #5680

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
use crate::{
change_detection::MAX_CHANGE_AGE,
storage::{SparseSetIndex, Storages},
system::Resource,
system::{Local, Resource},
world::{FromWorld, World},
};
pub use bevy_ecs_macros::Component;
use bevy_ptr::{OwningPtr, UnsafeCellDeref};
Expand All @@ -12,6 +13,7 @@ use std::{
alloc::Layout,
any::{Any, TypeId},
borrow::Cow,
marker::PhantomData,
mem::needs_drop,
};

Expand Down Expand Up @@ -698,3 +700,48 @@ impl ComponentTicks {
self.changed.set_changed(change_tick);
}
}

/// Initialize and fetch a [`ComponentId`] for a specific type.
///
/// # Example
/// ```rust
/// # use bevy_ecs::{system::Local, component::{Component, ComponentId, ComponentIdFor}};
/// #[derive(Component)]
/// struct Player;
/// fn my_system(component_id: Local<ComponentIdFor<Player>>) {
/// let component_id: ComponentId = component_id.into();
/// // ...
/// }
/// ```
pub struct ComponentIdFor<T: Component> {
Aceeri marked this conversation as resolved.
Show resolved Hide resolved
component_id: ComponentId,
phantom: PhantomData<T>,
}

impl<T: Component> FromWorld for ComponentIdFor<T> {
fn from_world(world: &mut World) -> Self {
Self {
component_id: world.init_component::<T>(),
phantom: PhantomData,
}
}
}

impl<T: Component> std::ops::Deref for ComponentIdFor<T> {
type Target = ComponentId;
fn deref(&self) -> &Self::Target {
&self.component_id
}
}

impl<T: Component> From<ComponentIdFor<T>> for ComponentId {
fn from(to_component_id: ComponentIdFor<T>) -> ComponentId {
*to_component_id
}
}

impl<'s, T: Component> From<Local<'s, ComponentIdFor<T>>> for ComponentId {
fn from(to_component_id: Local<ComponentIdFor<T>>) -> ComponentId {
**to_component_id
}
}
4 changes: 3 additions & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod event;
pub mod query;
#[cfg(feature = "bevy_reflect")]
pub mod reflect;
pub mod removal_detection;
pub mod schedule;
pub mod schedule_v3;
pub mod storage;
Expand All @@ -34,6 +35,7 @@ pub mod prelude {
entity::Entity,
event::{EventReader, EventWriter, Events},
query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without},
removal_detection::RemovedComponents,
schedule::{
IntoSystemDescriptor, RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel,
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
Expand All @@ -42,7 +44,7 @@ pub mod prelude {
adapter as system_adapter,
adapter::{dbg, error, ignore, info, unwrap, warn},
Commands, In, IntoPipeSystem, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands,
ParamSet, Query, RemovedComponents, Res, ResMut, Resource, System, SystemParamFunction,
ParamSet, Query, Res, ResMut, Resource, System, SystemParamFunction,
},
world::{FromWorld, World},
};
Expand Down
169 changes: 169 additions & 0 deletions crates/bevy_ecs/src/removal_detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
//! Alerting events when a component is removed from an entity.

use crate::{
self as bevy_ecs,
component::{Component, ComponentId, ComponentIdFor},
entity::Entity,
event::{Events, ManualEventIterator, ManualEventReader},
prelude::Local,
storage::SparseSet,
system::{ReadOnlySystemParam, SystemMeta, SystemParam},
world::World,
};

use std::{
fmt::Debug,
iter,
marker::PhantomData,
ops::{Deref, DerefMut},
option,
};

/// Wrapper around a [`ManualEventReader<Entity>`] so that we
/// can differentiate events between components.
#[derive(Debug)]
pub struct RemovedComponentReader<T>
Aceeri marked this conversation as resolved.
Show resolved Hide resolved
where
T: Component,
{
reader: ManualEventReader<Entity>,
marker: PhantomData<T>,
}

impl<T: Component> Default for RemovedComponentReader<T> {
fn default() -> Self {
Self {
reader: Default::default(),
marker: PhantomData,
}
}
}

impl<T: Component> Deref for RemovedComponentReader<T> {
type Target = ManualEventReader<Entity>;
fn deref(&self) -> &Self::Target {
&self.reader
}
}

impl<T: Component> DerefMut for RemovedComponentReader<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.reader
}
}

/// Wrapper around a map of components to [`Events<Entity>`].
/// So that we can find the events without naming the type directly.
#[derive(Default, Debug)]
pub struct RemovedComponentEvents {
Aceeri marked this conversation as resolved.
Show resolved Hide resolved
event_sets: SparseSet<ComponentId, Events<Entity>>,
}

impl RemovedComponentEvents {
pub fn new() -> Self {
Self::default()
}

pub fn update(&mut self) {
for (_component_id, events) in self.event_sets.iter_mut() {
events.update();
}
}

pub fn get(&self, component_id: impl Into<ComponentId>) -> Option<&Events<Entity>> {
self.event_sets.get(component_id.into())
}

pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
self.event_sets
.get_or_insert_with(component_id.into(), Default::default)
.send(entity);
}
}

/// A [`SystemParam`] that grants access to the entities that had their `T` [`Component`] removed.
///
/// Note that this does not allow you to see which data existed before removal.
/// If you need this, you will need to track the component data value on your own,
/// using a regularly scheduled system that requests `Query<(Entity, &T), Changed<T>>`
/// and stores the data somewhere safe to later cross-reference.
///
/// If you are using `bevy_ecs` as a standalone crate,
/// note that the `RemovedComponents` list will not be automatically cleared for you,
/// and will need to be manually flushed using [`World::clear_trackers`](crate::world::World::clear_trackers)
///
/// For users of `bevy` and `bevy_app`, this is automatically done in `bevy_app::App::update`.
/// For the main world, [`World::clear_trackers`](crate::world::World::clear_trackers) is run after the main schedule is run and after
/// `SubApp`'s have run.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::system::IntoSystem;
/// # use bevy_ecs::removal_detection::RemovedComponents;
/// #
/// # #[derive(Component)]
/// # struct MyComponent;
/// fn react_on_removal(mut removed: RemovedComponents<MyComponent>) {
/// removed.iter().for_each(|removed_entity| println!("{:?}", removed_entity));
/// }
/// # bevy_ecs::system::assert_is_system(react_on_removal);
/// ```
#[derive(SystemParam)]
pub struct RemovedComponents<'w, 's, T: Component> {
component_id: Local<'s, ComponentIdFor<T>>,
reader: Local<'s, RemovedComponentReader<T>>,
event_sets: &'w RemovedComponentEvents,
}

/// Iterator over entities that had a specific component removed.
///
/// See [`RemovedComponents`].
pub type RemovedIter<'a> =
iter::Flatten<option::IntoIter<iter::Cloned<ManualEventIterator<'a, Entity>>>>;

impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> {
pub fn iter(&mut self) -> RemovedIter<'_> {
self.event_sets
.get(**self.component_id)
.map(|events| self.reader.iter(events).cloned())
.into_iter()
.flatten()
}
}

impl<'a, 'w, 's: 'a, T> IntoIterator for &'a mut RemovedComponents<'w, 's, T>
where
T: Component,
{
type Item = Entity;
type IntoIter = RemovedIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}

// SAFETY: Only reads World removed component events
unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {}

// SAFETY: no component value access, removed component events can be read in parallel and are
// never mutably borrowed during system execution
unsafe impl<'a> SystemParam for &'a RemovedComponentEvents {
type State = ();
type Item<'w, 's> = &'w RemovedComponentEvents;

fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}

#[inline]
unsafe fn get_param<'w, 's>(
_state: &'s mut Self::State,
_system_meta: &SystemMeta,
world: &'w World,
_change_tick: u32,
) -> Self::Item<'w, 's> {
world.removed_components()
}
}
14 changes: 9 additions & 5 deletions crates/bevy_ecs/src/system/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
//! - [`NonSend`] and `Option<NonSend>`
//! - [`NonSendMut`] and `Option<NonSendMut>`
//! - [`&World`](crate::world::World)
//! - [`RemovedComponents`]
//! - [`RemovedComponents`](crate::removal_detection::RemovedComponents)
//! - [`SystemName`]
//! - [`SystemChangeTick`]
//! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata)
Expand Down Expand Up @@ -139,10 +139,11 @@ mod tests {
entity::{Entities, Entity},
prelude::{AnyOf, StageLabel},
query::{Added, Changed, Or, With, Without},
removal_detection::RemovedComponents,
schedule::{Schedule, Stage, SystemStage},
system::{
Commands, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, QueryComponentError,
RemovedComponents, Res, ResMut, Resource, System, SystemState,
Res, ResMut, Resource, System, SystemState,
},
world::{FromWorld, World},
};
Expand Down Expand Up @@ -602,7 +603,7 @@ mod tests {
world.entity_mut(spurious_entity).despawn();

fn validate_despawn(
removed_i32: RemovedComponents<W<i32>>,
mut removed_i32: RemovedComponents<W<i32>>,
despawned: Res<Despawned>,
mut n_systems: ResMut<NSystems>,
) {
Expand All @@ -627,13 +628,16 @@ mod tests {
world.entity_mut(entity_to_remove_w_from).remove::<W<i32>>();

fn validate_remove(
removed_i32: RemovedComponents<W<i32>>,
mut removed_i32: RemovedComponents<W<i32>>,
despawned: Res<Despawned>,
removed: Res<Removed>,
mut n_systems: ResMut<NSystems>,
) {
// The despawned entity from the previous frame was
// double buffered so we now have it in this system as well.
assert_eq!(
removed_i32.iter().collect::<Vec<_>>(),
&[removed.0],
&[despawned.0, removed.0],
"removing a component causes the correct entity to show up in the 'RemovedComponent' system parameter."
);

Expand Down
Loading