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,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{Event, EventWriter, Events},
|
event::{Event, EventWriter, Events},
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
query::With,
|
query::With,
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::{IntoScheduleConfigs, ScheduleLabel},
|
schedule::{IntoScheduleConfigs, ScheduleLabel},
|
||||||
system::{Commands, Query},
|
system::{Commands, Query},
|
||||||
|
@ -6,9 +6,9 @@ use bevy_ecs::{
|
|||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
hierarchy::ChildOf,
|
hierarchy::ChildOf,
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
query::{Changed, Or, QueryFilter, With, Without},
|
query::{Changed, Or, QueryFilter, With, Without},
|
||||||
relationship::{Relationship, RelationshipTarget},
|
relationship::{Relationship, RelationshipTarget},
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
schedule::{IntoScheduleConfigs, SystemSet},
|
schedule::{IntoScheduleConfigs, SystemSet},
|
||||||
system::{Commands, Local, Query},
|
system::{Commands, Local, Query},
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details.
|
//! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details.
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_ecs::{component::*, prelude::*};
|
use bevy_ecs::{component::*, lifecycle::ComponentHook, prelude::*};
|
||||||
use bevy_math::UVec2;
|
use bevy_math::UVec2;
|
||||||
use bevy_platform::collections::HashSet;
|
use bevy_platform::collections::HashSet;
|
||||||
use bevy_platform::time::Instant;
|
use bevy_platform::time::Instant;
|
||||||
|
@ -60,4 +60,4 @@ mod case4 {
|
|||||||
pub struct BarTargetOf(Entity);
|
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::Path(path) => path.to_token_stream(),
|
||||||
HookAttributeKind::Call(call) => {
|
HookAttributeKind::Call(call) => {
|
||||||
quote!({
|
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)
|
(#call)(world, ctx)
|
||||||
}
|
}
|
||||||
_internal_hook
|
_internal_hook
|
||||||
@ -658,7 +658,7 @@ fn hook_register_function_call(
|
|||||||
) -> Option<TokenStream2> {
|
) -> Option<TokenStream2> {
|
||||||
function.map(|meta| {
|
function.map(|meta| {
|
||||||
quote! {
|
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)
|
::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
|
/// 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]
|
#[inline]
|
||||||
pub fn has_add_observer(&self) -> bool {
|
pub fn has_add_observer(&self) -> bool {
|
||||||
self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER)
|
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
|
/// 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]
|
#[inline]
|
||||||
pub fn has_insert_observer(&self) -> bool {
|
pub fn has_insert_observer(&self) -> bool {
|
||||||
self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER)
|
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
|
/// 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]
|
#[inline]
|
||||||
pub fn has_replace_observer(&self) -> bool {
|
pub fn has_replace_observer(&self) -> bool {
|
||||||
self.flags().contains(ArchetypeFlags::ON_REPLACE_OBSERVER)
|
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
|
/// 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]
|
#[inline]
|
||||||
pub fn has_remove_observer(&self) -> bool {
|
pub fn has_remove_observer(&self) -> bool {
|
||||||
self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER)
|
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
|
/// 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]
|
#[inline]
|
||||||
pub fn has_despawn_observer(&self) -> bool {
|
pub fn has_despawn_observer(&self) -> bool {
|
||||||
self.flags().contains(ArchetypeFlags::ON_DESPAWN_OBSERVER)
|
self.flags().contains(ArchetypeFlags::ON_DESPAWN_OBSERVER)
|
||||||
|
@ -66,15 +66,13 @@ use crate::{
|
|||||||
RequiredComponents, StorageType, Tick,
|
RequiredComponents, StorageType, Tick,
|
||||||
},
|
},
|
||||||
entity::{Entities, Entity, EntityLocation},
|
entity::{Entities, Entity, EntityLocation},
|
||||||
|
lifecycle::{ON_ADD, ON_INSERT, ON_REMOVE, ON_REPLACE},
|
||||||
observer::Observers,
|
observer::Observers,
|
||||||
prelude::World,
|
prelude::World,
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
relationship::RelationshipHookMode,
|
relationship::RelationshipHookMode,
|
||||||
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
||||||
world::{
|
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut},
|
||||||
unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REMOVE,
|
|
||||||
ON_REPLACE,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use alloc::{boxed::Box, vec, vec::Vec};
|
use alloc::{boxed::Box, vec, vec::Vec};
|
||||||
use bevy_platform::collections::{HashMap, HashSet};
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
archetype::ArchetypeCreated, component::HookContext, prelude::*, world::DeferredWorld,
|
archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld,
|
||||||
};
|
};
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@ use crate::{
|
|||||||
bundle::BundleInfo,
|
bundle::BundleInfo,
|
||||||
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
|
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
|
||||||
entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent},
|
entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent},
|
||||||
|
lifecycle::{ComponentHook, ComponentHooks},
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
relationship::RelationshipHookMode,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
storage::{SparseSetIndex, SparseSets, Table, TableRow},
|
storage::{SparseSetIndex, SparseSets, Table, TableRow},
|
||||||
system::{Local, SystemParam},
|
system::{Local, SystemParam},
|
||||||
world::{DeferredWorld, FromWorld, World},
|
world::{FromWorld, World},
|
||||||
};
|
};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::{borrow::Cow, format, vec::Vec};
|
use alloc::{borrow::Cow, format, vec::Vec};
|
||||||
@ -376,7 +376,8 @@ use thiserror::Error;
|
|||||||
/// - `#[component(on_remove = on_remove_function)]`
|
/// - `#[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::world::DeferredWorld;
|
||||||
/// # use bevy_ecs::entity::Entity;
|
/// # use bevy_ecs::entity::Entity;
|
||||||
/// # use bevy_ecs::component::ComponentId;
|
/// # use bevy_ecs::component::ComponentId;
|
||||||
@ -405,7 +406,8 @@ use thiserror::Error;
|
|||||||
/// This also supports function calls that yield closures
|
/// 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;
|
/// # use bevy_ecs::world::DeferredWorld;
|
||||||
/// #
|
/// #
|
||||||
/// #[derive(Component)]
|
/// #[derive(Component)]
|
||||||
@ -657,244 +659,6 @@ pub enum StorageType {
|
|||||||
SparseSet,
|
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`].
|
/// Stores metadata for a type of component or resource stored in a specific [`World`].
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ComponentInfo {
|
pub struct ComponentInfo {
|
||||||
|
@ -68,7 +68,7 @@ pub trait Event: Send + Sync + 'static {
|
|||||||
///
|
///
|
||||||
/// # Warning
|
/// # 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).
|
/// and should always correspond to the implementation of [`component_id`](Event::component_id).
|
||||||
fn register_component_id(world: &mut World) -> ComponentId {
|
fn register_component_id(world: &mut World) -> ComponentId {
|
||||||
world.register_component::<EventWrapperComponent<Self>>()
|
world.register_component::<EventWrapperComponent<Self>>()
|
||||||
@ -82,7 +82,7 @@ pub trait Event: Send + Sync + 'static {
|
|||||||
///
|
///
|
||||||
/// # Warning
|
/// # 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).
|
/// and should always correspond to the implementation of [`register_component_id`](Event::register_component_id).
|
||||||
fn component_id(world: &World) -> Option<ComponentId> {
|
fn component_id(world: &World) -> Option<ComponentId> {
|
||||||
world.component_id::<EventWrapperComponent<Self>>()
|
world.component_id::<EventWrapperComponent<Self>>()
|
||||||
|
@ -10,8 +10,9 @@
|
|||||||
use crate::reflect::{ReflectComponent, ReflectFromWorld};
|
use crate::reflect::{ReflectComponent, ReflectFromWorld};
|
||||||
use crate::{
|
use crate::{
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
component::{Component, HookContext},
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
lifecycle::HookContext,
|
||||||
relationship::{RelatedSpawner, RelatedSpawnerCommands},
|
relationship::{RelatedSpawner, RelatedSpawnerCommands},
|
||||||
system::EntityCommands,
|
system::EntityCommands,
|
||||||
world::{DeferredWorld, EntityWorldMut, FromWorld, World},
|
world::{DeferredWorld, EntityWorldMut, FromWorld, World},
|
||||||
|
@ -41,6 +41,7 @@ pub mod event;
|
|||||||
pub mod hierarchy;
|
pub mod hierarchy;
|
||||||
pub mod intern;
|
pub mod intern;
|
||||||
pub mod label;
|
pub mod label;
|
||||||
|
pub mod lifecycle;
|
||||||
pub mod name;
|
pub mod name;
|
||||||
pub mod never;
|
pub mod never;
|
||||||
pub mod observer;
|
pub mod observer;
|
||||||
@ -48,7 +49,6 @@ pub mod query;
|
|||||||
#[cfg(feature = "bevy_reflect")]
|
#[cfg(feature = "bevy_reflect")]
|
||||||
pub mod reflect;
|
pub mod reflect;
|
||||||
pub mod relationship;
|
pub mod relationship;
|
||||||
pub mod removal_detection;
|
|
||||||
pub mod resource;
|
pub mod resource;
|
||||||
pub mod schedule;
|
pub mod schedule;
|
||||||
pub mod spawn;
|
pub mod spawn;
|
||||||
@ -76,12 +76,12 @@ pub mod prelude {
|
|||||||
error::{BevyError, Result},
|
error::{BevyError, Result},
|
||||||
event::{Event, EventMutator, EventReader, EventWriter, Events},
|
event::{Event, EventMutator, EventReader, EventWriter, Events},
|
||||||
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
|
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
|
||||||
|
lifecycle::{OnAdd, OnDespawn, OnInsert, OnRemove, OnReplace, RemovedComponents},
|
||||||
name::{Name, NameOrEntity},
|
name::{Name, NameOrEntity},
|
||||||
observer::{Observer, Trigger},
|
observer::{Observer, Trigger},
|
||||||
query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
|
query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
|
||||||
related,
|
related,
|
||||||
relationship::RelationshipTarget,
|
relationship::RelationshipTarget,
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::{
|
schedule::{
|
||||||
common_conditions::*, ApplyDeferred, IntoScheduleConfigs, IntoSystemSet, Schedule,
|
common_conditions::*, ApplyDeferred, IntoScheduleConfigs, IntoSystemSet, Schedule,
|
||||||
@ -96,7 +96,7 @@ pub mod prelude {
|
|||||||
},
|
},
|
||||||
world::{
|
world::{
|
||||||
EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
|
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::{
|
use crate::{
|
||||||
component::{
|
component::{Component, ComponentCloneBehavior, Mutable, StorageType},
|
||||||
Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType,
|
|
||||||
},
|
|
||||||
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
|
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
|
||||||
|
lifecycle::{ComponentHook, HookContext},
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
@ -391,6 +391,8 @@ pub struct Observers {
|
|||||||
|
|
||||||
impl Observers {
|
impl Observers {
|
||||||
pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers {
|
pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers {
|
||||||
|
use crate::lifecycle::*;
|
||||||
|
|
||||||
match event_type {
|
match event_type {
|
||||||
ON_ADD => &mut self.on_add,
|
ON_ADD => &mut self.on_add,
|
||||||
ON_INSERT => &mut self.on_insert,
|
ON_INSERT => &mut self.on_insert,
|
||||||
@ -402,6 +404,8 @@ impl Observers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
|
pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
|
||||||
|
use crate::lifecycle::*;
|
||||||
|
|
||||||
match event_type {
|
match event_type {
|
||||||
ON_ADD => Some(&self.on_add),
|
ON_ADD => Some(&self.on_add),
|
||||||
ON_INSERT => Some(&self.on_insert),
|
ON_INSERT => Some(&self.on_insert),
|
||||||
@ -479,6 +483,8 @@ impl Observers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
|
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
|
||||||
|
use crate::lifecycle::*;
|
||||||
|
|
||||||
match event_type {
|
match event_type {
|
||||||
ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
|
ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
|
||||||
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
|
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
|
||||||
|
@ -2,8 +2,9 @@ use alloc::{boxed::Box, vec};
|
|||||||
use core::any::Any;
|
use core::any::Any;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType},
|
component::{ComponentId, Mutable, StorageType},
|
||||||
error::{ErrorContext, ErrorHandler},
|
error::{ErrorContext, ErrorHandler},
|
||||||
|
lifecycle::{ComponentHook, HookContext},
|
||||||
observer::{ObserverDescriptor, ObserverTrigger},
|
observer::{ObserverDescriptor, ObserverTrigger},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
query::DebugCheckedUnwrap,
|
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
|
/// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters
|
||||||
/// erased.
|
/// erased.
|
||||||
|
@ -11,9 +11,10 @@ pub use relationship_query::*;
|
|||||||
pub use relationship_source_collection::*;
|
pub use relationship_source_collection::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{Component, HookContext, Mutable},
|
component::{Component, Mutable},
|
||||||
entity::{ComponentCloneCtx, Entity, SourceComponent},
|
entity::{ComponentCloneCtx, Entity, SourceComponent},
|
||||||
error::{ignore, CommandWithEntity, HandleError},
|
error::{ignore, CommandWithEntity, HandleError},
|
||||||
|
lifecycle::HookContext,
|
||||||
world::{DeferredWorld, EntityWorldMut},
|
world::{DeferredWorld, EntityWorldMut},
|
||||||
};
|
};
|
||||||
use log::warn;
|
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::{
|
use crate::{
|
||||||
change_detection::DetectChanges,
|
change_detection::DetectChanges,
|
||||||
event::{Event, EventReader},
|
event::{Event, EventReader},
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
prelude::{Component, Query, With},
|
prelude::{Component, Query, With},
|
||||||
query::QueryFilter,
|
query::QueryFilter,
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
system::{In, IntoSystem, Local, Res, System, SystemInput},
|
system::{In, IntoSystem, Local, Res, System, SystemInput},
|
||||||
};
|
};
|
||||||
|
@ -97,7 +97,7 @@
|
|||||||
//! - [`EventWriter`](crate::event::EventWriter)
|
//! - [`EventWriter`](crate::event::EventWriter)
|
||||||
//! - [`NonSend`] and `Option<NonSend>`
|
//! - [`NonSend`] and `Option<NonSend>`
|
||||||
//! - [`NonSendMut`] and `Option<NonSendMut>`
|
//! - [`NonSendMut`] and `Option<NonSendMut>`
|
||||||
//! - [`RemovedComponents`](crate::removal_detection::RemovedComponents)
|
//! - [`RemovedComponents`](crate::lifecycle::RemovedComponents)
|
||||||
//! - [`SystemName`]
|
//! - [`SystemName`]
|
||||||
//! - [`SystemChangeTick`]
|
//! - [`SystemChangeTick`]
|
||||||
//! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata)
|
//! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata)
|
||||||
@ -408,10 +408,10 @@ mod tests {
|
|||||||
component::{Component, Components},
|
component::{Component, Components},
|
||||||
entity::{Entities, Entity},
|
entity::{Entities, Entity},
|
||||||
error::Result,
|
error::Result,
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
name::Name,
|
name::Name,
|
||||||
prelude::{AnyOf, EntityRef, Trigger},
|
prelude::{AnyOf, EntityRef, OnAdd, Trigger},
|
||||||
query::{Added, Changed, Or, SpawnDetails, Spawned, With, Without},
|
query::{Added, Changed, Or, SpawnDetails, Spawned, With, Without},
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::{
|
schedule::{
|
||||||
common_conditions::resource_exists, ApplyDeferred, IntoScheduleConfigs, Schedule,
|
common_conditions::resource_exists, ApplyDeferred, IntoScheduleConfigs, Schedule,
|
||||||
@ -421,7 +421,7 @@ mod tests {
|
|||||||
Commands, In, InMut, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, Res,
|
Commands, In, InMut, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, Res,
|
||||||
ResMut, Single, StaticSystemParam, System, SystemState,
|
ResMut, Single, StaticSystemParam, System, SystemState,
|
||||||
},
|
},
|
||||||
world::{DeferredWorld, EntityMut, FromWorld, OnAdd, World},
|
world::{DeferredWorld, EntityMut, FromWorld, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::ScheduleSystem;
|
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::{
|
use crate::{
|
||||||
archetype::Archetype,
|
archetype::Archetype,
|
||||||
change_detection::{MaybeLocation, MutUntyped},
|
change_detection::{MaybeLocation, MutUntyped},
|
||||||
component::{ComponentId, HookContext, Mutable},
|
component::{ComponentId, Mutable},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{Event, EventId, Events, SendBatchIds},
|
event::{Event, EventId, Events, SendBatchIds},
|
||||||
|
lifecycle::{HookContext, ON_INSERT, ON_REPLACE},
|
||||||
observer::{Observers, TriggerTargets},
|
observer::{Observers, TriggerTargets},
|
||||||
prelude::{Component, QueryState},
|
prelude::{Component, QueryState},
|
||||||
query::{QueryData, QueryFilter},
|
query::{QueryData, QueryFilter},
|
||||||
@ -16,7 +17,7 @@ use crate::{
|
|||||||
world::{error::EntityMutableFetchError, EntityFetcher, WorldEntityFetch},
|
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.
|
/// A [`World`] reference that disallows structural ECS changes.
|
||||||
/// This includes initializing resources, registering components or spawning entities.
|
/// This includes initializing resources, registering components or spawning entities.
|
||||||
|
@ -14,15 +14,13 @@ use crate::{
|
|||||||
EntityIdLocation, EntityLocation,
|
EntityIdLocation, EntityLocation,
|
||||||
},
|
},
|
||||||
event::Event,
|
event::Event,
|
||||||
|
lifecycle::{ON_DESPAWN, ON_REMOVE, ON_REPLACE},
|
||||||
observer::Observer,
|
observer::Observer,
|
||||||
query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData},
|
query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData},
|
||||||
relationship::RelationshipHookMode,
|
relationship::RelationshipHookMode,
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
system::IntoObserverSystem,
|
system::IntoObserverSystem,
|
||||||
world::{
|
world::{error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World},
|
||||||
error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World,
|
|
||||||
ON_DESPAWN, ON_REMOVE, ON_REPLACE,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bevy_platform::collections::{HashMap, HashSet};
|
use bevy_platform::collections::{HashMap, HashSet};
|
||||||
@ -4756,7 +4754,8 @@ mod tests {
|
|||||||
use core::panic::AssertUnwindSafe;
|
use core::panic::AssertUnwindSafe;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use crate::component::{HookContext, Tick};
|
use crate::component::Tick;
|
||||||
|
use crate::lifecycle::HookContext;
|
||||||
use crate::{
|
use crate::{
|
||||||
change_detection::{MaybeLocation, MutUntyped},
|
change_detection::{MaybeLocation, MutUntyped},
|
||||||
component::ComponentId,
|
component::ComponentId,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
//! Defines the [`World`] and APIs for accessing it directly.
|
//! Defines the [`World`] and APIs for accessing it directly.
|
||||||
|
|
||||||
pub(crate) mod command_queue;
|
pub(crate) mod command_queue;
|
||||||
mod component_constants;
|
|
||||||
mod deferred_world;
|
mod deferred_world;
|
||||||
mod entity_fetch;
|
mod entity_fetch;
|
||||||
mod entity_ref;
|
mod entity_ref;
|
||||||
@ -14,13 +13,16 @@ pub mod unsafe_world_cell;
|
|||||||
#[cfg(feature = "bevy_reflect")]
|
#[cfg(feature = "bevy_reflect")]
|
||||||
pub mod reflect;
|
pub mod reflect;
|
||||||
|
|
||||||
use crate::error::{DefaultErrorHandler, ErrorHandler};
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD},
|
change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD},
|
||||||
world::command_queue::CommandQueue,
|
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 bevy_ecs_macros::FromWorld;
|
||||||
pub use component_constants::*;
|
|
||||||
pub use deferred_world::DeferredWorld;
|
pub use deferred_world::DeferredWorld;
|
||||||
pub use entity_fetch::{EntityFetcher, WorldEntityFetch};
|
pub use entity_fetch::{EntityFetcher, WorldEntityFetch};
|
||||||
pub use entity_ref::{
|
pub use entity_ref::{
|
||||||
@ -39,17 +41,17 @@ use crate::{
|
|||||||
},
|
},
|
||||||
change_detection::{MaybeLocation, MutUntyped, TicksMut},
|
change_detection::{MaybeLocation, MutUntyped, TicksMut},
|
||||||
component::{
|
component::{
|
||||||
CheckChangeTicks, Component, ComponentDescriptor, ComponentHooks, ComponentId,
|
CheckChangeTicks, Component, ComponentDescriptor, ComponentId, ComponentIds, ComponentInfo,
|
||||||
ComponentIds, ComponentInfo, ComponentTicks, Components, ComponentsQueuedRegistrator,
|
ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable,
|
||||||
ComponentsRegistrator, Mutable, RequiredComponents, RequiredComponentsError, Tick,
|
RequiredComponents, RequiredComponentsError, Tick,
|
||||||
},
|
},
|
||||||
entity::{Entities, Entity, EntityDoesNotExistError},
|
entity::{Entities, Entity, EntityDoesNotExistError},
|
||||||
entity_disabling::DefaultQueryFilters,
|
entity_disabling::DefaultQueryFilters,
|
||||||
event::{Event, EventId, Events, SendBatchIds},
|
event::{Event, EventId, Events, SendBatchIds},
|
||||||
|
lifecycle::RemovedComponentEvents,
|
||||||
observer::Observers,
|
observer::Observers,
|
||||||
query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState},
|
query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState},
|
||||||
relationship::RelationshipHookMode,
|
relationship::RelationshipHookMode,
|
||||||
removal_detection::RemovedComponentEvents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::{Schedule, ScheduleLabel, Schedules},
|
schedule::{Schedule, ScheduleLabel, Schedules},
|
||||||
storage::{ResourceData, Storages},
|
storage::{ResourceData, Storages},
|
||||||
@ -1444,7 +1446,7 @@ impl World {
|
|||||||
/// assert!(!transform.is_changed());
|
/// assert!(!transform.is_changed());
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`RemovedComponents`]: crate::removal_detection::RemovedComponents
|
/// [`RemovedComponents`]: crate::lifecycle::RemovedComponents
|
||||||
pub fn clear_trackers(&mut self) {
|
pub fn clear_trackers(&mut self) {
|
||||||
self.removed_components.update();
|
self.removed_components.update();
|
||||||
self.last_change_tick = self.increment_change_tick();
|
self.last_change_tick = self.increment_change_tick();
|
||||||
|
@ -8,10 +8,10 @@ use crate::{
|
|||||||
component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells},
|
component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells},
|
||||||
entity::{ContainsEntity, Entities, Entity, EntityDoesNotExistError, EntityLocation},
|
entity::{ContainsEntity, Entities, Entity, EntityDoesNotExistError, EntityLocation},
|
||||||
error::{DefaultErrorHandler, ErrorHandler},
|
error::{DefaultErrorHandler, ErrorHandler},
|
||||||
|
lifecycle::RemovedComponentEvents,
|
||||||
observer::Observers,
|
observer::Observers,
|
||||||
prelude::Component,
|
prelude::Component,
|
||||||
query::{DebugCheckedUnwrap, ReadOnlyQueryData},
|
query::{DebugCheckedUnwrap, ReadOnlyQueryData},
|
||||||
removal_detection::RemovedComponentEvents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
storage::{ComponentSparseSet, Storages, Table},
|
storage::{ComponentSparseSet, Storages, Table},
|
||||||
world::RawCommandQueue,
|
world::RawCommandQueue,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Contains the [`AutoFocus`] component and related machinery.
|
//! 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;
|
use crate::InputFocus;
|
||||||
|
|
||||||
|
@ -369,7 +369,7 @@ mod tests {
|
|||||||
|
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::HookContext, observer::Trigger, system::RunSystemOnce, world::DeferredWorld,
|
lifecycle::HookContext, observer::Trigger, system::RunSystemOnce, world::DeferredWorld,
|
||||||
};
|
};
|
||||||
use bevy_input::{
|
use bevy_input::{
|
||||||
keyboard::{Key, KeyCode},
|
keyboard::{Key, KeyCode},
|
||||||
|
@ -37,9 +37,9 @@ use bevy_derive::{Deref, DerefMut};
|
|||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
query::{Changed, Or},
|
query::{Changed, Or},
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::IntoScheduleConfigs,
|
schedule::IntoScheduleConfigs,
|
||||||
system::{Query, Res, ResMut},
|
system::{Query, Res, ResMut},
|
||||||
|
@ -8,9 +8,9 @@ use bevy_ecs::{
|
|||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::EventCursor,
|
event::EventCursor,
|
||||||
hierarchy::ChildOf,
|
hierarchy::ChildOf,
|
||||||
|
lifecycle::RemovedComponentEntity,
|
||||||
query::QueryBuilder,
|
query::QueryBuilder,
|
||||||
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
|
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
|
||||||
removal_detection::RemovedComponentEntity,
|
|
||||||
system::{In, Local},
|
system::{In, Local},
|
||||||
world::{EntityRef, EntityWorldMut, FilteredEntityRef, World},
|
world::{EntityRef, EntityWorldMut, FilteredEntityRef, World},
|
||||||
};
|
};
|
||||||
|
@ -22,9 +22,10 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
|||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::DetectChanges,
|
change_detection::DetectChanges,
|
||||||
component::{Component, HookContext},
|
component::Component,
|
||||||
entity::{ContainsEntity, Entity},
|
entity::{ContainsEntity, Entity},
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
|
lifecycle::HookContext,
|
||||||
prelude::With,
|
prelude::With,
|
||||||
query::Has,
|
query::Has,
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use bevy_app::Plugin;
|
use bevy_app::Plugin;
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::entity::EntityHash;
|
use bevy_ecs::entity::EntityHash;
|
||||||
|
use bevy_ecs::lifecycle::{OnAdd, OnRemove};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::{ContainsEntity, Entity, EntityEquivalent},
|
entity::{ContainsEntity, Entity, EntityEquivalent},
|
||||||
@ -9,7 +10,7 @@ use bevy_ecs::{
|
|||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
system::{Local, Query, ResMut, SystemState},
|
system::{Local, Query, ResMut, SystemState},
|
||||||
world::{Mut, OnAdd, OnRemove, World},
|
world::{Mut, World},
|
||||||
};
|
};
|
||||||
use bevy_platform::collections::{HashMap, HashSet};
|
use bevy_platform::collections::{HashMap, HashSet};
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
@ -490,10 +491,11 @@ mod tests {
|
|||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
lifecycle::{OnAdd, OnRemove},
|
||||||
observer::Trigger,
|
observer::Trigger,
|
||||||
query::With,
|
query::With,
|
||||||
system::{Query, ResMut},
|
system::{Query, ResMut},
|
||||||
world::{OnAdd, OnRemove, World},
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -3,8 +3,8 @@ mod render_layers;
|
|||||||
|
|
||||||
use core::any::TypeId;
|
use core::any::TypeId;
|
||||||
|
|
||||||
use bevy_ecs::component::HookContext;
|
|
||||||
use bevy_ecs::entity::EntityHashSet;
|
use bevy_ecs::entity::EntityHashSet;
|
||||||
|
use bevy_ecs::lifecycle::HookContext;
|
||||||
use bevy_ecs::world::DeferredWorld;
|
use bevy_ecs::world::DeferredWorld;
|
||||||
use derive_more::derive::{Deref, DerefMut};
|
use derive_more::derive::{Deref, DerefMut};
|
||||||
pub use range::*;
|
pub use range::*;
|
||||||
|
@ -10,9 +10,9 @@ use bevy_app::{App, Plugin, PostUpdate};
|
|||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::{Entity, EntityHashMap},
|
entity::{Entity, EntityHashMap},
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
query::{Changed, With},
|
query::{Changed, With},
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::IntoScheduleConfigs as _,
|
schedule::IntoScheduleConfigs as _,
|
||||||
system::{Local, Query, Res, ResMut},
|
system::{Local, Query, Res, ResMut},
|
||||||
|
@ -8,8 +8,8 @@ use bevy_ecs::{
|
|||||||
change_detection::{DetectChanges, DetectChangesMut},
|
change_detection::{DetectChanges, DetectChangesMut},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
hierarchy::{ChildOf, Children},
|
hierarchy::{ChildOf, Children},
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
query::With,
|
query::With,
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
system::{Commands, Query, ResMut},
|
system::{Commands, Query, ResMut},
|
||||||
world::Ref,
|
world::Ref,
|
||||||
};
|
};
|
||||||
|
@ -22,11 +22,12 @@ use bevy_ecs::{
|
|||||||
change_detection::DetectChanges,
|
change_detection::DetectChanges,
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
lifecycle::OnRemove,
|
||||||
observer::Trigger,
|
observer::Trigger,
|
||||||
query::With,
|
query::With,
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
system::{Commands, Local, Query},
|
system::{Commands, Local, Query},
|
||||||
world::{OnRemove, Ref},
|
world::Ref,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "custom_cursor")]
|
#[cfg(feature = "custom_cursor")]
|
||||||
use bevy_image::{Image, TextureAtlasLayout};
|
use bevy_image::{Image, TextureAtlasLayout};
|
||||||
|
@ -3,9 +3,9 @@ use std::collections::HashMap;
|
|||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::EventWriter,
|
event::EventWriter,
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
prelude::{Changed, Component},
|
prelude::{Changed, Component},
|
||||||
query::QueryFilter,
|
query::QueryFilter,
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
system::{Local, NonSendMarker, Query, SystemParamItem},
|
system::{Local, NonSendMarker, Query, SystemParamItem},
|
||||||
};
|
};
|
||||||
use bevy_input::keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput};
|
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.
|
//! between components (like hierarchies or parent-child links) and need to maintain correctness.
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
ecs::component::{ComponentHook, HookContext, Mutable, StorageType},
|
ecs::component::{Mutable, StorageType},
|
||||||
|
ecs::lifecycle::{ComponentHook, HookContext},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
ecs::{
|
ecs::{
|
||||||
component::{
|
component::{ComponentCloneBehavior, ComponentDescriptor, ComponentId, StorageType},
|
||||||
ComponentCloneBehavior, ComponentDescriptor, ComponentId, HookContext, StorageType,
|
lifecycle::HookContext,
|
||||||
},
|
|
||||||
world::DeferredWorld,
|
world::DeferredWorld,
|
||||||
},
|
},
|
||||||
platform::collections::HashMap,
|
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