Simpler treatment of Refs

This commit is contained in:
Tony Garnock-Jones 2021-02-23 15:53:42 +01:00
parent ec5146a8a7
commit 77fbab89dd
2 changed files with 26 additions and 54 deletions

View File

@ -5,51 +5,20 @@ export type Handle = number;
export type ExitReason = null | { ok: true } | { ok: false, err: Error }; export type ExitReason = null | { ok: true } | { ok: false, err: Error };
export type LocalAction = (t: Turn) => void; export type LocalAction = (t: Turn) => void;
export const assert = Symbol('assert');
export const retract = Symbol('retract');
export const message = Symbol('message');
export const sync = Symbol('sync');
export interface Entity { export interface Entity {
[assert]?(turn: Turn, assertion: Assertion, handle: Handle): void; assert?(turn: Turn, assertion: Assertion, handle: Handle): void;
[retract]?(turn: Turn, handle: Handle): void; retract?(turn: Turn, handle: Handle): void;
[message]?(turn: Turn, body: Assertion): void; message?(turn: Turn, body: Assertion): void;
[sync]?(turn: Turn, peer: Entity): void; sync?(turn: Turn, peer: Ref): void;
} }
export class Ref implements Entity { export interface Ref { readonly relay: Actor, readonly target: Entity };
readonly relay: Actor;
readonly target: Entity;
constructor(relay: Actor, target: Entity) {
this.relay = relay;
this.target = target;
}
[assert](turn: Turn, assertion: Assertion, handle: Handle): void {
this.target[assert]?.(turn, assertion, handle);
}
[retract](turn: Turn, handle: Handle): void {
this.target[retract]?.(turn, handle);
}
[message](turn: Turn, body: Assertion): void {
this.target[message]?.(turn, body);
}
[sync](turn: Turn, peer: Ref) {
this.target[sync] ? this.target[sync]!(turn, peer) : turn.message(peer, true);
}
}
type OutboundMap = Map<Handle, Ref>;
export class Actor { export class Actor {
readonly outbound: OutboundMap; readonly outbound: Map<Handle, Ref>;
exitReason: ExitReason = null; exitReason: ExitReason = null;
constructor(initialAssertions: OutboundMap = new Map()) { constructor(initialAssertions = new Map<Handle, Ref>()) {
this.outbound = initialAssertions; this.outbound = initialAssertions;
} }
@ -76,7 +45,7 @@ let nextHandle = 0;
export class Turn { export class Turn {
readonly actor: Actor; readonly actor: Actor;
readonly queues: Map<Actor, LocalAction[]> = new Map(); readonly queues = new Map<Actor, LocalAction[]>();
static for(actor: Actor, f: LocalAction): void { static for(actor: Actor, f: LocalAction): void {
const t = new Turn(actor); const t = new Turn(actor);
@ -89,12 +58,12 @@ export class Turn {
} }
ref(e: Entity): Ref { ref(e: Entity): Ref {
return (e instanceof Ref) ? e : new Ref(this.actor, e); return { relay: this.actor, target: e };
} }
spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void { spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
this.enqueue(this.actor, () => { this.enqueue(this.actor, () => {
const newOutbound: OutboundMap = new Map(); const newOutbound = new Map<Handle, Ref>();
initialAssertions.forEach(key => { initialAssertions.forEach(key => {
newOutbound.set(key, this.actor.outbound.get(key)!); // we trust initialAssertions newOutbound.set(key, this.actor.outbound.get(key)!); // we trust initialAssertions
this.actor.outbound.delete(key); this.actor.outbound.delete(key);
@ -112,7 +81,7 @@ export class Turn {
const h = nextHandle++; const h = nextHandle++;
this.enqueue(ref.relay, t => { this.enqueue(ref.relay, t => {
this.actor.outbound.set(h, ref); this.actor.outbound.set(h, ref);
ref[assert]?.(t, assertion, h); ref.target.assert?.(t, assertion, h);
}); });
return h; return h;
} }
@ -130,17 +99,20 @@ export class Turn {
_retract(ref: Ref, handle: Handle): void { _retract(ref: Ref, handle: Handle): void {
this.enqueue(ref.relay, t => { this.enqueue(ref.relay, t => {
this.actor.outbound.delete(handle); this.actor.outbound.delete(handle);
ref[retract]?.(t, handle); ref.target.retract?.(t, handle);
}); });
} }
sync(ref: Ref): Promise<Turn> { sync(ref: Ref): Promise<Turn> {
return new Promise(resolve => return new Promise(resolve => {
this.enqueue(ref.relay, t => ref[sync]?.(t, this.ref({ [message]: resolve })))); const k = this.ref({ message: resolve });
this.enqueue(ref.relay, t =>
ref.target.sync ? ref.target.sync!(t, k) : t.message(k, true));
});
} }
message(ref: Ref, assertion: Assertion): void { message(ref: Ref, assertion: Assertion): void {
this.enqueue(ref.relay, t => ref[message]?.(t, assertion)); this.enqueue(ref.relay, t => ref.target.message?.(t, assertion));
} }
enqueue(relay: Actor, a: LocalAction): void { enqueue(relay: Actor, a: LocalAction): void {

16
main.ts
View File

@ -1,4 +1,4 @@
import { Actor, Assertion, Entity, Handle, Ref, Turn, assert, message, retract } from './actor.js'; import { Actor, Assertion, Entity, Handle, Ref, Turn } from './actor.js';
import { Dictionary, IdentityMap, is, Record } from 'preserves'; import { Dictionary, IdentityMap, is, Record } from 'preserves';
import { Bag, ChangeDescription } from './bag'; import { Bag, ChangeDescription } from './bag';
@ -9,7 +9,7 @@ class Dataspace implements Entity {
readonly assertions = new Bag<Ref>(); readonly assertions = new Bag<Ref>();
readonly subscriptions: Dictionary<Map<Ref, Dictionary<Handle>>> = new Dictionary(); readonly subscriptions: Dictionary<Map<Ref, Dictionary<Handle>>> = new Dictionary();
[assert](turn: Turn, rec: Assertion, handle: Handle): void { assert(turn: Turn, rec: Assertion, handle: Handle): void {
if (!Record.isRecord<Ref>(rec)) return; if (!Record.isRecord<Ref>(rec)) return;
this.handleMap.set(handle, rec); this.handleMap.set(handle, rec);
if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return; if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return;
@ -27,7 +27,7 @@ class Dataspace implements Entity {
seen.has(rec) || seen.set(rec, turn.assert(peer, rec))); seen.has(rec) || seen.set(rec, turn.assert(peer, rec)));
} }
[retract](turn: Turn, upstreamHandle: Handle): void { retract(turn: Turn, upstreamHandle: Handle): void {
const rec = this.handleMap.get(upstreamHandle); const rec = this.handleMap.get(upstreamHandle);
if (rec === void 0) return; if (rec === void 0) return;
this.handleMap.delete(upstreamHandle); this.handleMap.delete(upstreamHandle);
@ -46,7 +46,7 @@ class Dataspace implements Entity {
} }
} }
[message](turn: Turn, rec: Assertion): void { message(turn: Turn, rec: Assertion): void {
if (!Record.isRecord<Ref>(rec)) return; if (!Record.isRecord<Ref>(rec)) return;
this.subscriptions.get(rec.label)?.forEach((_seen, peer) => turn.message(peer, rec)); this.subscriptions.get(rec.label)?.forEach((_seen, peer) => turn.message(peer, rec));
} }
@ -70,7 +70,7 @@ Turn.for(new Actor(), async (t: Turn) => {
} }
setValue(t, 0); setValue(t, 0);
t.assert(ds, Observe(SetBox.constructorInfo.label, t.ref({ t.assert(ds, Observe(SetBox.constructorInfo.label, t.ref({
[message](t: Turn, [newValue]: [number]): void { message(t: Turn, [newValue]: [number]): void {
// console.log(`Box: got ${newValue}`); // console.log(`Box: got ${newValue}`);
if (newValue % 25000 === 0) { if (newValue % 25000 === 0) {
const endTime = Date.now(); const endTime = Date.now();
@ -91,7 +91,7 @@ Turn.for(new Actor(), async (t: Turn) => {
console.log('Spawning Client'); console.log('Spawning Client');
let count = 0; let count = 0;
t.assert(ds, Observe(BoxState.constructorInfo.label, t.ref({ t.assert(ds, Observe(BoxState.constructorInfo.label, t.ref({
[assert](t: Turn, [currentValue]: [number]): void { assert(t: Turn, [currentValue]: [number]): void {
// console.log(`Client: got ${currentValue}`); // console.log(`Client: got ${currentValue}`);
if (currentValue === LIMIT) { if (currentValue === LIMIT) {
console.log(`Client: quitting at limit`); console.log(`Client: quitting at limit`);
@ -102,8 +102,8 @@ Turn.for(new Actor(), async (t: Turn) => {
} }
}))); })));
t.assert(ds, Observe(BoxState.constructorInfo.label, t.ref({ t.assert(ds, Observe(BoxState.constructorInfo.label, t.ref({
[assert](_t: Turn, _assertion: Assertion): void { count++; }, assert(_t: Turn, _assertion: Assertion): void { count++; },
[retract](t: Turn, _handle: Handle) { retract(t: Turn, _handle: Handle) {
if (--count === 0) { if (--count === 0) {
console.log('Client: detected box termination'); console.log('Client: detected box termination');
t.quit(); t.quit();