diff --git a/packages/core/examples/box-and-client.ts b/packages/core/examples/box-and-client.ts index 5ea219b..53e65c5 100755 --- a/packages/core/examples/box-and-client.ts +++ b/packages/core/examples/box-and-client.ts @@ -17,7 +17,7 @@ // along with this program. If not, see . //--------------------------------------------------------------------------- -import { bootModule, Skeleton, Record, Discard, Capture, Observe, Facet, DataflowObservableObject } from '..'; +import { bootModule, Skeleton, Record, Discard, Capture, Observe, Facet } from '..'; const __ = Discard._instance; const _$ = Capture(__); @@ -28,67 +28,66 @@ const N = 100000; console.time('box-and-client-' + N.toString()); -function boot(thisFacet: Facet) { - thisFacet.spawn('box', function (this: DataflowObservableObject & { - value: number; - }, thisFacet: Facet) { - thisFacet.declareField(this, 'value', 0); - thisFacet.addEndpoint(() => { - // console.log('recomputing published BoxState', this.value); - return { assertion: BoxState(this.value), analysis: null }; - }); - thisFacet.addDataflow(() => { - // console.log('dataflow saw new value', this.value); - if (this.value === N) { - thisFacet.stop(() => { - console.log('terminated box root facet'); +function boot(thisFacet: Facet<{}>) { + thisFacet.spawn<{ value: number }>('box', function (thisFacet) { + thisFacet.declareField(this, 'value', 0); + thisFacet.addEndpoint(function () { + // console.log('recomputing published BoxState', this.value); + return { assertion: BoxState(this.value), analysis: null }; + }); + thisFacet.addDataflow(function () { + // console.log('dataflow saw new value', this.value); + if (this.value === N) { + thisFacet.stop(function () { + console.log('terminated box root facet'); + }); + } + }); + thisFacet.addEndpoint(function () { + let analysis = Skeleton.analyzeAssertion(SetBox(_$)); + analysis.callback = thisFacet.wrap(function (thisFacet, evt, [v]) { + if (evt === Skeleton.EventType.MESSAGE) { + if (typeof v !== 'number') return; + thisFacet.scheduleScript(function () { + this.value = v; + // console.log('box updated value', v); + }); + } + }); + return { assertion: Observe(SetBox(_$)), analysis }; }); - } }); - thisFacet.addEndpoint(() => { - let analysis = Skeleton.analyzeAssertion(SetBox(_$)); - analysis.callback = thisFacet.wrap((thisFacet, evt, [v]) => { - if (evt === Skeleton.EventType.MESSAGE) { - if (typeof v !== 'number') return; - thisFacet.scheduleScript(() => { - this.value = v; - // console.log('box updated value', v); - }); - } - }); - return { assertion: Observe(SetBox(_$)), analysis }; - }); - }); - thisFacet.spawn('client', function (thisFacet: Facet) { - thisFacet.addEndpoint(() => { - let analysis = Skeleton.analyzeAssertion(BoxState(_$)); - analysis.callback = thisFacet.wrap((thisFacet, evt, [v]) => { - if (evt === Skeleton.EventType.ADDED) { - if (typeof v !== 'number') return; - thisFacet.scheduleScript(() => { - // console.log('client sending SetBox', v + 1); - thisFacet.send(SetBox(v + 1)); - }); - } - }); - return { assertion: Observe(BoxState(_$)), analysis }; + thisFacet.spawn('client', function (thisFacet: Facet<{}>) { + thisFacet.addEndpoint(function () { + let analysis = Skeleton.analyzeAssertion(BoxState(_$)); + analysis.callback = thisFacet.wrap(function (thisFacet, evt, [v]) { + if (evt === Skeleton.EventType.ADDED) { + if (typeof v !== 'number') return; + thisFacet.scheduleScript(function () { + // console.log('client sending SetBox', v + 1); + thisFacet.send(SetBox(v + 1)); + }); + } + }); + return { assertion: Observe(BoxState(_$)), analysis }; + }); + thisFacet.addEndpoint(function () { + let analysis = Skeleton.analyzeAssertion(BoxState(__)); + analysis.callback = thisFacet.wrap(function (thisFacet, evt, _vs) { + if (evt === Skeleton.EventType.REMOVED) { + thisFacet.scheduleScript(function () { + console.log('box gone'); + }); + } + }); + return { assertion: Observe(BoxState(__)), analysis }; + }); }); - thisFacet.addEndpoint(() => { - let analysis = Skeleton.analyzeAssertion(BoxState(__)); - analysis.callback = thisFacet.wrap((thisFacet, evt, _vs) => { - if (evt === Skeleton.EventType.REMOVED) { - thisFacet.scheduleScript(() => { - console.log('box gone'); - }); - } - }); - return { assertion: Observe(BoxState(__)), analysis }; - }); - }); - thisFacet.actor.dataspace.ground().addStopHandler(() => - console.timeEnd('box-and-client-' + N.toString())); + thisFacet.actor.dataspace.ground().addStopHandler(function () { + console.timeEnd('box-and-client-' + N.toString()); + }); } bootModule(boot); diff --git a/packages/core/src/runtime/dataspace.ts b/packages/core/src/runtime/dataspace.ts index c63fa49..1f42ef1 100644 --- a/packages/core/src/runtime/dataspace.ts +++ b/packages/core/src/runtime/dataspace.ts @@ -40,17 +40,18 @@ export type FacetId = ActorId; export type EndpointId = ActorId; export type Task = () => T; -export type Script = (f: Facet) => T; +export type Script = (this: Fields & DataflowObservableObject, f: Facet) => T; export type MaybeValue = Value | undefined; export type EndpointSpec = { assertion: MaybeValue, analysis: Skeleton.Analysis | null }; -export type ObserverCallback = (facet: Facet, bindings: Array) => void; +export type ObserverCallback = + (this: Fields, facet: Facet, bindings: Array) => void; -export type ObserverCallbacks = { - add?: ObserverCallback; - del?: ObserverCallback; - msg?: ObserverCallback; +export type ObserverCallbacks = { + add?: ObserverCallback; + del?: ObserverCallback; + msg?: ObserverCallback; } export const DataflowObservableObjectId = Symbol.for('DataflowObservableObjectId'); @@ -63,12 +64,12 @@ export function _canonicalizeDataflowObservable(i: DataflowObservable): string { return i[0][DataflowObservableObjectId]() + ',' + i[1]; } -export type DataflowDependent = Endpoint; +export type DataflowDependent = Endpoint; export function _canonicalizeDataflowDependent(i: DataflowDependent): string { return '' + i.id; } -export type ActivationScript = Script; +export type ActivationScript = Script; export abstract class Dataspace { nextId: ActorId = 0; @@ -81,7 +82,7 @@ export abstract class Dataspace { actors: IdentityMap = new IdentityMap(); activations: IdentitySet = new IdentitySet(); - constructor(bootProc: Script) { + constructor(bootProc: Script) { this.pendingTurns = [new Turn(null, [new Spawn(null, bootProc, new Set())])]; } @@ -129,7 +130,12 @@ export abstract class Dataspace { }); } - addActor(name: any, bootProc: Script, initialAssertions: Set, parentActor: Actor | null) { + addActor( + name: any, + bootProc: Script, + initialAssertions: Set, + parentActor: Actor | null) + { let ac = new Actor(this, name, initialAssertions, parentActor?.id); // debug('Spawn', ac && ac.toString()); this.applyPatch(ac, ac.adhocAssertions); @@ -181,7 +187,7 @@ export abstract class Dataspace { this.index.removeHandler(handler, handler.callback!); } - endpointHook(_facet: Facet, _endpoint: Endpoint) { + endpointHook(_facet: Facet, _endpoint: Endpoint) { // Subclasses may override } } @@ -190,7 +196,7 @@ export class Actor { readonly id: ActorId; readonly dataspace: Dataspace; readonly name: any; - rootFacet: Facet | null = null; + rootFacet: Facet | null = null; isRunnable: boolean = false; readonly pendingTasks: Array>>; pendingActions: Array; @@ -253,11 +259,15 @@ export class Actor { this.pendingTasks[priority].push(task); } - addFacet(parentFacet: Facet | null, bootProc: Script, checkInScript: boolean = false) { + addFacet( + parentFacet: Facet | null, + bootProc: Script, + checkInScript: boolean = false) + { if (checkInScript && parentFacet && !parentFacet.inScript) { throw new Error("Cannot add facet outside script; are you missing a `react { ... }`?"); } - let f = new Facet(this, parentFacet); + let f = new Facet(this, parentFacet); f.invokeScript(f => f.withNonScriptContext(() => bootProc.call(f.fields, f))); this.scheduleTask(() => { if ((parentFacet && !parentFacet.isLive) || f.isInert()) { @@ -351,12 +361,12 @@ class Message extends Action { } } -class Spawn extends Action { +class Spawn extends Action { readonly name: any; - readonly bootProc: Script; + readonly bootProc: Script; readonly initialAssertions: Set; - constructor(name: any, bootProc: Script, initialAssertions: Set = new Set()) { + constructor(name: any, bootProc: Script, initialAssertions: Set = new Set()) { super(); this.name = name; this.bootProc = bootProc; @@ -406,7 +416,7 @@ class Activation extends Action { perform(ds: Dataspace, ac: Actor | null): void { if (ds.activations.has(this.script)) return; ds.activations.add(this.script); - ds.addActor(this.name, rootFacet => rootFacet.addStartScript(this.script), new Set(), ac); + ds.addActor<{}>(this.name, rootFacet => rootFacet.addStartScript(this.script), new Set(), ac); } } @@ -424,18 +434,18 @@ export class Turn { } } -export class Facet { +export class Facet { readonly id: FacetId; isLive = true; readonly actor: Actor; - readonly parent: Facet | null; - readonly endpoints = new IdentityMap(); - readonly stopScripts: Array> = []; - readonly children = new IdentitySet(); - readonly fields: any; + readonly parent: Facet | null; + readonly endpoints = new IdentityMap>(); + readonly stopScripts: Array> = []; + readonly children = new IdentitySet>(); + readonly fields: Fields & DataflowObservableObject; inScript = true; - constructor(actor: Actor, parent: Facet | null) { + constructor(actor: Actor, parent: Facet | null) { this.id = actor.dataspace.nextId++; this.actor = actor; this.parent = parent; @@ -513,11 +523,11 @@ export class Facet { } // This alias exists because of the naive expansion done by the parser. - _stop(continuation?: Script) { + _stop(continuation?: Script) { this.stop(continuation); } - stop(continuation?: Script) { + stop(continuation?: Script) { this.parent!.invokeScript(() => { this.actor.scheduleTask(() => { this._terminate(); @@ -529,35 +539,35 @@ export class Facet { }); } - addStartScript(s: Script) { + addStartScript(s: Script) { this.ensureFacetSetup('`on start`'); this.scheduleScript(s); } - addStopScript(s: Script) { + addStopScript(s: Script) { this.ensureFacetSetup('`on stop`'); this.stopScripts.push(s); } - addEndpoint(updateFun: Script, isDynamic: boolean = true): Endpoint { + addEndpoint(updateFun: Script, isDynamic: boolean = true): Endpoint { const ep = new Endpoint(this, isDynamic, updateFun); this.actor.dataspace.endpointHook(this, ep); return ep; } - _addRawObserverEndpoint(specScript: Script, callbacks: ObserverCallbacks): Endpoint + _addRawObserverEndpoint(specScript: Script, callbacks: ObserverCallbacks): Endpoint { return this.addEndpoint(() => { - const spec = specScript(this); + const spec = specScript.call(this.fields, this); if (spec === void 0) { return { assertion: void 0, analysis: null }; } else { const analysis = Skeleton.analyzeAssertion(spec); analysis.callback = this.wrap((facet, evt, vs) => { switch (evt) { - case Skeleton.EventType.ADDED: callbacks.add?.(facet, vs); break; - case Skeleton.EventType.REMOVED: callbacks.del?.(facet, vs); break; - case Skeleton.EventType.MESSAGE: callbacks.msg?.(facet, vs); break; + case Skeleton.EventType.ADDED: callbacks.add?.call(facet.fields, facet, vs); break; + case Skeleton.EventType.REMOVED: callbacks.del?.call(facet.fields, facet, vs); break; + case Skeleton.EventType.MESSAGE: callbacks.msg?.call(facet.fields, facet, vs); break; } }); return { assertion: Observe(spec), analysis }; @@ -565,9 +575,9 @@ export class Facet { }); } - addObserverEndpoint(specThunk: (facet: Facet) => MaybeValue, callbacks: ObserverCallbacks): Endpoint { - const scriptify = (f?: ObserverCallback) => - f && ((facet: Facet, vs: Array) => + addObserverEndpoint(specThunk: (facet: Facet) => MaybeValue, callbacks: ObserverCallbacks): Endpoint { + const scriptify = (f?: ObserverCallback) => + f && ((facet: Facet, vs: Array) => facet.scheduleScript(() => f.call(facet.fields, facet, vs))); return this._addRawObserverEndpoint(specThunk, { add: scriptify(callbacks.add), @@ -576,7 +586,7 @@ export class Facet { }); } - addDataflow(subjectFun: Script, priority?: Priority): Endpoint { + addDataflow(subjectFun: Script, priority?: Priority): Endpoint { return this.addEndpoint(() => { let subjectId = this.actor.dataspace.dataflow.currentSubjectId; this.scheduleScript(() => { @@ -607,7 +617,7 @@ export class Facet { return s + ')'; } - invokeScript(script: Script, propagateErrors = false): T | undefined { + invokeScript(script: Script, propagateErrors = false): T | undefined { try { // console.group('Facet', facet && facet.toString()); return script.call(this.fields, this); @@ -623,11 +633,18 @@ export class Facet { } } - wrap, R>(fn: (f: Facet, ... args: T) => R): (... args: T) => R { + wrap, R>( + fn: (this: Fields & DataflowObservableObject, + f: Facet, ... args: T) => R + ): (... args: T) => R + { return (... actuals) => this.invokeScript(f => fn.call(f.fields, f, ... actuals), true)!; } - wrapExternal>(fn: (f: Facet, ... args: T) => void): (... args: T) => void { + wrapExternal>( + fn: (this: Fields & DataflowObservableObject, + f: Facet, ... args: T) => void + ): (... args: T) => void { const ac = this.actor; return (... actuals) => { if (this.isLive) { @@ -660,16 +677,16 @@ export class Facet { } // This alias exists because of the naive expansion done by the parser. - _spawn(name: any, bootProc: Script, initialAssertions?: Set) { + _spawn(name: any, bootProc: Script, initialAssertions?: Set) { this.spawn(name, bootProc, initialAssertions); } - spawn(name: any, bootProc: Script, initialAssertions?: Set) { + spawn(name: any, bootProc: Script, initialAssertions?: Set) { this.ensureNonFacetSetup('`spawn`'); this.enqueueScriptAction(new Spawn(name, bootProc, initialAssertions)); } - deferTurn(continuation: Script) { + deferTurn(continuation: Script) { this.ensureNonFacetSetup('`deferTurn`'); this.enqueueScriptAction(new DeferredTurn(this.wrap(continuation))); } @@ -679,7 +696,7 @@ export class Facet { this.enqueueScriptAction(new Activation(script, name ?? null)); } - scheduleScript(script: Script, priority?: Priority) { + scheduleScript(script: Script, priority?: Priority) { this.actor.scheduleTask(this.wrap(script), priority); } @@ -706,22 +723,22 @@ export class Facet { // delete obj[prop]; // } - addChildFacet(bootProc: Script) { + addChildFacet(bootProc: Script) { this.actor.addFacet(this, bootProc, true); } - withSelfDo(t: Script) { - t(this); + withSelfDo(t: Script) { + t.call(this.fields, this); } } -export class Endpoint { +export class Endpoint { readonly id: EndpointId; - readonly facet: Facet; - readonly updateFun: Script; + readonly facet: Facet; + readonly updateFun: Script; spec: EndpointSpec; - constructor(facet: Facet, isDynamic: boolean, updateFun: Script) { + constructor(facet: Facet, isDynamic: boolean, updateFun: Script) { facet.ensureFacetSetup('add endpoint'); let ac = facet.actor; let ds = ac.dataspace; diff --git a/packages/core/src/runtime/relay.ts b/packages/core/src/runtime/relay.ts index 5ce421b..0b70882 100644 --- a/packages/core/src/runtime/relay.ts +++ b/packages/core/src/runtime/relay.ts @@ -28,9 +28,9 @@ import { Ground } from './ground.js'; export const $QuitDataspace = new $Special("quit-dataspace"); export class NestedDataspace extends Dataspace { - readonly outerFacet: Facet; + readonly outerFacet: Facet<{}>; - constructor(outerFacet: Facet, bootProc: Script) { + constructor(outerFacet: Facet<{}>, bootProc: Script) { super(bootProc); this.outerFacet = outerFacet; } @@ -42,7 +42,7 @@ export class NestedDataspace extends Dataspace { } } - endpointHook(facet: Facet, innerEp: Endpoint) { + endpointHook(facet: Facet, innerEp: Endpoint) { super.endpointHook(facet, innerEp); const innerAssertion = innerEp.spec.assertion; @@ -105,7 +105,7 @@ export class NestedDataspace extends Dataspace { return net; } - hookEndpointLifecycle(innerEp: Endpoint, outerEp: Endpoint) { + hookEndpointLifecycle(innerEp: Endpoint, outerEp: Endpoint<{}>) { const _refresh = innerEp.refresh; innerEp.refresh = function () { _refresh.call(this); @@ -139,7 +139,7 @@ export class NestedDataspace extends Dataspace { } } -export function inNestedDataspace(bootProc: Script): Script { +export function inNestedDataspace(bootProc: Script): Script { return outerFacet => { outerFacet.addDataflow(function () {}); // ^ eww! Dummy endpoint to keep the root facet of the relay alive.