diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 61bbad3aedbd6..2adf6c2857571 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1483,8 +1483,8 @@ mod tests { component::Component, entity::Entity, event::{Event, EventWriter, Events}, + lifecycle::RemovedComponents, query::With, - removal_detection::RemovedComponents, resource::Resource, schedule::{IntoScheduleConfigs, ScheduleLabel}, system::{Commands, Query}, diff --git a/crates/bevy_app/src/propagate.rs b/crates/bevy_app/src/propagate.rs index 5d766a626c218..754ba3140ee3c 100644 --- a/crates/bevy_app/src/propagate.rs +++ b/crates/bevy_app/src/propagate.rs @@ -6,9 +6,9 @@ use bevy_ecs::{ component::Component, entity::Entity, hierarchy::ChildOf, + lifecycle::RemovedComponents, query::{Changed, Or, QueryFilter, With, Without}, relationship::{Relationship, RelationshipTarget}, - removal_detection::RemovedComponents, schedule::{IntoScheduleConfigs, SystemSet}, system::{Commands, Local, Query}, }; diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 3ae95d71ccf12..5b5d038fa0580 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -1,7 +1,7 @@ //! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details. use bevy_app::prelude::*; -use bevy_ecs::{component::*, prelude::*}; +use bevy_ecs::{component::*, lifecycle::ComponentHook, prelude::*}; use bevy_math::UVec2; use bevy_platform::collections::HashSet; use bevy_platform::time::Instant; diff --git a/crates/bevy_ecs/compile_fail/tests/ui/component_hook_relationship.rs b/crates/bevy_ecs/compile_fail/tests/ui/component_hook_relationship.rs index 4076819ee3d9d..79c21586441b2 100644 --- a/crates/bevy_ecs/compile_fail/tests/ui/component_hook_relationship.rs +++ b/crates/bevy_ecs/compile_fail/tests/ui/component_hook_relationship.rs @@ -60,4 +60,4 @@ mod case4 { pub struct BarTargetOf(Entity); } -fn foo_hook(_world: bevy_ecs::world::DeferredWorld, _ctx: bevy_ecs::component::HookContext) {} +fn foo_hook(_world: bevy_ecs::world::DeferredWorld, _ctx: bevy_ecs::lifecycle::HookContext) {} diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 00268cb680050..6a693f2ce5192 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -434,7 +434,7 @@ impl HookAttributeKind { HookAttributeKind::Path(path) => path.to_token_stream(), HookAttributeKind::Call(call) => { quote!({ - fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::component::HookContext) { + fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) { (#call)(world, ctx) } _internal_hook @@ -658,7 +658,7 @@ fn hook_register_function_call( ) -> Option { function.map(|meta| { quote! { - fn #hook() -> ::core::option::Option<#bevy_ecs_path::component::ComponentHook> { + fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> { ::core::option::Option::Some(#meta) } } diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 4e5b28dde82e6..7369028715fbe 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -693,7 +693,7 @@ impl Archetype { /// Returns true if any of the components in this archetype have at least one [`OnAdd`] observer /// - /// [`OnAdd`]: crate::world::OnAdd + /// [`OnAdd`]: crate::lifecycle::OnAdd #[inline] pub fn has_add_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER) @@ -701,7 +701,7 @@ impl Archetype { /// Returns true if any of the components in this archetype have at least one [`OnInsert`] observer /// - /// [`OnInsert`]: crate::world::OnInsert + /// [`OnInsert`]: crate::lifecycle::OnInsert #[inline] pub fn has_insert_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER) @@ -709,7 +709,7 @@ impl Archetype { /// Returns true if any of the components in this archetype have at least one [`OnReplace`] observer /// - /// [`OnReplace`]: crate::world::OnReplace + /// [`OnReplace`]: crate::lifecycle::OnReplace #[inline] pub fn has_replace_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REPLACE_OBSERVER) @@ -717,7 +717,7 @@ impl Archetype { /// Returns true if any of the components in this archetype have at least one [`OnRemove`] observer /// - /// [`OnRemove`]: crate::world::OnRemove + /// [`OnRemove`]: crate::lifecycle::OnRemove #[inline] pub fn has_remove_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER) @@ -725,7 +725,7 @@ impl Archetype { /// Returns true if any of the components in this archetype have at least one [`OnDespawn`] observer /// - /// [`OnDespawn`]: crate::world::OnDespawn + /// [`OnDespawn`]: crate::lifecycle::OnDespawn #[inline] pub fn has_despawn_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_DESPAWN_OBSERVER) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index a54406dde16ba..7d6c0129dc0a2 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -66,15 +66,13 @@ use crate::{ RequiredComponents, StorageType, Tick, }, entity::{Entities, Entity, EntityLocation}, + lifecycle::{ON_ADD, ON_INSERT, ON_REMOVE, ON_REPLACE}, observer::Observers, prelude::World, query::DebugCheckedUnwrap, relationship::RelationshipHookMode, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::{ - unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REMOVE, - ON_REPLACE, - }, + world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut}, }; use alloc::{boxed::Box, vec, vec::Vec}; use bevy_platform::collections::{HashMap, HashSet}; @@ -2123,7 +2121,7 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { #[cfg(test)] mod tests { use crate::{ - archetype::ArchetypeCreated, component::HookContext, prelude::*, world::DeferredWorld, + archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld, }; use alloc::vec; diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 96f0ffed9f666..338050f5fb748 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -5,12 +5,12 @@ use crate::{ bundle::BundleInfo, change_detection::{MaybeLocation, MAX_CHANGE_AGE}, entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent}, + lifecycle::{ComponentHook, ComponentHooks}, query::DebugCheckedUnwrap, - relationship::RelationshipHookMode, resource::Resource, storage::{SparseSetIndex, SparseSets, Table, TableRow}, system::{Local, SystemParam}, - world::{DeferredWorld, FromWorld, World}, + world::{FromWorld, World}, }; use alloc::boxed::Box; use alloc::{borrow::Cow, format, vec::Vec}; @@ -376,7 +376,8 @@ use thiserror::Error; /// - `#[component(on_remove = on_remove_function)]` /// /// ``` -/// # use bevy_ecs::component::{Component, HookContext}; +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::lifecycle::HookContext; /// # use bevy_ecs::world::DeferredWorld; /// # use bevy_ecs::entity::Entity; /// # use bevy_ecs::component::ComponentId; @@ -405,7 +406,8 @@ use thiserror::Error; /// This also supports function calls that yield closures /// /// ``` -/// # use bevy_ecs::component::{Component, HookContext}; +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::lifecycle::HookContext; /// # use bevy_ecs::world::DeferredWorld; /// # /// #[derive(Component)] @@ -657,244 +659,6 @@ pub enum StorageType { SparseSet, } -/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`. -pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, HookContext); - -/// Context provided to a [`ComponentHook`]. -#[derive(Clone, Copy, Debug)] -pub struct HookContext { - /// The [`Entity`] this hook was invoked for. - pub entity: Entity, - /// The [`ComponentId`] this hook was invoked for. - pub component_id: ComponentId, - /// The caller location is `Some` if the `track_caller` feature is enabled. - pub caller: MaybeLocation, - /// Configures how relationship hooks will run - pub relationship_hook_mode: RelationshipHookMode, -} - -/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`]. -/// -/// Hooks are functions that run when a component is added, overwritten, or removed from an entity. -/// These are intended to be used for structural side effects that need to happen when a component is added or removed, -/// and are not intended for general-purpose logic. -/// -/// For example, you might use a hook to update a cached index when a component is added, -/// to clean up resources when a component is removed, -/// or to keep hierarchical data structures across entities in sync. -/// -/// This information is stored in the [`ComponentInfo`] of the associated component. -/// -/// There is two ways of configuring hooks for a component: -/// 1. Defining the relevant hooks on the [`Component`] implementation -/// 2. Using the [`World::register_component_hooks`] method -/// -/// # Example 2 -/// -/// ``` -/// use bevy_ecs::prelude::*; -/// use bevy_platform::collections::HashSet; -/// -/// #[derive(Component)] -/// struct MyTrackedComponent; -/// -/// #[derive(Resource, Default)] -/// struct TrackedEntities(HashSet); -/// -/// let mut world = World::new(); -/// world.init_resource::(); -/// -/// // No entities with `MyTrackedComponent` have been added yet, so we can safely add component hooks -/// let mut tracked_component_query = world.query::<&MyTrackedComponent>(); -/// assert!(tracked_component_query.iter(&world).next().is_none()); -/// -/// world.register_component_hooks::().on_add(|mut world, context| { -/// let mut tracked_entities = world.resource_mut::(); -/// tracked_entities.0.insert(context.entity); -/// }); -/// -/// world.register_component_hooks::().on_remove(|mut world, context| { -/// let mut tracked_entities = world.resource_mut::(); -/// tracked_entities.0.remove(&context.entity); -/// }); -/// -/// let entity = world.spawn(MyTrackedComponent).id(); -/// let tracked_entities = world.resource::(); -/// assert!(tracked_entities.0.contains(&entity)); -/// -/// world.despawn(entity); -/// let tracked_entities = world.resource::(); -/// assert!(!tracked_entities.0.contains(&entity)); -/// ``` -#[derive(Debug, Clone, Default)] -pub struct ComponentHooks { - pub(crate) on_add: Option, - pub(crate) on_insert: Option, - pub(crate) on_replace: Option, - pub(crate) on_remove: Option, - pub(crate) on_despawn: Option, -} - -impl ComponentHooks { - pub(crate) fn update_from_component(&mut self) -> &mut Self { - if let Some(hook) = C::on_add() { - self.on_add(hook); - } - if let Some(hook) = C::on_insert() { - self.on_insert(hook); - } - if let Some(hook) = C::on_replace() { - self.on_replace(hook); - } - if let Some(hook) = C::on_remove() { - self.on_remove(hook); - } - if let Some(hook) = C::on_despawn() { - self.on_despawn(hook); - } - - self - } - - /// Register a [`ComponentHook`] that will be run when this component is added to an entity. - /// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as - /// adding all of its components. - /// - /// # Panics - /// - /// Will panic if the component already has an `on_add` hook - pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_add(hook) - .expect("Component already has an on_add hook") - } - - /// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`) - /// or replaced. - /// - /// An `on_insert` hook always runs after any `on_add` hooks (if the entity didn't already have the component). - /// - /// # Warning - /// - /// The hook won't run if the component is already present and is only mutated, such as in a system via a query. - /// As a result, this is *not* an appropriate mechanism for reliably updating indexes and other caches. - /// - /// # Panics - /// - /// Will panic if the component already has an `on_insert` hook - pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_insert(hook) - .expect("Component already has an on_insert hook") - } - - /// Register a [`ComponentHook`] that will be run when this component is about to be dropped, - /// such as being replaced (with `.insert`) or removed. - /// - /// If this component is inserted onto an entity that already has it, this hook will run before the value is replaced, - /// allowing access to the previous data just before it is dropped. - /// This hook does *not* run if the entity did not already have this component. - /// - /// An `on_replace` hook always runs before any `on_remove` hooks (if the component is being removed from the entity). - /// - /// # Warning - /// - /// The hook won't run if the component is already present and is only mutated, such as in a system via a query. - /// As a result, this is *not* an appropriate mechanism for reliably updating indexes and other caches. - /// - /// # Panics - /// - /// Will panic if the component already has an `on_replace` hook - pub fn on_replace(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_replace(hook) - .expect("Component already has an on_replace hook") - } - - /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. - /// Despawning an entity counts as removing all of its components. - /// - /// # Panics - /// - /// Will panic if the component already has an `on_remove` hook - pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_remove(hook) - .expect("Component already has an on_remove hook") - } - - /// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned. - /// - /// # Panics - /// - /// Will panic if the component already has an `on_despawn` hook - pub fn on_despawn(&mut self, hook: ComponentHook) -> &mut Self { - self.try_on_despawn(hook) - .expect("Component already has an on_despawn hook") - } - - /// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity. - /// - /// This is a fallible version of [`Self::on_add`]. - /// - /// Returns `None` if the component already has an `on_add` hook. - pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.on_add.is_some() { - return None; - } - self.on_add = Some(hook); - Some(self) - } - - /// Attempt to register a [`ComponentHook`] that will be run when this component is added (with `.insert`) - /// - /// This is a fallible version of [`Self::on_insert`]. - /// - /// Returns `None` if the component already has an `on_insert` hook. - pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.on_insert.is_some() { - return None; - } - self.on_insert = Some(hook); - Some(self) - } - - /// Attempt to register a [`ComponentHook`] that will be run when this component is replaced (with `.insert`) or removed - /// - /// This is a fallible version of [`Self::on_replace`]. - /// - /// Returns `None` if the component already has an `on_replace` hook. - pub fn try_on_replace(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.on_replace.is_some() { - return None; - } - self.on_replace = Some(hook); - Some(self) - } - - /// Attempt to register a [`ComponentHook`] that will be run when this component is removed from an entity. - /// - /// This is a fallible version of [`Self::on_remove`]. - /// - /// Returns `None` if the component already has an `on_remove` hook. - pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.on_remove.is_some() { - return None; - } - self.on_remove = Some(hook); - Some(self) - } - - /// Attempt to register a [`ComponentHook`] that will be run for each component on an entity when it is despawned. - /// - /// This is a fallible version of [`Self::on_despawn`]. - /// - /// Returns `None` if the component already has an `on_despawn` hook. - pub fn try_on_despawn(&mut self, hook: ComponentHook) -> Option<&mut Self> { - if self.on_despawn.is_some() { - return None; - } - self.on_despawn = Some(hook); - Some(self) - } -} - /// Stores metadata for a type of component or resource stored in a specific [`World`]. #[derive(Debug, Clone)] pub struct ComponentInfo { diff --git a/crates/bevy_ecs/src/event/base.rs b/crates/bevy_ecs/src/event/base.rs index d525ba2e57695..bb896c4f09140 100644 --- a/crates/bevy_ecs/src/event/base.rs +++ b/crates/bevy_ecs/src/event/base.rs @@ -68,7 +68,7 @@ pub trait Event: Send + Sync + 'static { /// /// # Warning /// - /// This method should not be overridden by implementors, + /// This method should not be overridden by implementers, /// and should always correspond to the implementation of [`component_id`](Event::component_id). fn register_component_id(world: &mut World) -> ComponentId { world.register_component::>() @@ -82,7 +82,7 @@ pub trait Event: Send + Sync + 'static { /// /// # Warning /// - /// This method should not be overridden by implementors, + /// This method should not be overridden by implementers, /// and should always correspond to the implementation of [`register_component_id`](Event::register_component_id). fn component_id(world: &World) -> Option { world.component_id::>() diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index e138dfcac8bd5..dfc32e60dbd2b 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -10,8 +10,9 @@ use crate::reflect::{ReflectComponent, ReflectFromWorld}; use crate::{ bundle::Bundle, - component::{Component, HookContext}, + component::Component, entity::Entity, + lifecycle::HookContext, relationship::{RelatedSpawner, RelatedSpawnerCommands}, system::EntityCommands, world::{DeferredWorld, EntityWorldMut, FromWorld, World}, diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 14802dfc8e69a..7c10127fccaf9 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -41,6 +41,7 @@ pub mod event; pub mod hierarchy; pub mod intern; pub mod label; +pub mod lifecycle; pub mod name; pub mod never; pub mod observer; @@ -48,7 +49,6 @@ pub mod query; #[cfg(feature = "bevy_reflect")] pub mod reflect; pub mod relationship; -pub mod removal_detection; pub mod resource; pub mod schedule; pub mod spawn; @@ -76,12 +76,12 @@ pub mod prelude { error::{BevyError, Result}, event::{Event, EventMutator, EventReader, EventWriter, Events}, hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children}, + lifecycle::{OnAdd, OnDespawn, OnInsert, OnRemove, OnReplace, RemovedComponents}, name::{Name, NameOrEntity}, observer::{Observer, Trigger}, query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, related, relationship::RelationshipTarget, - removal_detection::RemovedComponents, resource::Resource, schedule::{ common_conditions::*, ApplyDeferred, IntoScheduleConfigs, IntoSystemSet, Schedule, @@ -96,7 +96,7 @@ pub mod prelude { }, world::{ EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut, - FromWorld, OnAdd, OnInsert, OnRemove, OnReplace, World, + FromWorld, World, }, }; diff --git a/crates/bevy_ecs/src/lifecycle.rs b/crates/bevy_ecs/src/lifecycle.rs new file mode 100644 index 0000000000000..e228b416c3068 --- /dev/null +++ b/crates/bevy_ecs/src/lifecycle.rs @@ -0,0 +1,606 @@ +//! This module contains various tools to allow you to react to component insertion or removal, +//! as well as entity spawning and despawning. +//! +//! There are four main ways to react to these lifecycle events: +//! +//! 1. Using component hooks, which act as inherent constructors and destructors for components. +//! 2. Using [observers], which are a user-extensible way to respond to events, including component lifecycle events. +//! 3. Using the [`RemovedComponents`] system parameter, which offers an event-style interface. +//! 4. Using the [`Added`] query filter, which checks each component to see if it has been added since the last time a system ran. +//! +//! [observers]: crate::observer +//! [`Added`]: crate::query::Added +//! +//! # Types of lifecycle events +//! +//! There are five types of lifecycle events, split into two categories. First, we have lifecycle events that are triggered +//! when a component is added to an entity: +//! +//! - [`OnAdd`]: Triggered when a component is added to an entity that did not already have it. +//! - [`OnInsert`]: Triggered when a component is added to an entity, regardless of whether it already had it. +//! +//! When both events occur, [`OnAdd`] hooks are evaluated before [`OnInsert`]. +//! +//! Next, we have lifecycle events that are triggered when a component is removed from an entity: +//! +//! - [`OnReplace`]: Triggered when a component is removed from an entity, regardless if it is then replaced with a new value. +//! - [`OnRemove`]: Triggered when a component is removed from an entity and not replaced, before the component is removed. +//! - [`OnDespawn`]: Triggered for each component on an entity when it is despawned. +//! +//! [`OnReplace`] hooks are evaluated before [`OnRemove`], then finally [`OnDespawn`] hooks are evaluated. +//! +//! [`OnAdd`] and [`OnRemove`] are counterparts: they are only triggered when a component is added or removed +//! from an entity in such a way as to cause a change in the component's presence on that entity. +//! Similarly, [`OnInsert`] and [`OnReplace`] are counterparts: they are triggered when a component is added or replaced +//! on an entity, regardless of whether this results in a change in the component's presence on that entity. +//! +//! To reliably synchronize data structures using with component lifecycle events, +//! you can combine [`OnInsert`] and [`OnReplace`] to fully capture any changes to the data. +//! This is particularly useful in combination with immutable components, +//! to avoid any lifecycle-bypassing mutations. +//! +//! ## Lifecycle events and component types +//! +//! Despite the absence of generics, each lifecycle event is associated with a specific component. +//! When defining a component hook for a [`Component`] type, that component is used. +//! When listening to lifecycle events for observers, the `B: Bundle` generic is used. +//! +//! Each of these lifecycle events also corresponds to a fixed [`ComponentId`], +//! which are assigned during [`World`] initialization. +//! For example, [`OnAdd`] corresponds to [`ON_ADD`]. +//! This is used to skip [`TypeId`](core::any::TypeId) lookups in hot paths. +use crate::{ + change_detection::MaybeLocation, + component::{Component, ComponentId, ComponentIdFor, Tick}, + entity::Entity, + event::{Event, EventCursor, EventId, EventIterator, EventIteratorWithId, Events}, + relationship::RelationshipHookMode, + storage::SparseSet, + system::{Local, ReadOnlySystemParam, SystemMeta, SystemParam}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, +}; + +use derive_more::derive::Into; + +#[cfg(feature = "bevy_reflect")] +use bevy_reflect::Reflect; +use core::{ + fmt::Debug, + iter, + marker::PhantomData, + ops::{Deref, DerefMut}, + option, +}; + +/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`. +pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, HookContext); + +/// Context provided to a [`ComponentHook`]. +#[derive(Clone, Copy, Debug)] +pub struct HookContext { + /// The [`Entity`] this hook was invoked for. + pub entity: Entity, + /// The [`ComponentId`] this hook was invoked for. + pub component_id: ComponentId, + /// The caller location is `Some` if the `track_caller` feature is enabled. + pub caller: MaybeLocation, + /// Configures how relationship hooks will run + pub relationship_hook_mode: RelationshipHookMode, +} + +/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`]. +/// +/// Hooks are functions that run when a component is added, overwritten, or removed from an entity. +/// These are intended to be used for structural side effects that need to happen when a component is added or removed, +/// and are not intended for general-purpose logic. +/// +/// For example, you might use a hook to update a cached index when a component is added, +/// to clean up resources when a component is removed, +/// or to keep hierarchical data structures across entities in sync. +/// +/// This information is stored in the [`ComponentInfo`](crate::component::ComponentInfo) of the associated component. +/// +/// There are two ways of configuring hooks for a component: +/// 1. Defining the relevant hooks on the [`Component`] implementation +/// 2. Using the [`World::register_component_hooks`] method +/// +/// # Example +/// +/// ``` +/// use bevy_ecs::prelude::*; +/// use bevy_platform::collections::HashSet; +/// +/// #[derive(Component)] +/// struct MyTrackedComponent; +/// +/// #[derive(Resource, Default)] +/// struct TrackedEntities(HashSet); +/// +/// let mut world = World::new(); +/// world.init_resource::(); +/// +/// // No entities with `MyTrackedComponent` have been added yet, so we can safely add component hooks +/// let mut tracked_component_query = world.query::<&MyTrackedComponent>(); +/// assert!(tracked_component_query.iter(&world).next().is_none()); +/// +/// world.register_component_hooks::().on_add(|mut world, context| { +/// let mut tracked_entities = world.resource_mut::(); +/// tracked_entities.0.insert(context.entity); +/// }); +/// +/// world.register_component_hooks::().on_remove(|mut world, context| { +/// let mut tracked_entities = world.resource_mut::(); +/// tracked_entities.0.remove(&context.entity); +/// }); +/// +/// let entity = world.spawn(MyTrackedComponent).id(); +/// let tracked_entities = world.resource::(); +/// assert!(tracked_entities.0.contains(&entity)); +/// +/// world.despawn(entity); +/// let tracked_entities = world.resource::(); +/// assert!(!tracked_entities.0.contains(&entity)); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct ComponentHooks { + pub(crate) on_add: Option, + pub(crate) on_insert: Option, + pub(crate) on_replace: Option, + pub(crate) on_remove: Option, + pub(crate) on_despawn: Option, +} + +impl ComponentHooks { + pub(crate) fn update_from_component(&mut self) -> &mut Self { + if let Some(hook) = C::on_add() { + self.on_add(hook); + } + if let Some(hook) = C::on_insert() { + self.on_insert(hook); + } + if let Some(hook) = C::on_replace() { + self.on_replace(hook); + } + if let Some(hook) = C::on_remove() { + self.on_remove(hook); + } + if let Some(hook) = C::on_despawn() { + self.on_despawn(hook); + } + + self + } + + /// Register a [`ComponentHook`] that will be run when this component is added to an entity. + /// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as + /// adding all of its components. + /// + /// # Panics + /// + /// Will panic if the component already has an `on_add` hook + pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { + self.try_on_add(hook) + .expect("Component already has an on_add hook") + } + + /// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`) + /// or replaced. + /// + /// An `on_insert` hook always runs after any `on_add` hooks (if the entity didn't already have the component). + /// + /// # Warning + /// + /// The hook won't run if the component is already present and is only mutated, such as in a system via a query. + /// As a result, this needs to be combined with immutable components to serve as a mechanism for reliably updating indexes and other caches. + /// + /// # Panics + /// + /// Will panic if the component already has an `on_insert` hook + pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { + self.try_on_insert(hook) + .expect("Component already has an on_insert hook") + } + + /// Register a [`ComponentHook`] that will be run when this component is about to be dropped, + /// such as being replaced (with `.insert`) or removed. + /// + /// If this component is inserted onto an entity that already has it, this hook will run before the value is replaced, + /// allowing access to the previous data just before it is dropped. + /// This hook does *not* run if the entity did not already have this component. + /// + /// An `on_replace` hook always runs before any `on_remove` hooks (if the component is being removed from the entity). + /// + /// # Warning + /// + /// The hook won't run if the component is already present and is only mutated, such as in a system via a query. + /// As a result, this needs to be combined with immutable components to serve as a mechanism for reliably updating indexes and other caches. + /// + /// # Panics + /// + /// Will panic if the component already has an `on_replace` hook + pub fn on_replace(&mut self, hook: ComponentHook) -> &mut Self { + self.try_on_replace(hook) + .expect("Component already has an on_replace hook") + } + + /// Register a [`ComponentHook`] that will be run when this component is removed from an entity. + /// Despawning an entity counts as removing all of its components. + /// + /// # Panics + /// + /// Will panic if the component already has an `on_remove` hook + pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self { + self.try_on_remove(hook) + .expect("Component already has an on_remove hook") + } + + /// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned. + /// + /// # Panics + /// + /// Will panic if the component already has an `on_despawn` hook + pub fn on_despawn(&mut self, hook: ComponentHook) -> &mut Self { + self.try_on_despawn(hook) + .expect("Component already has an on_despawn hook") + } + + /// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity. + /// + /// This is a fallible version of [`Self::on_add`]. + /// + /// Returns `None` if the component already has an `on_add` hook. + pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.on_add.is_some() { + return None; + } + self.on_add = Some(hook); + Some(self) + } + + /// Attempt to register a [`ComponentHook`] that will be run when this component is added (with `.insert`) + /// + /// This is a fallible version of [`Self::on_insert`]. + /// + /// Returns `None` if the component already has an `on_insert` hook. + pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.on_insert.is_some() { + return None; + } + self.on_insert = Some(hook); + Some(self) + } + + /// Attempt to register a [`ComponentHook`] that will be run when this component is replaced (with `.insert`) or removed + /// + /// This is a fallible version of [`Self::on_replace`]. + /// + /// Returns `None` if the component already has an `on_replace` hook. + pub fn try_on_replace(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.on_replace.is_some() { + return None; + } + self.on_replace = Some(hook); + Some(self) + } + + /// Attempt to register a [`ComponentHook`] that will be run when this component is removed from an entity. + /// + /// This is a fallible version of [`Self::on_remove`]. + /// + /// Returns `None` if the component already has an `on_remove` hook. + pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.on_remove.is_some() { + return None; + } + self.on_remove = Some(hook); + Some(self) + } + + /// Attempt to register a [`ComponentHook`] that will be run for each component on an entity when it is despawned. + /// + /// This is a fallible version of [`Self::on_despawn`]. + /// + /// Returns `None` if the component already has an `on_despawn` hook. + pub fn try_on_despawn(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.on_despawn.is_some() { + return None; + } + self.on_despawn = Some(hook); + Some(self) + } +} + +/// [`ComponentId`] for [`OnAdd`] +pub const ON_ADD: ComponentId = ComponentId::new(0); +/// [`ComponentId`] for [`OnInsert`] +pub const ON_INSERT: ComponentId = ComponentId::new(1); +/// [`ComponentId`] for [`OnReplace`] +pub const ON_REPLACE: ComponentId = ComponentId::new(2); +/// [`ComponentId`] for [`OnRemove`] +pub const ON_REMOVE: ComponentId = ComponentId::new(3); +/// [`ComponentId`] for [`OnDespawn`] +pub const ON_DESPAWN: ComponentId = ComponentId::new(4); + +/// Trigger emitted when a component is inserted onto an entity that does not already have that +/// component. Runs before `OnInsert`. +/// See [`crate::lifecycle::ComponentHooks::on_add`] for more information. +#[derive(Event, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] +pub struct OnAdd; + +/// Trigger emitted when a component is inserted, regardless of whether or not the entity already +/// had that component. Runs after `OnAdd`, if it ran. +/// See [`crate::lifecycle::ComponentHooks::on_insert`] for more information. +#[derive(Event, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] +pub struct OnInsert; + +/// Trigger emitted when a component is removed from an entity, regardless +/// of whether or not it is later replaced. +/// +/// Runs before the value is replaced, so you can still access the original component data. +/// See [`crate::lifecycle::ComponentHooks::on_replace`] for more information. +#[derive(Event, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] +pub struct OnReplace; + +/// Trigger emitted when a component is removed from an entity, and runs before the component is +/// removed, so you can still access the component data. +/// See [`crate::lifecycle::ComponentHooks::on_remove`] for more information. +#[derive(Event, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] +pub struct OnRemove; + +/// Trigger emitted for each component on an entity when it is despawned. +/// See [`crate::lifecycle::ComponentHooks::on_despawn`] for more information. +#[derive(Event, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] +pub struct OnDespawn; + +/// Wrapper around [`Entity`] for [`RemovedComponents`]. +/// Internally, `RemovedComponents` uses these as an `Events`. +#[derive(Event, Debug, Clone, Into)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Clone))] +pub struct RemovedComponentEntity(Entity); + +/// Wrapper around a [`EventCursor`] so that we +/// can differentiate events between components. +#[derive(Debug)] +pub struct RemovedComponentReader +where + T: Component, +{ + reader: EventCursor, + marker: PhantomData, +} + +impl Default for RemovedComponentReader { + fn default() -> Self { + Self { + reader: Default::default(), + marker: PhantomData, + } + } +} + +impl Deref for RemovedComponentReader { + type Target = EventCursor; + fn deref(&self) -> &Self::Target { + &self.reader + } +} + +impl DerefMut for RemovedComponentReader { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.reader + } +} + +/// Stores the [`RemovedComponents`] event buffers for all types of component in a given [`World`]. +#[derive(Default, Debug)] +pub struct RemovedComponentEvents { + event_sets: SparseSet>, +} + +impl RemovedComponentEvents { + /// Creates an empty storage buffer for component removal events. + pub fn new() -> Self { + Self::default() + } + + /// For each type of component, swaps the event buffers and clears the oldest event buffer. + /// In general, this should be called once per frame/update. + pub fn update(&mut self) { + for (_component_id, events) in self.event_sets.iter_mut() { + events.update(); + } + } + + /// Returns an iterator over components and their entity events. + pub fn iter(&self) -> impl Iterator)> { + self.event_sets.iter() + } + + /// Gets the event storage for a given component. + pub fn get( + &self, + component_id: impl Into, + ) -> Option<&Events> { + self.event_sets.get(component_id.into()) + } + + /// Sends a removal event for the specified component. + pub fn send(&mut self, component_id: impl Into, entity: Entity) { + self.event_sets + .get_or_insert_with(component_id.into(), Default::default) + .send(RemovedComponentEntity(entity)); + } +} + +/// A [`SystemParam`] that yields entities that had their `T` [`Component`] +/// removed or have been despawned with it. +/// +/// This acts effectively the same as an [`EventReader`](crate::event::EventReader). +/// +/// Unlike hooks or observers (see the [lifecycle](crate) module docs), +/// this does not allow you to see which data existed before removal. +/// +/// 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`](World::clear_trackers). +/// +/// For users of `bevy` and `bevy_app`, [`World::clear_trackers`](World::clear_trackers) is +/// automatically called by `bevy_app::App::update` and `bevy_app::SubApp::update`. +/// For the main world, this is delayed until after all `SubApp`s have run. +/// +/// # Examples +/// +/// Basic usage: +/// +/// ``` +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::lifecycle::RemovedComponents; +/// # +/// # #[derive(Component)] +/// # struct MyComponent; +/// fn react_on_removal(mut removed: RemovedComponents) { +/// removed.read().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: ComponentIdFor<'s, T>, + reader: Local<'s, RemovedComponentReader>, + event_sets: &'w RemovedComponentEvents, +} + +/// Iterator over entities that had a specific component removed. +/// +/// See [`RemovedComponents`]. +pub type RemovedIter<'a> = iter::Map< + iter::Flatten>>>, + fn(RemovedComponentEntity) -> Entity, +>; + +/// Iterator over entities that had a specific component removed. +/// +/// See [`RemovedComponents`]. +pub type RemovedIterWithId<'a> = iter::Map< + iter::Flatten>>, + fn( + (&RemovedComponentEntity, EventId), + ) -> (Entity, EventId), +>; + +fn map_id_events( + (entity, id): (&RemovedComponentEntity, EventId), +) -> (Entity, EventId) { + (entity.clone().into(), id) +} + +// For all practical purposes, the api surface of `RemovedComponents` +// should be similar to `EventReader` to reduce confusion. +impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> { + /// Fetch underlying [`EventCursor`]. + pub fn reader(&self) -> &EventCursor { + &self.reader + } + + /// Fetch underlying [`EventCursor`] mutably. + pub fn reader_mut(&mut self) -> &mut EventCursor { + &mut self.reader + } + + /// Fetch underlying [`Events`]. + pub fn events(&self) -> Option<&Events> { + self.event_sets.get(self.component_id.get()) + } + + /// Destructures to get a mutable reference to the `EventCursor` + /// and a reference to `Events`. + /// + /// This is necessary since Rust can't detect destructuring through methods and most + /// usecases of the reader uses the `Events` as well. + pub fn reader_mut_with_events( + &mut self, + ) -> Option<( + &mut RemovedComponentReader, + &Events, + )> { + self.event_sets + .get(self.component_id.get()) + .map(|events| (&mut *self.reader, events)) + } + + /// Iterates over the events this [`RemovedComponents`] has not seen yet. This updates the + /// [`RemovedComponents`]'s event counter, which means subsequent event reads will not include events + /// that happened before now. + pub fn read(&mut self) -> RemovedIter<'_> { + self.reader_mut_with_events() + .map(|(reader, events)| reader.read(events).cloned()) + .into_iter() + .flatten() + .map(RemovedComponentEntity::into) + } + + /// Like [`read`](Self::read), except also returning the [`EventId`] of the events. + pub fn read_with_id(&mut self) -> RemovedIterWithId<'_> { + self.reader_mut_with_events() + .map(|(reader, events)| reader.read_with_id(events)) + .into_iter() + .flatten() + .map(map_id_events) + } + + /// Determines the number of removal events available to be read from this [`RemovedComponents`] without consuming any. + pub fn len(&self) -> usize { + self.events() + .map(|events| self.reader.len(events)) + .unwrap_or(0) + } + + /// Returns `true` if there are no events available to read. + pub fn is_empty(&self) -> bool { + self.events() + .is_none_or(|events| self.reader.is_empty(events)) + } + + /// Consumes all available events. + /// + /// This means these events will not appear in calls to [`RemovedComponents::read()`] or + /// [`RemovedComponents::read_with_id()`] and [`RemovedComponents::is_empty()`] will return `true`. + pub fn clear(&mut self) { + if let Some((reader, events)) = self.reader_mut_with_events() { + reader.clear(events); + } + } +} + +// SAFETY: Only reads World removed component events +unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {} + +// SAFETY: no component value access. +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: UnsafeWorldCell<'w>, + _change_tick: Tick, + ) -> Self::Item<'w, 's> { + world.removed_components() + } +} diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 2c2d42b1c91be..bd45072a5a332 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -1,8 +1,7 @@ use crate::{ - component::{ - Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType, - }, + component::{Component, ComponentCloneBehavior, Mutable, StorageType}, entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent}, + lifecycle::{ComponentHook, HookContext}, world::World, }; use alloc::vec::Vec; diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index ed5a8b176f64a..6fcfa1621c6f0 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -391,6 +391,8 @@ pub struct Observers { impl Observers { pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers { + use crate::lifecycle::*; + match event_type { ON_ADD => &mut self.on_add, ON_INSERT => &mut self.on_insert, @@ -402,6 +404,8 @@ impl Observers { } pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> { + use crate::lifecycle::*; + match event_type { ON_ADD => Some(&self.on_add), ON_INSERT => Some(&self.on_insert), @@ -479,6 +483,8 @@ impl Observers { } pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option { + use crate::lifecycle::*; + match event_type { ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER), ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER), diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index c3acff2e6eaee..61cc0973f3eea 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -2,8 +2,9 @@ use alloc::{boxed::Box, vec}; use core::any::Any; use crate::{ - component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType}, + component::{ComponentId, Mutable, StorageType}, error::{ErrorContext, ErrorHandler}, + lifecycle::{ComponentHook, HookContext}, observer::{ObserverDescriptor, ObserverTrigger}, prelude::*, query::DebugCheckedUnwrap, @@ -410,7 +411,7 @@ fn observer_system_runner>( } } -/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`crate::component::ComponentHooks::on_add`). +/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`crate::lifecycle::ComponentHooks::on_add`). /// /// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters /// erased. diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 9ec67ce36a18a..00334cb6d0063 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -11,9 +11,10 @@ pub use relationship_query::*; pub use relationship_source_collection::*; use crate::{ - component::{Component, HookContext, Mutable}, + component::{Component, Mutable}, entity::{ComponentCloneCtx, Entity, SourceComponent}, error::{ignore, CommandWithEntity, HandleError}, + lifecycle::HookContext, world::{DeferredWorld, EntityWorldMut}, }; use log::warn; diff --git a/crates/bevy_ecs/src/removal_detection.rs b/crates/bevy_ecs/src/removal_detection.rs deleted file mode 100644 index 64cc63a7ce254..0000000000000 --- a/crates/bevy_ecs/src/removal_detection.rs +++ /dev/null @@ -1,268 +0,0 @@ -//! Alerting events when a component is removed from an entity. - -use crate::{ - component::{Component, ComponentId, ComponentIdFor, Tick}, - entity::Entity, - event::{Event, EventCursor, EventId, EventIterator, EventIteratorWithId, Events}, - prelude::Local, - storage::SparseSet, - system::{ReadOnlySystemParam, SystemMeta, SystemParam}, - world::{unsafe_world_cell::UnsafeWorldCell, World}, -}; - -use derive_more::derive::Into; - -#[cfg(feature = "bevy_reflect")] -use bevy_reflect::Reflect; -use core::{ - fmt::Debug, - iter, - marker::PhantomData, - ops::{Deref, DerefMut}, - option, -}; - -/// Wrapper around [`Entity`] for [`RemovedComponents`]. -/// Internally, `RemovedComponents` uses these as an `Events`. -#[derive(Event, Debug, Clone, Into)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Clone))] -pub struct RemovedComponentEntity(Entity); - -/// Wrapper around a [`EventCursor`] so that we -/// can differentiate events between components. -#[derive(Debug)] -pub struct RemovedComponentReader -where - T: Component, -{ - reader: EventCursor, - marker: PhantomData, -} - -impl Default for RemovedComponentReader { - fn default() -> Self { - Self { - reader: Default::default(), - marker: PhantomData, - } - } -} - -impl Deref for RemovedComponentReader { - type Target = EventCursor; - fn deref(&self) -> &Self::Target { - &self.reader - } -} - -impl DerefMut for RemovedComponentReader { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.reader - } -} - -/// Stores the [`RemovedComponents`] event buffers for all types of component in a given [`World`]. -#[derive(Default, Debug)] -pub struct RemovedComponentEvents { - event_sets: SparseSet>, -} - -impl RemovedComponentEvents { - /// Creates an empty storage buffer for component removal events. - pub fn new() -> Self { - Self::default() - } - - /// For each type of component, swaps the event buffers and clears the oldest event buffer. - /// In general, this should be called once per frame/update. - pub fn update(&mut self) { - for (_component_id, events) in self.event_sets.iter_mut() { - events.update(); - } - } - - /// Returns an iterator over components and their entity events. - pub fn iter(&self) -> impl Iterator)> { - self.event_sets.iter() - } - - /// Gets the event storage for a given component. - pub fn get( - &self, - component_id: impl Into, - ) -> Option<&Events> { - self.event_sets.get(component_id.into()) - } - - /// Sends a removal event for the specified component. - pub fn send(&mut self, component_id: impl Into, entity: Entity) { - self.event_sets - .get_or_insert_with(component_id.into(), Default::default) - .send(RemovedComponentEntity(entity)); - } -} - -/// A [`SystemParam`] that yields entities that had their `T` [`Component`] -/// removed or have been despawned with it. -/// -/// This acts effectively the same as an [`EventReader`](crate::event::EventReader). -/// -/// 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`](World::clear_trackers). -/// -/// For users of `bevy` and `bevy_app`, [`World::clear_trackers`](World::clear_trackers) is -/// automatically called by `bevy_app::App::update` and `bevy_app::SubApp::update`. -/// For the main world, this is delayed until after all `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.read().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: ComponentIdFor<'s, T>, - reader: Local<'s, RemovedComponentReader>, - event_sets: &'w RemovedComponentEvents, -} - -/// Iterator over entities that had a specific component removed. -/// -/// See [`RemovedComponents`]. -pub type RemovedIter<'a> = iter::Map< - iter::Flatten>>>, - fn(RemovedComponentEntity) -> Entity, ->; - -/// Iterator over entities that had a specific component removed. -/// -/// See [`RemovedComponents`]. -pub type RemovedIterWithId<'a> = iter::Map< - iter::Flatten>>, - fn( - (&RemovedComponentEntity, EventId), - ) -> (Entity, EventId), ->; - -fn map_id_events( - (entity, id): (&RemovedComponentEntity, EventId), -) -> (Entity, EventId) { - (entity.clone().into(), id) -} - -// For all practical purposes, the api surface of `RemovedComponents` -// should be similar to `EventReader` to reduce confusion. -impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> { - /// Fetch underlying [`EventCursor`]. - pub fn reader(&self) -> &EventCursor { - &self.reader - } - - /// Fetch underlying [`EventCursor`] mutably. - pub fn reader_mut(&mut self) -> &mut EventCursor { - &mut self.reader - } - - /// Fetch underlying [`Events`]. - pub fn events(&self) -> Option<&Events> { - self.event_sets.get(self.component_id.get()) - } - - /// Destructures to get a mutable reference to the `EventCursor` - /// and a reference to `Events`. - /// - /// This is necessary since Rust can't detect destructuring through methods and most - /// usecases of the reader uses the `Events` as well. - pub fn reader_mut_with_events( - &mut self, - ) -> Option<( - &mut RemovedComponentReader, - &Events, - )> { - self.event_sets - .get(self.component_id.get()) - .map(|events| (&mut *self.reader, events)) - } - - /// Iterates over the events this [`RemovedComponents`] has not seen yet. This updates the - /// [`RemovedComponents`]'s event counter, which means subsequent event reads will not include events - /// that happened before now. - pub fn read(&mut self) -> RemovedIter<'_> { - self.reader_mut_with_events() - .map(|(reader, events)| reader.read(events).cloned()) - .into_iter() - .flatten() - .map(RemovedComponentEntity::into) - } - - /// Like [`read`](Self::read), except also returning the [`EventId`] of the events. - pub fn read_with_id(&mut self) -> RemovedIterWithId<'_> { - self.reader_mut_with_events() - .map(|(reader, events)| reader.read_with_id(events)) - .into_iter() - .flatten() - .map(map_id_events) - } - - /// Determines the number of removal events available to be read from this [`RemovedComponents`] without consuming any. - pub fn len(&self) -> usize { - self.events() - .map(|events| self.reader.len(events)) - .unwrap_or(0) - } - - /// Returns `true` if there are no events available to read. - pub fn is_empty(&self) -> bool { - self.events() - .is_none_or(|events| self.reader.is_empty(events)) - } - - /// Consumes all available events. - /// - /// This means these events will not appear in calls to [`RemovedComponents::read()`] or - /// [`RemovedComponents::read_with_id()`] and [`RemovedComponents::is_empty()`] will return `true`. - pub fn clear(&mut self) { - if let Some((reader, events)) = self.reader_mut_with_events() { - reader.clear(events); - } - } -} - -// SAFETY: Only reads World removed component events -unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {} - -// SAFETY: no component value access. -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: UnsafeWorldCell<'w>, - _change_tick: Tick, - ) -> Self::Item<'w, 's> { - world.removed_components() - } -} diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 2b31ad50c7605..ab706c0f32522 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -402,9 +402,9 @@ pub mod common_conditions { use crate::{ change_detection::DetectChanges, event::{Event, EventReader}, + lifecycle::RemovedComponents, prelude::{Component, Query, With}, query::QueryFilter, - removal_detection::RemovedComponents, resource::Resource, system::{In, IntoSystem, Local, Res, System, SystemInput}, }; diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 91ba8660d4cbd..f79cb4f5d8c05 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -97,7 +97,7 @@ //! - [`EventWriter`](crate::event::EventWriter) //! - [`NonSend`] and `Option` //! - [`NonSendMut`] and `Option` -//! - [`RemovedComponents`](crate::removal_detection::RemovedComponents) +//! - [`RemovedComponents`](crate::lifecycle::RemovedComponents) //! - [`SystemName`] //! - [`SystemChangeTick`] //! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata) @@ -408,10 +408,10 @@ mod tests { component::{Component, Components}, entity::{Entities, Entity}, error::Result, + lifecycle::RemovedComponents, name::Name, - prelude::{AnyOf, EntityRef, Trigger}, + prelude::{AnyOf, EntityRef, OnAdd, Trigger}, query::{Added, Changed, Or, SpawnDetails, Spawned, With, Without}, - removal_detection::RemovedComponents, resource::Resource, schedule::{ common_conditions::resource_exists, ApplyDeferred, IntoScheduleConfigs, Schedule, @@ -421,7 +421,7 @@ mod tests { Commands, In, InMut, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, Res, ResMut, Single, StaticSystemParam, System, SystemState, }, - world::{DeferredWorld, EntityMut, FromWorld, OnAdd, World}, + world::{DeferredWorld, EntityMut, FromWorld, World}, }; use super::ScheduleSystem; diff --git a/crates/bevy_ecs/src/world/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs deleted file mode 100644 index ea2899c5f916a..0000000000000 --- a/crates/bevy_ecs/src/world/component_constants.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Internal components used by bevy with a fixed component id. -//! Constants are used to skip [`TypeId`] lookups in hot paths. -use super::*; -#[cfg(feature = "bevy_reflect")] -use bevy_reflect::Reflect; - -/// [`ComponentId`] for [`OnAdd`] -pub const ON_ADD: ComponentId = ComponentId::new(0); -/// [`ComponentId`] for [`OnInsert`] -pub const ON_INSERT: ComponentId = ComponentId::new(1); -/// [`ComponentId`] for [`OnReplace`] -pub const ON_REPLACE: ComponentId = ComponentId::new(2); -/// [`ComponentId`] for [`OnRemove`] -pub const ON_REMOVE: ComponentId = ComponentId::new(3); -/// [`ComponentId`] for [`OnDespawn`] -pub const ON_DESPAWN: ComponentId = ComponentId::new(4); - -/// Trigger emitted when a component is inserted onto an entity that does not already have that -/// component. Runs before `OnInsert`. -/// See [`crate::component::ComponentHooks::on_add`] for more information. -#[derive(Event, Debug)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] -pub struct OnAdd; - -/// Trigger emitted when a component is inserted, regardless of whether or not the entity already -/// had that component. Runs after `OnAdd`, if it ran. -/// See [`crate::component::ComponentHooks::on_insert`] for more information. -#[derive(Event, Debug)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] -pub struct OnInsert; - -/// Trigger emitted when a component is inserted onto an entity that already has that component. -/// Runs before the value is replaced, so you can still access the original component data. -/// See [`crate::component::ComponentHooks::on_replace`] for more information. -#[derive(Event, Debug)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] -pub struct OnReplace; - -/// Trigger emitted when a component is removed from an entity, and runs before the component is -/// removed, so you can still access the component data. -/// See [`crate::component::ComponentHooks::on_remove`] for more information. -#[derive(Event, Debug)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] -pub struct OnRemove; - -/// Trigger emitted for each component on an entity when it is despawned. -/// See [`crate::component::ComponentHooks::on_despawn`] for more information. -#[derive(Event, Debug)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] -pub struct OnDespawn; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 5a20046b2fda3..da51b8e09a2c6 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -3,9 +3,10 @@ use core::ops::Deref; use crate::{ archetype::Archetype, change_detection::{MaybeLocation, MutUntyped}, - component::{ComponentId, HookContext, Mutable}, + component::{ComponentId, Mutable}, entity::Entity, event::{Event, EventId, Events, SendBatchIds}, + lifecycle::{HookContext, ON_INSERT, ON_REPLACE}, observer::{Observers, TriggerTargets}, prelude::{Component, QueryState}, query::{QueryData, QueryFilter}, @@ -16,7 +17,7 @@ use crate::{ world::{error::EntityMutableFetchError, EntityFetcher, WorldEntityFetch}, }; -use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World, ON_INSERT, ON_REPLACE}; +use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World}; /// A [`World`] reference that disallows structural ECS changes. /// This includes initializing resources, registering components or spawning entities. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index dd653831d97c5..7dbf74b7d9bbf 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -14,15 +14,13 @@ use crate::{ EntityIdLocation, EntityLocation, }, event::Event, + lifecycle::{ON_DESPAWN, ON_REMOVE, ON_REPLACE}, observer::Observer, query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData}, relationship::RelationshipHookMode, resource::Resource, system::IntoObserverSystem, - world::{ - error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World, - ON_DESPAWN, ON_REMOVE, ON_REPLACE, - }, + world::{error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World}, }; use alloc::vec::Vec; use bevy_platform::collections::{HashMap, HashSet}; @@ -4756,7 +4754,8 @@ mod tests { use core::panic::AssertUnwindSafe; use std::sync::OnceLock; - use crate::component::{HookContext, Tick}; + use crate::component::Tick; + use crate::lifecycle::HookContext; use crate::{ change_detection::{MaybeLocation, MutUntyped}, component::ComponentId, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d84717755b158..ba037bdb95654 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1,7 +1,6 @@ //! Defines the [`World`] and APIs for accessing it directly. pub(crate) mod command_queue; -mod component_constants; mod deferred_world; mod entity_fetch; mod entity_ref; @@ -14,13 +13,16 @@ pub mod unsafe_world_cell; #[cfg(feature = "bevy_reflect")] pub mod reflect; -use crate::error::{DefaultErrorHandler, ErrorHandler}; pub use crate::{ change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}, world::command_queue::CommandQueue, }; +use crate::{ + error::{DefaultErrorHandler, ErrorHandler}, + lifecycle::{ComponentHooks, ON_ADD, ON_DESPAWN, ON_INSERT, ON_REMOVE, ON_REPLACE}, + prelude::{OnAdd, OnDespawn, OnInsert, OnRemove, OnReplace}, +}; pub use bevy_ecs_macros::FromWorld; -pub use component_constants::*; pub use deferred_world::DeferredWorld; pub use entity_fetch::{EntityFetcher, WorldEntityFetch}; pub use entity_ref::{ @@ -39,17 +41,17 @@ use crate::{ }, change_detection::{MaybeLocation, MutUntyped, TicksMut}, component::{ - CheckChangeTicks, Component, ComponentDescriptor, ComponentHooks, ComponentId, - ComponentIds, ComponentInfo, ComponentTicks, Components, ComponentsQueuedRegistrator, - ComponentsRegistrator, Mutable, RequiredComponents, RequiredComponentsError, Tick, + CheckChangeTicks, Component, ComponentDescriptor, ComponentId, ComponentIds, ComponentInfo, + ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable, + RequiredComponents, RequiredComponentsError, Tick, }, entity::{Entities, Entity, EntityDoesNotExistError}, entity_disabling::DefaultQueryFilters, event::{Event, EventId, Events, SendBatchIds}, + lifecycle::RemovedComponentEvents, observer::Observers, query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState}, relationship::RelationshipHookMode, - removal_detection::RemovedComponentEvents, resource::Resource, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, @@ -1444,7 +1446,7 @@ impl World { /// assert!(!transform.is_changed()); /// ``` /// - /// [`RemovedComponents`]: crate::removal_detection::RemovedComponents + /// [`RemovedComponents`]: crate::lifecycle::RemovedComponents pub fn clear_trackers(&mut self) { self.removed_components.update(); self.last_change_tick = self.increment_change_tick(); diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 8fb084d444352..ca898f95313da 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -8,10 +8,10 @@ use crate::{ component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells}, entity::{ContainsEntity, Entities, Entity, EntityDoesNotExistError, EntityLocation}, error::{DefaultErrorHandler, ErrorHandler}, + lifecycle::RemovedComponentEvents, observer::Observers, prelude::Component, query::{DebugCheckedUnwrap, ReadOnlyQueryData}, - removal_detection::RemovedComponentEvents, resource::Resource, storage::{ComponentSparseSet, Storages, Table}, world::RawCommandQueue, diff --git a/crates/bevy_input_focus/src/autofocus.rs b/crates/bevy_input_focus/src/autofocus.rs index 72024418d2e65..f6a862db88abb 100644 --- a/crates/bevy_input_focus/src/autofocus.rs +++ b/crates/bevy_input_focus/src/autofocus.rs @@ -1,6 +1,6 @@ //! Contains the [`AutoFocus`] component and related machinery. -use bevy_ecs::{component::HookContext, prelude::*, world::DeferredWorld}; +use bevy_ecs::{lifecycle::HookContext, prelude::*, world::DeferredWorld}; use crate::InputFocus; diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index e8879d7a9c217..436e838fb1b94 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -369,7 +369,7 @@ mod tests { use alloc::string::String; use bevy_ecs::{ - component::HookContext, observer::Trigger, system::RunSystemOnce, world::DeferredWorld, + lifecycle::HookContext, observer::Trigger, system::RunSystemOnce, world::DeferredWorld, }; use bevy_input::{ keyboard::{Key, KeyCode}, diff --git a/crates/bevy_pbr/src/lightmap/mod.rs b/crates/bevy_pbr/src/lightmap/mod.rs index cb7aecc2cb8f5..567bbce67436f 100644 --- a/crates/bevy_pbr/src/lightmap/mod.rs +++ b/crates/bevy_pbr/src/lightmap/mod.rs @@ -37,9 +37,9 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, entity::Entity, + lifecycle::RemovedComponents, query::{Changed, Or}, reflect::ReflectComponent, - removal_detection::RemovedComponents, resource::Resource, schedule::IntoScheduleConfigs, system::{Query, Res, ResMut}, diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 2f7b912a3d14f..62ba8af661cbe 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -8,9 +8,9 @@ use bevy_ecs::{ entity::Entity, event::EventCursor, hierarchy::ChildOf, + lifecycle::RemovedComponentEntity, query::QueryBuilder, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, - removal_detection::RemovedComponentEntity, system::{In, Local}, world::{EntityRef, EntityWorldMut, FilteredEntityRef, World}, }; diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index af4189e941c5d..fd3b8cb4b2e1b 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -22,9 +22,10 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChanges, - component::{Component, HookContext}, + component::Component, entity::{ContainsEntity, Entity}, event::EventReader, + lifecycle::HookContext, prelude::With, query::Has, reflect::ReflectComponent, diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 90613451dd5e0..a5dcebaaa86dc 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -1,6 +1,7 @@ use bevy_app::Plugin; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHash; +use bevy_ecs::lifecycle::{OnAdd, OnRemove}; use bevy_ecs::{ component::Component, entity::{ContainsEntity, Entity, EntityEquivalent}, @@ -9,7 +10,7 @@ use bevy_ecs::{ reflect::ReflectComponent, resource::Resource, system::{Local, Query, ResMut, SystemState}, - world::{Mut, OnAdd, OnRemove, World}, + world::{Mut, World}, }; use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -490,10 +491,11 @@ mod tests { use bevy_ecs::{ component::Component, entity::Entity, + lifecycle::{OnAdd, OnRemove}, observer::Trigger, query::With, system::{Query, ResMut}, - world::{OnAdd, OnRemove, World}, + world::World, }; use super::{ diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 43533b53540b7..13b8ac74d439b 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -3,8 +3,8 @@ mod render_layers; use core::any::TypeId; -use bevy_ecs::component::HookContext; use bevy_ecs::entity::EntityHashSet; +use bevy_ecs::lifecycle::HookContext; use bevy_ecs::world::DeferredWorld; use derive_more::derive::{Deref, DerefMut}; pub use range::*; diff --git a/crates/bevy_render/src/view/visibility/range.rs b/crates/bevy_render/src/view/visibility/range.rs index 2559d3b8d2983..80f89ce936bde 100644 --- a/crates/bevy_render/src/view/visibility/range.rs +++ b/crates/bevy_render/src/view/visibility/range.rs @@ -10,9 +10,9 @@ use bevy_app::{App, Plugin, PostUpdate}; use bevy_ecs::{ component::Component, entity::{Entity, EntityHashMap}, + lifecycle::RemovedComponents, query::{Changed, With}, reflect::ReflectComponent, - removal_detection::RemovedComponents, resource::Resource, schedule::IntoScheduleConfigs as _, system::{Local, Query, Res, ResMut}, diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index a5e53d981e546..484acbd4af63e 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -8,8 +8,8 @@ use bevy_ecs::{ change_detection::{DetectChanges, DetectChangesMut}, entity::Entity, hierarchy::{ChildOf, Children}, + lifecycle::RemovedComponents, query::With, - removal_detection::RemovedComponents, system::{Commands, Query, ResMut}, world::Ref, }; diff --git a/crates/bevy_winit/src/cursor.rs b/crates/bevy_winit/src/cursor.rs index 63c8a2bb2d41c..e3e4cb9f87357 100644 --- a/crates/bevy_winit/src/cursor.rs +++ b/crates/bevy_winit/src/cursor.rs @@ -22,11 +22,12 @@ use bevy_ecs::{ change_detection::DetectChanges, component::Component, entity::Entity, + lifecycle::OnRemove, observer::Trigger, query::With, reflect::ReflectComponent, system::{Commands, Local, Query}, - world::{OnRemove, Ref}, + world::Ref, }; #[cfg(feature = "custom_cursor")] use bevy_image::{Image, TextureAtlasLayout}; diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 97483c7358464..873949ea89e41 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use bevy_ecs::{ entity::Entity, event::EventWriter, + lifecycle::RemovedComponents, prelude::{Changed, Component}, query::QueryFilter, - removal_detection::RemovedComponents, system::{Local, NonSendMarker, Query, SystemParamItem}, }; use bevy_input::keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput}; diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 11c469ad1506a..4ce6b7b09bd23 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -14,7 +14,8 @@ //! between components (like hierarchies or parent-child links) and need to maintain correctness. use bevy::{ - ecs::component::{ComponentHook, HookContext, Mutable, StorageType}, + ecs::component::{Mutable, StorageType}, + ecs::lifecycle::{ComponentHook, HookContext}, prelude::*, }; use std::collections::HashMap; diff --git a/examples/ecs/immutable_components.rs b/examples/ecs/immutable_components.rs index 5f4f2db8c4a14..e9d3c3b3837f4 100644 --- a/examples/ecs/immutable_components.rs +++ b/examples/ecs/immutable_components.rs @@ -2,9 +2,8 @@ use bevy::{ ecs::{ - component::{ - ComponentCloneBehavior, ComponentDescriptor, ComponentId, HookContext, StorageType, - }, + component::{ComponentCloneBehavior, ComponentDescriptor, ComponentId, StorageType}, + lifecycle::HookContext, world::DeferredWorld, }, platform::collections::HashMap, diff --git a/release-content/migration-guides/component-lifecycle-module.md b/release-content/migration-guides/component-lifecycle-module.md new file mode 100644 index 0000000000000..830ab351ac39e --- /dev/null +++ b/release-content/migration-guides/component-lifecycle-module.md @@ -0,0 +1,14 @@ +--- +title: Component lifecycle reorganization +pull_requests: [19543] +--- + +To improve documentation, discoverability and internal organization, we've gathered all of the component lifecycle-related code we could and moved it into a dedicated `lifecycle` module. + +The lifecycle / observer types (`OnAdd`, `OnInsert`, `OnRemove`, `OnReplace`, `OnDespawn`) have been moved from the `bevy_ecs::world` to `bevy_ecs::lifecycle`. + +The same move has been done for the more internal (but public) `ComponentId` constants: `ON_ADD`, `ON_INSERT`, `ON_REMOVE`, `ON_REPLACE`, `ON_DESPAWN`. + +The code for hooks (`HookContext`, `ComponentHook`, `ComponentHooks`) has been extracted from the very long `bevy_ecs::components` module, and now lives in the `bevy_ecs::lifecycle` module. + +The `RemovedComponents` `SystemParam`, along with the public `RemovedIter`, `RemovedIterWithId` and `RemovedComponentEvents` have also been moved into this module as they serve a similar role. All references to `bevy_ecs::removal_detection` can be replaced with `bevy_ecs::lifecycle`.