diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 39f89b95ffaafb..04c68edd712003 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -4,6 +4,7 @@ use crate::{ change_detection::MAX_CHANGE_AGE, storage::{SparseSetIndex, Storages}, system::Resource, + world::{FromWorld, World}, }; pub use bevy_ecs_macros::Component; use bevy_ptr::{OwningPtr, UnsafeCellDeref}; @@ -12,6 +13,7 @@ use std::{ alloc::Layout, any::{Any, TypeId}, borrow::Cow, + marker::PhantomData, mem::needs_drop, }; @@ -671,3 +673,41 @@ 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, ComponentIdFor}}; +/// #[derive(Component)] +/// struct Player; +/// fn my_system(my_component_id: Local>) { +/// // ... +/// } +/// ``` +pub struct ComponentIdFor { + component_id: ComponentId, + phantom: PhantomData, +} + +impl FromWorld for ComponentIdFor { + fn from_world(world: &mut World) -> Self { + Self { + component_id: world.init_component::(), + phantom: PhantomData, + } + } +} + +impl std::ops::Deref for ComponentIdFor { + type Target = ComponentId; + fn deref(&self) -> &Self::Target { + &self.component_id + } +} + +impl From> for ComponentId { + fn from(to_component_id: ComponentIdFor) -> ComponentId { + *to_component_id + } +} diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 46f1a8dc4281c6..08e4e916051de3 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -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 storage; pub mod system; @@ -33,6 +34,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, @@ -41,7 +43,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, Mut, World}, }; diff --git a/crates/bevy_ecs/src/removal_detection.rs b/crates/bevy_ecs/src/removal_detection.rs new file mode 100644 index 00000000000000..be274895912cd0 --- /dev/null +++ b/crates/bevy_ecs/src/removal_detection.rs @@ -0,0 +1,143 @@ +//! 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, +}; +use bevy_ecs_macros::SystemParam; + +use std::{ + fmt::Debug, + iter, + marker::PhantomData, + ops::{Deref, DerefMut}, + option, +}; + +/// Wrapper around a [`ManualEventReader`] so that we +/// can differentiate events between components. +#[derive(Debug)] +pub struct RemovedComponentReader +where + T: Component, +{ + reader: ManualEventReader, + marker: PhantomData, +} + +impl Default for RemovedComponentReader { + fn default() -> Self { + Self { + reader: Default::default(), + marker: PhantomData, + } + } +} + +impl Deref for RemovedComponentReader { + type Target = ManualEventReader; + fn deref(&self) -> &Self::Target { + &self.reader + } +} + +impl DerefMut for RemovedComponentReader { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.reader + } +} + +/// Wrapper around a map of components to [`Events`]. +/// So that we can find the events without naming the type directly. +#[derive(Default, Debug)] +pub struct RemovedComponentEvents { + event_sets: SparseSet>, +} + +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) -> Option<&Events> { + self.event_sets.get(component_id.into()) + } + + pub fn send(&mut self, component_id: impl Into, 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>` +/// 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) { +/// 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>, + reader: Local<'s, RemovedComponentReader>, + event_sets: &'w RemovedComponentEvents, +} + +type RemovedIter<'a> = + iter::Flatten>>>; + +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() + } +} diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index be66144b4a6a8a..78529490330a20 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -84,7 +84,7 @@ //! - [`NonSend`] and `Option` //! - [`NonSendMut`] and `Option` //! - [`&World`](crate::world::World) -//! - [`RemovedComponents`] +//! - [`RemovedComponents`](crate::removal_detection::RemovedComponents) //! - [`SystemName`] //! - [`SystemChangeTick`] //! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata) @@ -140,10 +140,11 @@ mod tests { entity::{Entities, Entity}, prelude::AnyOf, 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}, }; @@ -603,7 +604,7 @@ mod tests { world.entity_mut(spurious_entity).despawn(); fn validate_despawn( - removed_i32: RemovedComponents>, + mut removed_i32: RemovedComponents>, despawned: Res, mut n_systems: ResMut, ) { @@ -628,13 +629,16 @@ mod tests { world.entity_mut(entity_to_remove_w_from).remove::>(); fn validate_remove( - removed_i32: RemovedComponents>, + mut removed_i32: RemovedComponents>, + despawned: Res, removed: Res, mut n_systems: ResMut, ) { + // 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::>(), - &[removed.0], + &[despawned.0, removed.0], "removing a component causes the correct entity to show up in the 'RemovedComponent' system parameter." ); diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 7b09919698a331..1df42b352882ee 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -3,11 +3,12 @@ use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundles, change_detection::Ticks, - component::{Component, ComponentId, ComponentTicks, Components, Tick}, - entity::{Entities, Entity}, + component::{ComponentId, ComponentTicks, Components, Tick}, + entity::Entities, query::{ Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery, }, + removal_detection::RemovedComponentEvents, system::{CommandQueue, Commands, Query, SystemMeta}, world::{FromWorld, World}, }; @@ -917,103 +918,6 @@ unsafe impl SystemParamState for LocalState { Local(state.0.get()) } } - -/// 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>` -/// 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`] -/// -/// For users of `bevy` and `bevy_app`, this is automatically done in `bevy_app::App::update`. -/// For the main 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::system::RemovedComponents; -/// # -/// # #[derive(Component)] -/// # struct MyComponent; -/// -/// fn react_on_removal(removed: RemovedComponents) { -/// removed.iter().for_each(|removed_entity| println!("{:?}", removed_entity)); -/// } -/// -/// # bevy_ecs::system::assert_is_system(react_on_removal); -/// ``` -pub struct RemovedComponents<'a, T: Component> { - world: &'a World, - component_id: ComponentId, - marker: PhantomData, -} - -impl<'a, T: Component> RemovedComponents<'a, T> { - /// Returns an iterator over the entities that had their `T` [`Component`] removed. - pub fn iter(&self) -> std::iter::Cloned> { - self.world.removed_with_id(self.component_id) - } -} - -impl<'a, T: Component> IntoIterator for &'a RemovedComponents<'a, T> { - type Item = Entity; - type IntoIter = std::iter::Cloned>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -// SAFETY: Only reads World components -unsafe impl<'a, T: Component> ReadOnlySystemParam for RemovedComponents<'a, T> {} - -/// The [`SystemParamState`] of [`RemovedComponents`]. -#[doc(hidden)] -pub struct RemovedComponentsState { - component_id: ComponentId, - marker: PhantomData, -} - -impl<'a, T: Component> SystemParam for RemovedComponents<'a, T> { - type State = RemovedComponentsState; -} - -// SAFETY: no component access. removed component entity collections can be read in parallel and are -// never mutably borrowed during system execution -unsafe impl SystemParamState for RemovedComponentsState { - type Item<'w, 's> = RemovedComponents<'w, T>; - - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { - Self { - component_id: world.init_component::(), - marker: PhantomData, - } - } - - #[inline] - unsafe fn get_param<'w, 's>( - state: &'s mut Self, - _system_meta: &SystemMeta, - world: &'w World, - _change_tick: u32, - ) -> Self::Item<'w, 's> { - RemovedComponents { - world, - component_id: state.component_id, - marker: PhantomData, - } - } -} - /// Shared borrow of a non-[`Send`] resource. /// /// Only `Send` resources may be accessed with the [`Res`] [`SystemParam`]. In case that the @@ -1414,6 +1318,36 @@ unsafe impl SystemParamState for BundlesState { } } +impl<'a> SystemParam for &'a RemovedComponentEvents { + type State = RemovedComponentEventsState; +} + +// SAFETY: Only reads World removed component events +unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {} + +/// The [`SystemParamState`] of [`RemovedComponentEvents`]. +#[doc(hidden)] +pub struct RemovedComponentEventsState; + +// SAFETY: no component value access +unsafe impl SystemParamState for RemovedComponentEventsState { + type Item<'w, 's> = &'w RemovedComponentEvents; + + fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { + Self + } + + #[inline] + unsafe fn get_param<'w, 's>( + _state: &'s mut Self, + _system_meta: &SystemMeta, + world: &'w World, + _change_tick: u32, + ) -> Self::Item<'w, 's> { + world.removed_components() + } +} + /// A [`SystemParam`] that reads the previous and current change ticks of the system. /// /// A system's change ticks are updated each time it runs: diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 251945d711b09c..45286f91bae0d0 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -7,7 +7,8 @@ use crate::{ TickCells, }, entity::{Entities, Entity, EntityLocation}, - storage::{Column, ComponentSparseSet, SparseSet, Storages}, + removal_detection::RemovedComponentEvents, + storage::{Column, ComponentSparseSet, Storages}, world::{Mut, World}, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -540,9 +541,7 @@ impl<'w> EntityMut<'w> { let entity = self.entity; for component_id in bundle_info.component_ids.iter().cloned() { if old_archetype.contains(component_id) { - removed_components - .get_or_insert_with(component_id, Vec::new) - .push(entity); + removed_components.send(component_id, entity); // Make sure to drop components stored in sparse sets. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. @@ -581,13 +580,11 @@ impl<'w> EntityMut<'w> { .expect("entity should exist at this point."); let table_row; let moved_entity; + { let archetype = &mut world.archetypes[location.archetype_id]; for component_id in archetype.components() { - let removed_components = world - .removed_components - .get_or_insert_with(component_id, Vec::new); - removed_components.push(self.entity); + world.removed_components.send(component_id, self.entity); } let remove_result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = remove_result.swapped_entity { @@ -807,14 +804,13 @@ unsafe fn get_ticks( unsafe fn take_component<'a>( components: &Components, storages: &'a mut Storages, - removed_components: &mut SparseSet>, + removed_components: &mut RemovedComponentEvents, component_id: ComponentId, entity: Entity, location: EntityLocation, ) -> OwningPtr<'a> { let component_info = components.get_info_unchecked(component_id); - let removed_components = removed_components.get_or_insert_with(component_id, Vec::new); - removed_components.push(entity); + removed_components.send(component_id, entity); match component_info.storage_type() { StorageType::Table => { let table = &mut storages.tables[location.table_id]; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index e4569f6be52206..de8b9dcd92d5f9 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -17,7 +17,8 @@ use crate::{ entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, event::{Event, Events}, query::{QueryState, ReadOnlyWorldQuery, WorldQuery}, - storage::{ResourceData, SparseSet, Storages}, + removal_detection::RemovedComponentEvents, + storage::{ResourceData, Storages}, system::Resource, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -57,7 +58,7 @@ pub struct World { pub(crate) archetypes: Archetypes, pub(crate) storages: Storages, pub(crate) bundles: Bundles, - pub(crate) removed_components: SparseSet>, + pub(crate) removed_components: RemovedComponentEvents, /// Access cache used by [WorldCell]. pub(crate) archetype_component_access: ArchetypeComponentAccess, main_thread_validator: MainThreadValidator, @@ -143,6 +144,12 @@ impl World { &self.bundles } + /// Retrieves this world's [`RemovedComponentEvents`] collection + #[inline] + pub fn removed_components(&self) -> &RemovedComponentEvents { + &self.removed_components + } + /// Retrieves a [`WorldCell`], which safely enables multiple mutable World accesses at the same /// time, provided those accesses do not conflict with each other. #[inline] @@ -636,12 +643,9 @@ impl World { /// assert!(!transform.is_changed()); /// ``` /// - /// [`RemovedComponents`]: crate::system::RemovedComponents + /// [`RemovedComponents`]: crate::removal_detection::RemovedComponents pub fn clear_trackers(&mut self) { - for entities in self.removed_components.values_mut() { - entities.clear(); - } - + self.removed_components.update(); self.last_change_tick = self.increment_change_tick(); } @@ -738,12 +742,12 @@ impl World { /// Returns an iterator of entities that had components of type `T` removed /// since the last call to [`World::clear_trackers`]. - pub fn removed(&self) -> std::iter::Cloned> { - if let Some(component_id) = self.components.get_id(TypeId::of::()) { - self.removed_with_id(component_id) - } else { - [].iter().cloned() - } + pub fn removed(&self) -> impl DoubleEndedIterator + '_ { + self.components + .get_id(TypeId::of::()) + .map(|component_id| self.removed_with_id(component_id)) + .into_iter() + .flatten() } /// Returns an iterator of entities that had components with the given `component_id` removed @@ -751,12 +755,12 @@ impl World { pub fn removed_with_id( &self, component_id: ComponentId, - ) -> std::iter::Cloned> { - if let Some(removed) = self.removed_components.get(component_id) { - removed.iter().cloned() - } else { - [].iter().cloned() - } + ) -> impl DoubleEndedIterator + '_ { + self.removed_components + .get(component_id) + .map(|removed| removed.iter_current_update_events().cloned()) + .into_iter() + .flatten() } /// Inserts a new resource with standard starting values. diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index 43715a8cbd4ae4..712790cde0380e 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -5,7 +5,8 @@ use bevy_ecs::{ entity::Entity, event::EventReader, query::{Changed, ReadOnlyWorldQuery, With, Without}, - system::{Query, RemovedComponents, Res, ResMut, Resource}, + removal_detection::RemovedComponents, + system::{Query, Res, ResMut, Resource}, }; use bevy_hierarchy::{Children, Parent}; use bevy_log::warn; @@ -229,9 +230,9 @@ pub fn flex_node_system( (With, Changed), >, children_query: Query<(Entity, &Children), (With, Changed)>, - removed_children: RemovedComponents, + mut removed_children: RemovedComponents, mut node_transform_query: Query<(Entity, &mut Node, &mut Transform, Option<&Parent>)>, - removed_nodes: RemovedComponents, + mut removed_nodes: RemovedComponents, ) { // update window root nodes for window in windows.iter() { @@ -269,7 +270,7 @@ pub fn flex_node_system( } // clean up removed nodes - flex_surface.remove_entities(&removed_nodes); + flex_surface.remove_entities(removed_nodes.iter()); // update window children (for now assuming all Nodes live in the primary window) if let Some(primary_window) = windows.get_primary() { @@ -277,7 +278,7 @@ pub fn flex_node_system( } // update and remove children - for entity in &removed_children { + for entity in removed_children.iter() { flex_surface.try_remove_children(entity); } for (entity, children) in &children_query { diff --git a/examples/ecs/removal_detection.rs b/examples/ecs/removal_detection.rs index f931066a4a4ec3..f867df5e825755 100644 --- a/examples/ecs/removal_detection.rs +++ b/examples/ecs/removal_detection.rs @@ -51,10 +51,10 @@ fn remove_component( } } -fn react_on_removal(removed: RemovedComponents, mut query: Query<&mut Sprite>) { +fn react_on_removal(mut removed: RemovedComponents, mut query: Query<&mut Sprite>) { // `RemovedComponents::iter()` returns an interator with the `Entity`s that had their // `Component` `T` (in this case `MyComponent`) removed at some point earlier during the frame. - for entity in removed.iter() { + for entity in &mut removed { if let Ok(mut sprite) = query.get_mut(entity) { sprite.color.set_r(0.0); }