API reference

Every public class, method, and event in @codexo/exojs. Generated from source.

C

classSignal

@codexo/exojs / core / stable

Lightweight typed event emitter. Each `Signal` represents one named notification channel (e.g. `onResize`, `onFrame`). Listeners are added with Signal.add or Signal.once, removed with Signal.remove, and notified with Signal.dispatch. `Args` is the tuple of arguments passed to listeners - type-checked end to end so a `new Signal<[number, string]>()` enforces both `dispatch(1, 'x')` and the listener signature `(n: number, s: string) => ...`. Handlers are stored as direct function references (no wrapper objects). `dispatch` tracks re-entrancy with a depth counter instead of a snapshot copy, so no allocation occurs per dispatch and a listener that dispatches this same Signal again nests safely. Handlers added or removed during dispatch take effect on the next call, never the dispatch in progress - both `add` and `remove` mid-dispatch defer their mutation until after the outermost dispatch finishes, and reconcile per handler (the *last* `add`/`remove` requested for a given handler during one outermost dispatch wins, resolved against whether `_handlers` - never mutated while any dispatch is in progress - actually contained that handler when dispatch started) rather than each call consulting the other's un-flushed queue as if it were already-applied state. Signal.destroy is safe to call from inside a listener: it terminates the dispatch that triggered it (and every dispatch nested inside it on the same call stack) immediately, without invoking any further listener at any nesting level.

1
props
8
methods
0
events
Import
import { Signal } from '@codexo/exojs'

Lightweight typed event emitter. Each `Signal` represents one named notification channel (e.g. `onResize`, `onFrame`). Listeners are added with Signal.add or Signal.once, removed with Signal.remove, and notified with Signal.dispatch.

`Args` is the tuple of arguments passed to listeners - type-checked end to end so a `new Signal<[number, string]>()` enforces both `dispatch(1, 'x')` and the listener signature `(n: number, s: string) => ...`.

Handlers are stored as direct function references (no wrapper objects). `dispatch` tracks re-entrancy with a depth counter instead of a snapshot copy, so no allocation occurs per dispatch and a listener that dispatches this same Signal again nests safely. Handlers added or removed during dispatch take effect on the next call, never the dispatch in progress - both `add` and `remove` mid-dispatch defer their mutation until after the outermost dispatch finishes, and reconcile per handler (the *last* `add`/`remove` requested for a given handler during one outermost dispatch wins, resolved against whether `_handlers` - never mutated while any dispatch is in progress - actually contained that handler when dispatch started) rather than each call consulting the other's un-flushed queue as if it were already-applied state. Signal.destroy is safe to call from inside a listener: it terminates the dispatch that triggered it (and every dispatch nested inside it on the same call stack) immediately, without invoking any further listener at any nesting level.

Constructors1
new(): Signal<Args>
Methods8
add(handler: SignalHandler<Args>): this
Register a listener. Idempotent - adding the same handler reference twice is a no-op. Use arrow functions or pre-bound methods to ensure correct this inside the handler. Adding a handler while this Signal is dispatching defers registration until the outermost dispatch finishes - it does not receive the dispatch in progress, only the next one. A remove followed by an add for the same handler within one outermost dispatch nets to "still registered" - the add is not silently dropped just because the remove was queued first. No-op once Signal.destroy has been called.
clear(): this
Remove every listener. No-op once Signal.destroy has been called.
destroy(): void
Permanently empty and disable this Signal. Idempotent - calling it more than once, or on an already-empty Signal, is safe. add/remove/ clear become no-ops afterward. Safe to call from inside a listener while this Signal is dispatching: the dispatch that triggered it (and every dispatch nested inside it on the same call stack) terminates immediately after - no further listener, at any nesting level, is invoked, and any add/remove queued earlier in the now-aborted dispatch(es) is discarded rather than applied once the call stack unwinds.
dispatch(params: Args): this
Notify every registered listener in registration order. Listeners may safely add or remove themselves or others during dispatch - both kinds of mutation are deferred until after the outermost dispatch completes. A listener that calls Signal.destroy aborts this dispatch (and every dispatch nested inside it) immediately: no further listener, at any nesting level, is invoked, and any add/remove queued earlier in the now-aborted dispatch(es) is discarded rather than applied.
dispatchIsolated(onError: (error: unknown) => void, params: Args): this
Notify every registered listener in registration order, isolating each listener's exceptions individually instead of letting the first throw abort the whole dispatch - used for lifecycle signals (Scene.onActivate/onSuspend, SceneDirector.onStateChange/ onChangeScene/onStartScene/onStopScene) where a listener must never be able to abort a state transition that already happened, or silently prevent every listener registered after it from running. A throwing listener is reported to onError (itself guarded - a throwing onError callback never propagates back into this dispatch) and dispatch continues to the remaining listeners. A listener that calls Signal.destroy still aborts this dispatch immediately, the same as in Signal.dispatch - destruction is not a "throw" onError can observe or recover from. _dispatchDepth/pending-add/pending-remove bookkeeping is guaranteed via finally, so a throw here can never corrupt a later dispatch()/add()/remove() call on this Signal the way an unguarded throw inside Signal.dispatch would. dispatch and dispatchIsolated share the same depth counter, so nesting either variant inside the other still defers adds/removals correctly until the outermost call finishes.
has(handler: SignalHandler<Args>): boolean
true when handler is currently registered.
once(handler: SignalHandler<Args>): this
Register a listener that auto-removes itself after the first dispatch. The internal wrapper reference differs from handler, so calling Signal.remove with the original handler reference does NOT remove it - use Signal.clear to undo a once registration. The wrapper latches after its first call, so handler fires at most once even if the wrapper is still present in _handlers for more than one dispatch pass - e.g. a nested dispatch of this same Signal sees the same wrapper the outer dispatch does, since the self-removal below is itself deferred until the outermost dispatch completes.
remove(handler: SignalHandler<Args>): this
Remove a previously registered handler. No-op if absent, and no-op once Signal.destroy has been called. See Signal.add for how a remove and a later add for the same handler reconcile when both are requested during the same outermost dispatch.
Properties1
count: number
Number of currently registered listeners.
Source