Skip to content

Events and pipelines ​

gamekit has two separate tools for "something happened" and "something is about to happen":

EventScopeMutablePipeline
Use it forTelling listeners about a completed fact.Letting rules change, answer or cancel a request.
Listeners canObserve.Modify the input, produce(result) or cancel().
Keyed byEventKey<T>Nothing; one pipeline per kind of request.
Errors in a listenerGo to the scope's optional error sink; later listeners still run.Propagate to the caller.
ts
import { EventKey, EventScope, EventSubscription, MutablePipeline, PipelineContext, PipelineResult } from "gamekit";

class Hit {
    amount: int;
    constructor(amount: int) { this.amount = amount; }
}

// An event scope delivers completed facts to listeners, keyed by EventKey.
const DAMAGED: EventKey<Hit> = new EventKey<Hit>("damaged");
const scope: EventScope = new EventScope();
const sub: EventSubscription = scope.subscribe(DAMAGED, (hit: Hit): void => {
    console.log("took " + hit.amount);
});
scope.emit(DAMAGED, new Hit(7));
sub.dispose();
scope.emit(DAMAGED, new Hit(9));   // nobody listening now

// A pipeline lets rules change a request before it happens.
const damage: MutablePipeline<Hit, int> = new MutablePipeline<Hit, int>();
damage.add((ctx: PipelineContext<Hit, int>): void => {
    ctx.input.amount = ctx.input.amount - 2;            // armour
});
damage.add((ctx: PipelineContext<Hit, int>): void => {
    if (ctx.input.amount <= 0) { ctx.cancel(); return; }
    ctx.produce(ctx.input.amount);
});
const strong: PipelineResult<int> = damage.run(new Hit(10));
const weak: PipelineResult<int> = damage.run(new Hit(1));
console.log("strong: " + (strong.result ?? 0) + ", weak cancelled: " + weak.cancelled);
text
took 7
strong: 8, weak cancelled: true

EventScope ​

  • subscribe(key, listener) returns an EventSubscription; call dispose() on it to stop listening. Subscribing twice gives two separate subscriptions, run in the order they were made.
  • emit(key, event) is synchronous. It takes a snapshot of listeners first: a listener removed during delivery still gets this event, and a listener added during delivery waits for the next one.
  • Delivery is by key identity. The key's label is only for debugging.
  • There is no automatic bubbling. forward(target, key, event) is the one way to pass an event to another scope.
  • close() ends the scope; closed scopes ignore new subscriptions and emits. Every entity has one at entity.events, closed when the entity is disposed.

MutablePipeline ​

  • add(stage) returns a PipelineSubscription you can dispose.
  • run(input) calls the stages in the order they were added, each with a PipelineContext holding input. A stage may change the input, call produce(result) or call cancel(), which stops later stages.
  • run returns a PipelineResult with cancelled and result.
  • Like events, a run snapshots its stages: changes made during a run apply to the next run.

Entity effects use a pipeline too: entity.effects.pipeline sees every effect before it is applied. See Effects.

Reference: EventScope, EventKey, MutablePipeline, PipelineContext.