In-memory trigger dispatcher for SoHL.

Accessible at runtime as sohl.events.

Documents subscribe to triggers — named lifecycle moments shared with Foundry's CONFIG.ActiveEffect.expiryEvents vocabulary (updateWorldTime, combatStart, combatEnd, roundStart, roundEnd, turnStart, turnEnd, plus any custom names registered via registerSohlTrigger). When a trigger fires, matching subscriptions dispatch by executing the action named by the subscription's actionName on the owning document's logic — the trigger context (with the subscription's payload) becomes the action's scope. An event therefore reuses the very same action a user could invoke manually.

Time scheduling is one trigger among many: a scheduleAt(uuid, actionName, fireAt, payload) call creates a one-shot subscription on updateWorldTime that fires when world time reaches fireAt.

Each subscription is uniquely identified by (uuid, actionName). Re-subscribing with the same identity overwrites the previous entry. Documents re-register their subscriptions on every preparation cycle, so only the most recent registration matters.

The queue is a pure projection of document state: every client — GM or player — populates its own copy from its own document preparation, so subscribe / unsubscribe / scheduleAt run on all clients. A player's queue is therefore a permission-scoped subset of the active GM's (it holds only the subscriptions for documents that client can see), which is exactly enough to answer sheet-side date queries (nextFireTime / timeUntil) locally.

Only fire is gated to the active GM. Nothing else evolves schedule state, so no GM-only side effect can drift the clients out of sync — schedule changes flow through document updates (which replicate) and their re-preparation.

A fire(ctx) dispatches each matching subscription once, from a snapshot taken at the start of the call. For updateWorldTime, "matching" additionally requires the subscription be due (fireAt undefined or <= worldTime), and due subscriptions dispatch in ascending fireAt order. Subscriptions a handler adds mid-dispatch are not re-fired within the same call — they wait for the next fire.

The queue deliberately does not catch up recurring events over a large time jump. That is the consuming document's responsibility: its handler resolves every elapsed interval in (lastAnchor, worldTime] in one pass and persists the advanced anchor, and its finalize() re-registers the single next occurrence beyond worldTime. See the Event Queue reference doc for the contract.

Re-entrant fire(...) for the same trigger name is capped at MAX_TRIGGER_DEPTH (16); beyond that the call aborts with an error. This backstops non-time loops (e.g. a combatStart handler that fires combatStart). Because there is no cascade, no same-tick guard is needed: a handler that re-arms during dispatch simply schedules a subscription that the next fire sees.

Predicates that throw are caught and logged; that dispatch is skipped; the subscription is not removed. Predicates may legitimately fail on stale data (e.g., resolving an effect that was just deleted) and one bad tick should not permanently disarm a check.

Constructors

Accessors

Methods

  • Fire a trigger. Dispatches each matching subscription once, from a snapshot taken at the start of the call. Active-GM only.

    For updateWorldTime, only due subscriptions (fireAt undefined or <= worldTime) match, and they dispatch in ascending fireAt order. For all other triggers, every matching subscription dispatches once in insertion order. Subscriptions added by a handler mid-dispatch are not re-fired within this call.

    Parameters

    Returns Promise<void>

  • Whether a (uuid, actionName) subscription is currently registered.

    Parameters

    • uuid: string

      The subscribed document's UUID.

    • actionName: string

      The subscription actionName.

    Returns boolean

    True when a subscription exists for the pair.

  • The scheduled world time a (uuid, actionName) subscription will fire at, or undefined when there is no such subscription (or it carries no fireAt). Answers on any client for documents that client can see.

    Parameters

    • uuid: string

      The subscribed document's UUID.

    • actionName: string

      The subscription actionName.

    Returns undefined | number

    The absolute world-time fireAt, or undefined.

  • Resolve uuid and offer actionName on its logic — post an owner-gated [Perform] reminder card rather than performing the action. The single consent primitive (issue #579): the queue reminds; the human performs. The card's button is addressed to uuid (an item, actor, or any document with a logic.speaker), so the owner's click runs the same action on that document's logic. Errors are logged and swallowed so one failure cannot abort a batch.

    Reused by dispatchOne (time/combat subscriptions) and by the scene-region bridge (issue #593), which offers a region-authored action to the entering token's actor.

    Parameters

    • uuid: string

      The document whose logic runs the action on [Perform].

    • actionName: string

      The action shortcode to offer.

    • ctx: SohlTriggerContext

      The trigger context revived as the action's scope on click. Its payload.visibility === "gm" whispers the reminder to the GM.

    • Optionaloptions: { dedupeAt?: number }

      Offer options.

      • OptionaldedupeAt?: number

        Offers a given (uuid, actionName, dedupeAt) occurrence once (time-scheduled reminders); omit to offer every call.

    Returns Promise<void>

  • Convenience: schedule a one-shot dispatch when world time reaches fireAt. Equivalent to subscribing to updateWorldTime with oneShot: true.

    Recurring schedules are the consumer's responsibility: the handler advances the document's persisted anchor and its finalize() re-registers the next occurrence (see the class overview).

    Parameters

    • uuid: string

      The document to dispatch to when the time is reached.

    • actionName: string

      The subscription actionName passed to the handler.

    • fireAt: number

      The world time at or after which to fire.

    • Optionalpayload: Record<string, unknown>

      Optional payload forwarded to the handler.

    • OptionalsceneUuid: string

      Optional scene the schedule is bound to (issue #590); offered only while that scene is active. Omit for a world-wide schedule.

    Returns void

  • Register or overwrite a subscription. Runs on all clients (the queue is a projection of document state); only fire is GM-gated.

    If a subscription with the same (uuid, actionName) already exists, its fields are replaced.

    Parameters

    Returns void

  • Seconds from the current world time until a (uuid, actionName) subscription fires: positive for the future, negative for the past, 0 for now. undefined when the subscription is absent or has no fireAt.

    Parameters

    • uuid: string

      The subscribed document's UUID.

    • actionName: string

      The subscription actionName.

    Returns undefined | number

    Signed seconds until fire, or undefined.

  • Remove a subscription. Runs on all clients; safe when absent.

    Parameters

    • uuid: string

      The subscribed document's UUID.

    • actionName: string

      The subscription actionName to remove.

    Returns void