diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 6e8097584317a..9973d0ccc1db2 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -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}; @@ -12,6 +13,7 @@ use std::{ alloc::Layout, any::{Any, TypeId}, borrow::Cow, + marker::PhantomData, mem::needs_drop, }; @@ -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>) { +/// let component_id: ComponentId = component_id.into(); +/// // ... +/// } +/// ``` +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 + } +} + +impl<'s, T: Component> From>> for ComponentId { + fn from(to_component_id: Local>) -> ComponentId { + **to_component_id + } +} diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 8ccffe54293a6..5a202277bf9da 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 schedule_v3; pub mod storage; @@ -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, @@ -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}, }; diff --git a/crates/bevy_ecs/src/removal_detection.rs b/crates/bevy_ecs/src/removal_detection.rs new file mode 100644 index 0000000000000..e0e0bd7430cde --- /dev/null +++ b/crates/bevy_ecs/src/removal_detection.rs @@ -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`] 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, +} + +/// Iterator over entities that had a specific component removed. +/// +/// See [`RemovedComponents`]. +pub 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() + } +} + +// 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() + } +} diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 3f338cbda66aa..01871e6204fa3 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) @@ -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}, }; @@ -602,7 +603,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, ) { @@ -627,13 +628,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 c97dcf397e396..64fba4ea8b095 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -3,8 +3,8 @@ use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundles, change_detection::{Ticks, TicksMut}, - component::{Component, ComponentId, ComponentTicks, Components}, - entity::{Entities, Entity}, + component::{ComponentId, ComponentTicks, Components}, + entity::Entities, query::{ Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery, }, @@ -19,7 +19,6 @@ use bevy_utils::synccell::SyncCell; use std::{ borrow::Cow, fmt::Debug, - marker::PhantomData, ops::{Deref, DerefMut}, }; @@ -787,89 +786,6 @@ unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { } } -/// 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> {} - -// SAFETY: no component access. removed component entity collections can be read in parallel and are -// never mutably borrowed during system execution -unsafe impl<'a, T: Component> SystemParam for RemovedComponents<'a, T> { - type State = ComponentId; - type Item<'w, 's> = RemovedComponents<'w, T>; - - fn init_state(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { - world.init_component::() - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - _system_meta: &SystemMeta, - world: &'w World, - _change_tick: u32, - ) -> Self::Item<'w, 's> { - RemovedComponents { - world, - 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 @@ -1482,6 +1398,7 @@ mod tests { query::{ReadOnlyWorldQuery, WorldQuery}, system::Query, }; + use std::marker::PhantomData; // Compile test for https://github.com/bevyengine/bevy/pull/2838. #[derive(SystemParam)] diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 7ab064500ff3a..cfefb0707652f 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -6,7 +6,8 @@ use crate::{ Component, ComponentId, ComponentStorage, ComponentTicks, Components, StorageType, }, entity::{Entities, Entity, EntityLocation}, - storage::{SparseSet, Storages}, + removal_detection::RemovedComponentEvents, + storage::Storages, world::{Mut, World}, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -439,9 +440,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`. @@ -480,13 +479,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 { @@ -839,15 +836,14 @@ pub(crate) unsafe fn get_mut_by_id( pub(crate) unsafe fn take_component<'a>( storages: &'a mut Storages, components: &Components, - removed_components: &mut SparseSet>, + removed_components: &mut RemovedComponentEvents, component_id: ComponentId, entity: Entity, location: EntityLocation, ) -> OwningPtr<'a> { // SAFETY: caller promises component_id to be valid 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 87913d2b59ddb..0001a754bb60d 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -19,7 +19,8 @@ use crate::{ entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, event::{Event, Events}, query::{DebugCheckedUnwrap, QueryState, ReadOnlyWorldQuery, WorldQuery}, - storage::{Column, ComponentSparseSet, ResourceData, SparseSet, Storages, TableRow}, + removal_detection::RemovedComponentEvents, + storage::{Column, ComponentSparseSet, ResourceData, Storages, TableRow}, system::Resource, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -62,7 +63,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]. Is only accessed in the `Drop` impl of `WorldCell`. pub(crate) archetype_component_access: UnsafeCell, pub(crate) change_tick: AtomicU32, @@ -165,6 +166,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] @@ -666,12 +673,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(); } @@ -768,12 +772,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 @@ -781,12 +785,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() } /// Initializes a new resource and returns the [`ComponentId`] created for it. diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index 329dc04a96b8e..37dca527b73e2 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -6,7 +6,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; @@ -233,9 +234,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, ) { // assume one window for time being... // TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621 @@ -280,13 +281,13 @@ 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) flex_surface.set_window_children(primary_window_entity, root_node_query.iter()); // 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/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 8f5bdeaf05cf5..d3ba832cb2ee4 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -2,7 +2,8 @@ use bevy_ecs::{ entity::Entity, event::EventWriter, prelude::{Changed, Component, Resource}, - system::{Commands, NonSendMut, Query, RemovedComponents}, + removal_detection::RemovedComponents, + system::{Commands, NonSendMut, Query}, world::Mut, }; use bevy_utils::{ @@ -89,7 +90,7 @@ pub(crate) fn create_window<'a>( pub struct WindowTitleCache(HashMap); pub(crate) fn despawn_window( - closed: RemovedComponents, + mut closed: RemovedComponents, window_entities: Query<&Window>, mut close_events: EventWriter, mut winit_windows: NonSendMut, diff --git a/examples/ecs/removal_detection.rs b/examples/ecs/removal_detection.rs index b2e0cbc5042d4..e97a811361173 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 iterator 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); }