# Events, Messages, Observers & Hooks 0.18 split the old single event system into **Messages** (pull, buffered) and **Events** (push, observed). They are different traits with different derives — using the wrong one won't compile, and porting old code mechanically to the wrong side is an architecture bug. ## Messages — pull-based, double-buffered The renamed successor of `EventWriter`/`EventReader`. Use for decoupled many-to-many data flow where several systems batch-process the same stream. ```rust #[derive(Message)] struct Collision { a: Entity, b: Entity } fn produce(mut writer: MessageWriter) { writer.write(Collision { a, b }); // also: write_batch(iter), write_default() } fn consume(mut reader: MessageReader) { for c in reader.read() { /* each reader has its own cursor */ } } ``` - Double-buffered: a message survives one `update()` cycle and is dropped on the next. A reader that runs before the writer in the same frame sees it next frame. - Multiple readers run concurrently; multiple writers of the same type cannot. - Reader extras: `len()`, `is_empty()`, `clear()`. - Run condition: `on_message::()`. ## Events — push-based, immediate, observed ```rust #[derive(Event)] struct Speak { text: String } // Global observer app.add_observer(|on: On| println!("{}", on.text)); // Entity-scoped observer — fires only for this entity commands.entity(id).observe(|on: On| { /* ... */ }); // Trigger commands.trigger(Speak { text: "hi".into() }); world.trigger_ref(&mut event); // inspect the event after observers mutate it ``` Observer param `On<'w, 't, E, B>` (`B: Bundle` filters by component): - `event()` / `event_mut()` — event data (also `Deref` for direct field access) - `trigger()` / `trigger_mut()` — trigger context - `observer()` — the observer entity Observers run immediately during the trigger, not at a schedule point. ## EntityEvent — entity-targeted ```rust #[derive(EntityEvent)] struct Hit { #[event_target] entity: Entity, damage: f32, } ``` `#[entity_event(propagate)]` makes the event bubble up the `ChildOf` chain — useful for UI-like hit forwarding. **0.19**: `EntityComponentsTrigger` gained archetype fields — destructure with `..`: `let EntityComponentsTrigger { components, .. } = e.trigger();` ## Lifecycle events Built-in events observers can watch per component: | Event | Fires | Order | |-------|-------|-------| | `Add` | component added where absent | first | | `Insert` | any insert (add or replace) | after `Add` | | `Replace` | component value overwritten by a new insert over an existing value | before `Remove` | | `Remove` | component removed | after `Replace` | | `Despawn` | entity despawned (per component) | last | ```rust world.add_observer(|on: On| { /* entity just gained Health */ }); ``` ## Component hooks — synchronous, inline For callbacks that must be atomic with the ECS operation itself (not deferred, not scheduled): ```rust #[derive(Component)] #[component(on_add = init_health_bar)] struct Health(f32); fn init_health_bar(world: DeferredWorld, ctx: HookContext) { /* ... */ } ``` Available: `on_add`, `on_insert`, `on_replace`, `on_remove`, `on_despawn`. ## Choosing the mechanism | Mechanism | Model | Reach for it when | |-----------|-------|-------------------| | Messages | pull, buffered, scheduled | several systems independently process a stream; frame-batched work | | Event + observer | push, immediate | something must react *now* (gameplay reactions, structural responses) | | EntityEvent | push, targeted, can propagate | the reaction belongs to a specific entity | | Component hook | synchronous, inline | the response must not be separable from the ECS operation | | `Changed` / `Added` | per-tick polling | cheap frame-wise checks inside a normal system |