From 146b30ed4204b97dd735aaad4cf3d87067fe477b Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Thu, 20 Jul 2023 18:31:02 +0100 Subject: [PATCH] Refactor skeletons --- src/syndicate/bags.nim | 11 +- src/syndicate/dataspaces.nim | 14 +-- src/syndicate/patterns.nim | 16 ++- src/syndicate/skeletons.nim | 230 +++++++++++++++++++++-------------- syndicate.nimble | 2 +- 5 files changed, 162 insertions(+), 111 deletions(-) diff --git a/src/syndicate/bags.nim b/src/syndicate/bags.nim index 5639f03..8586ae8 100644 --- a/src/syndicate/bags.nim +++ b/src/syndicate/bags.nim @@ -37,5 +37,12 @@ proc change*[T](bag: var Bag[T]; key: T; delta: int; clamp = false): ChangeDescr if result in {cdAbsentToAbsent, cdPresentToAbsent}: bag.del(key) -iterator items*[T](bag: Bag[T]): (int, T) = - for k, v in bag: yield(v, k) +iterator items*[T](bag: Bag[T]): T = + for x in bag.keys: yield x + +proc `$`*(bag: Bag): string = + result.add '{' + for x in bag.keys: + if result.len > 1: result.add ' ' + result.add $x + result.add '}' diff --git a/src/syndicate/dataspaces.nim b/src/syndicate/dataspaces.nim index 344eec0..8bc1768 100644 --- a/src/syndicate/dataspaces.nim +++ b/src/syndicate/dataspaces.nim @@ -24,14 +24,12 @@ method publish(ds: Dataspace; turn: var Turn; a: AssertionRef; h: Handle) {.gcsa ds.handleMap[h] = a.value method retract(ds: Dataspace; turn: var Turn; h: Handle) {.gcsafe.} = - try: - let v = ds.handleMap[h] - if remove(ds.index, turn, v): - ds.handleMap.del h - var obs: Observe - if obs.fromPreserve v: - ds.index.remove(turn, obs.pattern, obs.observer) - except KeyError: discard + let v = ds.handleMap[h] + if remove(ds.index, turn, v): + ds.handleMap.del h + var obs: Observe + if obs.fromPreserve v: + ds.index.remove(turn, obs.pattern, obs.observer) method message(ds: Dataspace; turn: var Turn; a: AssertionRef) {.gcsafe.} = ds.index.deliverMessage(turn, a.value) diff --git a/src/syndicate/patterns.nim b/src/syndicate/patterns.nim index 3b0aa54..2334802 100644 --- a/src/syndicate/patterns.nim +++ b/src/syndicate/patterns.nim @@ -276,11 +276,13 @@ proc recordPattern*(label: Preserve[Ref], fields: varargs[Pattern]): Pattern = type Value = Preserve[Ref] - Path = seq[Value] + Path* = seq[Value] + Paths* = seq[Path] + Captures* = seq[Value] Analysis* = tuple - constPaths: seq[Path] + constPaths: Paths constValues: seq[Value] - capturePaths: seq[Path] + capturePaths: Paths func walk(result: var Analysis; path: var Path; p: Pattern) @@ -317,11 +319,13 @@ func projectPath*(v: Value; path: Path): Option[Value] = result = preserves.step(result.get, index) if result.isNone: break -func projectPaths*(v: Value; paths: seq[Path]): seq[Value] = - result = newSeq[Value](paths.len) +func projectPaths*(v: Value; paths: Paths): Option[Captures] = + var res = newSeq[Value](paths.len) for i, path in paths: var vv = projectPath(v, path) - if vv.isSome: result[i] = get(vv) + if vv.isSome: res[i] = get(vv) + else: return + some res func matches*(pat: Pattern; pr: Value): bool = let analysis = analyse(pat) diff --git a/src/syndicate/skeletons.nim b/src/syndicate/skeletons.nim index c840e87..337a23d 100644 --- a/src/syndicate/skeletons.nim +++ b/src/syndicate/skeletons.nim @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway +# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense import std/[hashes, lists, options, sets, tables] @@ -38,76 +38,126 @@ proc classOf(p: Pattern): Class = else: Class(kind: classNone) +proc `$`(class: Class): string = + case class.kind + of classNone: result = "null" + of classRecord: + result.add($class.label) + result.add(':') + result.add($class.arity) + of classSequence: + result.add('[') + result.add($class.arity) + result.add(']') + of classDictionary: + result = "{…:…}" + type EventKind = enum addedEvent, removedEvent, messageEvent AssertionCache = HashSet[Value] - Paths = seq[Path] - ObserverGroup = ref object # Endpoints - cachedCaptures: Bag[seq[Value]] - observers: Table[Ref, TableRef[seq[Value], Handle]] + cachedCaptures: Bag[Captures] + observers: Table[Ref, TableRef[Captures, Handle]] Leaf = ref object - cachedAssertions: AssertionCache + cache: AssertionCache observerGroups: Table[Paths, ObserverGroup] + LeafMap = TableRef[seq[Value], Leaf] + Continuation = ref object - cachedAssertions: AssertionCache - leafMap: Table[Paths, TableRef[seq[Value], Leaf]] - - Selector = tuple[popCount: int; index: Value] - - Node = ref object - edges: Table[Selector, TableRef[Class, Node]] - continuation: Continuation + cache: AssertionCache + leafMap: Table[Paths, LeafMap] func isEmpty(leaf: Leaf): bool = - leaf.cachedAssertions.len == 0 and leaf.observerGroups.len == 0 + leaf.cache.len == 0 and leaf.observerGroups.len == 0 + +func isEmpty(cont: Continuation): bool = + cont.cache.len == 0 and cont.leafMap.len == 0 + +proc `$`(x: Leaf|Continuation): string = + cast[ByteAddress](x[].unsafeAddr).toHex type ContinuationProc = proc (c: Continuation; v: Value) {.gcsafe.} LeafProc = proc (l: Leaf; v: Value) {.gcsafe.} ObserverProc = proc (turn: var Turn; group: ObserverGroup; vs: seq[Value]) {.gcsafe.} -type TermStack = SinglyLinkedList[Value] +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 + +proc `$`(node: Node): string = + $(cast[ByteAddress](unsafeAddr node[]) and 0xffffff) + +type TermStack = seq[Value] proc push(stack: TermStack; val: Value): Termstack = result = stack - prepend(result, val) + add(result, val) proc pop(stack: TermStack; n: int): TermStack = - result = stack - var n = n - while n > 0: - result.remove(result.head) - assert not stack.head.isNil, "popped too far" - dec n + assert n <= stack.len + stack[stack.low..(stack.high-n)] proc top(stack: TermStack): Value = - assert not stack.head.isNil, "stack is empty" - stack.head.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(turn: var Turn; cont: Continuation; outerValue: Value; event: EventKind) = + proc walk(cont: Continuation; turn: var Turn) = modCont(cont, outerValue) + assert not cont.isEmpty() for constPaths, constValMap in cont.leafMap.pairs: let constVals = projectPaths(outerValue, constPaths) - var leaf = constValMap.getOrDefault(constVals) - if leaf.isNil and event == addedEvent: - new leaf - constValMap[constVals] = leaf - if not leaf.isNil: - modLeaf(leaf, outerValue) - for capturePaths, observerGroup in leaf.observerGroups.pairs: - modObs(turn, observerGroup, projectPaths(outerValue, capturePaths)) - # TODO: cleanup dead leaves + if constVals.isSome: + case event + of addedEvent, messageEvent: + let leaf = constValMap.getLeaf(get constVals) + modLeaf(leaf, outerValue) + assert not leaf.isEmpty + for capturePaths, observerGroup in leaf.observerGroups.pairs: + let captures = projectPaths(outerValue, capturePaths) + if captures.isSome: + modObs(turn, observerGroup, get captures) + else: + raiseAssert $event & " not handled" + # TODO: cleanup dead leaves - proc walk(node: Node; turn: var Turn; outerValue: Value; event: EventKind; termStack: TermStack) = - walk(turn, node.continuation, outerValue, event) + proc walk(node: Node; turn: var Turn; termStack: TermStack) = + walk(node.continuation, turn) + assert not node.isEmpty for selector, table in node.edges: let nextStack = pop(termStack, selector.popCount) @@ -117,10 +167,9 @@ proc modify(node: Node; turn: var Turn; outerValue: Value; event: EventKind; if nextClass.kind != classNone: let nextNode = table.getOrDefault(nextClass) if not nextNode.isNil: - walk(nextNode, turn, outerValue, event, push(nextStack, get nextValue)) + walk(nextNode, turn, push(nextStack, get nextValue)) - var stack: TermStack - walk(node, turn, outerValue, event, push(stack, @[outerValue].toPreserve(Ref))) + walk(node, turn, @[@[outerValue].toPreserve(Ref)]) proc getOrNew[A, B, C](t: var Table[A, TableRef[B, C]], k: A): TableRef[B, C] = result = t.getOrDefault(k) @@ -148,21 +197,22 @@ proc extendWalk(node: Node; popCount: Natural; stepIndex: Value; pat: Pattern; p result = extendWalk(node, popCount, stepIndex, pat.dbind.pattern, path) of PatternKind.DCompound: let - class = classOf pat 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.cachedAssertions: + for a in node.continuation.cache: var v = projectPath(a, path) if v.isSome and class == classOf(get v): - result.nextNode.continuation.cachedAssertions.incl a - for i, p in pat.dcompound.pairs: - add(path, i) - result = extendWalk(result.nextNode, result.popCount, i, p, path) + 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) @@ -174,83 +224,75 @@ type Index* = object allAssertions: Bag[Value] root: Node - observerCount: int 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: Ref) = let + cont = index.root.extend(pattern) analysis = analyse pattern - continuation = index.root.extend pattern - var constValMap = continuation.leafMap.getOrDefault(analysis.constPaths) - if constValMap.isNil: - 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) - continuation.leafMap[analysis.constPaths] = constValMap - var leaf = constValMap.getOrDefault(analysis.constValues) - if leaf.isNil: - new leaf - constValMap[analysis.constValues] = leaf - var observerGroup = leaf.observerGroups.getOrDefault(analysis.capturePaths) - if observerGroup.isNil: - new observerGroup - for a in leaf.cachedAssertions: - discard observerGroup.cachedCaptures.change(projectPaths(a, analysis.capturePaths), +1) - leaf.observerGroups[analysis.capturePaths] = observerGroup + 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 (count, captures) in observerGroup.cachedCaptures: - captureMap[captures] = publish(turn, observer, captures) - observerGroup.observers[observer] = captureMap + 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: Ref) = - var + let + cont = index.root.extend(pattern) analysis = analyse pattern - continuation = index.root.extend pattern - let constValMap = continuation.leafMap.getOrDefault(analysis.constPaths) + constValMap = cont.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: + 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.isEmpty: - constValMap.del(analysis.constValues) - if constValMap.len == 0: - continuation.leafMap.del(analysis.constPaths) + 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 = +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.cachedAssertions.incl(v) + c.cache.incl(v) proc modLeaf(l: Leaf; v: Value) = - l.cachedAssertions.incl(v) + l.cache.incl(v) proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) = - if group.cachedCaptures.change(vs, +1) == cdAbsentToPresent: + let change = group.cachedCaptures.change(vs, +1) + if change == cdAbsentToPresent: for (observer, captureMap) in group.observers.pairs: - let a = vs.toPreserve(Ref) - captureMap[vs] = publish(turn, observer, a) + captureMap[vs] = publish(turn, observer, vs.toPreserve(Ref)) # 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.cachedAssertions.excl(v) + c.cache.excl(v) proc modLeaf(l: Leaf; v: Value) = - l.cachedAssertions.excl(v) + 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: diff --git a/syndicate.nimble b/syndicate.nimble index f11f4d2..9f67c97 100644 --- a/syndicate.nimble +++ b/syndicate.nimble @@ -1,6 +1,6 @@ # Package -version = "20230713" +version = "20230720" author = "Emery Hemingway" description = "Syndicated actors for conversational concurrency" license = "Unlicense"