Examples and bug-fixes
This commit is contained in:
parent
0b746764dd
commit
f9d1e694e0
|
@ -2,9 +2,12 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
const { bootModule, Dataspace, Skeleton, Ground, Record, Discard, Capture, Observe } = require('..');
|
||||
const __ = Discard._instance;
|
||||
const _$ = Capture(__);
|
||||
import {
|
||||
Pattern as P,
|
||||
Observe, fromObserve,
|
||||
Record,
|
||||
Actor, Dataspace,
|
||||
} from '..';
|
||||
|
||||
const BoxState = Record.makeConstructor()(Symbol.for('BoxState'), ['value']);
|
||||
const SetBox = Record.makeConstructor()(Symbol.for('SetBox'), ['newValue']);
|
||||
|
@ -13,63 +16,63 @@ const N = 100000;
|
|||
|
||||
console.time('box-and-client-' + N.toString());
|
||||
|
||||
function boot(thisFacet) {
|
||||
thisFacet.spawn('box', function (thisFacet) {
|
||||
thisFacet.declareField(this, 'value', 0);
|
||||
thisFacet.addEndpoint(() => {
|
||||
// console.log('recomputing published BoxState', this.value);
|
||||
return { assertion: BoxState(this.value), analysis: null };
|
||||
Actor.boot(t => {
|
||||
t.activeFacet.preventInertCheck();
|
||||
const ds = t.ref(new Dataspace());
|
||||
|
||||
t.spawn(t => {
|
||||
t.activeFacet.actor.name = 'box';
|
||||
const boxValue = t.field(0, 'value');
|
||||
|
||||
t.assertDataflow(_t => {
|
||||
// console.log('recomputing published BoxState', boxValue.value);
|
||||
return {
|
||||
target: ds,
|
||||
assertion: BoxState(boxValue.value),
|
||||
};
|
||||
});
|
||||
thisFacet.addDataflow(() => {
|
||||
// console.log('dataflow saw new value', this.value);
|
||||
if (this.value === N) {
|
||||
thisFacet.stop(() => {
|
||||
|
||||
t.dataflow(t => {
|
||||
// console.log('dataflow saw new value', boxValue.value);
|
||||
if (boxValue.value === N) {
|
||||
t.stop(t.activeFacet, _t => {
|
||||
console.log('terminated box root facet');
|
||||
});
|
||||
}
|
||||
});
|
||||
thisFacet.addEndpoint(() => {
|
||||
let analysis = Skeleton.analyzeAssertion(SetBox(_$));
|
||||
analysis.callback = thisFacet.wrap((thisFacet, evt, vs) => {
|
||||
if (evt === Skeleton.EventType.MESSAGE) {
|
||||
thisFacet.scheduleScript(() => {
|
||||
this.value = vs[0];
|
||||
// console.log('box updated value', vs[0]);
|
||||
});
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(SetBox.constructorInfo.label, P.bind()),
|
||||
observer: t.ref({
|
||||
message(_t, [v]) {
|
||||
boxValue.value = v;
|
||||
// console.log('box updated value', v);
|
||||
}
|
||||
});
|
||||
return { assertion: Observe(SetBox(_$)), analysis };
|
||||
});
|
||||
})
|
||||
})));
|
||||
});
|
||||
|
||||
thisFacet.spawn('client', function (thisFacet) {
|
||||
thisFacet.addEndpoint(() => {
|
||||
let analysis = Skeleton.analyzeAssertion(BoxState(_$));
|
||||
analysis.callback = thisFacet.wrap((thisFacet, evt, [v]) => {
|
||||
if (evt === Skeleton.EventType.ADDED) {
|
||||
thisFacet.scheduleScript(() => {
|
||||
t.spawn(t => {
|
||||
t.activeFacet.actor.name = 'client';
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(BoxState.constructorInfo.label, P.bind()),
|
||||
observer: t.ref({
|
||||
assert(t, [v], _handle) {
|
||||
// console.log('client sending SetBox', v + 1);
|
||||
thisFacet.send(SetBox(v + 1));
|
||||
});
|
||||
t.message(ds, SetBox(v + 1));
|
||||
}
|
||||
});
|
||||
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(() => {
|
||||
})
|
||||
})));
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(BoxState.constructorInfo.label, P._),
|
||||
observer: t.ref({
|
||||
retract(_t) {
|
||||
console.log('box gone');
|
||||
});
|
||||
console.timeEnd('box-and-client-' + N.toString());
|
||||
}
|
||||
})
|
||||
})));
|
||||
});
|
||||
return { assertion: Observe(BoxState(__)), analysis };
|
||||
});
|
||||
});
|
||||
|
||||
thisFacet.actor.dataspace.ground().addStopHandler(() =>
|
||||
console.timeEnd('box-and-client-' + N.toString()));
|
||||
}
|
||||
|
||||
bootModule(boot);
|
||||
});
|
||||
|
|
|
@ -2,83 +2,77 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { bootModule, Skeleton, Record, Discard, Capture, Observe, Facet, Value } from '..';
|
||||
const __ = Discard._instance;
|
||||
const _$ = Capture(__);
|
||||
import {
|
||||
Pattern as P,
|
||||
Observe, fromObserve,
|
||||
Record,
|
||||
Actor, Dataspace,
|
||||
} from '..';
|
||||
|
||||
// The current pattern representation puts Capture and Discard record
|
||||
// instances into Record fields, so those record fields have to be
|
||||
// prepared to type them, which is why we see `number | Pattern` here
|
||||
// rather than the ideal `number`.
|
||||
//
|
||||
type Pattern = ReturnType<typeof Capture> | ReturnType<typeof Discard>;
|
||||
const BoxState = Record.makeConstructor<{value: number | Pattern}>()(Symbol.for('BoxState'), ['value']);
|
||||
const SetBox = Record.makeConstructor<{newValue: number | Pattern}>()(Symbol.for('SetBox'), ['newValue']);
|
||||
const BoxState = Record.makeConstructor<{value: number}>()(Symbol.for('BoxState'), ['value']);
|
||||
const SetBox = Record.makeConstructor<{newValue: number}>()(Symbol.for('SetBox'), ['newValue']);
|
||||
|
||||
const N = 100000;
|
||||
|
||||
console.time('box-and-client-' + N.toString());
|
||||
|
||||
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 };
|
||||
Actor.boot(t => {
|
||||
t.activeFacet.preventInertCheck();
|
||||
const ds = t.ref(new Dataspace());
|
||||
|
||||
t.spawn(t => {
|
||||
t.activeFacet.actor.name = 'box';
|
||||
const boxValue = t.field<number>(0, 'value');
|
||||
|
||||
t.assertDataflow(_t => {
|
||||
// console.log('recomputing published BoxState', boxValue.value);
|
||||
return {
|
||||
target: ds,
|
||||
assertion: BoxState(boxValue.value),
|
||||
};
|
||||
});
|
||||
thisFacet.addDataflow(function () {
|
||||
// console.log('dataflow saw new value', this.value);
|
||||
if (this.value === N) {
|
||||
thisFacet.stop(function () {
|
||||
|
||||
t.dataflow(t => {
|
||||
// console.log('dataflow saw new value', boxValue.value);
|
||||
if (boxValue.value === N) {
|
||||
t.stop(t.activeFacet, _t => {
|
||||
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;
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(SetBox.constructorInfo.label, P.bind()),
|
||||
observer: t.ref({
|
||||
message(_t, [v]: [number]) {
|
||||
boxValue.value = v;
|
||||
// console.log('box updated value', v);
|
||||
});
|
||||
}
|
||||
});
|
||||
return { assertion: Observe(SetBox(_$)), 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 () {
|
||||
t.spawn(t => {
|
||||
t.activeFacet.actor.name = 'client';
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(BoxState.constructorInfo.label, P.bind()),
|
||||
observer: t.ref({
|
||||
assert(t, [v]: [number], _handle) {
|
||||
// console.log('client sending SetBox', v + 1);
|
||||
thisFacet.send(SetBox(v + 1));
|
||||
});
|
||||
t.message(ds, 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 () {
|
||||
})
|
||||
})));
|
||||
|
||||
t.assert(ds, fromObserve(Observe({
|
||||
pattern: P.rec(BoxState.constructorInfo.label, P._),
|
||||
observer: t.ref({
|
||||
retract(_t) {
|
||||
console.log('box gone');
|
||||
});
|
||||
}
|
||||
});
|
||||
return { assertion: Observe(BoxState(__)), analysis };
|
||||
});
|
||||
});
|
||||
|
||||
thisFacet.actor.dataspace.ground().addStopHandler(function () {
|
||||
console.timeEnd('box-and-client-' + N.toString());
|
||||
}
|
||||
})
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
bootModule(boot);
|
||||
});
|
||||
|
|
|
@ -3,12 +3,24 @@
|
|||
|
||||
export * from '@preserves/core';
|
||||
|
||||
export * from './runtime/randomid.js';
|
||||
export * from './runtime/bag.js';
|
||||
export * as API from './runtime/api.js';
|
||||
export * as Skeleton from './runtime/skeleton.js';
|
||||
export * as Schemas from './schemas.js';
|
||||
export { Observe, asObserve, toObserve, fromObserve } from './gen/dataspace.js';
|
||||
export * as DataspacePatterns from './gen/dataspacePatterns.js';
|
||||
|
||||
export * from './runtime/actor.js';
|
||||
export * from './runtime/bag.js';
|
||||
export * as Dataflow from './runtime/dataflow.js';
|
||||
export * from './runtime/dataspace.js';
|
||||
export * as Pattern from './runtime/pattern.js';
|
||||
export * from './runtime/randomid.js';
|
||||
export * as Rewrite from './runtime/rewrite.js';
|
||||
export * as Skeleton from './runtime/skeleton.js';
|
||||
export * as Task from './runtime/task.js';
|
||||
|
||||
export * as Cryptography from './transport/cryptography.js';
|
||||
export * as WireProtocol from './transport/protocol.js';
|
||||
export * as Relay from './transport/relay.js';
|
||||
export * as Sturdy from './transport/sturdy.js';
|
||||
|
||||
import { randomId } from './runtime/randomid.js';
|
||||
|
||||
|
|
|
@ -55,13 +55,27 @@ export type DataflowBlock = (t: Turn) => void;
|
|||
|
||||
export class Actor {
|
||||
readonly id = nextActorId++;
|
||||
name: AnyValue = this.id;
|
||||
readonly root: Facet;
|
||||
_dataflowGraph: DataflowGraph | null = null;
|
||||
exitReason: ExitReason = null;
|
||||
readonly exitHooks: Array<LocalAction> = [];
|
||||
|
||||
constructor(bootProc: LocalAction, initialAssertions: OutboundMap = new Map()) {
|
||||
static boot(bootProc: LocalAction, initialAssertions: OutboundMap = new Map()): Actor {
|
||||
const newActor = new Actor(initialAssertions);
|
||||
newActor._boot(bootProc);
|
||||
return newActor;
|
||||
}
|
||||
|
||||
static __unsafeNew(initialAssertions: OutboundMap = new Map()) {
|
||||
return new Actor(initialAssertions);
|
||||
}
|
||||
|
||||
private constructor(initialAssertions: OutboundMap = new Map()) {
|
||||
this.root = new Facet(this, null, initialAssertions);
|
||||
}
|
||||
|
||||
_boot(bootProc: LocalAction) {
|
||||
Turn.for(new Facet(this, this.root), stopIfInertAfter(bootProc));
|
||||
}
|
||||
|
||||
|
@ -81,7 +95,7 @@ export class Actor {
|
|||
if (this.exitReason !== null) return;
|
||||
this.exitReason = reason;
|
||||
if (!reason.ok) {
|
||||
console.error(`Actor ${this.id} crashed:`, reason.err);
|
||||
console.error(`${this} crashed:`, reason.err);
|
||||
}
|
||||
this.exitHooks.forEach(hook => hook(t));
|
||||
queueTask(() => Turn.for(this.root, t => this.root._terminate(t, reason.ok), true));
|
||||
|
@ -91,6 +105,10 @@ export class Actor {
|
|||
if (this._dataflowGraph === null) return;
|
||||
this._dataflowGraph.repairDamage(block => block(t));
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `Actor(${this.name.asPreservesText()})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class Facet {
|
||||
|
@ -129,6 +147,12 @@ export class Facet {
|
|||
};
|
||||
}
|
||||
|
||||
_halfLink(other: Facet): void {
|
||||
const h = nextHandle++;
|
||||
const e = { handle: h, peer: { relay: other, target: new StopOnRetract() }, established: true };
|
||||
this.outbound.set(h, e);
|
||||
}
|
||||
|
||||
_terminate(t: Turn, orderly: boolean): void {
|
||||
if (!this.isLive) return;
|
||||
this.isLive = false;
|
||||
|
@ -154,6 +178,24 @@ export class Facet {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
idChain(): string {
|
||||
let facetIds = [];
|
||||
for (let f: Facet | null = this; f !== null; f = f.parent) {
|
||||
facetIds.push(f.id);
|
||||
}
|
||||
return facetIds.reverse().join(':') + ':' + this.actor.name.asPreservesText();
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `Facet(#{this.idChain()})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class StopOnRetract implements Partial<Entity> {
|
||||
retract(turn: Turn, _handle: Handle): void {
|
||||
turn.stop();
|
||||
}
|
||||
}
|
||||
|
||||
export function _sync_impl(turn: Turn, e: Partial<Entity>, peer: Ref): void {
|
||||
|
@ -217,14 +259,27 @@ export class Turn {
|
|||
}
|
||||
|
||||
spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
|
||||
this.enqueue(this.activeFacet, () => {
|
||||
this._spawn(bootProc, initialAssertions);
|
||||
}
|
||||
|
||||
_spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): Actor {
|
||||
const newOutbound: OutboundMap = new Map();
|
||||
initialAssertions.forEach(key => {
|
||||
newOutbound.set(key, this.activeFacet.outbound.get(key)!); // we trust initialAssertions
|
||||
this.activeFacet.outbound.delete(key);
|
||||
});
|
||||
queueTask(() => new Actor(bootProc, newOutbound));
|
||||
initialAssertions.forEach(key => newOutbound.set(key, this.activeFacet.outbound.get(key)!));
|
||||
// ^ we trust initialAssertions, so can use `!` safely
|
||||
|
||||
const newActor = Actor.__unsafeNew(newOutbound);
|
||||
this.enqueue(this.activeFacet, () => {
|
||||
initialAssertions.forEach(key => this.activeFacet.outbound.delete(key));
|
||||
queueTask(() => newActor._boot(bootProc));
|
||||
});
|
||||
return newActor;
|
||||
}
|
||||
|
||||
spawnLink(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
|
||||
if (!this.activeFacet.isLive) return;
|
||||
const newActor = this._spawn(bootProc, initialAssertions);
|
||||
this.activeFacet._halfLink(newActor.root);
|
||||
newActor.root._halfLink(this.activeFacet);
|
||||
}
|
||||
|
||||
stopActor(): void {
|
||||
|
@ -317,11 +372,11 @@ export class Turn {
|
|||
if (a !== null) this.enqueue(ref.relay, t => ref.target.message?.(t, assertion));
|
||||
}
|
||||
|
||||
enqueue(relay: Facet, a: LocalAction): void {
|
||||
enqueue(relay: Facet, a0: LocalAction): void {
|
||||
if (this.queues === null) {
|
||||
throw new Error("Attempt to reuse a committed Turn");
|
||||
}
|
||||
a = t => t._inFacet(relay, a);
|
||||
const a: LocalAction = t => t._inFacet(relay, a0);
|
||||
this.queues.get(relay.actor)?.push(a) ?? this.queues.set(relay.actor, [a]);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
// Keep these definitions in sync with internals.ts from the compiler package
|
||||
|
||||
export type NonEmptySkeleton<Shape> = { shape: Shape, members: Skeleton<Shape>[] };
|
||||
export type Skeleton<Shape> = null | NonEmptySkeleton<Shape>;
|
|
@ -1,7 +1,7 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { canonicalString, is, Record, RecordConstructorInfo, Value } from '@preserves/core';
|
||||
import { canonicalString, is, KeyedDictionary, Record, RecordConstructorInfo, Value } from '@preserves/core';
|
||||
import { AnyValue } from './actor.js';
|
||||
import * as P from '../gen/dataspacePatterns.js';
|
||||
|
||||
|
@ -146,3 +146,44 @@ export function withoutCaptures(p: P.Pattern): P.Pattern {
|
|||
}
|
||||
return walk(p);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Constructor helpers
|
||||
|
||||
export function bind(p?: P.Pattern): P.Pattern {
|
||||
return P.Pattern.DBind(P.DBind(p ?? _));
|
||||
}
|
||||
|
||||
export function discard(): P.Pattern {
|
||||
return P.Pattern.DDiscard(P.DDiscard());
|
||||
}
|
||||
|
||||
export const _ = discard();
|
||||
|
||||
export function lit(v: AnyValue): P.Pattern {
|
||||
return P.Pattern.DLit(P.DLit(v));
|
||||
}
|
||||
|
||||
export function rec(label: AnyValue, ... fields: P.Pattern[]): P.Pattern {
|
||||
return P.Pattern.DCompound(P.DCompound.rec({
|
||||
ctor: P.CRec({
|
||||
label,
|
||||
arity: fields.length,
|
||||
}),
|
||||
members: new KeyedDictionary(fields.map((p, i) => [i, p])),
|
||||
}));
|
||||
}
|
||||
|
||||
export function arr(... patterns: P.Pattern[]): P.Pattern {
|
||||
return P.Pattern.DCompound(P.DCompound.arr({
|
||||
ctor: P.CArr(patterns.length),
|
||||
members: new KeyedDictionary(patterns.map((p, i) => [i, p])),
|
||||
}));
|
||||
}
|
||||
|
||||
export function dict(... entries: [AnyValue, P.Pattern][]): P.Pattern {
|
||||
return P.Pattern.DCompound(P.DCompound.dict({
|
||||
ctor: P.CDict(),
|
||||
members: new KeyedDictionary(entries),
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
export * as dataspacePatterns from './gen/dataspacePatterns.js';
|
||||
export * as dataspace from './gen/dataspace.js';
|
||||
export * as gatekeeper from './gen/gatekeeper.js';
|
||||
export * as protocol from './gen/protocol.js';
|
||||
export * as service from './gen/service.js';
|
||||
export * as stream from './gen/stream.js';
|
||||
export * as sturdy from './gen/sturdy.js';
|
||||
export * as tcp from './gen/tcp.js';
|
||||
export * as timer from './gen/timer.js';
|
||||
export * as transportAddress from './gen/transportAddress.js';
|
||||
export * as worker from './gen/worker.js';
|
|
@ -107,10 +107,7 @@ export class Membrane {
|
|||
}
|
||||
|
||||
export const INERT_REF: Ref = {
|
||||
relay: (() => {
|
||||
const a = new Actor(t => t.stop());
|
||||
return a.root;
|
||||
})(),
|
||||
relay: Actor.boot(t => t.stop()).root,
|
||||
target: {},
|
||||
};
|
||||
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
export const randomBytes = void 0;
|
||||
export const createHmac = void 0;
|
||||
|
|
Loading…
Reference in New Issue