/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones import { IdentityMap, KeyedDictionary, stringify, strip } from '@preserves/core'; import { Index, IndexObserver } from './skeleton.js'; import { Actor, AnyValue, Assertion, DetailedAction, 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 DataspaceObserver implements IndexObserver { readonly captureMap = new KeyedDictionary, Handle>(); 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'); } } export class Dataspace implements Partial { private static _local: Ref | undefined = void 0; static get local(): Ref { if (Dataspace._local === void 0) { Dataspace.boot(ds => { Turn.activeFacet.actor.name = Symbol.for('Dataspace.local'); Dataspace._local = ds; }); } return Dataspace._local!; } // 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.local.relay.actor.space); } } readonly options: DataspaceOptions; readonly index = new Index(); readonly handleMap = new IdentityMap(); readonly observerMap = new KeyedDictionary(); readonly data = this; 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(strip(v)); if (o !== void 0) { const io = new DataspaceObserver(o.observer); this.observerMap.set(o, io); this.index.addObserver(o.pattern, io, 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; this.handleMap.delete(handle); const is_last = this.index.removeAssertion(v, Turn.active); this.options.tracer?.('-', v, this, is_last); if (is_last) { const o = toObserve(strip(v)); if (o !== void 0) { const io = this.observerMap.get(o); if (io !== void 0) { this.index.removeObserver(o.pattern, io, Turn.active); this.observerMap.delete(o); } } 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 { const assertionMap = new IdentityMap(); 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 { const facetMap = new IdentityMap(); 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): Handle { return Turn.active.assert(ds, Observe({ pattern, observer: Turn.ref(e) })); }