The EventBus is pub/sub for Systems. It's how Systems coordinate without referencing each other.
Basics
Every System receives the EventBus in its constructor (new MySystem(world.getEventBus())) and stores it on this.eventBus. From inside a System, though, don't call eventBus.on directly — use the System's listen() wrapper, which ties the subscription to the System's lifetime:
Subscriptions made via listen() (and its siblings listenOnce() and listenForEntity()) are unsubscribed automatically when the System is removed from the World — no unsubscribe bookkeeping, no onShutdown drain. Raw on()/once()/off() remain the public API for everything that isn't a System — Components, app glue, UI panels — where you store the returned unsubscribe function and call it when you're done.
Naming convention
Event names are dotted strings: '<emitter>.<event>'. Each component package conventionally exports a constants object so callers don't typo names:
*Events— names the System emits*InputEvents— names the System listens to
Importing the constants gives you autocomplete and typo-protection:
What's in event data
The second argument to emit is arbitrary — JSON-serializable shapes are best. Keep the data small. Don't put renderer handles, function references, or live Entity objects in event payloads. Use IDs and primitives:
Wildcards and pattern matching
The EventBus supports glob-style listeners for debugging and viz tools:
The second argument to the callback is the actual event name that triggered the call — useful when a wildcard catches multiple events.
Don't lean on wildcards for production logic. They make event flow hard to trace. Use them for the EventBus debugger viz panel and similar tooling.
Backpressure and ordering
The EventBus is synchronous. When you call emit, every subscriber's callback fires before emit returns. This means:
- Order is preserved: subscribers called in subscription order
- No queue, no async — events don't pile up between frames
- A subscriber that's slow blocks the emitter
If a subscriber emits another event from inside its callback, that recursive emit also runs synchronously. Be careful with loops — A emits, B listens and emits B, A listens to B and emits again. Use a circuit breaker (if (this.processing) return;) for events that can cycle.
Off and once
Inspecting traffic
In development, the EventBus viz panel (in @babylonjsmarket/arcade/viz) shows live event traffic grouped by emitter. Wire it up during development to spot:
- Events being emitted with no listeners (typo on the listener side)
- Event storms (one event triggering 200 emits per frame)
- Missing emissions (a subscriber that never fires because the emit path is dead)
Where to next
- Renderers — the adapter interface and how to pick one
- SceneLoader — loading entities from JSON