Component Lifecycle Hook & Observer Trigger for replaced values (#14212)
# Objective Fixes #14202 ## Solution Add `on_replaced` component hook and `OnReplaced` observer trigger ## Testing - Did you test these changes? If so, how? - Updated & added unit tests --- ## Changelog - Added new `on_replaced` component hook and `OnReplaced` observer trigger for performing cleanup on component values when they are overwritten with `.insert()`
This commit is contained in:
parent
e79f91fc45
commit
0f7c548a4a
@ -58,6 +58,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||||||
|
|
||||||
let on_add = hook_register_function_call(quote! {on_add}, attrs.on_add);
|
let on_add = hook_register_function_call(quote! {on_add}, attrs.on_add);
|
||||||
let on_insert = hook_register_function_call(quote! {on_insert}, attrs.on_insert);
|
let on_insert = hook_register_function_call(quote! {on_insert}, attrs.on_insert);
|
||||||
|
let on_replace = hook_register_function_call(quote! {on_replace}, attrs.on_replace);
|
||||||
let on_remove = hook_register_function_call(quote! {on_remove}, attrs.on_remove);
|
let on_remove = hook_register_function_call(quote! {on_remove}, attrs.on_remove);
|
||||||
|
|
||||||
ast.generics
|
ast.generics
|
||||||
@ -76,6 +77,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||||||
fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) {
|
fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) {
|
||||||
#on_add
|
#on_add
|
||||||
#on_insert
|
#on_insert
|
||||||
|
#on_replace
|
||||||
#on_remove
|
#on_remove
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,12 +88,14 @@ pub const COMPONENT: &str = "component";
|
|||||||
pub const STORAGE: &str = "storage";
|
pub const STORAGE: &str = "storage";
|
||||||
pub const ON_ADD: &str = "on_add";
|
pub const ON_ADD: &str = "on_add";
|
||||||
pub const ON_INSERT: &str = "on_insert";
|
pub const ON_INSERT: &str = "on_insert";
|
||||||
|
pub const ON_REPLACE: &str = "on_replace";
|
||||||
pub const ON_REMOVE: &str = "on_remove";
|
pub const ON_REMOVE: &str = "on_remove";
|
||||||
|
|
||||||
struct Attrs {
|
struct Attrs {
|
||||||
storage: StorageTy,
|
storage: StorageTy,
|
||||||
on_add: Option<ExprPath>,
|
on_add: Option<ExprPath>,
|
||||||
on_insert: Option<ExprPath>,
|
on_insert: Option<ExprPath>,
|
||||||
|
on_replace: Option<ExprPath>,
|
||||||
on_remove: Option<ExprPath>,
|
on_remove: Option<ExprPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +114,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
|||||||
storage: StorageTy::Table,
|
storage: StorageTy::Table,
|
||||||
on_add: None,
|
on_add: None,
|
||||||
on_insert: None,
|
on_insert: None,
|
||||||
|
on_replace: None,
|
||||||
on_remove: None,
|
on_remove: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,6 +137,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
|||||||
} else if nested.path.is_ident(ON_INSERT) {
|
} else if nested.path.is_ident(ON_INSERT) {
|
||||||
attrs.on_insert = Some(nested.value()?.parse::<ExprPath>()?);
|
attrs.on_insert = Some(nested.value()?.parse::<ExprPath>()?);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else if nested.path.is_ident(ON_REPLACE) {
|
||||||
|
attrs.on_replace = Some(nested.value()?.parse::<ExprPath>()?);
|
||||||
|
Ok(())
|
||||||
} else if nested.path.is_ident(ON_REMOVE) {
|
} else if nested.path.is_ident(ON_REMOVE) {
|
||||||
attrs.on_remove = Some(nested.value()?.parse::<ExprPath>()?);
|
attrs.on_remove = Some(nested.value()?.parse::<ExprPath>()?);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -121,6 +121,7 @@ pub(crate) struct AddBundle {
|
|||||||
/// indicate if the component is newly added to the target archetype or if it already existed
|
/// indicate if the component is newly added to the target archetype or if it already existed
|
||||||
pub bundle_status: Vec<ComponentStatus>,
|
pub bundle_status: Vec<ComponentStatus>,
|
||||||
pub added: Vec<ComponentId>,
|
pub added: Vec<ComponentId>,
|
||||||
|
pub mutated: Vec<ComponentId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components
|
/// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components
|
||||||
@ -205,6 +206,7 @@ impl Edges {
|
|||||||
archetype_id: ArchetypeId,
|
archetype_id: ArchetypeId,
|
||||||
bundle_status: Vec<ComponentStatus>,
|
bundle_status: Vec<ComponentStatus>,
|
||||||
added: Vec<ComponentId>,
|
added: Vec<ComponentId>,
|
||||||
|
mutated: Vec<ComponentId>,
|
||||||
) {
|
) {
|
||||||
self.add_bundle.insert(
|
self.add_bundle.insert(
|
||||||
bundle_id,
|
bundle_id,
|
||||||
@ -212,6 +214,7 @@ impl Edges {
|
|||||||
archetype_id,
|
archetype_id,
|
||||||
bundle_status,
|
bundle_status,
|
||||||
added,
|
added,
|
||||||
|
mutated,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -317,10 +320,12 @@ bitflags::bitflags! {
|
|||||||
pub(crate) struct ArchetypeFlags: u32 {
|
pub(crate) struct ArchetypeFlags: u32 {
|
||||||
const ON_ADD_HOOK = (1 << 0);
|
const ON_ADD_HOOK = (1 << 0);
|
||||||
const ON_INSERT_HOOK = (1 << 1);
|
const ON_INSERT_HOOK = (1 << 1);
|
||||||
const ON_REMOVE_HOOK = (1 << 2);
|
const ON_REPLACE_HOOK = (1 << 2);
|
||||||
const ON_ADD_OBSERVER = (1 << 3);
|
const ON_REMOVE_HOOK = (1 << 3);
|
||||||
const ON_INSERT_OBSERVER = (1 << 4);
|
const ON_ADD_OBSERVER = (1 << 4);
|
||||||
const ON_REMOVE_OBSERVER = (1 << 5);
|
const ON_INSERT_OBSERVER = (1 << 5);
|
||||||
|
const ON_REPLACE_OBSERVER = (1 << 6);
|
||||||
|
const ON_REMOVE_OBSERVER = (1 << 7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,6 +605,12 @@ impl Archetype {
|
|||||||
self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK)
|
self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if any of the components in this archetype have `on_replace` hooks
|
||||||
|
#[inline]
|
||||||
|
pub fn has_replace_hook(&self) -> bool {
|
||||||
|
self.flags().contains(ArchetypeFlags::ON_REPLACE_HOOK)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if any of the components in this archetype have `on_remove` hooks
|
/// Returns true if any of the components in this archetype have `on_remove` hooks
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn has_remove_hook(&self) -> bool {
|
pub fn has_remove_hook(&self) -> bool {
|
||||||
@ -622,6 +633,14 @@ impl Archetype {
|
|||||||
self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER)
|
self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if any of the components in this archetype have at least one [`OnReplace`] observer
|
||||||
|
///
|
||||||
|
/// [`OnReplace`]: crate::world::OnReplace
|
||||||
|
#[inline]
|
||||||
|
pub fn has_replace_observer(&self) -> bool {
|
||||||
|
self.flags().contains(ArchetypeFlags::ON_REPLACE_OBSERVER)
|
||||||
|
}
|
||||||
|
|
||||||
/// 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::world::OnRemove
|
||||||
|
@ -17,7 +17,7 @@ use crate::{
|
|||||||
prelude::World,
|
prelude::World,
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, ON_ADD, ON_INSERT},
|
world::{unsafe_world_cell::UnsafeWorldCell, ON_ADD, ON_INSERT, ON_REPLACE},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy_ptr::{ConstNonNull, OwningPtr};
|
use bevy_ptr::{ConstNonNull, OwningPtr};
|
||||||
@ -456,11 +456,13 @@ impl BundleInfo {
|
|||||||
let mut new_sparse_set_components = Vec::new();
|
let mut new_sparse_set_components = Vec::new();
|
||||||
let mut bundle_status = Vec::with_capacity(self.component_ids.len());
|
let mut bundle_status = Vec::with_capacity(self.component_ids.len());
|
||||||
let mut added = Vec::new();
|
let mut added = Vec::new();
|
||||||
|
let mut mutated = Vec::new();
|
||||||
|
|
||||||
let current_archetype = &mut archetypes[archetype_id];
|
let current_archetype = &mut archetypes[archetype_id];
|
||||||
for component_id in self.component_ids.iter().cloned() {
|
for component_id in self.component_ids.iter().cloned() {
|
||||||
if current_archetype.contains(component_id) {
|
if current_archetype.contains(component_id) {
|
||||||
bundle_status.push(ComponentStatus::Mutated);
|
bundle_status.push(ComponentStatus::Mutated);
|
||||||
|
mutated.push(component_id);
|
||||||
} else {
|
} else {
|
||||||
bundle_status.push(ComponentStatus::Added);
|
bundle_status.push(ComponentStatus::Added);
|
||||||
added.push(component_id);
|
added.push(component_id);
|
||||||
@ -476,7 +478,7 @@ impl BundleInfo {
|
|||||||
if new_table_components.is_empty() && new_sparse_set_components.is_empty() {
|
if new_table_components.is_empty() && new_sparse_set_components.is_empty() {
|
||||||
let edges = current_archetype.edges_mut();
|
let edges = current_archetype.edges_mut();
|
||||||
// the archetype does not change when we add this bundle
|
// the archetype does not change when we add this bundle
|
||||||
edges.insert_add_bundle(self.id, archetype_id, bundle_status, added);
|
edges.insert_add_bundle(self.id, archetype_id, bundle_status, added, mutated);
|
||||||
archetype_id
|
archetype_id
|
||||||
} else {
|
} else {
|
||||||
let table_id;
|
let table_id;
|
||||||
@ -526,6 +528,7 @@ impl BundleInfo {
|
|||||||
new_archetype_id,
|
new_archetype_id,
|
||||||
bundle_status,
|
bundle_status,
|
||||||
added,
|
added,
|
||||||
|
mutated,
|
||||||
);
|
);
|
||||||
new_archetype_id
|
new_archetype_id
|
||||||
}
|
}
|
||||||
@ -665,6 +668,30 @@ impl<'w> BundleInserter<'w> {
|
|||||||
let bundle_info = self.bundle_info.as_ref();
|
let bundle_info = self.bundle_info.as_ref();
|
||||||
let add_bundle = self.add_bundle.as_ref();
|
let add_bundle = self.add_bundle.as_ref();
|
||||||
let table = self.table.as_mut();
|
let table = self.table.as_mut();
|
||||||
|
let archetype = self.archetype.as_ref();
|
||||||
|
|
||||||
|
// SAFETY: All components in the bundle are guaranteed to exist in the World
|
||||||
|
// as they must be initialized before creating the BundleInfo.
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: Mutable references do not alias and will be dropped after this block
|
||||||
|
let mut deferred_world = self.world.into_deferred();
|
||||||
|
|
||||||
|
deferred_world.trigger_on_replace(
|
||||||
|
archetype,
|
||||||
|
entity,
|
||||||
|
add_bundle.mutated.iter().copied(),
|
||||||
|
);
|
||||||
|
if archetype.has_replace_observer() {
|
||||||
|
deferred_world.trigger_observers(
|
||||||
|
ON_REPLACE,
|
||||||
|
entity,
|
||||||
|
add_bundle.mutated.iter().copied(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Archetype gets borrowed when running the on_replace observers above,
|
||||||
|
// so this reference can only be promoted from shared to &mut down here, after they have been ran
|
||||||
let archetype = self.archetype.as_mut();
|
let archetype = self.archetype.as_mut();
|
||||||
|
|
||||||
let (new_archetype, new_location) = match &mut self.result {
|
let (new_archetype, new_location) = match &mut self.result {
|
||||||
@ -1132,7 +1159,7 @@ mod tests {
|
|||||||
struct A;
|
struct A;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
#[component(on_add = a_on_add, on_insert = a_on_insert, on_remove = a_on_remove)]
|
#[component(on_add = a_on_add, on_insert = a_on_insert, on_replace = a_on_replace, on_remove = a_on_remove)]
|
||||||
struct AMacroHooks;
|
struct AMacroHooks;
|
||||||
|
|
||||||
fn a_on_add(mut world: DeferredWorld, _: Entity, _: ComponentId) {
|
fn a_on_add(mut world: DeferredWorld, _: Entity, _: ComponentId) {
|
||||||
@ -1143,10 +1170,14 @@ mod tests {
|
|||||||
world.resource_mut::<R>().assert_order(1);
|
world.resource_mut::<R>().assert_order(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn a_on_remove<T1, T2>(mut world: DeferredWorld, _: T1, _: T2) {
|
fn a_on_replace<T1, T2>(mut world: DeferredWorld, _: T1, _: T2) {
|
||||||
world.resource_mut::<R>().assert_order(2);
|
world.resource_mut::<R>().assert_order(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn a_on_remove<T1, T2>(mut world: DeferredWorld, _: T1, _: T2) {
|
||||||
|
world.resource_mut::<R>().assert_order(3);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct B;
|
struct B;
|
||||||
|
|
||||||
@ -1173,15 +1204,14 @@ mod tests {
|
|||||||
world.init_resource::<R>();
|
world.init_resource::<R>();
|
||||||
world
|
world
|
||||||
.register_component_hooks::<A>()
|
.register_component_hooks::<A>()
|
||||||
.on_add(|mut world, _, _| {
|
.on_add(|mut world, _, _| world.resource_mut::<R>().assert_order(0))
|
||||||
world.resource_mut::<R>().assert_order(0);
|
|
||||||
})
|
|
||||||
.on_insert(|mut world, _, _| world.resource_mut::<R>().assert_order(1))
|
.on_insert(|mut world, _, _| world.resource_mut::<R>().assert_order(1))
|
||||||
.on_remove(|mut world, _, _| world.resource_mut::<R>().assert_order(2));
|
.on_replace(|mut world, _, _| world.resource_mut::<R>().assert_order(2))
|
||||||
|
.on_remove(|mut world, _, _| world.resource_mut::<R>().assert_order(3));
|
||||||
|
|
||||||
let entity = world.spawn(A).id();
|
let entity = world.spawn(A).id();
|
||||||
world.despawn(entity);
|
world.despawn(entity);
|
||||||
assert_eq!(3, world.resource::<R>().0);
|
assert_eq!(4, world.resource::<R>().0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1192,7 +1222,7 @@ mod tests {
|
|||||||
let entity = world.spawn(AMacroHooks).id();
|
let entity = world.spawn(AMacroHooks).id();
|
||||||
world.despawn(entity);
|
world.despawn(entity);
|
||||||
|
|
||||||
assert_eq!(3, world.resource::<R>().0);
|
assert_eq!(4, world.resource::<R>().0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1201,21 +1231,36 @@ mod tests {
|
|||||||
world.init_resource::<R>();
|
world.init_resource::<R>();
|
||||||
world
|
world
|
||||||
.register_component_hooks::<A>()
|
.register_component_hooks::<A>()
|
||||||
.on_add(|mut world, _, _| {
|
.on_add(|mut world, _, _| world.resource_mut::<R>().assert_order(0))
|
||||||
world.resource_mut::<R>().assert_order(0);
|
.on_insert(|mut world, _, _| world.resource_mut::<R>().assert_order(1))
|
||||||
})
|
.on_replace(|mut world, _, _| world.resource_mut::<R>().assert_order(2))
|
||||||
.on_insert(|mut world, _, _| {
|
.on_remove(|mut world, _, _| world.resource_mut::<R>().assert_order(3));
|
||||||
world.resource_mut::<R>().assert_order(1);
|
|
||||||
})
|
|
||||||
.on_remove(|mut world, _, _| {
|
|
||||||
world.resource_mut::<R>().assert_order(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut entity = world.spawn_empty();
|
let mut entity = world.spawn_empty();
|
||||||
entity.insert(A);
|
entity.insert(A);
|
||||||
entity.remove::<A>();
|
entity.remove::<A>();
|
||||||
entity.flush();
|
entity.flush();
|
||||||
assert_eq!(3, world.resource::<R>().0);
|
assert_eq!(4, world.resource::<R>().0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn component_hook_order_replace() {
|
||||||
|
let mut world = World::new();
|
||||||
|
world
|
||||||
|
.register_component_hooks::<A>()
|
||||||
|
.on_replace(|mut world, _, _| world.resource_mut::<R>().assert_order(0))
|
||||||
|
.on_insert(|mut world, _, _| {
|
||||||
|
if let Some(mut r) = world.get_resource_mut::<R>() {
|
||||||
|
r.assert_order(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let entity = world.spawn(A).id();
|
||||||
|
world.init_resource::<R>();
|
||||||
|
let mut entity = world.entity_mut(entity);
|
||||||
|
entity.insert(A);
|
||||||
|
entity.flush();
|
||||||
|
assert_eq!(2, world.resource::<R>().0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -100,6 +100,7 @@ use std::{
|
|||||||
/// Alternatively to the example shown in [`ComponentHooks`]' documentation, hooks can be configured using following attributes:
|
/// Alternatively to the example shown in [`ComponentHooks`]' documentation, hooks can be configured using following attributes:
|
||||||
/// - `#[component(on_add = on_add_function)]`
|
/// - `#[component(on_add = on_add_function)]`
|
||||||
/// - `#[component(on_insert = on_insert_function)]`
|
/// - `#[component(on_insert = on_insert_function)]`
|
||||||
|
/// - `#[component(on_replace = on_replace_function)]`
|
||||||
/// - `#[component(on_remove = on_remove_function)]`
|
/// - `#[component(on_remove = on_remove_function)]`
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@ -114,8 +115,8 @@ use std::{
|
|||||||
/// // Another possible way of configuring hooks:
|
/// // Another possible way of configuring hooks:
|
||||||
/// // #[component(on_add = my_on_add_hook, on_insert = my_on_insert_hook)]
|
/// // #[component(on_add = my_on_add_hook, on_insert = my_on_insert_hook)]
|
||||||
/// //
|
/// //
|
||||||
/// // We don't have a remove hook, so we can leave it out:
|
/// // We don't have a replace or remove hook, so we can leave them out:
|
||||||
/// // #[component(on_remove = my_on_remove_hook)]
|
/// // #[component(on_replace = my_on_replace_hook, on_remove = my_on_remove_hook)]
|
||||||
/// struct ComponentA;
|
/// struct ComponentA;
|
||||||
///
|
///
|
||||||
/// fn my_on_add_hook(world: DeferredWorld, entity: Entity, id: ComponentId) {
|
/// fn my_on_add_hook(world: DeferredWorld, entity: Entity, id: ComponentId) {
|
||||||
@ -280,6 +281,7 @@ pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId);
|
|||||||
pub struct ComponentHooks {
|
pub struct ComponentHooks {
|
||||||
pub(crate) on_add: Option<ComponentHook>,
|
pub(crate) on_add: Option<ComponentHook>,
|
||||||
pub(crate) on_insert: Option<ComponentHook>,
|
pub(crate) on_insert: Option<ComponentHook>,
|
||||||
|
pub(crate) on_replace: Option<ComponentHook>,
|
||||||
pub(crate) on_remove: Option<ComponentHook>,
|
pub(crate) on_remove: Option<ComponentHook>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,6 +316,28 @@ impl ComponentHooks {
|
|||||||
.expect("Component id: {:?}, already has an on_insert hook")
|
.expect("Component id: {:?}, 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 id: {:?}, already has an on_replace hook")
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
|
/// 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.
|
/// Despawning an entity counts as removing all of its components.
|
||||||
///
|
///
|
||||||
@ -351,6 +375,19 @@ impl ComponentHooks {
|
|||||||
Some(self)
|
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.
|
/// 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`].
|
/// This is a fallible version of [`Self::on_remove`].
|
||||||
@ -442,6 +479,9 @@ impl ComponentInfo {
|
|||||||
if self.hooks().on_insert.is_some() {
|
if self.hooks().on_insert.is_some() {
|
||||||
flags.insert(ArchetypeFlags::ON_INSERT_HOOK);
|
flags.insert(ArchetypeFlags::ON_INSERT_HOOK);
|
||||||
}
|
}
|
||||||
|
if self.hooks().on_replace.is_some() {
|
||||||
|
flags.insert(ArchetypeFlags::ON_REPLACE_HOOK);
|
||||||
|
}
|
||||||
if self.hooks().on_remove.is_some() {
|
if self.hooks().on_remove.is_some() {
|
||||||
flags.insert(ArchetypeFlags::ON_REMOVE_HOOK);
|
flags.insert(ArchetypeFlags::ON_REMOVE_HOOK);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,8 @@ pub mod prelude {
|
|||||||
SystemParamFunction,
|
SystemParamFunction,
|
||||||
},
|
},
|
||||||
world::{
|
world::{
|
||||||
EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove, World,
|
EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove, OnReplace,
|
||||||
|
World,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,7 @@ pub struct Observers {
|
|||||||
// Cached ECS observers to save a lookup most common triggers.
|
// Cached ECS observers to save a lookup most common triggers.
|
||||||
on_add: CachedObservers,
|
on_add: CachedObservers,
|
||||||
on_insert: CachedObservers,
|
on_insert: CachedObservers,
|
||||||
|
on_replace: CachedObservers,
|
||||||
on_remove: CachedObservers,
|
on_remove: CachedObservers,
|
||||||
// Map from trigger type to set of observers
|
// Map from trigger type to set of observers
|
||||||
cache: HashMap<ComponentId, CachedObservers>,
|
cache: HashMap<ComponentId, CachedObservers>,
|
||||||
@ -179,6 +180,7 @@ impl Observers {
|
|||||||
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,
|
||||||
|
ON_REPLACE => &mut self.on_replace,
|
||||||
ON_REMOVE => &mut self.on_remove,
|
ON_REMOVE => &mut self.on_remove,
|
||||||
_ => self.cache.entry(event_type).or_default(),
|
_ => self.cache.entry(event_type).or_default(),
|
||||||
}
|
}
|
||||||
@ -188,6 +190,7 @@ impl Observers {
|
|||||||
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),
|
||||||
|
ON_REPLACE => Some(&self.on_replace),
|
||||||
ON_REMOVE => Some(&self.on_remove),
|
ON_REMOVE => Some(&self.on_remove),
|
||||||
_ => self.cache.get(&event_type),
|
_ => self.cache.get(&event_type),
|
||||||
}
|
}
|
||||||
@ -258,6 +261,7 @@ impl Observers {
|
|||||||
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),
|
||||||
|
ON_REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER),
|
||||||
ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER),
|
ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
@ -271,6 +275,7 @@ impl Observers {
|
|||||||
if self.on_add.component_observers.contains_key(&component_id) {
|
if self.on_add.component_observers.contains_key(&component_id) {
|
||||||
flags.insert(ArchetypeFlags::ON_ADD_OBSERVER);
|
flags.insert(ArchetypeFlags::ON_ADD_OBSERVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.on_insert
|
.on_insert
|
||||||
.component_observers
|
.component_observers
|
||||||
@ -278,6 +283,15 @@ impl Observers {
|
|||||||
{
|
{
|
||||||
flags.insert(ArchetypeFlags::ON_INSERT_OBSERVER);
|
flags.insert(ArchetypeFlags::ON_INSERT_OBSERVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self
|
||||||
|
.on_replace
|
||||||
|
.component_observers
|
||||||
|
.contains_key(&component_id)
|
||||||
|
{
|
||||||
|
flags.insert(ArchetypeFlags::ON_REPLACE_OBSERVER);
|
||||||
|
}
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.on_remove
|
.on_remove
|
||||||
.component_observers
|
.component_observers
|
||||||
@ -418,7 +432,9 @@ mod tests {
|
|||||||
use bevy_ptr::OwningPtr;
|
use bevy_ptr::OwningPtr;
|
||||||
|
|
||||||
use crate as bevy_ecs;
|
use crate as bevy_ecs;
|
||||||
use crate::observer::{EmitDynamicTrigger, Observer, ObserverDescriptor, ObserverState};
|
use crate::observer::{
|
||||||
|
EmitDynamicTrigger, Observer, ObserverDescriptor, ObserverState, OnReplace,
|
||||||
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::traversal::Traversal;
|
use crate::traversal::Traversal;
|
||||||
|
|
||||||
@ -474,11 +490,12 @@ mod tests {
|
|||||||
|
|
||||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
|
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
|
||||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
||||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(2));
|
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(2));
|
||||||
|
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(3));
|
||||||
|
|
||||||
let entity = world.spawn(A).id();
|
let entity = world.spawn(A).id();
|
||||||
world.despawn(entity);
|
world.despawn(entity);
|
||||||
assert_eq!(3, world.resource::<R>().0);
|
assert_eq!(4, world.resource::<R>().0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -488,13 +505,14 @@ mod tests {
|
|||||||
|
|
||||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
|
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
|
||||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
||||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(2));
|
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(2));
|
||||||
|
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(3));
|
||||||
|
|
||||||
let mut entity = world.spawn_empty();
|
let mut entity = world.spawn_empty();
|
||||||
entity.insert(A);
|
entity.insert(A);
|
||||||
entity.remove::<A>();
|
entity.remove::<A>();
|
||||||
entity.flush();
|
entity.flush();
|
||||||
assert_eq!(3, world.resource::<R>().0);
|
assert_eq!(4, world.resource::<R>().0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -504,13 +522,34 @@ mod tests {
|
|||||||
|
|
||||||
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<R>| res.assert_order(0));
|
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<R>| res.assert_order(0));
|
||||||
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<R>| res.assert_order(1));
|
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<R>| res.assert_order(1));
|
||||||
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<R>| res.assert_order(2));
|
world.observe(|_: Trigger<OnReplace, S>, mut res: ResMut<R>| res.assert_order(2));
|
||||||
|
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<R>| res.assert_order(3));
|
||||||
|
|
||||||
let mut entity = world.spawn_empty();
|
let mut entity = world.spawn_empty();
|
||||||
entity.insert(S);
|
entity.insert(S);
|
||||||
entity.remove::<S>();
|
entity.remove::<S>();
|
||||||
entity.flush();
|
entity.flush();
|
||||||
assert_eq!(3, world.resource::<R>().0);
|
assert_eq!(4, world.resource::<R>().0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn observer_order_replace() {
|
||||||
|
let mut world = World::new();
|
||||||
|
world.init_resource::<R>();
|
||||||
|
|
||||||
|
let entity = world.spawn(A).id();
|
||||||
|
|
||||||
|
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(0));
|
||||||
|
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
||||||
|
|
||||||
|
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||||
|
// and therefore does not automatically flush.
|
||||||
|
world.flush();
|
||||||
|
|
||||||
|
let mut entity = world.entity_mut(entity);
|
||||||
|
entity.insert(A);
|
||||||
|
entity.flush();
|
||||||
|
assert_eq!(2, world.resource::<R>().0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -7,8 +7,10 @@ use crate::{self as bevy_ecs};
|
|||||||
pub const ON_ADD: ComponentId = ComponentId::new(0);
|
pub const ON_ADD: ComponentId = ComponentId::new(0);
|
||||||
/// [`ComponentId`] for [`OnInsert`]
|
/// [`ComponentId`] for [`OnInsert`]
|
||||||
pub const ON_INSERT: ComponentId = ComponentId::new(1);
|
pub const ON_INSERT: ComponentId = ComponentId::new(1);
|
||||||
|
/// [`ComponentId`] for [`OnReplace`]
|
||||||
|
pub const ON_REPLACE: ComponentId = ComponentId::new(2);
|
||||||
/// [`ComponentId`] for [`OnRemove`]
|
/// [`ComponentId`] for [`OnRemove`]
|
||||||
pub const ON_REMOVE: ComponentId = ComponentId::new(2);
|
pub const ON_REMOVE: ComponentId = ComponentId::new(3);
|
||||||
|
|
||||||
/// Trigger emitted when a component is added to an entity.
|
/// Trigger emitted when a component is added to an entity.
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
@ -18,6 +20,10 @@ pub struct OnAdd;
|
|||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
pub struct OnInsert;
|
pub struct OnInsert;
|
||||||
|
|
||||||
|
/// Trigger emitted when a component is replaced on an entity.
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct OnReplace;
|
||||||
|
|
||||||
/// Trigger emitted when a component is removed from an entity.
|
/// Trigger emitted when a component is removed from an entity.
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
pub struct OnRemove;
|
pub struct OnRemove;
|
||||||
|
@ -317,6 +317,28 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Triggers all `on_replace` hooks for [`ComponentId`] in target.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Caller must ensure [`ComponentId`] in target exist in self.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) unsafe fn trigger_on_replace(
|
||||||
|
&mut self,
|
||||||
|
archetype: &Archetype,
|
||||||
|
entity: Entity,
|
||||||
|
targets: impl Iterator<Item = ComponentId>,
|
||||||
|
) {
|
||||||
|
if archetype.has_replace_hook() {
|
||||||
|
for component_id in targets {
|
||||||
|
// SAFETY: Caller ensures that these components exist
|
||||||
|
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
|
||||||
|
if let Some(hook) = hooks.on_replace {
|
||||||
|
hook(DeferredWorld { world: self.world }, entity, component_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Triggers all `on_remove` hooks for [`ComponentId`] in target.
|
/// Triggers all `on_remove` hooks for [`ComponentId`] in target.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
@ -330,9 +352,8 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
) {
|
) {
|
||||||
if archetype.has_remove_hook() {
|
if archetype.has_remove_hook() {
|
||||||
for component_id in targets {
|
for component_id in targets {
|
||||||
let hooks =
|
|
||||||
// SAFETY: Caller ensures that these components exist
|
// SAFETY: Caller ensures that these components exist
|
||||||
unsafe { self.world.components().get_info_unchecked(component_id) }.hooks();
|
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
|
||||||
if let Some(hook) = hooks.on_remove {
|
if let Some(hook) = hooks.on_remove {
|
||||||
hook(DeferredWorld { world: self.world }, entity, component_id);
|
hook(DeferredWorld { world: self.world }, entity, component_id);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ use bevy_ptr::{OwningPtr, Ptr};
|
|||||||
use std::{any::TypeId, marker::PhantomData};
|
use std::{any::TypeId, marker::PhantomData};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE};
|
use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE, ON_REPLACE};
|
||||||
|
|
||||||
/// A read-only reference to a particular [`Entity`] and all of its components.
|
/// A read-only reference to a particular [`Entity`] and all of its components.
|
||||||
///
|
///
|
||||||
@ -904,7 +904,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
|
|
||||||
// SAFETY: all bundle components exist in World
|
// SAFETY: all bundle components exist in World
|
||||||
unsafe {
|
unsafe {
|
||||||
trigger_on_remove_hooks_and_observers(
|
trigger_on_replace_and_on_remove_hooks_and_observers(
|
||||||
&mut deferred_world,
|
&mut deferred_world,
|
||||||
old_archetype,
|
old_archetype,
|
||||||
entity,
|
entity,
|
||||||
@ -1085,7 +1085,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
|
|
||||||
// SAFETY: all bundle components exist in World
|
// SAFETY: all bundle components exist in World
|
||||||
unsafe {
|
unsafe {
|
||||||
trigger_on_remove_hooks_and_observers(
|
trigger_on_replace_and_on_remove_hooks_and_observers(
|
||||||
&mut deferred_world,
|
&mut deferred_world,
|
||||||
old_archetype,
|
old_archetype,
|
||||||
entity,
|
entity,
|
||||||
@ -1222,6 +1222,10 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
|
|
||||||
// SAFETY: All components in the archetype exist in world
|
// SAFETY: All components in the archetype exist in world
|
||||||
unsafe {
|
unsafe {
|
||||||
|
deferred_world.trigger_on_replace(archetype, self.entity, archetype.components());
|
||||||
|
if archetype.has_replace_observer() {
|
||||||
|
deferred_world.trigger_observers(ON_REPLACE, self.entity, archetype.components());
|
||||||
|
}
|
||||||
deferred_world.trigger_on_remove(archetype, self.entity, archetype.components());
|
deferred_world.trigger_on_remove(archetype, self.entity, archetype.components());
|
||||||
if archetype.has_remove_observer() {
|
if archetype.has_remove_observer() {
|
||||||
deferred_world.trigger_observers(ON_REMOVE, self.entity, archetype.components());
|
deferred_world.trigger_observers(ON_REMOVE, self.entity, archetype.components());
|
||||||
@ -1423,12 +1427,16 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// SAFETY: all components in the archetype must exist in world
|
/// SAFETY: all components in the archetype must exist in world
|
||||||
unsafe fn trigger_on_remove_hooks_and_observers(
|
unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers(
|
||||||
deferred_world: &mut DeferredWorld,
|
deferred_world: &mut DeferredWorld,
|
||||||
archetype: &Archetype,
|
archetype: &Archetype,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
bundle_info: &BundleInfo,
|
bundle_info: &BundleInfo,
|
||||||
) {
|
) {
|
||||||
|
deferred_world.trigger_on_replace(archetype, entity, bundle_info.iter_components());
|
||||||
|
if archetype.has_replace_observer() {
|
||||||
|
deferred_world.trigger_observers(ON_REPLACE, entity, bundle_info.iter_components());
|
||||||
|
}
|
||||||
deferred_world.trigger_on_remove(archetype, entity, bundle_info.iter_components());
|
deferred_world.trigger_on_remove(archetype, entity, bundle_info.iter_components());
|
||||||
if archetype.has_remove_observer() {
|
if archetype.has_remove_observer() {
|
||||||
deferred_world.trigger_observers(ON_REMOVE, entity, bundle_info.iter_components());
|
deferred_world.trigger_observers(ON_REMOVE, entity, bundle_info.iter_components());
|
||||||
|
@ -164,6 +164,7 @@ impl World {
|
|||||||
fn bootstrap(&mut self) {
|
fn bootstrap(&mut self) {
|
||||||
assert_eq!(ON_ADD, self.init_component::<OnAdd>());
|
assert_eq!(ON_ADD, self.init_component::<OnAdd>());
|
||||||
assert_eq!(ON_INSERT, self.init_component::<OnInsert>());
|
assert_eq!(ON_INSERT, self.init_component::<OnInsert>());
|
||||||
|
assert_eq!(ON_REPLACE, self.init_component::<OnReplace>());
|
||||||
assert_eq!(ON_REMOVE, self.init_component::<OnRemove>());
|
assert_eq!(ON_REMOVE, self.init_component::<OnRemove>());
|
||||||
}
|
}
|
||||||
/// Creates a new empty [`World`].
|
/// Creates a new empty [`World`].
|
||||||
|
@ -22,7 +22,7 @@ use std::collections::HashMap;
|
|||||||
/// using [`Component`] derive macro:
|
/// using [`Component`] derive macro:
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// #[derive(Component)]
|
/// #[derive(Component)]
|
||||||
/// #[component(on_add = ..., on_insert = ..., on_remove = ...)]
|
/// #[component(on_add = ..., on_insert = ..., on_replace = ..., on_remove = ...)]
|
||||||
/// ```
|
/// ```
|
||||||
struct MyComponent(KeyCode);
|
struct MyComponent(KeyCode);
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ fn setup(world: &mut World) {
|
|||||||
// This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast
|
// This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast
|
||||||
world
|
world
|
||||||
.register_component_hooks::<MyComponent>()
|
.register_component_hooks::<MyComponent>()
|
||||||
// There are 3 component lifecycle hooks: `on_add`, `on_insert` and `on_remove`
|
// There are 4 component lifecycle hooks: `on_add`, `on_insert`, `on_replace` and `on_remove`
|
||||||
// A hook has 3 arguments:
|
// A hook has 3 arguments:
|
||||||
// - a `DeferredWorld`, this allows access to resource and component data as well as `Commands`
|
// - a `DeferredWorld`, this allows access to resource and component data as well as `Commands`
|
||||||
// - the entity that triggered the hook
|
// - the entity that triggered the hook
|
||||||
@ -85,6 +85,13 @@ fn setup(world: &mut World) {
|
|||||||
.on_insert(|world, _, _| {
|
.on_insert(|world, _, _| {
|
||||||
println!("Current Index: {:?}", world.resource::<MyComponentIndex>());
|
println!("Current Index: {:?}", world.resource::<MyComponentIndex>());
|
||||||
})
|
})
|
||||||
|
// `on_replace` will trigger when a component is inserted onto an entity that already had it,
|
||||||
|
// and runs before the value is replaced.
|
||||||
|
// Also triggers when a component is removed from an entity, and runs before `on_remove`
|
||||||
|
.on_replace(|mut world, entity, _| {
|
||||||
|
let value = world.get::<MyComponent>(entity).unwrap().0;
|
||||||
|
world.resource_mut::<MyComponentIndex>().remove(&value);
|
||||||
|
})
|
||||||
// `on_remove` will trigger when a component is removed from an entity,
|
// `on_remove` will trigger when a component is removed from an entity,
|
||||||
// since it runs before the component is removed you can still access the component data
|
// since it runs before the component is removed you can still access the component data
|
||||||
.on_remove(|mut world, entity, component_id| {
|
.on_remove(|mut world, entity, component_id| {
|
||||||
@ -93,7 +100,6 @@ fn setup(world: &mut World) {
|
|||||||
"Component: {:?} removed from: {:?} with value {:?}",
|
"Component: {:?} removed from: {:?} with value {:?}",
|
||||||
component_id, entity, value
|
component_id, entity, value
|
||||||
);
|
);
|
||||||
world.resource_mut::<MyComponentIndex>().remove(&value);
|
|
||||||
// You can also issue commands through `.commands()`
|
// You can also issue commands through `.commands()`
|
||||||
world.commands().entity(entity).despawn();
|
world.commands().entity(entity).despawn();
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user