Events

Decoupled message bus facilitating strongly typed, event-driven communication across the React UI, 3D Engine Operators, and headless Systems.
Published: 7/10/2026

Aircada features a unified, highly optimized global event bus. By routing transient triggers, actions, and broadcasts through a typed event registry, Aircada prevents string hallucinations and ensures robust interoperability between 2D React overlays and 3D simulation loops.

Imperative System Usage (Internal Logic)#

Direct access to the event bus via AircadaContext (this.air.events) for custom systems or managers.
typescript
import { AircadaContext } from "@aircada/spec";
import { Events, EventPayloads } from "../registry/registry.generated";

@System({ name: "Custom Logic System", type: "custom_logic_system" })
export class CustomLogicSystem {
    public initialize() {
        // 1. Receiving: Add a listener
        const unsub = this.air.events.addListener<EventPayloads[typeof Events.REQUEST_DOOR_OPEN]>(
            Events.REQUEST_DOOR_OPEN,
            (payload) => {
                // 2. Transmitting: Invoke an event
                this.air.events.invoke<EventPayloads[typeof Events.DOOR_OPENED]>(
                    Events.DOOR_OPENED,
                    { doorId: payload.doorId, newAngle: 90 }
                );
            }
        );
    }
}
warning
Always clean up your listeners during teardown to prevent memory leaks in the engine environment.

3D Operator Usage (@aircada/spec)#

Using @Input and @Output decorators to bind class methods and properties to the global event bus.
typescript
import { Operator, Context, FloatProperty, Input, Output, AirEvent, AircadaContext } from "@aircada/spec";
import { Events, EventPayloads } from "../registry/registry.generated";

@Operator({ name: "Door Logic" })
export class DoorOperator {
    @Context() air!: AircadaContext;
    
    @FloatProperty
    doorAngle!: number;

    // 1. Transmitting: Define an Output event
    @Output({ name: "On Door Opened", emits: Events.DOOR_OPENED })
    onDoorOpened = new AirEvent<EventPayloads[typeof Events.DOOR_OPENED]>();

    // 2. Receiving: Define an Input method triggered by the global bus
    @Input({ name: "Open Door", onEvent: Events.REQUEST_DOOR_OPEN })
    handleOpenRequest(payload: EventPayloads[typeof Events.REQUEST_DOOR_OPEN]) {
        // Perform logic and invoke output to broadcast back to global bus
        this.onDoorOpened.invoke({ doorId: payload.doorId, newAngle: 90 });
    }
}

React UI Usage (@aircada/react)#

Using the useAirEvent hook for both transmitting and receiving events in React components.

Always strongly type the generic payload when using the hook.

typescript
import { useAirEvent } from '@aircada/react';
import { Events, EventPayloads } from './registry/registry.generated';

export function DoorController() {
    // 1. Transmitting: Emitting an event to the global bus
    const emitOpenDoor = useAirEvent<EventPayloads[typeof Events.REQUEST_DOOR_OPEN]>(
        Events.REQUEST_DOOR_OPEN
    );

    // 2. Receiving: Listening to an event from the global bus
    useAirEvent<EventPayloads[typeof Events.DOOR_OPENED]>(
        Events.DOOR_OPENED, 
        (payload) => {
            console.log(payload.doorId);
        }
    );
}

The Source of Truth (registry.generated.ts)#

Defining event shapes in the auto-generated registry is highly recommended for type safety, though any string can technically be used as an event key.

Highly Recommended: Importing from the registry ensures valid bus addresses and provides full TypeScript intellisense for payloads.

typescript
// src/registry/registry.generated.ts
export const Events = {
    REQUEST_DOOR_OPEN: "REQUEST_DOOR_OPEN",
    DOOR_OPENED: "DOOR_OPENED"
} as const;

export interface EventPayloads {
    [Events.REQUEST_DOOR_OPEN]: { doorId: string };
    [Events.DOOR_OPENED]: { doorId: string; newAngle: number };
}