2021-12-01 16:24:29 +00:00
|
|
|
/// SPDX-License-Identifier: GPL-3.0-or-later
|
2024-02-03 14:59:22 +00:00
|
|
|
/// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
2021-12-01 15:27:06 +00:00
|
|
|
|
2024-01-07 12:02:01 +00:00
|
|
|
import { IdentitySet, Value, embeddedId, is, fromJS, stringify, Dictionary, KeyedSet, Tuple } from '@preserves/core';
|
2021-12-02 13:40:24 +00:00
|
|
|
import { Cell, Field, Graph } from './dataflow.js';
|
2023-02-06 14:16:57 +00:00
|
|
|
import { Caveat, runRewrites } from './rewrite.js';
|
2023-05-19 12:57:03 +00:00
|
|
|
import { ActorSpace } from './space.js';
|
2023-05-28 10:03:45 +00:00
|
|
|
import { ActionDescription, StructuredTask, TaskAction } from './task.js';
|
2023-05-19 12:57:03 +00:00
|
|
|
import { randomId } from './randomid.js';
|
2023-05-28 10:03:45 +00:00
|
|
|
import * as Q from '../gen/queuedTasks.js';
|
2021-12-01 15:27:06 +00:00
|
|
|
|
|
|
|
export type AnyValue = Value<Ref>;
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
if ('stackTraceLimit' in Error) {
|
|
|
|
Error.stackTraceLimit = Infinity;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Assertion = Value<Ref>;
|
|
|
|
export type Handle = number;
|
2021-12-01 16:13:00 +00:00
|
|
|
export type ExitReason = null | { ok: true } | { ok: false, err: unknown };
|
2021-12-03 00:46:41 +00:00
|
|
|
export type LocalAction = () => void;
|
2023-05-28 09:18:29 +00:00
|
|
|
export type DetailedAction<T = AnyValue> = LocalAction & { detail: T };
|
2021-12-01 15:27:06 +00:00
|
|
|
|
2022-01-24 13:11:41 +00:00
|
|
|
export type Assertable = Assertion | { __as_preserve__: <T>() => Value<T> } | { __as_preserve__: () => Assertion };
|
|
|
|
|
2021-12-01 15:27:06 +00:00
|
|
|
export interface Entity {
|
2021-12-03 00:46:41 +00:00
|
|
|
assert(assertion: Assertion, handle: Handle): void;
|
|
|
|
retract(handle: Handle): void;
|
|
|
|
message(body: Assertion): void;
|
|
|
|
sync(peer: Ref): void;
|
2021-12-09 21:11:46 +00:00
|
|
|
data?: unknown;
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export type Cap = Ref;
|
|
|
|
|
|
|
|
export interface Ref {
|
|
|
|
readonly relay: Facet;
|
|
|
|
readonly target: Partial<Entity>;
|
2023-02-06 14:16:57 +00:00
|
|
|
readonly attenuation?: Caveat[];
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
2021-12-09 17:51:41 +00:00
|
|
|
export class RefImpl implements Ref {
|
|
|
|
readonly relay: Facet;
|
|
|
|
readonly target: Partial<Entity>;
|
2023-02-06 14:16:57 +00:00
|
|
|
readonly attenuation?: Caveat[];
|
2021-12-09 17:51:41 +00:00
|
|
|
|
2023-02-06 14:16:57 +00:00
|
|
|
constructor(relay: Facet, target: Partial<Entity>, attenuation?: Caveat[]) {
|
2021-12-09 17:51:41 +00:00
|
|
|
this.relay = relay;
|
|
|
|
this.target = target;
|
|
|
|
this.attenuation = attenuation;
|
|
|
|
}
|
|
|
|
|
|
|
|
toString() {
|
|
|
|
let entityRepr = '' + this.target;
|
|
|
|
if (entityRepr === '[object Object]') {
|
|
|
|
entityRepr = '#' + embeddedId(this.target);
|
|
|
|
}
|
|
|
|
let sig = '';
|
|
|
|
if ('assert' in this.target) sig = sig + 'A';
|
|
|
|
if ('retract' in this.target) sig = sig + 'R';
|
|
|
|
if ('message' in this.target) sig = sig + 'M';
|
|
|
|
if ('sync' in this.target) sig = sig + 'S';
|
|
|
|
return `⌜${this.relay.idChain()}<${sig}>${entityRepr}⌝`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-01 15:27:06 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
export function isRef(v: any): v is Ref {
|
|
|
|
return 'relay' in v && v.relay instanceof Facet && 'target' in v;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function toRef(_v: any): Ref | undefined {
|
|
|
|
return isRef(_v) ? _v : void 0;
|
|
|
|
}
|
|
|
|
|
2022-01-24 13:11:41 +00:00
|
|
|
export function assertionFrom(a: Assertable): Assertion {
|
|
|
|
if (typeof a === 'object' && '__as_preserve__' in a) {
|
|
|
|
return fromJS(a);
|
|
|
|
} else {
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-16 19:31:51 +00:00
|
|
|
type OutboundAssertion = { handle: Handle, peer: Ref, crossSpace: Assertion | null, established: boolean };
|
2021-12-01 15:27:06 +00:00
|
|
|
type OutboundMap = Map<Handle, OutboundAssertion>;
|
|
|
|
|
|
|
|
let nextActorId = 0;
|
|
|
|
export const __setNextActorId = (v: number) => nextActorId = v;
|
|
|
|
|
2021-12-02 13:40:24 +00:00
|
|
|
export type DataflowGraph = Graph<DataflowBlock, Cell>;
|
2021-12-03 00:46:41 +00:00
|
|
|
export type DataflowBlock = () => void;
|
2021-12-02 13:40:24 +00:00
|
|
|
|
2021-12-01 15:27:06 +00:00
|
|
|
export class Actor {
|
2023-05-19 12:57:03 +00:00
|
|
|
name: AnyValue = Symbol.for('A-' + randomId(16));
|
|
|
|
readonly space: ActorSpace;
|
2021-12-01 15:27:06 +00:00
|
|
|
readonly root: Facet;
|
2021-12-02 13:40:24 +00:00
|
|
|
_dataflowGraph: DataflowGraph | null = null;
|
2021-12-01 15:27:06 +00:00
|
|
|
exitReason: ExitReason = null;
|
|
|
|
readonly exitHooks: Array<LocalAction> = [];
|
|
|
|
|
2023-12-01 19:53:18 +00:00
|
|
|
static boot(
|
|
|
|
bootProc: LocalAction,
|
|
|
|
initialAssertions: OutboundMap = new Map(),
|
|
|
|
space = new ActorSpace(),
|
|
|
|
): Actor {
|
2023-12-01 14:03:32 +00:00
|
|
|
const newActor = new Actor(space, initialAssertions);
|
2021-12-02 15:04:07 +00:00
|
|
|
newActor._boot(bootProc);
|
|
|
|
return newActor;
|
|
|
|
}
|
|
|
|
|
2023-05-19 12:57:03 +00:00
|
|
|
static __unsafeNew(space: ActorSpace, initialAssertions: OutboundMap = new Map()) {
|
|
|
|
return new Actor(space, initialAssertions);
|
2021-12-02 15:04:07 +00:00
|
|
|
}
|
|
|
|
|
2023-05-19 12:57:03 +00:00
|
|
|
private constructor(space: ActorSpace, initialAssertions: OutboundMap = new Map()) {
|
|
|
|
this.space = space;
|
2021-12-01 15:27:06 +00:00
|
|
|
this.root = new Facet(this, null, initialAssertions);
|
2023-05-19 12:57:03 +00:00
|
|
|
if (!space.register(this)) {
|
|
|
|
this._terminateWith({ ok: false, err: 'Spawned into shutdown ActorSpace' });
|
|
|
|
}
|
2021-12-02 15:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_boot(bootProc: LocalAction) {
|
2021-12-01 15:27:06 +00:00
|
|
|
Turn.for(new Facet(this, this.root), stopIfInertAfter(bootProc));
|
|
|
|
}
|
|
|
|
|
2021-12-02 13:40:24 +00:00
|
|
|
get dataflowGraph(): DataflowGraph {
|
|
|
|
if (this._dataflowGraph === null) {
|
|
|
|
this._dataflowGraph =
|
2023-12-03 21:35:47 +00:00
|
|
|
new Graph((b: DataflowBlock) => '' + embeddedId(b),
|
|
|
|
Cell.canonicalizer,
|
|
|
|
g => Array.from(g.values()));
|
2021-12-02 13:40:24 +00:00
|
|
|
}
|
|
|
|
return this._dataflowGraph;
|
|
|
|
}
|
|
|
|
|
2024-03-08 11:05:17 +00:00
|
|
|
atExit(a: LocalAction): () => void {
|
2021-12-01 15:27:06 +00:00
|
|
|
this.exitHooks.push(a);
|
2024-03-08 11:05:17 +00:00
|
|
|
return () => {
|
|
|
|
const i = this.exitHooks.indexOf(a);
|
|
|
|
if (i !== -1) this.exitHooks.splice(i, 1);
|
|
|
|
};
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
2021-12-24 22:29:26 +00:00
|
|
|
_terminateWith(reason: Exclude<ExitReason, null>) {
|
2021-12-01 15:27:06 +00:00
|
|
|
if (this.exitReason !== null) return;
|
|
|
|
this.exitReason = reason;
|
|
|
|
if (!reason.ok) {
|
2021-12-02 15:04:07 +00:00
|
|
|
console.error(`${this} crashed:`, reason.err);
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
2021-12-03 00:46:41 +00:00
|
|
|
this.exitHooks.forEach(hook => hook());
|
2021-12-24 22:29:26 +00:00
|
|
|
this.root._terminate(reason.ok);
|
2023-05-19 12:57:03 +00:00
|
|
|
this.space.deregister(this);
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
2021-12-02 13:40:24 +00:00
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
repairDataflowGraph() {
|
2021-12-02 13:40:24 +00:00
|
|
|
if (this._dataflowGraph === null) return;
|
2021-12-03 00:46:41 +00:00
|
|
|
this._dataflowGraph.repairDamage(block => block());
|
2021-12-02 13:40:24 +00:00
|
|
|
}
|
2021-12-02 15:04:07 +00:00
|
|
|
|
|
|
|
toString(): string {
|
2022-01-26 13:44:35 +00:00
|
|
|
return `Actor(${stringify(this.name)})`;
|
2021-12-02 15:04:07 +00:00
|
|
|
}
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export class Facet {
|
|
|
|
readonly id = nextActorId++;
|
|
|
|
readonly actor: Actor;
|
|
|
|
readonly parent: Facet | null;
|
|
|
|
readonly children = new Set<Facet>();
|
|
|
|
readonly outbound: OutboundMap;
|
|
|
|
readonly shutdownActions: Array<LocalAction> = [];
|
|
|
|
// ^ shutdownActions are not exitHooks - those run even on error. These are for clean shutdown
|
|
|
|
isLive = true;
|
|
|
|
inertCheckPreventers = 0;
|
|
|
|
|
|
|
|
constructor(actor: Actor, parent: Facet | null, initialAssertions: OutboundMap = new Map()) {
|
|
|
|
this.actor = actor;
|
|
|
|
this.parent = parent;
|
|
|
|
if (parent) parent.children.add(this);
|
|
|
|
this.outbound = initialAssertions;
|
|
|
|
}
|
|
|
|
|
2024-01-07 12:02:01 +00:00
|
|
|
wrap<T extends Tuple<any>>(f: (... args: T) => void): (... args: T) => void {
|
|
|
|
return (... args) => this.turn(() => f(... args));
|
|
|
|
}
|
|
|
|
|
2021-12-02 23:55:42 +00:00
|
|
|
turn(a: LocalAction) {
|
|
|
|
Turn.for(this, a);
|
|
|
|
}
|
|
|
|
|
2021-12-01 15:27:06 +00:00
|
|
|
onStop(a: LocalAction): void {
|
|
|
|
this.shutdownActions.push(a);
|
|
|
|
}
|
|
|
|
|
|
|
|
isInert(): boolean {
|
2021-12-12 22:01:53 +00:00
|
|
|
const noKids = this.children.size === 0;
|
|
|
|
const noOutboundHandles = this.outbound.size === 0;
|
|
|
|
// The only outbound handle the root facet of an actor may have is a link
|
|
|
|
// assertion, from _halfLink(). This is not to be considered a "real"
|
|
|
|
// assertion for purposes of keeping the facet alive!
|
|
|
|
const isRootFacet = this.parent === null;
|
|
|
|
const noInertCheckPreventers = this.inertCheckPreventers === 0;
|
|
|
|
return noKids && (noOutboundHandles || isRootFacet) && noInertCheckPreventers;
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
preventInertCheck(): () => void {
|
|
|
|
let armed = true;
|
|
|
|
this.inertCheckPreventers++;
|
|
|
|
return () => {
|
|
|
|
if (!armed) return;
|
|
|
|
armed = false;
|
|
|
|
this.inertCheckPreventers--;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-12-02 15:04:07 +00:00
|
|
|
_halfLink(other: Facet): void {
|
|
|
|
const h = nextHandle++;
|
2023-06-16 10:55:37 +00:00
|
|
|
const e = {
|
|
|
|
handle: h,
|
|
|
|
peer: { relay: other, target: new StopOnRetract() },
|
2023-06-16 19:31:51 +00:00
|
|
|
crossSpace: null,
|
2023-06-16 10:55:37 +00:00
|
|
|
established: true,
|
|
|
|
};
|
2021-12-02 15:04:07 +00:00
|
|
|
this.outbound.set(h, e);
|
|
|
|
}
|
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
_terminate(orderly: boolean): void {
|
2021-12-01 15:27:06 +00:00
|
|
|
if (!this.isLive) return;
|
|
|
|
this.isLive = false;
|
|
|
|
|
|
|
|
const parent = this.parent;
|
|
|
|
if (parent) parent.children.delete(this);
|
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
Turn.active._inFacet(this, () => {
|
|
|
|
this.children.forEach(child => child._terminate(orderly));
|
2021-12-12 22:02:25 +00:00
|
|
|
if (orderly) {
|
|
|
|
Turn.active._inFacet(parent ?? this, () => {
|
|
|
|
this.shutdownActions.forEach(a => a());
|
|
|
|
});
|
|
|
|
}
|
2021-12-03 00:46:41 +00:00
|
|
|
this.outbound.forEach(e => Turn.active._retract(e));
|
2021-12-01 15:27:06 +00:00
|
|
|
|
|
|
|
if (orderly) {
|
2021-12-24 22:29:26 +00:00
|
|
|
if (parent) {
|
|
|
|
if (parent.isInert()) {
|
|
|
|
parent._terminate(true);
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
2021-12-24 22:29:26 +00:00
|
|
|
} else {
|
|
|
|
this.actor._terminateWith({ ok: true });
|
|
|
|
}
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-12-02 15:04:07 +00:00
|
|
|
|
2023-05-28 10:24:28 +00:00
|
|
|
idChainValues(): AnyValue[] {
|
|
|
|
let pieces = [];
|
2021-12-02 15:04:07 +00:00
|
|
|
for (let f: Facet | null = this; f !== null; f = f.parent) {
|
2023-05-28 10:24:28 +00:00
|
|
|
pieces.push(f.id);
|
2021-12-02 15:04:07 +00:00
|
|
|
}
|
2023-05-28 10:24:28 +00:00
|
|
|
pieces.push(this.actor.name);
|
|
|
|
pieces.reverse();
|
|
|
|
return pieces;
|
|
|
|
}
|
|
|
|
|
|
|
|
idChain(): string {
|
|
|
|
return this.idChainValues().map(v => stringify(v)).join(':');
|
2021-12-02 15:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toString(): string {
|
2021-12-09 17:51:57 +00:00
|
|
|
return `Facet(${this.idChain()})`;
|
2021-12-02 15:04:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-28 09:38:05 +00:00
|
|
|
export const STOP_ON_RETRACT = Symbol('stop-on-retract'); // NB. NOT A GLOBAL SYMBOL
|
|
|
|
|
2021-12-02 15:04:07 +00:00
|
|
|
export class StopOnRetract implements Partial<Entity> {
|
2021-12-03 00:46:41 +00:00
|
|
|
retract(_handle: Handle): void {
|
2024-03-09 22:05:40 +00:00
|
|
|
Turn.active.stop(Turn.activeFacet);
|
2021-12-02 15:04:07 +00:00
|
|
|
}
|
2023-05-28 09:38:05 +00:00
|
|
|
data = STOP_ON_RETRACT;
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
export function _sync_impl(e: Partial<Entity>, peer: Ref): void {
|
|
|
|
e.sync ? e.sync!(peer) : Turn.active.message(peer, true);
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let nextHandle = 0;
|
|
|
|
let nextTurnId = 0;
|
|
|
|
|
|
|
|
export class Turn {
|
2021-12-03 00:46:41 +00:00
|
|
|
static active: Turn = void 0 as unknown as Turn;
|
|
|
|
|
|
|
|
static get activeFacet(): Facet {
|
|
|
|
return Turn.active.activeFacet;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ref<T extends Partial<Entity>>(e: T): Ref {
|
|
|
|
return Turn.active.ref(e);
|
|
|
|
}
|
|
|
|
|
2021-12-01 15:27:06 +00:00
|
|
|
readonly id = nextTurnId++;
|
2021-12-02 13:40:24 +00:00
|
|
|
_activeFacet: Facet;
|
2023-05-28 09:18:29 +00:00
|
|
|
queues: Map<Actor, StructuredTask<TaskAction>[]> | null;
|
2021-12-01 15:27:06 +00:00
|
|
|
|
|
|
|
static for(facet: Facet, f: LocalAction, zombieTurn = false): void {
|
|
|
|
if (!zombieTurn) {
|
|
|
|
if (facet.actor.exitReason !== null) return;
|
|
|
|
if (!facet.isLive) return;
|
|
|
|
}
|
2023-12-21 01:13:45 +00:00
|
|
|
if (!facet.actor.space.isRunning()) {
|
|
|
|
facet.actor.space.queueTask({
|
|
|
|
perform() { Turn.for(facet, f, zombieTurn); },
|
|
|
|
describe() { return { type: 'externalTurn' } },
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2021-12-01 15:27:06 +00:00
|
|
|
const t = new Turn(facet);
|
|
|
|
try {
|
2021-12-03 00:46:41 +00:00
|
|
|
const saved = Turn.active;
|
|
|
|
Turn.active = t;
|
|
|
|
try {
|
|
|
|
f();
|
|
|
|
facet.actor.repairDataflowGraph();
|
|
|
|
} finally {
|
|
|
|
Turn.active = saved;
|
|
|
|
}
|
2021-12-02 13:40:24 +00:00
|
|
|
t.deliver();
|
2021-12-01 15:27:06 +00:00
|
|
|
} catch (err) {
|
2021-12-24 22:29:26 +00:00
|
|
|
Turn.for(facet.actor.root, () => facet.actor._terminateWith({ ok: false, err }));
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-28 09:18:29 +00:00
|
|
|
private constructor(facet: Facet, queues = new Map<Actor, StructuredTask<TaskAction>[]>()) {
|
2021-12-02 13:40:24 +00:00
|
|
|
this._activeFacet = facet;
|
2021-12-01 15:27:06 +00:00
|
|
|
this.queues = queues;
|
|
|
|
}
|
|
|
|
|
2021-12-02 13:40:24 +00:00
|
|
|
get activeFacet(): Facet {
|
|
|
|
return this._activeFacet;
|
|
|
|
}
|
|
|
|
|
2021-12-01 15:27:06 +00:00
|
|
|
_inFacet(facet: Facet, f: LocalAction): void {
|
2021-12-02 13:40:24 +00:00
|
|
|
const saved = this._activeFacet;
|
|
|
|
this._activeFacet = facet;
|
2021-12-03 00:46:41 +00:00
|
|
|
f();
|
2021-12-02 13:40:24 +00:00
|
|
|
this._activeFacet = saved;
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ref<T extends Partial<Entity>>(e: T): Ref {
|
2021-12-09 17:51:41 +00:00
|
|
|
return new RefImpl(this.activeFacet, e);
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
facet(bootProc: LocalAction): Facet {
|
|
|
|
const newFacet = new Facet(this.activeFacet.actor, this.activeFacet);
|
|
|
|
this._inFacet(newFacet, stopIfInertAfter(bootProc));
|
|
|
|
return newFacet;
|
|
|
|
}
|
|
|
|
|
2021-12-02 23:55:42 +00:00
|
|
|
// Alias for syndicatec code generator to use
|
2024-03-09 22:05:40 +00:00
|
|
|
_stop(facet: Facet, continuation?: LocalAction) {
|
2021-12-02 23:55:42 +00:00
|
|
|
this.stop(facet, continuation);
|
|
|
|
}
|
|
|
|
|
2024-03-09 22:05:40 +00:00
|
|
|
stop(facet: Facet, continuation?: LocalAction) {
|
2022-01-08 12:38:10 +00:00
|
|
|
if (continuation) facet.onStop(continuation);
|
|
|
|
facet._terminate(true);
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
2021-12-02 23:55:42 +00:00
|
|
|
// Alias for syndicatec code generator to use
|
2023-05-28 09:18:29 +00:00
|
|
|
_spawn(bootProc: LocalAction | DetailedAction, initialAssertions = new IdentitySet<Handle>()): Actor {
|
2021-12-12 22:02:51 +00:00
|
|
|
return this.spawn(bootProc, initialAssertions);
|
2021-12-02 23:55:42 +00:00
|
|
|
}
|
|
|
|
|
2023-05-28 09:18:29 +00:00
|
|
|
spawn(bootProc: LocalAction | DetailedAction, initialAssertions = new IdentitySet<Handle>()): Actor {
|
2021-12-12 22:02:51 +00:00
|
|
|
return this.__spawn(bootProc, initialAssertions);
|
2021-12-02 15:04:07 +00:00
|
|
|
}
|
|
|
|
|
2023-05-28 09:18:29 +00:00
|
|
|
__spawn(bootProc: LocalAction | DetailedAction, initialAssertions = new IdentitySet<Handle>()): Actor {
|
2021-12-02 15:04:07 +00:00
|
|
|
const newOutbound: OutboundMap = new Map();
|
|
|
|
initialAssertions.forEach(key => newOutbound.set(key, this.activeFacet.outbound.get(key)!));
|
|
|
|
// ^ we trust initialAssertions, so can use `!` safely
|
|
|
|
|
2023-05-19 12:57:03 +00:00
|
|
|
const newActor = Actor.__unsafeNew(this.activeFacet.actor.space, newOutbound);
|
2023-05-28 10:03:45 +00:00
|
|
|
const detail: Q.OptionalAny<Ref> = 'detail' in bootProc ? Q.OptionalAny.some(bootProc.detail) : Q.OptionalAny.none();
|
2023-05-28 09:18:29 +00:00
|
|
|
const spawningFacet = this.activeFacet;
|
|
|
|
this.enqueue(spawningFacet,
|
|
|
|
() => {
|
|
|
|
initialAssertions.forEach(key => spawningFacet.outbound.delete(key));
|
|
|
|
newActor.space.queueTask({
|
|
|
|
perform() { newActor._boot(bootProc); },
|
|
|
|
describe() { return { type: 'bootActor', detail }; },
|
|
|
|
});
|
|
|
|
},
|
2023-05-28 10:03:45 +00:00
|
|
|
() => {
|
|
|
|
const a = new KeyedSet<number, Ref>();
|
|
|
|
initialAssertions.forEach(h => a.add(h));
|
|
|
|
return Q.ActionDescription.spawnActor({ detail, initialAssertions: a });
|
|
|
|
});
|
2021-12-02 15:04:07 +00:00
|
|
|
return newActor;
|
|
|
|
}
|
|
|
|
|
2021-12-02 23:55:42 +00:00
|
|
|
// Alias for syndicatec code generator to use
|
2021-12-12 22:02:51 +00:00
|
|
|
_spawnLink(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): Actor | null {
|
|
|
|
return this.spawnLink(bootProc, initialAssertions);
|
2021-12-02 23:55:42 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 22:02:51 +00:00
|
|
|
spawnLink(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): Actor | null {
|
|
|
|
if (!this.activeFacet.isLive) return null;
|
2021-12-02 23:55:42 +00:00
|
|
|
const newActor = this.__spawn(bootProc, initialAssertions);
|
2021-12-02 15:04:07 +00:00
|
|
|
this.activeFacet._halfLink(newActor.root);
|
|
|
|
newActor.root._halfLink(this.activeFacet);
|
2021-12-12 22:02:51 +00:00
|
|
|
return newActor;
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
stopActor(): void {
|
2023-05-28 09:18:29 +00:00
|
|
|
this.enqueue(this.activeFacet.actor.root,
|
|
|
|
() => this.activeFacet.actor._terminateWith({ ok: true }),
|
2023-05-28 10:03:45 +00:00
|
|
|
() => Q.ActionDescription.stopActor(Q.OptionalAny.none()));
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
crash(err: Error): void {
|
2023-05-28 09:18:29 +00:00
|
|
|
this.enqueue(this.activeFacet.actor.root,
|
|
|
|
() => this.activeFacet.actor._terminateWith({ ok: false, err }),
|
2023-05-28 10:03:45 +00:00
|
|
|
() => Q.ActionDescription.stopActor(Q.OptionalAny.some(Dictionary.fromJS({
|
|
|
|
message: err.message,
|
|
|
|
stack: err.stack ? err.stack : false,
|
|
|
|
}))));
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
2022-04-28 20:03:30 +00:00
|
|
|
field<V>(initial: V, name?: string): Field<V> {
|
2021-12-02 13:40:24 +00:00
|
|
|
return new Field(this.activeFacet.actor.dataflowGraph, initial, name);
|
|
|
|
}
|
|
|
|
|
2021-12-02 23:55:42 +00:00
|
|
|
// Alias for syndicatec code generator to use
|
|
|
|
_dataflow(a: LocalAction) {
|
|
|
|
this.dataflow(a);
|
|
|
|
}
|
|
|
|
|
2021-12-02 13:40:24 +00:00
|
|
|
dataflow(a: LocalAction) {
|
|
|
|
const f = this.activeFacet;
|
2021-12-11 14:43:56 +00:00
|
|
|
f.preventInertCheck();
|
2021-12-03 00:46:41 +00:00
|
|
|
const b = () => f.isLive && Turn.active._inFacet(f, a);
|
|
|
|
f.onStop(() => f.actor.dataflowGraph.forgetSubject(b));
|
|
|
|
f.actor.dataflowGraph.withSubject(b, b);
|
2021-12-02 13:40:24 +00:00
|
|
|
}
|
|
|
|
|
2021-12-09 21:12:02 +00:00
|
|
|
assertDataflow(assertionFunction: () => {
|
|
|
|
target: Ref | undefined,
|
2022-01-24 13:11:41 +00:00
|
|
|
assertion: Assertable | undefined
|
2021-12-09 21:12:02 +00:00
|
|
|
}) {
|
2021-12-02 13:40:24 +00:00
|
|
|
let handle: Handle | undefined = void 0;
|
|
|
|
let target: Ref | undefined = void 0;
|
2022-01-24 13:11:41 +00:00
|
|
|
let assertion: Assertable | undefined = void 0;
|
2021-12-03 00:46:41 +00:00
|
|
|
this.dataflow(() => {
|
|
|
|
let {target: nextTarget, assertion: nextAssertion} = assertionFunction();
|
2021-12-02 13:40:24 +00:00
|
|
|
if (target !== nextTarget || !is(assertion, nextAssertion)) {
|
|
|
|
target = nextTarget;
|
|
|
|
assertion = nextAssertion;
|
2021-12-03 00:46:41 +00:00
|
|
|
handle = Turn.active.replace(target, handle, assertion);
|
2021-12-02 13:40:24 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-01-24 13:11:41 +00:00
|
|
|
assert(ref: Ref, assertion: Assertable): Handle {
|
2021-12-01 15:27:06 +00:00
|
|
|
const h = nextHandle++;
|
|
|
|
this._assert(ref, assertion, h);
|
|
|
|
return h;
|
|
|
|
}
|
|
|
|
|
2022-01-24 13:11:41 +00:00
|
|
|
_assert(ref: Ref, assertable: Assertable, h: Handle) {
|
|
|
|
const assertion = assertionFrom(assertable);
|
2021-12-01 15:27:06 +00:00
|
|
|
const a = runRewrites(ref.attenuation, assertion);
|
|
|
|
if (a !== null) {
|
2023-06-16 10:55:37 +00:00
|
|
|
const crossSpace = this.activeFacet.actor.space !== ref.relay.actor.space;
|
2023-06-16 19:31:51 +00:00
|
|
|
const e = { handle: h, peer: ref, crossSpace: crossSpace ? a : null, established: false };
|
2021-12-01 15:27:06 +00:00
|
|
|
this.activeFacet.outbound.set(h, e);
|
2023-05-28 09:18:29 +00:00
|
|
|
this.enqueue(ref.relay,
|
|
|
|
() => {
|
|
|
|
e.established = true;
|
2023-06-16 10:55:37 +00:00
|
|
|
if (crossSpace) {
|
|
|
|
ref.relay.actor.space.registerInbound(h, ref, a);
|
|
|
|
}
|
2023-05-28 09:18:29 +00:00
|
|
|
ref.target.assert?.(a, h);
|
|
|
|
},
|
2023-05-28 10:03:45 +00:00
|
|
|
() => Q.ActionDescription.assert({
|
2023-05-28 09:18:29 +00:00
|
|
|
target: ref,
|
2023-06-16 19:31:51 +00:00
|
|
|
crossSpace,
|
2023-05-28 09:18:29 +00:00
|
|
|
handle: h,
|
2023-06-16 19:31:51 +00:00
|
|
|
assertion: a,
|
2023-05-28 10:03:45 +00:00
|
|
|
}));
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
retract(h: Handle | undefined): void {
|
|
|
|
if (h !== void 0) {
|
|
|
|
const e = this.activeFacet.outbound.get(h);
|
|
|
|
if (e === void 0) return;
|
|
|
|
this._retract(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-24 13:11:41 +00:00
|
|
|
replace(ref: Ref | undefined, h: Handle | undefined, assertion: Assertable | undefined): Handle | undefined {
|
2021-12-02 13:40:24 +00:00
|
|
|
const newHandle = (assertion === void 0 || ref === void 0)
|
|
|
|
? void 0
|
|
|
|
: this.assert(ref, assertion);
|
2021-12-01 15:27:06 +00:00
|
|
|
this.retract(h);
|
|
|
|
return newHandle;
|
|
|
|
}
|
|
|
|
|
|
|
|
_retract(e: OutboundAssertion): void {
|
|
|
|
this.activeFacet.outbound.delete(e.handle);
|
2023-05-28 09:18:29 +00:00
|
|
|
this.enqueue(e.peer.relay,
|
|
|
|
() => {
|
|
|
|
if (e.established) {
|
|
|
|
e.established = false;
|
2023-06-16 19:31:51 +00:00
|
|
|
if (e.crossSpace) e.peer.relay.actor.space.deregisterInbound(e.handle);
|
2023-05-28 09:18:29 +00:00
|
|
|
e.peer.target.retract?.(e.handle);
|
|
|
|
}
|
|
|
|
},
|
2023-05-28 10:03:45 +00:00
|
|
|
() => Q.ActionDescription.retract({
|
2023-05-28 09:18:29 +00:00
|
|
|
target: e.peer,
|
2023-06-16 19:31:51 +00:00
|
|
|
crossSpace: e.crossSpace !== null,
|
2023-05-28 09:18:29 +00:00
|
|
|
handle: e.handle,
|
2023-05-28 10:03:45 +00:00
|
|
|
}));
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
sync(ref: Ref): Promise<void> {
|
|
|
|
return new Promise(resolve => this._sync(ref, this.ref({ message() { resolve() } })));
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_sync(ref: Ref, peer: Ref): void {
|
2023-05-28 09:18:29 +00:00
|
|
|
this.enqueue(ref.relay,
|
|
|
|
() => _sync_impl(ref.target, peer),
|
2023-05-28 10:03:45 +00:00
|
|
|
() => Q.ActionDescription.sync({
|
2023-05-28 09:18:29 +00:00
|
|
|
target: ref,
|
|
|
|
callback: peer,
|
2023-05-28 10:03:45 +00:00
|
|
|
}));
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
2022-01-24 13:11:41 +00:00
|
|
|
message(ref: Ref, assertable: Assertable): void {
|
|
|
|
const assertion = assertionFrom(assertable);
|
2021-12-01 15:27:06 +00:00
|
|
|
const a = runRewrites(ref.attenuation, assertion);
|
2023-05-28 09:18:29 +00:00
|
|
|
if (a !== null) {
|
|
|
|
this.enqueue(ref.relay,
|
|
|
|
() => ref.target.message?.(assertion),
|
2023-05-28 10:03:45 +00:00
|
|
|
() => Q.ActionDescription.message({
|
2023-05-28 09:18:29 +00:00
|
|
|
target: ref,
|
|
|
|
assertion,
|
2023-05-28 10:03:45 +00:00
|
|
|
}));
|
2023-05-28 09:18:29 +00:00
|
|
|
}
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
2022-01-20 19:48:30 +00:00
|
|
|
every(periodMilliseconds: number, a: LocalAction): any {
|
|
|
|
const facet = this.activeFacet;
|
|
|
|
facet.preventInertCheck();
|
|
|
|
let handle: any = setInterval(() => {
|
|
|
|
facet.turn(a);
|
|
|
|
}, periodMilliseconds);
|
|
|
|
facet.onStop(() => {
|
|
|
|
if (handle !== null) {
|
|
|
|
clearInterval(handle);
|
|
|
|
handle = null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
2021-12-12 22:02:58 +00:00
|
|
|
after(delayMilliseconds: number, a: LocalAction): any {
|
|
|
|
const facet = this.activeFacet;
|
|
|
|
const release = facet.preventInertCheck();
|
|
|
|
return setTimeout(() => {
|
|
|
|
release();
|
|
|
|
facet.turn(a);
|
|
|
|
}, delayMilliseconds);
|
|
|
|
}
|
|
|
|
|
2023-05-28 10:03:45 +00:00
|
|
|
enqueue(relay: Facet, a0: LocalAction, detail: () => ActionDescription): void {
|
2021-12-01 15:27:06 +00:00
|
|
|
if (this.queues === null) {
|
|
|
|
throw new Error("Attempt to reuse a committed Turn");
|
|
|
|
}
|
2023-05-28 09:18:29 +00:00
|
|
|
const a: StructuredTask<TaskAction> = {
|
|
|
|
perform() { Turn.active._inFacet(relay, a0); },
|
2023-05-28 10:03:45 +00:00
|
|
|
describe() { return { targetFacet: relay, action: detail() }; },
|
2023-05-28 09:18:29 +00:00
|
|
|
};
|
2021-12-02 13:40:24 +00:00
|
|
|
this.queues.get(relay.actor)?.push(a) ?? this.queues.set(relay.actor, [a]);
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
|
2021-12-02 13:40:24 +00:00
|
|
|
deliver() {
|
|
|
|
this.queues!.forEach((q, actor) =>
|
2023-05-28 09:18:29 +00:00
|
|
|
actor.space.queueTask({
|
|
|
|
perform() { Turn.for(actor.root, () => q.forEach(f => f.perform())); },
|
|
|
|
describe() { return { type: 'turn', tasks: q.map(f => f.describe()) }; },
|
|
|
|
}));
|
2021-12-02 13:40:24 +00:00
|
|
|
this.queues = null;
|
2021-12-01 15:27:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function stopIfInertAfter(a: LocalAction): LocalAction {
|
2021-12-03 00:46:41 +00:00
|
|
|
return () => {
|
|
|
|
const facet = Turn.activeFacet;
|
|
|
|
a();
|
2023-05-28 09:18:29 +00:00
|
|
|
Turn.active.enqueue(facet,
|
|
|
|
() => {
|
|
|
|
if ((facet.parent && !facet.parent.isLive) || facet.isInert()) {
|
|
|
|
Turn.active.stop(facet);
|
|
|
|
}
|
|
|
|
},
|
2023-05-28 10:03:45 +00:00
|
|
|
Q.ActionDescription.inertCheck);
|
2021-12-01 15:27:06 +00:00
|
|
|
};
|
|
|
|
}
|