Component lifecycle reorganization and documentation (#19543)
# Objective I set out with one simple goal: clearly document the differences between each of the component lifecycle events via module docs. Unfortunately, no such module existed: the various lifecycle code was scattered to the wind. Without a unified module, it's very hard to discover the related types, and there's nowhere good to put my shiny new documentation. ## Solution 1. Unify the assorted types into a single `bevy_ecs::component_lifecycle` module. 2. Write docs. 3. Write a migration guide. ## Testing Thanks CI! ## Follow-up 1. The lifecycle event names are pretty confusing, especially `OnReplace`. We should consider renaming those. No bikeshedding in my PR though! 2. Observers need real module docs too :( 3. Any additional functional changes should be done elsewhere; this is a simple docs and re-org PR. --------- Co-authored-by: theotherphil <phil.j.ellison@gmail.com>
This commit is contained in:
parent
c35df3ca66
commit
6ddd0f16a8
@ -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},
|
||||
|
@ -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},
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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) {}
|
||||
|
@ -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<TokenStream2> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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<T: Eq + Ord + Copy>(source: &mut Vec<T>, 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;
|
||||
|
||||
|
@ -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<Entity>);
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// world.init_resource::<TrackedEntities>();
|
||||
///
|
||||
/// // 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::<MyTrackedComponent>().on_add(|mut world, context| {
|
||||
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
|
||||
/// tracked_entities.0.insert(context.entity);
|
||||
/// });
|
||||
///
|
||||
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, context| {
|
||||
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
|
||||
/// tracked_entities.0.remove(&context.entity);
|
||||
/// });
|
||||
///
|
||||
/// let entity = world.spawn(MyTrackedComponent).id();
|
||||
/// let tracked_entities = world.resource::<TrackedEntities>();
|
||||
/// assert!(tracked_entities.0.contains(&entity));
|
||||
///
|
||||
/// world.despawn(entity);
|
||||
/// let tracked_entities = world.resource::<TrackedEntities>();
|
||||
/// assert!(!tracked_entities.0.contains(&entity));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ComponentHooks {
|
||||
pub(crate) on_add: Option<ComponentHook>,
|
||||
pub(crate) on_insert: Option<ComponentHook>,
|
||||
pub(crate) on_replace: Option<ComponentHook>,
|
||||
pub(crate) on_remove: Option<ComponentHook>,
|
||||
pub(crate) on_despawn: Option<ComponentHook>,
|
||||
}
|
||||
|
||||
impl ComponentHooks {
|
||||
pub(crate) fn update_from_component<C: Component + ?Sized>(&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 {
|
||||
|
@ -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::<EventWrapperComponent<Self>>()
|
||||
@ -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<ComponentId> {
|
||||
world.component_id::<EventWrapperComponent<Self>>()
|
||||
|
@ -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},
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
606
crates/bevy_ecs/src/lifecycle.rs
Normal file
606
crates/bevy_ecs/src/lifecycle.rs
Normal file
@ -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<Entity>);
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// world.init_resource::<TrackedEntities>();
|
||||
///
|
||||
/// // 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::<MyTrackedComponent>().on_add(|mut world, context| {
|
||||
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
|
||||
/// tracked_entities.0.insert(context.entity);
|
||||
/// });
|
||||
///
|
||||
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, context| {
|
||||
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
|
||||
/// tracked_entities.0.remove(&context.entity);
|
||||
/// });
|
||||
///
|
||||
/// let entity = world.spawn(MyTrackedComponent).id();
|
||||
/// let tracked_entities = world.resource::<TrackedEntities>();
|
||||
/// assert!(tracked_entities.0.contains(&entity));
|
||||
///
|
||||
/// world.despawn(entity);
|
||||
/// let tracked_entities = world.resource::<TrackedEntities>();
|
||||
/// assert!(!tracked_entities.0.contains(&entity));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ComponentHooks {
|
||||
pub(crate) on_add: Option<ComponentHook>,
|
||||
pub(crate) on_insert: Option<ComponentHook>,
|
||||
pub(crate) on_replace: Option<ComponentHook>,
|
||||
pub(crate) on_remove: Option<ComponentHook>,
|
||||
pub(crate) on_despawn: Option<ComponentHook>,
|
||||
}
|
||||
|
||||
impl ComponentHooks {
|
||||
pub(crate) fn update_from_component<C: Component + ?Sized>(&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<RemovedComponentEntity>`.
|
||||
#[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<RemovedComponentEntity>`] so that we
|
||||
/// can differentiate events between components.
|
||||
#[derive(Debug)]
|
||||
pub struct RemovedComponentReader<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
reader: EventCursor<RemovedComponentEntity>,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Component> Default for RemovedComponentReader<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reader: Default::default(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Deref for RemovedComponentReader<T> {
|
||||
type Target = EventCursor<RemovedComponentEntity>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.reader
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> DerefMut for RemovedComponentReader<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.reader
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the [`RemovedComponents`] event buffers for all types of component in a given [`World`].
|
||||
#[derive(Default, Debug)]
|
||||
pub struct RemovedComponentEvents {
|
||||
event_sets: SparseSet<ComponentId, Events<RemovedComponentEntity>>,
|
||||
}
|
||||
|
||||
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<Item = (&ComponentId, &Events<RemovedComponentEntity>)> {
|
||||
self.event_sets.iter()
|
||||
}
|
||||
|
||||
/// Gets the event storage for a given component.
|
||||
pub fn get(
|
||||
&self,
|
||||
component_id: impl Into<ComponentId>,
|
||||
) -> Option<&Events<RemovedComponentEntity>> {
|
||||
self.event_sets.get(component_id.into())
|
||||
}
|
||||
|
||||
/// Sends a removal event for the specified component.
|
||||
pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
|
||||
self.event_sets
|
||||
.get_or_insert_with(component_id.into(), Default::default)
|
||||
.send(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<MyComponent>) {
|
||||
/// 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<T>>,
|
||||
event_sets: &'w RemovedComponentEvents,
|
||||
}
|
||||
|
||||
/// Iterator over entities that had a specific component removed.
|
||||
///
|
||||
/// See [`RemovedComponents`].
|
||||
pub type RemovedIter<'a> = iter::Map<
|
||||
iter::Flatten<option::IntoIter<iter::Cloned<EventIterator<'a, RemovedComponentEntity>>>>,
|
||||
fn(RemovedComponentEntity) -> Entity,
|
||||
>;
|
||||
|
||||
/// Iterator over entities that had a specific component removed.
|
||||
///
|
||||
/// See [`RemovedComponents`].
|
||||
pub type RemovedIterWithId<'a> = iter::Map<
|
||||
iter::Flatten<option::IntoIter<EventIteratorWithId<'a, RemovedComponentEntity>>>,
|
||||
fn(
|
||||
(&RemovedComponentEntity, EventId<RemovedComponentEntity>),
|
||||
) -> (Entity, EventId<RemovedComponentEntity>),
|
||||
>;
|
||||
|
||||
fn map_id_events(
|
||||
(entity, id): (&RemovedComponentEntity, EventId<RemovedComponentEntity>),
|
||||
) -> (Entity, EventId<RemovedComponentEntity>) {
|
||||
(entity.clone().into(), id)
|
||||
}
|
||||
|
||||
// For all practical purposes, the api surface of `RemovedComponents<T>`
|
||||
// should be similar to `EventReader<T>` to reduce confusion.
|
||||
impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> {
|
||||
/// Fetch underlying [`EventCursor`].
|
||||
pub fn reader(&self) -> &EventCursor<RemovedComponentEntity> {
|
||||
&self.reader
|
||||
}
|
||||
|
||||
/// Fetch underlying [`EventCursor`] mutably.
|
||||
pub fn reader_mut(&mut self) -> &mut EventCursor<RemovedComponentEntity> {
|
||||
&mut self.reader
|
||||
}
|
||||
|
||||
/// Fetch underlying [`Events`].
|
||||
pub fn events(&self) -> Option<&Events<RemovedComponentEntity>> {
|
||||
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<T>,
|
||||
&Events<RemovedComponentEntity>,
|
||||
)> {
|
||||
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()
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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<ArchetypeFlags> {
|
||||
use crate::lifecycle::*;
|
||||
|
||||
match event_type {
|
||||
ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
|
||||
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
|
||||
|
@ -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<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -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;
|
||||
|
@ -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<RemovedComponentEntity>`.
|
||||
#[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<RemovedComponentEntity>`] so that we
|
||||
/// can differentiate events between components.
|
||||
#[derive(Debug)]
|
||||
pub struct RemovedComponentReader<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
reader: EventCursor<RemovedComponentEntity>,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Component> Default for RemovedComponentReader<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reader: Default::default(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Deref for RemovedComponentReader<T> {
|
||||
type Target = EventCursor<RemovedComponentEntity>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.reader
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> DerefMut for RemovedComponentReader<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.reader
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the [`RemovedComponents`] event buffers for all types of component in a given [`World`].
|
||||
#[derive(Default, Debug)]
|
||||
pub struct RemovedComponentEvents {
|
||||
event_sets: SparseSet<ComponentId, Events<RemovedComponentEntity>>,
|
||||
}
|
||||
|
||||
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<Item = (&ComponentId, &Events<RemovedComponentEntity>)> {
|
||||
self.event_sets.iter()
|
||||
}
|
||||
|
||||
/// Gets the event storage for a given component.
|
||||
pub fn get(
|
||||
&self,
|
||||
component_id: impl Into<ComponentId>,
|
||||
) -> Option<&Events<RemovedComponentEntity>> {
|
||||
self.event_sets.get(component_id.into())
|
||||
}
|
||||
|
||||
/// Sends a removal event for the specified component.
|
||||
pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
|
||||
self.event_sets
|
||||
.get_or_insert_with(component_id.into(), Default::default)
|
||||
.send(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<T>>`
|
||||
/// and stores the data somewhere safe to later cross-reference.
|
||||
///
|
||||
/// If you are using `bevy_ecs` as a standalone crate,
|
||||
/// note that the `RemovedComponents` list will not be automatically cleared for you,
|
||||
/// and will need to be manually flushed using [`World::clear_trackers`](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<MyComponent>) {
|
||||
/// 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<T>>,
|
||||
event_sets: &'w RemovedComponentEvents,
|
||||
}
|
||||
|
||||
/// Iterator over entities that had a specific component removed.
|
||||
///
|
||||
/// See [`RemovedComponents`].
|
||||
pub type RemovedIter<'a> = iter::Map<
|
||||
iter::Flatten<option::IntoIter<iter::Cloned<EventIterator<'a, RemovedComponentEntity>>>>,
|
||||
fn(RemovedComponentEntity) -> Entity,
|
||||
>;
|
||||
|
||||
/// Iterator over entities that had a specific component removed.
|
||||
///
|
||||
/// See [`RemovedComponents`].
|
||||
pub type RemovedIterWithId<'a> = iter::Map<
|
||||
iter::Flatten<option::IntoIter<EventIteratorWithId<'a, RemovedComponentEntity>>>,
|
||||
fn(
|
||||
(&RemovedComponentEntity, EventId<RemovedComponentEntity>),
|
||||
) -> (Entity, EventId<RemovedComponentEntity>),
|
||||
>;
|
||||
|
||||
fn map_id_events(
|
||||
(entity, id): (&RemovedComponentEntity, EventId<RemovedComponentEntity>),
|
||||
) -> (Entity, EventId<RemovedComponentEntity>) {
|
||||
(entity.clone().into(), id)
|
||||
}
|
||||
|
||||
// For all practical purposes, the api surface of `RemovedComponents<T>`
|
||||
// should be similar to `EventReader<T>` to reduce confusion.
|
||||
impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> {
|
||||
/// Fetch underlying [`EventCursor`].
|
||||
pub fn reader(&self) -> &EventCursor<RemovedComponentEntity> {
|
||||
&self.reader
|
||||
}
|
||||
|
||||
/// Fetch underlying [`EventCursor`] mutably.
|
||||
pub fn reader_mut(&mut self) -> &mut EventCursor<RemovedComponentEntity> {
|
||||
&mut self.reader
|
||||
}
|
||||
|
||||
/// Fetch underlying [`Events`].
|
||||
pub fn events(&self) -> Option<&Events<RemovedComponentEntity>> {
|
||||
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<T>,
|
||||
&Events<RemovedComponentEntity>,
|
||||
)> {
|
||||
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()
|
||||
}
|
||||
}
|
@ -467,9 +467,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},
|
||||
};
|
||||
|
@ -97,7 +97,7 @@
|
||||
//! - [`EventWriter`](crate::event::EventWriter)
|
||||
//! - [`NonSend`] and `Option<NonSend>`
|
||||
//! - [`NonSendMut`] and `Option<NonSendMut>`
|
||||
//! - [`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;
|
||||
|
@ -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;
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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},
|
||||
|
@ -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},
|
||||
|
@ -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},
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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::{
|
||||
|
@ -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::*;
|
||||
|
@ -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},
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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`.
|
Loading…
Reference in New Issue
Block a user