Events and pipelines
gamekit has two separate tools for "something happened" and "something is about to happen":
EventScope | MutablePipeline | |
|---|---|---|
| Use it for | Telling listeners about a completed fact. | Letting rules change, answer or cancel a request. |
| Listeners can | Observe. | Modify the input, produce(result) or cancel(). |
| Keyed by | EventKey<T> | Nothing; one pipeline per kind of request. |
| Errors in a listener | Go 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: trueEventScope
subscribe(key, listener)returns anEventSubscription; calldispose()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 atentity.events, closed when the entity is disposed.
MutablePipeline
add(stage)returns aPipelineSubscriptionyou can dispose.run(input)calls the stages in the order they were added, each with aPipelineContextholdinginput. A stage may change the input, callproduce(result)or callcancel(), which stops later stages.runreturns aPipelineResultwithcancelledandresult.- 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.