diff --git a/actor.ts b/actor.ts index 304d709..b92ef52 100644 --- a/actor.ts +++ b/actor.ts @@ -1,68 +1,48 @@ -import { Value } from 'preserves'; +import { IdentitySet, Value } from 'preserves'; -type Assertion = Value; +export type Assertion = Value>; -type ActorId = number; +export type Handle = any; -type ExitReason = null | { ok: true } | { ok: false, err: Error }; +export type ExitReason = null | { ok: true } | { ok: false, err: Error }; -let nextActorId: ActorId = 0; +export const assert = Symbol('assert'); +export const retract = Symbol('retract'); +export const message = Symbol('message'); +export const sync = Symbol('sync'); +export const synced = Symbol('synced'); -type AssertionHandle = object; +export interface Entity { + [assert](turn: Turn, assertion: Assertion, handle: Handle): void; + [retract](turn: Turn, handle: Handle): void; + [message](turn: Turn, message: Assertion): void; +} -const assert = Symbol('assert'); -const retract = Symbol('retract'); -const message = Symbol('message'); +export interface SyncTarget { + [sync](turn: Turn, source: Ref): void; +} -type RestParameters any> = - T extends (arg: any, ...args: infer P) => any ? P : never; +export interface SyncSource { + [synced](turn: Turn): void; +} -type Event = - | { type: typeof assert, args: RestParameters } - | { type: typeof retract, args: RestParameters } - | { type: typeof message, args: RestParameters } - -type Action = Event; - -class Peer { +export class Ref { readonly actor: Actor; - readonly target: Facet; - - constructor(actor: Actor, target: Facet) { + readonly target: T; + constructor(actor: Actor, target: T) { this.actor = actor; this.target = target; } } -interface Facet { - [assert](turn: Turn, assertion: Assertion, handle: AssertionHandle): void; - [retract](turn: Turn, handle: AssertionHandle): void; - [message](turn: Turn, message: Assertion): void; -} +export type OutboundMap = Map; -class Actor { - readonly id: ActorId = nextActorId++; +export class Actor implements SyncTarget { + readonly outbound: OutboundMap; exitReason: ExitReason = null; - scheduleTurn(target: Facet, turn: Turn) { - queueMicrotask(() => { - if (this.alive) { - try { - const event = turn.event; - (target as any)[event.type](turn, ...event.args); - // ^ This is safe. Try replacing it with the following to see: - // - // switch (event.type) { - // case assert: target[event.type](turn, ...event.args); break; - // case retract: target[event.type](turn, ...event.args); break; - // case message: target[event.type](turn, ...event.args); break; - // } - turn.finish(); - } catch (err) { - this.terminateWith({ ok: false, err }); - } - } - }); + constructor(initialAssertions: OutboundMap = new Map()) { + this.outbound = initialAssertions; } get alive(): boolean { @@ -76,40 +56,137 @@ class Actor { terminateWith(reason: Exclude) { if (this.alive) { this.exitReason = reason; - // TODO cleanup + Turn.for(this, t => this.outbound.forEach(([peer, _a], h) => t._retract(peer, h))); } } -} -class Turn { - readonly recipient: Actor; - readonly event: Event; - readonly actions: Map> = new Map(); - - constructor(recipient: Actor, event: Event) { - this.recipient = recipient; - this.event = event; + ref(t: T): Ref { + return new Ref(this, t); } - enqueueAction(peer: Peer, action: Action) { - let targetMap = this.actions.get(peer.actor); - if (targetMap === void 0) { - targetMap = new Map(); - this.actions.set(peer.actor, targetMap); - } - let actions = targetMap.get(peer.target); - if (actions === void 0) { - actions = []; - targetMap.set(peer.target, actions); - } - actions.push(action); - } - - finish() { - this.actions.forEach((targetMap, actor) => { - targetMap.forEach((actions, target) => { - actions.forEach(action => actor.scheduleTurn(target, new Turn(actor, action))); - }); + execute(proc: () => void): void { + queueMicrotask(() => { + if (this.alive) { + try { + proc(); + } catch (err) { + this.terminateWith({ ok: false, err }); + } + } }); } + + [sync](t: Turn, source: Ref): void { + t.synced(source); + } +} + +let nextHandle = 0; + +type LocalAction = (t: Turn) => void; + +export class Turn { + readonly actor: Actor | null; // whose turn it is to act during this Turn + readonly queues: Map = new Map(); + readonly localActions: Array = []; + completed = false; + + static for(actor: Actor | null, f: (t: Turn) => void): void { + const t = new Turn(actor); + f(t); + t.complete(); + } + + private constructor(actor: Actor | null) { + this.actor = actor; + } + + _ensureActor(what: string): Actor { + if (this.actor === null) { + throw new Error(`Cannot ${what} from non-Actor context`); + } + return this.actor; + } + + spawn(bootProc: (t: Turn) => void, initialAssertions?: IdentitySet): void { + if ((initialAssertions !== void 0) && (initialAssertions.size > 0)) { + this._ensureActor("spawn with initialAssertions"); + } + this.localActions.push(() => { + const transferred = this.actor === null + ? void 0 + : extractFromMap(this.actor.outbound, initialAssertions); + const child = new Actor(transferred); + child.execute(() => Turn.for(child, bootProc)); + }); + } + + assert(location: Ref, assertion: Assertion): Handle { + this._ensureActor("assert"); + const h = nextHandle++; + this.enqueue(location.actor, t => { + this.actor!.outbound.set(h, [location, assertion]); + location.target[assert](t, assertion, h); + }); + return h; + } + + retract(h: Handle): void { + this._retract(this._ensureActor("retract").outbound.get(h)![0], h); + } + + _retract(location: Ref, handle: Handle): void { + this.enqueue(location.actor, t => { + this.actor!.outbound.delete(handle); + location.target[retract](t, handle); + }); + } + + sync(location: Ref): Promise { + return new Promise(resolve => { + const k = this._ensureActor("sync").ref({ [synced]: resolve }); + this.enqueue(location.actor, t => location.target[sync](t, k)); + }); + } + + synced(syncable: Ref): void { + this.enqueue(syncable.actor, t => syncable.target[synced](t)); + } + + message(location: Ref, assertion: Assertion): void { + this.enqueue(location.actor, t => location.target[message](t, assertion)); + } + + enqueue(actor: Actor, a: LocalAction): void { + let queue = this.queues.get(actor); + if (queue === void 0) { + queue = []; + this.queues.set(actor, queue); + } + queue.push(a); + } + + complete(): void { + if (this.completed) { + throw new Error("Reuse of completed Turn!"); + } + this.completed = true; + this.queues.forEach((queue, actor) => + actor.execute(() => queue.forEach(f => Turn.for(actor, f)))); + this.localActions.forEach(f => Turn.for(this.actor, f)); + } +} + +function extractFromMap(map: Map, keys?: IdentitySet): Map { + const result: Map = new Map(); + if (keys !== void 0) { + keys.forEach(key => { + const value = map.get(key); + if (value !== void 0) { + map.delete(key); + result.set(key, value); + } + }); + } + return result; } diff --git a/interfaces_test.ts b/interfaces_test.ts index 1ab853e..e10f0e6 100644 --- a/interfaces_test.ts +++ b/interfaces_test.ts @@ -1,4 +1,4 @@ -import { tuple, message, Methods, Messages, perform } from './interfaces.js'; +import { message, Methods, Messages, perform } from './interfaces.js'; const m = message({ selector: 'b', @@ -10,19 +10,24 @@ const m2 = message({ args: [], // callback: (result: void) => console.log('result:', result), }); -type X = Methods; -type Y = Messages; +type X1 = Methods; +type X2 = Methods; +type Y = Messages; perform({ a(ctxt: string, x: number): string { return `${ctxt}(${x + 1})`; }, - b(ctxt: string, y: number): number { return y * 2; }, - z(ctxt: string, ...v: number[]) { return 3; }, + b(_ctxt: string, y: number): number { return y * 2; }, + z(_ctxt: string, ..._v: number[]) { return 3; }, v(ctxt: string, x: number, y: string, z: boolean): string { console.log('in v'); return `ctxt ${ctxt} x ${x} y ${y} z ${z}`; }, w(ctxt: string, m: [string, number]) { console.log('w', ctxt, m); }, w2(ctxt: string, x: number, ...m: [string, number][]) { console.log('w2', ctxt, x, m); }, + q(ctxt: string, n: number, m?: string): string { + console.log('in q'); + return `q ctxt ${ctxt} n ${n} m ${m}`; + }, }, // { @@ -32,9 +37,15 @@ perform({ // }, { - selector: 'v', + selector: 'q', args: [123, 'hi', true], callback: (result: string) => console.log('result:', result) }, +// { +// selector: 'v', +// args: [123, 'hi', true], +// callback: (result: string) => console.log('result:', result) +// }, + 'C'); diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..ec3f6fc --- /dev/null +++ b/main.ts @@ -0,0 +1,7 @@ +import { Actor, Turn } from './actor.js'; + +Turn.for(null, async (t: Turn) => { + const a = new Actor().ref({ + + }); +});