syndicate-js/packages/core/src/runtime/dataspace.ts

131 lines
4.8 KiB
TypeScript

/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { IdentityMap, KeyedDictionary, stringify } from '@preserves/core';
import { Index, IndexObserver } from './skeleton.js';
import { Actor, AnyValue, Assertion, Entity, Facet, Handle, LocalAction, Ref, Turn } from './actor.js';
import { Observe, toObserve } from '../gen/dataspace.js';
import * as P from '../gen/dataspacePatterns.js';
export type DataspaceOptions = {
tracer?: (event: '+' | '-' | '!',
assertion: Assertion,
dataspace: Dataspace,
is_significant: boolean) => void,
dumpIndex?: boolean,
};
export class Dataspace implements Partial<Entity> {
readonly options: DataspaceOptions;
readonly index = new Index();
readonly handleMap = new IdentityMap<Handle, Assertion>();
readonly observerMap = new IdentityMap<Ref, IndexObserver<Turn>>();
constructor(options?: DataspaceOptions) {
this.options = options ?? {};
}
assert(v: Assertion, handle: Handle): void {
const is_new = this.index.addAssertion(v, Turn.active);
this.options.tracer?.('+', v, this, is_new);
if (is_new) {
const o = toObserve(v);
if (o !== void 0) {
const target = o.observer;
const captureMap = new KeyedDictionary<Array<AnyValue>, Handle, Ref>();
const observer: IndexObserver<Turn> = {
onAssert(captures, t: Turn) {
captureMap.set(captures, t.assert(target, captures));
},
onRetract(vs, t: Turn) {
t.retract(captureMap.get(vs));
captureMap.delete(vs);
},
onMessage(vs, t: Turn) {
t.message(target, vs);
},
onRemoval(t: Turn) {
captureMap.forEach((handle, _captures) => t.retract(handle));
},
dump(): string {
return Array.from(captureMap.entries()).map((handle, values) =>
`captured ${stringify(values)} handle ${handle}`).join('\n');
},
};
this.observerMap.set(target, observer);
this.index.addObserver(o.pattern, observer, Turn.active);
}
if (this.options.dumpIndex ?? false) this.index.dump();
}
this.handleMap.set(handle, v);
}
retract(handle: Handle): void {
const v = this.handleMap.get(handle);
if (v === void 0) return;
const is_last = this.index.removeAssertion(v, Turn.active);
this.options.tracer?.('-', v, this, is_last);
if (is_last) {
const o = toObserve(v);
if (o !== void 0) {
const io = this.observerMap.get(o.observer);
if (io !== void 0) {
this.index.removeObserver(o.pattern, io, Turn.active);
this.observerMap.delete(o.observer);
}
}
if (this.options.dumpIndex ?? false) this.index.dump();
}
}
message(v: Assertion): void {
this.options.tracer?.('!', v, this, true);
this.index.deliverMessage(v, Turn.active);
}
static boot(bootProc: (ds: Ref) => void, options?: DataspaceOptions): Actor {
return Actor.boot(() => {
Turn.activeFacet.preventInertCheck();
const ds = Turn.active.ref(new Dataspace(options));
bootProc(ds);
});
}
}
export function assertionObserver(f: (a: Assertion) => LocalAction | undefined): Partial<Entity> {
const assertionMap = new IdentityMap<Handle, LocalAction>();
return {
assert(a: Assertion, h: Handle): void {
const g = f(a) ?? null;
if (g !== null) {
assertionMap.set(h, g);
}
},
retract(h: Handle): void {
assertionMap.get(h)?.();
assertionMap.delete(h);
},
};
}
export function assertionFacetObserver(f: (a: Assertion) => void, inertOk: boolean = true): Partial<Entity> {
const facetMap = new IdentityMap<Handle, Facet>();
return {
assert(a: Assertion, h: Handle): void {
facetMap.set(h, Turn.active.facet(() => {
if (inertOk) Turn.activeFacet.preventInertCheck();
f(a);
}));
},
retract(h: Handle): void {
const facet = facetMap.get(h);
if (facet) Turn.active.stop(facet);
facetMap.delete(h);
},
};
}
export function assertObserve(ds: Ref, pattern: P.Pattern, e: Partial<Entity>): Handle {
return Turn.active.assert(ds, Observe({ pattern, observer: Turn.ref(e) }));
}