# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway # SPDX-License-Identifier: Unlicense import std/[hashes, lists, options, sets, tables] import preserves import ./actors, ./bags, ./patterns template trace(args: varargs[untyped]) = stderr.writeLine(args) type Value = Preserve[Ref] Path = seq[Value] proc projectPath(v: Value; path: Path): Value = result = v for index in path: result = result[index] proc projectPaths(v: Value; paths: seq[Path]): seq[Value] = result.setLen(paths.len) for i, path in paths: result[i] = projectPath(v, path) trace "projected ", v, " by paths ", paths, " to ", result type Class = distinct string proc hash(cls: Class): Hash {.borrow.} proc `==`(x, y: Class): bool {.borrow.} proc classOf*(v: Value): Class = if v.isRecord: result = Class $v.label & "/" & $v.arity elif v.isSequence: result = Class $v.len elif v.isDictionary: result = Class "{}" proc classOf*(p: Pattern): Class = if p.orKind == PatternKind.DCompound: case p.dcompound.orKind of DCompoundKind.rec: result = Class $p.dcompound.rec.label & "/" & $p.dcompound.rec.fields.len of DCompoundKind.arr: result = Class $p.dcompound.arr.items.len of DCompoundKind.dict: result = Class "{}" proc step(value, index: Value): Value = try: result = value[index] except KeyError, ValueError: trace "step failed, ", index, " not in ", value type EventKind = enum addedEvent, removedEvent, messageEvent AssertionCache = HashSet[Value] ObserverGroup = ref object cachedCaptures: Bag[seq[Value]] observers: Table[Ref, TableRef[seq[Value], Handle]] Leaf = ref object cachedAssertions: AssertionCache observerGroups: Table[seq[Path], ObserverGroup] Continuation = ref object cachedAssertions: AssertionCache leafMap: Table[seq[Path], TableRef[seq[Value], Leaf]] Selector = tuple[popCount: int; index: Value] Node = ref object edges: Table[Selector, TableRef[Class, Node]] continuation: Continuation using continuation: Continuation leaf: Leaf node: Node proc isEmpty(leaf): bool = leaf.cachedAssertions.len == 0 and leaf.observerGroups.len == 0 type ContinuationProc = proc (c: Continuation; v: Value) {.gcsafe.} LeafProc = proc (l: Leaf; v: Value) {.gcsafe.} ObserverProc = proc (turn: var Turn; h: ObserverGroup; vs: seq[Value]) {.gcsafe.} proc modify(node; turn: var Turn; operation: EventKind; outerValue: Value; mCont: ContinuationProc; mLeaf: LeafProc; mObserverGroup: ObserverProc) = trace "modify node with outerValue ", outerValue proc walkNode(turn: var Turn; node; termStack: SinglyLinkedList[Value]) = mCont(node.continuation, outerValue) if node.continuation.leafMap.len == 0: trace "node.continuation leafMap is empty" for (constPaths, constValMap) in node.continuation.leafMap.pairs: trace "got entry in node.continuation.leafMap for ", constPaths let constValues = projectPaths(outerValue, constPaths) var leaf = constValMap.getOrDefault(constValues) if leaf.isNil and operation == addedEvent: new leaf constValMap[constValues] = leaf if not leaf.isNil: mLeaf(leaf, outerValue) for (capturePaths, observerGroup) in leaf.observerGroups.pairs: mObserverGroup(turn, observerGroup, projectPaths(outerValue, capturePaths)) if operation == removedEvent and leaf.isEmpty: constValMap.del(constValues) if constValues.len == 0: node.continuation.leafMap.del(constPaths) for (selector, table) in node.edges.pairs: var nextStack = termStack for _ in 1..selector.popCount: nextStack.head = nextStack.head.next trace "step ", nextStack.head.value, " with ", selector.index let nextValue = step(nextStack.head.value, selector.index) nextClass = classOf nextValue if nextClass != Class"": let nextNode = table.getOrDefault(nextClass) if not nextNode.isNil: nextStack.prepend(nextValue) walkNode(turn, nextNode, nextStack) var stack: SinglyLinkedList[Value] stack.prepend(outerValue) walkNode(turn, node, stack) proc extend(node: Node; pat: Pattern): Continuation = trace "extend node with ", pat var path: Path proc walkNode(node: Node; popCount: Natural; stepIndex: Value; pat: Pattern): tuple[popCount: Natural, nextNode: Node] = trace "walkNode step ", stepIndex, " of ", pat case pat.orKind of PatternKind.DDiscard, PatternKind.DLit: result = (popCount, node) of PatternKind.DBind: result = walkNode(node, popCount, stepIndex, pat.dbind.pattern) of PatternKind.DCompound: let selector = (popCount, stepIndex,) var table = node.edges.getOrDefault(selector) if table.isNil: trace "allocate new table for selector ", selector, " for ", pat table = newTable[Class, Node]() node.edges[selector] = table else: trace "got a table for ", pat, " with selector ", selector let class = classOf pat result.nextNode = table.getOrDefault(class) if result.nextNode.isNil: trace "allocate result.nextNode for ", string class new result.nextNode table[class] = result.nextNode new result.nextNode.continuation for a in node.continuation.cachedAssertions: if class == classOf projectPath(a, path): result.nextNode.continuation.cachedAssertions.incl a result.popCount = 0 template walkKey(pat: Pattern; stepIndex: Value) = trace "walkKey ", pat, " with step ", stepIndex path.add(stepIndex) result = walkNode(result.nextNode, popCount, stepIndex, pat) discard path.pop() case pat.dcompound.orKind of DCompoundKind.rec: for k, e in pat.dcompound.rec.fields: walkKey(e, k.toPreserve(Ref)) of DCompoundKind.arr: for k, e in pat.dcompound.arr.items: walkKey(e, k.toPreserve(Ref)) of DCompoundKind.dict: for k, e in pat.dcompound.dict.entries: walkKey(e, k) result.popCount.inc walkNode(node, 0, toPreserve(0, Ref), pat).nextNode.continuation type Index* = object allAssertions: Bag[Value] root: Node proc initIndex*(): Index = Index(root: Node(continuation: Continuation())) proc add*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) = trace "add pattern ", pattern, " for ", observer let analysis = analyse pattern continuation = index.root.extend pattern var constValMap = continuation.leafMap.getOrDefault(analysis.constPaths) if constValMap.isNil: trace "allocate constValMap in leafMap for ", analysis.constPaths new constValMap for a in continuation.cachedAssertions: let key = projectPaths(a, analysis.constPaths) var leaf = constValMap.getOrDefault(key) if leaf.isNil: new leaf constValMap[key] = leaf leaf.cachedAssertions.incl(a) trace "update leafMap for ", analysis.constPaths continuation.leafMap[analysis.constPaths] = constValMap var leaf = constValMap.getOrDefault(analysis.constValues) if leaf.isNil: new leaf constValMap[analysis.constValues] = leaf trace "get observerGroup for ", analysis.capturePaths var observerGroup = leaf.observerGroups.getOrDefault(analysis.capturePaths) if observerGroup.isNil: trace "allocate observerGroup for ", analysis.capturePaths new observerGroup for a in leaf.cachedAssertions: discard observerGroup.cachedCaptures.change(projectPaths(a, analysis.capturePaths), +1) leaf.observerGroups[analysis.capturePaths] = observerGroup var captureMap = newTable[seq[Value], Handle]() for (count, captures) in observerGroup.cachedCaptures: captureMap[captures] = publish(turn, observer, captures) observerGroup.observers[observer] = captureMap proc remove*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) = var analysis = analyse pattern continuation = index.root.extend pattern let constValMap = continuation.leafMap.getOrDefault(analysis.constPaths) if not constValMap.isNil: let leaf = constValMap.getOrDefault(analysis.constValues) if not leaf.isNil: let observerGroup = leaf.observerGroups.getOrDefault(analysis.capturePaths) if not observerGroup.isNil: let captureMap = observerGroup.observers.getOrDefault(observer) if not captureMap.isNil: for handle in captureMap.values: retract(observer.target, turn, handle) observerGroup.observers.del(observer) if observerGroup.observers.len == 0: leaf.observerGroups.del(analysis.capturePaths) if leaf.isEmpty: constValMap.del(analysis.constValues) if constValMap.len == 0: continuation.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 index.root.modify( turn, addedEvent, outerValue, (proc (c: Continuation; v: Value) = c.cachedAssertions.incl(v)), (proc (l: Leaf; v: Value) = l.cachedAssertions.incl(v)), (proc (turn: var Turn; group: ObserverGroup; vs: seq[Value]) = if group.cachedCaptures.change(vs, +1) == cdAbsentToPresent: for (observer, captureMap) in group.observers.pairs: let a = vs.toPreserve(Ref) trace "publish to dataspace observer ", observer, " ", a captureMap[vs] = publish(turn, observer, a))) # TODO: this handle is coming from the facet? of cdPresentToAbsent: result = true index.root.modify( turn, removedEvent, outerValue, (proc (c: Continuation; v: Value) = c.cachedAssertions.excl(v)), (proc (l: Leaf; v: Value) = l.cachedAssertions.excl(v)), (proc (turn: var Turn; group: ObserverGroup; vs: seq[Value]) = if group.cachedCaptures.change(vs, -1) == cdPresentToAbsent: for (observer, captureMap) in group.observers.pairs: retract(observer.target, turn, captureMap[vs]) captureMap.del(vs))) 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: Assertion): bool = adjustAssertion(index, turn, v, +1) proc remove*(index: var Index; turn: var Turn; v: Assertion): bool = adjustAssertion(index, turn, v, -1) proc deliverMessage*(index: 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, messageEvent, v, continuationNoop, leafNoop, observersCb)