# Bevy Skill Research: Events, Messages & Observers > **Bevy version**: 0.18, with 0.19 notes > **Date**: 2026-07-07 ## The Event/Message Split (0.18 Breaking Change) In 0.18, the old `Event`/`EventWriter`/`EventReader` system was **split into two distinct systems**: | Concept | Trait | Writer | Reader | Model | |---------|-------|--------|--------|-------| | Messages | `#[derive(Message)]` | `MessageWriter` | `MessageReader` | Pull-based, double-buffered, batch | | Events | `#[derive(Event)]` | `commands.trigger()` | Observers (`On`) | Push-based, immediate | ```rust // Messages — for batch processing, decoupled many-to-many #[derive(Message)] struct Collision { entity_a: Entity, entity_b: Entity } // Events — for push-based observer reactions #[derive(Event)] struct Speak { message: String } ``` ## Messages (Pull-Based, Buffered) The renamed successor to the old EventWriter/EventReader pattern. - `MessageWriter::write(msg)` / `write_batch(iter)` / `write_default()` - `MessageReader::read()` returns iterator, advances cursor; `len()`, `is_empty()`, `clear()` - Double-buffered: messages survive one `update()` cycle, cleared on the next - Multiple readers can run concurrently; writers for the same type cannot ```rust fn send(mut writer: MessageWriter) { writer.write(Collision { entity_a: a, entity_b: b }); } fn receive(mut reader: MessageReader) { for collision in reader.read() { // handle } } ``` ## Events + Observers (Push-Based, Immediate) **Observer system parameter**: `On<'w, 't, E, B>` where `B: Bundle` filters by component. - `event()` / `event_mut()` — access event data - `trigger()` / `trigger_mut()` — access trigger context - `observer()` — the observer entity - Implements `Deref` for direct field access ### Registration ```rust // Global observer app.add_observer(|on: On| { println!("{}", on.message); }); // Entity-scoped observer commands.entity(id).observe(|on: On| { // only fires for this specific entity }); ``` ### Triggering ```rust commands.trigger(Speak { message: "Hello!".into() }); world.trigger_ref(&mut event); // inspect after observers mutate ``` ### EntityEvent (Entity-Targeted Events) ```rust #[derive(EntityEvent)] struct Hit { #[event_target] entity: Entity, damage: f32, } ``` Supports `#[entity_event(propagate)]` for automatic hierarchy traversal (event bubbles up through ChildOf chain). ## Lifecycle Events (Component Triggers) Built-in events fired on component lifecycle changes: | Event | Fires when | Order | |-------|-----------|-------| | `Add` | Component added to entity that lacked it | First | | `Insert` | Any insert (add or replace) | After `Add` | | `Replace` | Component removed regardless of replacement | Before `Remove` | | `Remove` | Component removed from entity | After `Replace` | | `Despawn` | Entity despawned (per component) | Last | ```rust world.add_observer(|_: On| { /* entity just got Health */ }); world.add_observer(|_: On| { /* Health was removed */ }); ``` ## Low-Level Component Hooks Synchronous callbacks during the ECS operation itself (not scheduled systems). These run inline — not deferred. ```rust #[derive(Component)] #[component(on_add = my_hook)] #[component(on_insert = my_hook)] struct MyComponent; fn my_hook(world: DeferredWorld, ctx: HookContext) { /* ... */ } ``` Available: `on_add`, `on_insert`, `on_replace`, `on_remove`, `on_despawn`. ## When to Use What | Mechanism | Model | Best for | |-----------|-------|----------| | `MessageWriter`/`MessageReader` | Pull, scheduled, buffered | Batch processing, decoupled many-to-many communication | | `Event` + Observers | Push, immediate | React NOW to something (structural changes, gameplay events) | | Component hooks | Synchronous, inline | Deterministic callback AS PART OF the ECS operation | | `Changed` / `Added` filters | Pull, per-tick polling | Check mutations each frame in a normal system | **Rule of thumb**: Use Messages when multiple systems need to process the same data independently. Use Events + Observers when you need immediate reaction to something happening. Use hooks when the response must be atomic with the ECS operation. ## Migration Notes **0.17 → 0.18**: `EventWriter` → `MessageWriter`, `EventReader` → `MessageReader`, `Events` → `Messages`. `Event` trait is now observer-only. `on_event` run condition → `on_message`. **0.18 → 0.19**: `EntityComponentsTrigger` gains archetype fields — destructuring requires `..`: ```rust let EntityComponentsTrigger { components, .. } = e.trigger(); // 0.19 ```