//! This example illustrates the different ways you can employ component lifecycle hooks.1//!2//! Whenever possible, prefer using Bevy's change detection or Events for reacting to component changes.3//! Events generally offer better performance and more flexible integration into Bevy's systems.4//! Hooks are useful to enforce correctness but have limitations (only one hook per component,5//! less ergonomic than events).6//!7//! Here are some cases where components hooks might be necessary:8//!9//! - Maintaining indexes: If you need to keep custom data structures (like a spatial index) in10//! sync with the addition/removal of components.11//!12//! - Enforcing structural rules: When you have systems that depend on specific relationships13//! between components (like hierarchies or parent-child links) and need to maintain correctness.1415use bevy::{16ecs::component::{Mutable, StorageType},17ecs::lifecycle::{ComponentHook, HookContext},18prelude::*,19};20use std::collections::HashMap;2122#[derive(Debug)]23/// Hooks can also be registered during component initialization by24/// using [`Component`] derive macro:25/// ```no_run26/// #[derive(Component)]27/// #[component(on_add = ..., on_insert = ..., on_replace = ..., on_remove = ...)]28/// ```29struct MyComponent(KeyCode);3031impl Component for MyComponent {32const STORAGE_TYPE: StorageType = StorageType::Table;33type Mutability = Mutable;3435/// Hooks can also be registered during component initialization by36/// implementing the associated method37fn on_add() -> Option<ComponentHook> {38// We don't have an `on_add` hook so we'll just return None.39// Note that this is the default behavior when not implementing a hook.40None41}42}4344#[derive(Resource, Default, Debug, Deref, DerefMut)]45struct MyComponentIndex(HashMap<KeyCode, Entity>);4647#[derive(BufferedEvent)]48struct MyEvent;4950fn main() {51App::new()52.add_plugins(DefaultPlugins)53.add_systems(Startup, setup)54.add_systems(Update, trigger_hooks)55.init_resource::<MyComponentIndex>()56.add_event::<MyEvent>()57.run();58}5960fn setup(world: &mut World) {61// In order to register component hooks the component must:62// - not be currently in use by any entities in the world63// - not already have a hook of that kind registered64// This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast65world66.register_component_hooks::<MyComponent>()67// There are 4 component lifecycle hooks: `on_add`, `on_insert`, `on_replace` and `on_remove`68// A hook has 2 arguments:69// - a `DeferredWorld`, this allows access to resource and component data as well as `Commands`70// - a `HookContext`, this provides access to the following contextual information:71// - the entity that triggered the hook72// - the component id of the triggering component, this is mostly used for dynamic components73// - the location of the code that caused the hook to trigger74//75// `on_add` will trigger when a component is inserted onto an entity without it76.on_add(77|mut world,78HookContext {79entity,80component_id,81caller,82..83}| {84// You can access component data from within the hook85let value = world.get::<MyComponent>(entity).unwrap().0;86println!(87"{component_id:?} added to {entity} with value {value:?}{}",88caller89.map(|location| format!("due to {location}"))90.unwrap_or_default()91);92// Or access resources93world94.resource_mut::<MyComponentIndex>()95.insert(value, entity);96// Or send events97world.write_event(MyEvent);98},99)100// `on_insert` will trigger when a component is inserted onto an entity,101// regardless of whether or not it already had it and after `on_add` if it ran102.on_insert(|world, _| {103println!("Current Index: {:?}", world.resource::<MyComponentIndex>());104})105// `on_replace` will trigger when a component is inserted onto an entity that already had it,106// and runs before the value is replaced.107// Also triggers when a component is removed from an entity, and runs before `on_remove`108.on_replace(|mut world, context| {109let value = world.get::<MyComponent>(context.entity).unwrap().0;110world.resource_mut::<MyComponentIndex>().remove(&value);111})112// `on_remove` will trigger when a component is removed from an entity,113// since it runs before the component is removed you can still access the component data114.on_remove(115|mut world,116HookContext {117entity,118component_id,119caller,120..121}| {122let value = world.get::<MyComponent>(entity).unwrap().0;123println!(124"{component_id:?} removed from {entity} with value {value:?}{}",125caller126.map(|location| format!("due to {location}"))127.unwrap_or_default()128);129// You can also issue commands through `.commands()`130world.commands().entity(entity).despawn();131},132);133}134135fn trigger_hooks(136mut commands: Commands,137keys: Res<ButtonInput<KeyCode>>,138index: Res<MyComponentIndex>,139) {140for (key, entity) in index.iter() {141if !keys.pressed(*key) {142commands.entity(*entity).remove::<MyComponent>();143}144}145for key in keys.get_just_pressed() {146commands.spawn(MyComponent(*key));147}148}149150151