2021-12-01 16:24:29 +00:00
|
|
|
/// SPDX-License-Identifier: GPL-3.0-or-later
|
2023-01-17 10:43:15 +00:00
|
|
|
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
2021-01-11 22:35:36 +00:00
|
|
|
|
2023-05-02 13:19:38 +00:00
|
|
|
import { IdentityMap, KeyedDictionary, stringify } from '@preserves/core';
|
|
|
|
import { Index, IndexObserver } from './skeleton.js';
|
2023-12-01 19:53:18 +00:00
|
|
|
import { Actor, AnyValue, Assertion, DetailedAction, Entity, Facet, Handle, LocalAction, Ref, Turn } from './actor.js';
|
2022-01-24 13:11:41 +00:00
|
|
|
import { Observe, toObserve } from '../gen/dataspace.js';
|
2021-12-02 23:55:42 +00:00
|
|
|
import * as P from '../gen/dataspacePatterns.js';
|
2021-01-11 22:35:36 +00:00
|
|
|
|
2021-12-11 15:49:24 +00:00
|
|
|
export type DataspaceOptions = {
|
2022-01-16 14:11:01 +00:00
|
|
|
tracer?: (event: '+' | '-' | '!',
|
|
|
|
assertion: Assertion,
|
|
|
|
dataspace: Dataspace,
|
|
|
|
is_significant: boolean) => void,
|
2021-12-11 14:43:32 +00:00
|
|
|
dumpIndex?: boolean,
|
|
|
|
};
|
|
|
|
|
2023-05-28 10:25:44 +00:00
|
|
|
export class DataspaceObserver implements IndexObserver<Turn> {
|
|
|
|
readonly captureMap = new KeyedDictionary<Array<AnyValue>, Handle, Ref>();
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
public readonly target: Ref,
|
|
|
|
) {}
|
|
|
|
|
|
|
|
onAssert(captures: Assertion[], t: Turn) {
|
|
|
|
this.captureMap.set(captures, t.assert(this.target, captures));
|
|
|
|
}
|
|
|
|
|
|
|
|
onRetract(vs: Assertion[], t: Turn) {
|
|
|
|
t.retract(this.captureMap.get(vs));
|
|
|
|
this.captureMap.delete(vs);
|
|
|
|
}
|
|
|
|
|
|
|
|
onMessage(vs: Assertion[], t: Turn) {
|
|
|
|
t.message(this.target, vs);
|
|
|
|
}
|
|
|
|
|
|
|
|
onRemoval(t: Turn) {
|
|
|
|
this.captureMap.forEach((handle, _captures) => t.retract(handle));
|
|
|
|
}
|
|
|
|
|
|
|
|
dump(): string {
|
|
|
|
return Array.from(this.captureMap.entries()).map((handle, values) =>
|
|
|
|
`captured ${stringify(values)} handle ${handle}`).join('\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-01 16:13:00 +00:00
|
|
|
export class Dataspace implements Partial<Entity> {
|
2023-12-01 19:53:18 +00:00
|
|
|
private static _global: Ref | undefined = void 0;
|
|
|
|
|
|
|
|
static get global(): Ref {
|
|
|
|
if (Dataspace._global === void 0) {
|
|
|
|
Dataspace.boot(ds => {
|
2023-12-20 08:39:07 +00:00
|
|
|
Turn.activeFacet.actor.name = Symbol.for('Dataspace.global');
|
2023-12-01 19:53:18 +00:00
|
|
|
// Cast to any because `global` is otherwise readonly (!)
|
|
|
|
Dataspace._global = ds;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return Dataspace._global!;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Alias for syndicatec code generator to use, plus hook for fallback use outside a Turn
|
|
|
|
static _spawnLink(bootProc: LocalAction | DetailedAction): Actor | null {
|
|
|
|
if (Turn.active) {
|
|
|
|
return Turn.active._spawnLink(bootProc);
|
|
|
|
} else {
|
|
|
|
throw new Error("Cannot spawnLink outside an active Turn");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Alias for syndicatec code generator to use, plus hook for fallback use outside a Turn
|
|
|
|
static _spawn(bootProc: LocalAction | DetailedAction): Actor {
|
|
|
|
if (Turn.active) {
|
|
|
|
return Turn.active._spawn(bootProc);
|
|
|
|
} else {
|
|
|
|
return Actor.boot(bootProc, void 0, Dataspace.global.relay.actor.space);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-11 14:43:32 +00:00
|
|
|
readonly options: DataspaceOptions;
|
2021-12-02 13:40:24 +00:00
|
|
|
readonly index = new Index();
|
2022-01-16 14:11:01 +00:00
|
|
|
readonly handleMap = new IdentityMap<Handle, Assertion>();
|
2023-05-28 10:25:44 +00:00
|
|
|
readonly observerMap = new IdentityMap<Ref, DataspaceObserver>();
|
2023-05-25 21:46:29 +00:00
|
|
|
readonly data = this;
|
2021-12-02 13:40:24 +00:00
|
|
|
|
2021-12-11 14:43:32 +00:00
|
|
|
constructor(options?: DataspaceOptions) {
|
|
|
|
this.options = options ?? {};
|
|
|
|
}
|
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
assert(v: Assertion, handle: Handle): void {
|
2023-05-12 09:32:17 +00:00
|
|
|
const is_new = this.index.addAssertion(v, Turn.active);
|
2022-01-16 14:11:01 +00:00
|
|
|
this.options.tracer?.('+', v, this, is_new);
|
|
|
|
if (is_new) {
|
|
|
|
const o = toObserve(v);
|
|
|
|
if (o !== void 0) {
|
2023-05-02 13:19:38 +00:00
|
|
|
const target = o.observer;
|
2023-05-28 10:25:44 +00:00
|
|
|
const observer = new DataspaceObserver(target);
|
2023-05-02 13:19:38 +00:00
|
|
|
this.observerMap.set(target, observer);
|
2023-05-12 09:32:17 +00:00
|
|
|
this.index.addObserver(o.pattern, observer, Turn.active);
|
2022-01-16 14:11:01 +00:00
|
|
|
}
|
|
|
|
if (this.options.dumpIndex ?? false) this.index.dump();
|
2021-12-02 13:40:24 +00:00
|
|
|
}
|
2022-01-16 14:11:01 +00:00
|
|
|
this.handleMap.set(handle, v);
|
2021-01-11 22:35:36 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
retract(handle: Handle): void {
|
2022-01-16 14:11:01 +00:00
|
|
|
const v = this.handleMap.get(handle);
|
|
|
|
if (v === void 0) return;
|
2023-05-12 09:32:17 +00:00
|
|
|
const is_last = this.index.removeAssertion(v, Turn.active);
|
2022-01-16 14:11:01 +00:00
|
|
|
this.options.tracer?.('-', v, this, is_last);
|
|
|
|
if (is_last) {
|
|
|
|
const o = toObserve(v);
|
|
|
|
if (o !== void 0) {
|
2023-05-02 13:19:38 +00:00
|
|
|
const io = this.observerMap.get(o.observer);
|
|
|
|
if (io !== void 0) {
|
2023-05-12 09:32:17 +00:00
|
|
|
this.index.removeObserver(o.pattern, io, Turn.active);
|
2023-05-02 13:19:38 +00:00
|
|
|
this.observerMap.delete(o.observer);
|
|
|
|
}
|
2022-01-16 14:11:01 +00:00
|
|
|
}
|
|
|
|
if (this.options.dumpIndex ?? false) this.index.dump();
|
2021-12-02 13:40:24 +00:00
|
|
|
}
|
2021-01-11 22:35:36 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
message(v: Assertion): void {
|
2022-01-16 14:11:01 +00:00
|
|
|
this.options.tracer?.('!', v, this, true);
|
2023-05-12 09:32:17 +00:00
|
|
|
this.index.deliverMessage(v, Turn.active);
|
2021-01-11 22:35:36 +00:00
|
|
|
}
|
2021-12-09 21:12:41 +00:00
|
|
|
|
2021-12-11 14:43:32 +00:00
|
|
|
static boot(bootProc: (ds: Ref) => void, options?: DataspaceOptions): Actor {
|
2021-12-09 21:12:41 +00:00
|
|
|
return Actor.boot(() => {
|
|
|
|
Turn.activeFacet.preventInertCheck();
|
2021-12-11 14:43:32 +00:00
|
|
|
const ds = Turn.active.ref(new Dataspace(options));
|
2021-12-09 21:12:41 +00:00
|
|
|
bootProc(ds);
|
|
|
|
});
|
|
|
|
}
|
2021-01-11 22:35:36 +00:00
|
|
|
}
|
2021-12-02 23:55:42 +00:00
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
export function assertionObserver(f: (a: Assertion) => LocalAction | undefined): Partial<Entity> {
|
2021-12-02 23:55:42 +00:00
|
|
|
const assertionMap = new IdentityMap<Handle, LocalAction>();
|
|
|
|
return {
|
2021-12-03 00:46:41 +00:00
|
|
|
assert(a: Assertion, h: Handle): void {
|
|
|
|
const g = f(a) ?? null;
|
2021-12-02 23:55:42 +00:00
|
|
|
if (g !== null) {
|
|
|
|
assertionMap.set(h, g);
|
|
|
|
}
|
|
|
|
},
|
2021-12-03 00:46:41 +00:00
|
|
|
retract(h: Handle): void {
|
|
|
|
assertionMap.get(h)?.();
|
2021-12-02 23:55:42 +00:00
|
|
|
assertionMap.delete(h);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
export function assertionFacetObserver(f: (a: Assertion) => void, inertOk: boolean = true): Partial<Entity> {
|
2021-12-02 23:55:42 +00:00
|
|
|
const facetMap = new IdentityMap<Handle, Facet>();
|
|
|
|
return {
|
2021-12-03 00:46:41 +00:00
|
|
|
assert(a: Assertion, h: Handle): void {
|
|
|
|
facetMap.set(h, Turn.active.facet(() => {
|
|
|
|
if (inertOk) Turn.activeFacet.preventInertCheck();
|
|
|
|
f(a);
|
2021-12-02 23:55:42 +00:00
|
|
|
}));
|
|
|
|
},
|
2021-12-03 00:46:41 +00:00
|
|
|
retract(h: Handle): void {
|
2021-12-02 23:55:42 +00:00
|
|
|
const facet = facetMap.get(h);
|
2021-12-03 00:46:41 +00:00
|
|
|
if (facet) Turn.active.stop(facet);
|
2021-12-02 23:55:42 +00:00
|
|
|
facetMap.delete(h);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-12-03 00:46:41 +00:00
|
|
|
export function assertObserve(ds: Ref, pattern: P.Pattern, e: Partial<Entity>): Handle {
|
2022-01-24 13:11:41 +00:00
|
|
|
return Turn.active.assert(ds, Observe({ pattern, observer: Turn.ref(e) }));
|
2021-12-02 23:55:42 +00:00
|
|
|
}
|