More
This commit is contained in:
parent
84569b8c90
commit
c746340e15
231
actor.ts
231
actor.ts
|
@ -1,68 +1,48 @@
|
|||
import { Value } from 'preserves';
|
||||
import { IdentitySet, Value } from 'preserves';
|
||||
|
||||
type Assertion = Value<Peer>;
|
||||
export type Assertion = Value<Ref<Entity>>;
|
||||
|
||||
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<SyncSource>): void;
|
||||
}
|
||||
|
||||
type RestParameters<T extends (arg: any, ...args: any) => 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<Facet[typeof assert]> }
|
||||
| { type: typeof retract, args: RestParameters<Facet[typeof retract]> }
|
||||
| { type: typeof message, args: RestParameters<Facet[typeof message]> }
|
||||
|
||||
type Action = Event;
|
||||
|
||||
class Peer {
|
||||
export class Ref<T = Entity> {
|
||||
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<Handle, [Ref, Assertion]>;
|
||||
|
||||
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<ExitReason, null>) {
|
||||
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<Actor, Map<Facet, Action[]>> = new Map();
|
||||
|
||||
constructor(recipient: Actor, event: Event) {
|
||||
this.recipient = recipient;
|
||||
this.event = event;
|
||||
ref<T>(t: T): Ref<T> {
|
||||
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<SyncSource>): 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<Actor, LocalAction[]> = new Map();
|
||||
readonly localActions: Array<LocalAction> = [];
|
||||
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<Handle>): 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<SyncTarget>): Promise<Turn> {
|
||||
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<SyncSource>): 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<K, V>(map: Map<K, V>, keys?: IdentitySet<K>): Map<K, V> {
|
||||
const result: Map<K, V> = 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;
|
||||
}
|
||||
|
|
|
@ -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<typeof m | typeof m2, [string]>;
|
||||
type Y = Messages<X, [string]>;
|
||||
type X1 = Methods<typeof m, [string]>;
|
||||
type X2 = Methods<typeof m | typeof m2, [string]>;
|
||||
type Y = Messages<X1, [string]>;
|
||||
|
||||
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');
|
||||
|
|
Loading…
Reference in New Issue