# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense ## https://git.syndicate-lang.org/syndicate-lang/syndicate-rkt/src/commit/90c4c60699069b496491b81ee63b5a45ffd638cb/syndicate/HOWITWORKS.md import std/[hashes, options, sets, tables] import preserves import ./actors, ./bags, ./patterns import ./protocols/dataspacePatterns type DCompound = dataspacePatterns.DCompound Pattern = dataspacePatterns.Pattern Path = seq[Value] ClassKind = enum classNone, classRecord, classSequence, classDictionary Class = object kind: ClassKind label: Value arity: int func classOf(v: Value): Class = case v.kind of pkRecord: Class(kind: classRecord, label: v.label, arity: v.arity) of pkSequence: Class(kind: classSequence, arity: v.len) of pkDictionary: Class(kind: classDictionary) else: Class(kind: classNone) proc classOf(p: Pattern): Class = if p.orKind == PatternKind.DCompound: case p.dcompound.orKind of DCompoundKind.rec: Class(kind: classRecord, label: p.dcompound.rec.label, arity: p.dcompound.rec.fields.len) of DCompoundKind.arr: Class(kind: classSequence, arity: p.dcompound.arr.items.len) of DCompoundKind.dict: Class(kind: classDictionary) else: Class(kind: classNone) type EventKind = enum addedEvent, removedEvent, messageEvent AssertionCache = HashSet[Value] ObserverGroup = ref object # Endpoints cachedCaptures: Bag[Captures] observers: Table[Cap, TableRef[Captures, Handle]] Leaf = ref object cache: AssertionCache observerGroups: Table[Paths, ObserverGroup] LeafMap = TableRef[seq[Value], Leaf] Continuation = ref object cache: AssertionCache leafMap: Table[Paths, LeafMap] func isEmpty(leaf: Leaf): bool = leaf.cache.len == 0 and leaf.observerGroups.len == 0 func isEmpty(cont: Continuation): bool = cont.cache.len == 0 and cont.leafMap.len == 0 type ContinuationProc = proc (c: Continuation; v: Value) {.closure.} LeafProc = proc (l: Leaf; v: Value) {.closure.} ObserverProc = proc (turn: var Turn; group: ObserverGroup; vs: seq[Value]) {.closure.} proc getLeaves(cont: Continuation; constPaths: Paths): LeafMap = result = cont.leafMap.getOrDefault(constPaths) if result.isNil: new result cont.leafMap[constPaths] = result assert not cont.isEmpty for ass in cont.cache: let key = projectPaths(ass, constPaths) if key.isSome: var leaf = result.getOrDefault(get key) if leaf.isNil: new leaf result[get key] = leaf leaf.cache.incl(ass) proc getLeaf(leafMap: LeafMap; constVals: seq[Value]): Leaf = result = leafMap.getOrDefault(constVals) if result.isNil: new result leafMap[constVals] = result type Selector = tuple[popCount: int; index: Value] Node = ref object continuation: Continuation edges: Table[Selector, TableRef[Class, Node]] func isEmpty(node: Node): bool = node.continuation.isEmpty and node.edges.len == 0 type TermStack = seq[Value] proc push(stack: TermStack; val: Value): Termstack = result = stack add(result, val) proc pop(stack: TermStack; n: int): TermStack = assert n <= stack.len stack[stack.low..(stack.high-n)] proc top(stack: TermStack): Value = assert stack.len > 0 stack[stack.high] proc modify(node: Node; turn: var Turn; outerValue: Value; event: EventKind; modCont: ContinuationProc; modLeaf: LeafProc; modObs: ObserverProc) = proc walk(cont: Continuation; turn: var Turn) = modCont(cont, outerValue) for constPaths, constValMap in cont.leafMap.pairs: let constVals = projectPaths(outerValue, constPaths) if constVals.isSome: case event of addedEvent, messageEvent: let leaf = constValMap.getLeaf(get constVals) modLeaf(leaf, outerValue) for capturePaths, observerGroup in leaf.observerGroups.pairs: let captures = projectPaths(outerValue, capturePaths) if captures.isSome: modObs(turn, observerGroup, get captures) of removedEvent: let leaf = constValMap.getOrDefault(get constVals) if not leaf.isNil: modLeaf(leaf, outerValue) for capturePaths, observerGroup in leaf.observerGroups.pairs: let captures = projectPaths(outerValue, capturePaths) if captures.isSome: modObs(turn, observerGroup, get captures) if leaf.isEmpty: constValMap.del(get constVals) proc walk(node: Node; turn: var Turn; termStack: TermStack) = walk(node.continuation, turn) for selector, table in node.edges: let nextStack = pop(termStack, selector.popCount) nextValue = step(nextStack.top, selector.index) if nextValue.isSome: let nextClass = classOf(get nextValue) if nextClass.kind != classNone: let nextNode = table.getOrDefault(nextClass) if not nextNode.isNil: walk(nextNode, turn, push(nextStack, get nextValue)) if event == removedEvent and nextNode.isEmpty: table.del(nextClass) walk(node, turn, @[@[outerValue].toPreserves]) proc getOrNew[A, B, C](t: var Table[A, TableRef[B, C]], k: A): TableRef[B, C] = result = t.getOrDefault(k) if result.isNil: result = newTable[B, C]() t[k] = result iterator pairs(dc: DCompound): (Value, Pattern) = case dc.orKind of DCompoundKind.rec: for i, p in dc.rec.fields: yield (i.toPreserves, p,) of DCompoundKind.arr: for i, p in dc.arr.items: yield (i.toPreserves, p,) of DCompoundKind.dict: for pair in dc.dict.entries.pairs: yield pair proc extendWalk(node: Node; popCount: Natural; stepIndex: Value; pat: Pattern; path: var Path): tuple[popCount: Natural, nextNode: Node] = case pat.orKind of PatternKind.DDiscard, PatternKind.DLit: result = (popCount, node) of PatternKind.DBind: result = extendWalk(node, popCount, stepIndex, pat.dbind.pattern, path) of PatternKind.DCompound: let selector: Selector = (popCount, stepIndex,) table = node.edges.getOrNew(selector) class = classOf pat result.nextNode = table.getOrDefault(class) if result.nextNode.isNil: new result.nextNode table[class] = result.nextNode new result.nextNode.continuation for a in node.continuation.cache: var v = step(a, path) if v.isSome and class == classOf(get v): result.nextNode.continuation.cache.incl a result.popCount = 0 for step, p in pat.dcompound.pairs: add(path, step) result = extendWalk(result.nextNode, result.popCount, step, p, path) discard pop(path) inc(result.popCount) proc extend(node: var Node; pat: Pattern): Continuation = var path: Path extendWalk(node, 0, 0.toPreserves, pat, path).nextNode.continuation type Index* = object allAssertions: Bag[Value] root: Node proc initIndex*(): Index = Index(root: Node(continuation: Continuation())) proc getEndpoints(leaf: Leaf; capturePaths: Paths): ObserverGroup = result = leaf.observerGroups.getOrDefault(capturePaths) if result.isNil: new result leaf.observerGroups[capturePaths] = result for term in leaf.cache: # leaf.cache would be empty if observers come before assertions let captures = projectPaths(term, capturePaths) if captures.isSome: discard result.cachedCaptures.change(get captures, +1) proc add*(index: var Index; turn: var Turn; pattern: Pattern; observer: Cap) = let cont = index.root.extend(pattern) analysis = analyse pattern constValMap = cont.getLeaves(analysis.constPaths) leaf = constValMap.getLeaf(analysis.constValues) endpoints = leaf.getEndpoints(analysis.capturePaths) # TODO if endpoints.cachedCaptures.len > 0: var captureMap = newTable[seq[Value], Handle]() for capture in endpoints.cachedCaptures.items: captureMap[capture] = publish(turn, observer, capture) endpoints.observers[observer] = captureMap proc remove*(index: var Index; turn: var Turn; pattern: Pattern; observer: Cap) = let cont = index.root.extend(pattern) analysis = analyse pattern constValMap = cont.leafMap.getOrDefault(analysis.constPaths) if not constValMap.isNil: let leaf = constValMap.getOrDefault(analysis.constValues) if not leaf.isNil: let endpoints = leaf.observerGroups.getOrDefault(analysis.capturePaths) if not endpoints.isNil: var captureMap: TableRef[seq[Value], Handle] if endpoints.observers.pop(observer, captureMap): for handle in captureMap.values: retract(turn, handle) if endpoints.observers.len == 0: leaf.observerGroups.del(analysis.capturePaths) if leaf.observerGroups.len == 0: constValMap.del(analysis.constValues) if constValMap.len == 0: cont.leafMap.del(analysis.constPaths) proc adjustAssertion(index: var Index; turn: var Turn; outerValue: Value; delta: int): bool = case index.allAssertions.change(outerValue, delta) of cdAbsentToPresent: result = true proc modContinuation(c: Continuation; v: Value) = c.cache.incl(v) proc modLeaf(l: Leaf; v: Value) = l.cache.incl(v) proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) = let change = group.cachedCaptures.change(vs, +1) if change == cdAbsentToPresent: for (observer, captureMap) in group.observers.pairs: captureMap[vs] = publish(turn, observer, vs.toPreserves) # TODO: this handle is coming from the facet? modify(index.root, turn, outerValue, addedEvent, modContinuation, modLeaf, modObserver) of cdPresentToAbsent: result = true proc modContinuation(c: Continuation; v: Value) = c.cache.excl(v) proc modLeaf(l: Leaf; v: Value) = l.cache.excl(v) proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) = if group.cachedCaptures.change(vs, -1) == cdPresentToAbsent: for (observer, captureMap) in group.observers.pairs: var h: Handle if captureMap.take(vs, h): retract(observer.target, turn, h) modify(index.root, turn, outerValue, removedEvent, modContinuation, modLeaf, modObserver) else: discard proc continuationNoop(c: Continuation; v: Value) = discard proc leafNoop(l: Leaf; v: Value) = discard proc add*(index: var Index; turn: var Turn; v: Value): bool = adjustAssertion(index, turn, v, +1) proc remove*(index: var Index; turn: var Turn; v: Value): bool = adjustAssertion(index, turn, v, -1) proc deliverMessage*(index: var Index; turn: var Turn; v: Value) = proc observersCb(turn: var Turn; group: ObserverGroup; vs: seq[Value]) = for observer in group.observers.keys: message(turn, observer, vs) index.root.modify(turn, v, messageEvent, continuationNoop, leafNoop, observersCb)