Fixes: #18963 Follows up on: #17977 Adopts: #18966 In 0.16, `EventWriter::send` was renamed to `EventWriter::write`, but many methods were missed (sorry about that). This completes that refactor by renaming all `send` methods and internals. | Old | New | |-------------------------------------|--------------------------------------| | `World::send_event` | `World::write_event` | | `World::send_event_default` | `World::write_event_default` | | `World::send_event_batch` | `World::write_event_batch` | | `DeferredWorld::send_event` | `DeferredWorld::write_event` | | `DeferredWorld::send_event_default` | `DeferredWorld::write_event_default` | | `DeferredWorld::send_event_batch` | `DeferredWorld::write_event_batch` | | `Commands::send_event` | `Commmands::write_event` | | `Events::send` | `Events::write` | | `Events::send_default` | `Events::write_default` | | `Events::send_batch` | `Events::write_batch` | | `RemovedComponentEvents::send` | `RemovedComponentEvents::write` | | `command::send_event` | `commmand::write_event` | | `SendBatchIds` | `WriteBatchIds` | --------- Co-authored-by: shwwwa <shwwwa.dev@gmail.com>
		
			
				
	
	
		
			150 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			150 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
//! This example illustrates the different ways you can employ component lifecycle hooks.
 | 
						|
//!
 | 
						|
//! Whenever possible, prefer using Bevy's change detection or Events for reacting to component changes.
 | 
						|
//! Events generally offer better performance and more flexible integration into Bevy's systems.
 | 
						|
//! Hooks are useful to enforce correctness but have limitations (only one hook per component,
 | 
						|
//! less ergonomic than events).
 | 
						|
//!
 | 
						|
//! Here are some cases where components hooks might be necessary:
 | 
						|
//!
 | 
						|
//! - Maintaining indexes: If you need to keep custom data structures (like a spatial index) in
 | 
						|
//!   sync with the addition/removal of components.
 | 
						|
//!
 | 
						|
//! - Enforcing structural rules: When you have systems that depend on specific relationships
 | 
						|
//!   between components (like hierarchies or parent-child links) and need to maintain correctness.
 | 
						|
 | 
						|
use bevy::{
 | 
						|
    ecs::component::{Mutable, StorageType},
 | 
						|
    ecs::lifecycle::{ComponentHook, HookContext},
 | 
						|
    prelude::*,
 | 
						|
};
 | 
						|
use std::collections::HashMap;
 | 
						|
 | 
						|
#[derive(Debug)]
 | 
						|
/// Hooks can also be registered during component initialization by
 | 
						|
/// using [`Component`] derive macro:
 | 
						|
/// ```no_run
 | 
						|
/// #[derive(Component)]
 | 
						|
/// #[component(on_add = ..., on_insert = ..., on_replace = ..., on_remove = ...)]
 | 
						|
/// ```
 | 
						|
struct MyComponent(KeyCode);
 | 
						|
 | 
						|
impl Component for MyComponent {
 | 
						|
    const STORAGE_TYPE: StorageType = StorageType::Table;
 | 
						|
    type Mutability = Mutable;
 | 
						|
 | 
						|
    /// Hooks can also be registered during component initialization by
 | 
						|
    /// implementing the associated method
 | 
						|
    fn on_add() -> Option<ComponentHook> {
 | 
						|
        // We don't have an `on_add` hook so we'll just return None.
 | 
						|
        // Note that this is the default behavior when not implementing a hook.
 | 
						|
        None
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Resource, Default, Debug, Deref, DerefMut)]
 | 
						|
struct MyComponentIndex(HashMap<KeyCode, Entity>);
 | 
						|
 | 
						|
#[derive(Event, BufferedEvent)]
 | 
						|
struct MyEvent;
 | 
						|
 | 
						|
fn main() {
 | 
						|
    App::new()
 | 
						|
        .add_plugins(DefaultPlugins)
 | 
						|
        .add_systems(Startup, setup)
 | 
						|
        .add_systems(Update, trigger_hooks)
 | 
						|
        .init_resource::<MyComponentIndex>()
 | 
						|
        .add_event::<MyEvent>()
 | 
						|
        .run();
 | 
						|
}
 | 
						|
 | 
						|
fn setup(world: &mut World) {
 | 
						|
    // In order to register component hooks the component must:
 | 
						|
    // - not be currently in use by any entities in the world
 | 
						|
    // - not already have a hook of that kind registered
 | 
						|
    // This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast
 | 
						|
    world
 | 
						|
        .register_component_hooks::<MyComponent>()
 | 
						|
        // There are 4 component lifecycle hooks: `on_add`, `on_insert`, `on_replace` and `on_remove`
 | 
						|
        // A hook has 2 arguments:
 | 
						|
        // - a `DeferredWorld`, this allows access to resource and component data as well as `Commands`
 | 
						|
        // - a `HookContext`, this provides access to the following contextual information:
 | 
						|
        //   - the entity that triggered the hook
 | 
						|
        //   - the component id of the triggering component, this is mostly used for dynamic components
 | 
						|
        //   - the location of the code that caused the hook to trigger
 | 
						|
        //
 | 
						|
        // `on_add` will trigger when a component is inserted onto an entity without it
 | 
						|
        .on_add(
 | 
						|
            |mut world,
 | 
						|
             HookContext {
 | 
						|
                 entity,
 | 
						|
                 component_id,
 | 
						|
                 caller,
 | 
						|
                 ..
 | 
						|
             }| {
 | 
						|
                // You can access component data from within the hook
 | 
						|
                let value = world.get::<MyComponent>(entity).unwrap().0;
 | 
						|
                println!(
 | 
						|
                    "{component_id:?} added to {entity} with value {value:?}{}",
 | 
						|
                    caller
 | 
						|
                        .map(|location| format!("due to {location}"))
 | 
						|
                        .unwrap_or_default()
 | 
						|
                );
 | 
						|
                // Or access resources
 | 
						|
                world
 | 
						|
                    .resource_mut::<MyComponentIndex>()
 | 
						|
                    .insert(value, entity);
 | 
						|
                // Or send events
 | 
						|
                world.write_event(MyEvent);
 | 
						|
            },
 | 
						|
        )
 | 
						|
        // `on_insert` will trigger when a component is inserted onto an entity,
 | 
						|
        // regardless of whether or not it already had it and after `on_add` if it ran
 | 
						|
        .on_insert(|world, _| {
 | 
						|
            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, context| {
 | 
						|
            let value = world.get::<MyComponent>(context.entity).unwrap().0;
 | 
						|
            world.resource_mut::<MyComponentIndex>().remove(&value);
 | 
						|
        })
 | 
						|
        // `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
 | 
						|
        .on_remove(
 | 
						|
            |mut world,
 | 
						|
             HookContext {
 | 
						|
                 entity,
 | 
						|
                 component_id,
 | 
						|
                 caller,
 | 
						|
                 ..
 | 
						|
             }| {
 | 
						|
                let value = world.get::<MyComponent>(entity).unwrap().0;
 | 
						|
                println!(
 | 
						|
                    "{component_id:?} removed from {entity} with value {value:?}{}",
 | 
						|
                    caller
 | 
						|
                        .map(|location| format!("due to {location}"))
 | 
						|
                        .unwrap_or_default()
 | 
						|
                );
 | 
						|
                // You can also issue commands through `.commands()`
 | 
						|
                world.commands().entity(entity).despawn();
 | 
						|
            },
 | 
						|
        );
 | 
						|
}
 | 
						|
 | 
						|
fn trigger_hooks(
 | 
						|
    mut commands: Commands,
 | 
						|
    keys: Res<ButtonInput<KeyCode>>,
 | 
						|
    index: Res<MyComponentIndex>,
 | 
						|
) {
 | 
						|
    for (key, entity) in index.iter() {
 | 
						|
        if !keys.pressed(*key) {
 | 
						|
            commands.entity(*entity).remove::<MyComponent>();
 | 
						|
        }
 | 
						|
    }
 | 
						|
    for key in keys.get_just_pressed() {
 | 
						|
        commands.spawn(MyComponent(*key));
 | 
						|
    }
 | 
						|
}
 |