import { Assertion, Entity, Handle, Ref, Turn } from 'actor'; import { Dictionary, IdentityMap, is, preserves, Record, Tuple } from 'preserves'; import { Bag, ChangeDescription } from './bag'; // Q. Why keep "Observe"? Why not do the clever trick of asserting the // observer, and having the dataspace read the implicit pattern it's // interested in off its attenuator? // // A. (1) Because we want to have the possibility of more than one // variety of pattern language. For example, here we have a simple // label match, but we'll quickly want something about as rich as the // pattern language in attenuators. And later, we could easily want // something with, perhaps, as much power as RELAX-NG or similar. (2) // Because we want to have onlookers have some hope of seeing whether // a pattern of interest to them is being observed, and if we used // attenuators to match, we'd have to expose visibility into // attenuators into the pattern language. See next question. // // Q. What kinds of constraints on the pattern language are there? // // A. It should be fast to evaluate; ideally, JITtable? It should // allow patterns on patterns, to some degree. This is for two // reasons: we occasionally want to observe observers, and we // frequently want to use attenuators limit the kinds of patterns that // a principal may observe. As such, it's good to choose a language // that enforced some kind of normal forms for its patterns, so // observer-observers and attenuator patterns don't have to deal with // spurious variation. // export const Observe = Record.makeConstructor<{label: Assertion, observer: Ref}, Ref>()( Symbol.for('Observe'), ['label', 'observer']); export class Dataspace implements Partial { readonly handleMap: IdentityMap> = new IdentityMap(); readonly assertions = new Bag(); readonly subscriptions: Dictionary>> = new Dictionary(); assert(turn: Turn, rec: Assertion, handle: Handle): void { // console.log(preserves`ds ${turn.actor.id} assert ${rec} ${handle}`); if (!Record.isRecord, Ref>(rec)) return; this.handleMap.set(handle, rec); if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return; if (Observe.isClassOf(rec)) { const label = Observe._.label(rec)!; const observer = Observe._.observer(rec) as Ref; const seen = new Dictionary(); if (!this.subscriptions.has(label)) this.subscriptions.set(label, new Map()); this.subscriptions.get(label)!.set(observer, seen); this.assertions.forEach((_count, prev) => is((prev as Record).label, label) && seen.set(prev, turn.assert(observer, prev))); } this.subscriptions.get(rec.label)?.forEach((seen, peer) => seen.has(rec) || seen.set(rec, turn.assert(peer, rec))); } retract(turn: Turn, upstreamHandle: Handle): void { const rec = this.handleMap.get(upstreamHandle); // console.log(preserves`ds ${turn.actor.id} retract ${rec} ${upstreamHandle}`); if (rec === void 0) return; this.handleMap.delete(upstreamHandle); if (this.assertions.change(rec, -1) !== ChangeDescription.PRESENT_TO_ABSENT) return; this.subscriptions.get(rec.label)?.forEach((seen, _peer) => { const downstreamHandle = seen.get(rec); if (downstreamHandle !== void 0) { turn.retract(downstreamHandle); seen.delete(rec); } }); if (Observe.isClassOf(rec)) { let peerMap = this.subscriptions.get(Observe._.label(rec)!)!; peerMap.delete(Observe._.observer(rec) as Ref); if (peerMap.size === 0) this.subscriptions.delete(Observe._.label(rec)!); } } message(turn: Turn, rec: Assertion): void { // console.log(preserves`ds ${turn.actor.id} message ${rec}`); if (!Record.isRecord, Ref>(rec)) return; this.subscriptions.get(rec.label)?.forEach((_seen, peer) => turn.message(peer, rec)); } }