Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Events

Resources are good for “the current state of X.” Events are for “something happened” — damage was dealt, a file finished loading, a button was clicked. Events<T> is a double-buffered queue: an event sent during tick N stays visible to every reader for the rest of N and all of N + 1, then is dropped.

Sending and reading

struct Damage(u32);

app.add_event::<Damage>();

fn deal_damage(mut writer: EventWriter<Damage>) {
    writer.send(Damage(5));
}

fn on_damage(mut reader: EventReader<Damage>) {
    for event in reader.iter() {
        println!("took {} damage", event.0);
    }
}

app.add_event::<T>() does two things: inserts the Events<T> resource, and registers the per-tick aging step that gives the two-tick guarantee above. An EventWriter<T>/EventReader<T> used before this call panics with a hint pointing back at add_event — same hard-requirement mechanism as Res<T> from the previous chapter, just checking for Events<T> specifically.

Why two ticks?

Systems run in a fixed order within a tick, so a reader registered before the writer in the stage order would never see a same-tick send if events only lived for the tick they were sent in — it already ran by the time the writer fires. Keeping an event visible through the next tick as well means every reader sees every event exactly once, regardless of where in the pipeline it happens to run relative to the writer. Each EventReader<T> tracks its own read cursor privately (the same way Local<T> persists per-system state), so multiple independent readers of the same event type never interfere with each other.

Option<EventReader<T>> / Option<EventWriter<T>>

Exactly like Option<Res<T>>: use these when a system should just skip its event-related work if T hasn’t been registered yet, instead of hard-panicking:

fn maybe_log_damage(reader: Option<EventReader<Damage>>) {
    let Some(mut reader) = reader else { return };
    for event in reader.iter() {
        // ...
    }
}

This matters most for library-ish code — a plugin that optionally reacts to an event type the host application may or may not have registered, without forcing that application to always register it.

Events whose payload arrives from a background task — a downloaded file, a GPU readback — use a different constructor, add_async_event, covered in the next chapter alongside the rest of Pebble’s async story.