Refactor skeletons

This commit is contained in:
Emery Hemingway 2023-07-20 18:31:02 +01:00
parent ca12c1ae03
commit 146b30ed42
5 changed files with 162 additions and 111 deletions

View File

@ -37,5 +37,12 @@ proc change*[T](bag: var Bag[T]; key: T; delta: int; clamp = false): ChangeDescr
if result in {cdAbsentToAbsent, cdPresentToAbsent}: if result in {cdAbsentToAbsent, cdPresentToAbsent}:
bag.del(key) bag.del(key)
iterator items*[T](bag: Bag[T]): (int, T) = iterator items*[T](bag: Bag[T]): T =
for k, v in bag: yield(v, k) 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 '}'

View File

@ -24,14 +24,12 @@ method publish(ds: Dataspace; turn: var Turn; a: AssertionRef; h: Handle) {.gcsa
ds.handleMap[h] = a.value ds.handleMap[h] = a.value
method retract(ds: Dataspace; turn: var Turn; h: Handle) {.gcsafe.} = method retract(ds: Dataspace; turn: var Turn; h: Handle) {.gcsafe.} =
try: let v = ds.handleMap[h]
let v = ds.handleMap[h] if remove(ds.index, turn, v):
if remove(ds.index, turn, v): ds.handleMap.del h
ds.handleMap.del h var obs: Observe
var obs: Observe if obs.fromPreserve v:
if obs.fromPreserve v: ds.index.remove(turn, obs.pattern, obs.observer)
ds.index.remove(turn, obs.pattern, obs.observer)
except KeyError: discard
method message(ds: Dataspace; turn: var Turn; a: AssertionRef) {.gcsafe.} = method message(ds: Dataspace; turn: var Turn; a: AssertionRef) {.gcsafe.} =
ds.index.deliverMessage(turn, a.value) ds.index.deliverMessage(turn, a.value)

View File

@ -276,11 +276,13 @@ proc recordPattern*(label: Preserve[Ref], fields: varargs[Pattern]): Pattern =
type type
Value = Preserve[Ref] Value = Preserve[Ref]
Path = seq[Value] Path* = seq[Value]
Paths* = seq[Path]
Captures* = seq[Value]
Analysis* = tuple Analysis* = tuple
constPaths: seq[Path] constPaths: Paths
constValues: seq[Value] constValues: seq[Value]
capturePaths: seq[Path] capturePaths: Paths
func walk(result: var Analysis; path: var Path; p: Pattern) 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) result = preserves.step(result.get, index)
if result.isNone: break if result.isNone: break
func projectPaths*(v: Value; paths: seq[Path]): seq[Value] = func projectPaths*(v: Value; paths: Paths): Option[Captures] =
result = newSeq[Value](paths.len) var res = newSeq[Value](paths.len)
for i, path in paths: for i, path in paths:
var vv = projectPath(v, path) 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 = func matches*(pat: Pattern; pr: Value): bool =
let analysis = analyse(pat) let analysis = analyse(pat)

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway # SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense # SPDX-License-Identifier: Unlicense
import std/[hashes, lists, options, sets, tables] import std/[hashes, lists, options, sets, tables]
@ -38,76 +38,126 @@ proc classOf(p: Pattern): Class =
else: else:
Class(kind: classNone) 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 type
EventKind = enum addedEvent, removedEvent, messageEvent EventKind = enum addedEvent, removedEvent, messageEvent
AssertionCache = HashSet[Value] AssertionCache = HashSet[Value]
Paths = seq[Path]
ObserverGroup = ref object # Endpoints ObserverGroup = ref object # Endpoints
cachedCaptures: Bag[seq[Value]] cachedCaptures: Bag[Captures]
observers: Table[Ref, TableRef[seq[Value], Handle]] observers: Table[Ref, TableRef[Captures, Handle]]
Leaf = ref object Leaf = ref object
cachedAssertions: AssertionCache cache: AssertionCache
observerGroups: Table[Paths, ObserverGroup] observerGroups: Table[Paths, ObserverGroup]
LeafMap = TableRef[seq[Value], Leaf]
Continuation = ref object Continuation = ref object
cachedAssertions: AssertionCache cache: AssertionCache
leafMap: Table[Paths, TableRef[seq[Value], Leaf]] leafMap: Table[Paths, LeafMap]
Selector = tuple[popCount: int; index: Value]
Node = ref object
edges: Table[Selector, TableRef[Class, Node]]
continuation: Continuation
func isEmpty(leaf: Leaf): bool = 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 type
ContinuationProc = proc (c: Continuation; v: Value) {.gcsafe.} ContinuationProc = proc (c: Continuation; v: Value) {.gcsafe.}
LeafProc = proc (l: Leaf; v: Value) {.gcsafe.} LeafProc = proc (l: Leaf; v: Value) {.gcsafe.}
ObserverProc = proc (turn: var Turn; group: ObserverGroup; vs: seq[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 = proc push(stack: TermStack; val: Value): Termstack =
result = stack result = stack
prepend(result, val) add(result, val)
proc pop(stack: TermStack; n: int): TermStack = proc pop(stack: TermStack; n: int): TermStack =
result = stack assert n <= stack.len
var n = n stack[stack.low..(stack.high-n)]
while n > 0:
result.remove(result.head)
assert not stack.head.isNil, "popped too far"
dec n
proc top(stack: TermStack): Value = proc top(stack: TermStack): Value =
assert not stack.head.isNil, "stack is empty" assert stack.len > 0
stack.head.value stack[stack.high]
proc modify(node: Node; turn: var Turn; outerValue: Value; event: EventKind; proc modify(node: Node; turn: var Turn; outerValue: Value; event: EventKind;
modCont: ContinuationProc; modLeaf: LeafProc; modObs: ObserverProc) = 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) modCont(cont, outerValue)
assert not cont.isEmpty()
for constPaths, constValMap in cont.leafMap.pairs: for constPaths, constValMap in cont.leafMap.pairs:
let constVals = projectPaths(outerValue, constPaths) let constVals = projectPaths(outerValue, constPaths)
var leaf = constValMap.getOrDefault(constVals) if constVals.isSome:
if leaf.isNil and event == addedEvent: case event
new leaf of addedEvent, messageEvent:
constValMap[constVals] = leaf let leaf = constValMap.getLeaf(get constVals)
if not leaf.isNil: modLeaf(leaf, outerValue)
modLeaf(leaf, outerValue) assert not leaf.isEmpty
for capturePaths, observerGroup in leaf.observerGroups.pairs: for capturePaths, observerGroup in leaf.observerGroups.pairs:
modObs(turn, observerGroup, projectPaths(outerValue, capturePaths)) let captures = projectPaths(outerValue, capturePaths)
# TODO: cleanup dead leaves 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) = proc walk(node: Node; turn: var Turn; termStack: TermStack) =
walk(turn, node.continuation, outerValue, event) walk(node.continuation, turn)
assert not node.isEmpty
for selector, table in node.edges: for selector, table in node.edges:
let let
nextStack = pop(termStack, selector.popCount) nextStack = pop(termStack, selector.popCount)
@ -117,10 +167,9 @@ proc modify(node: Node; turn: var Turn; outerValue: Value; event: EventKind;
if nextClass.kind != classNone: if nextClass.kind != classNone:
let nextNode = table.getOrDefault(nextClass) let nextNode = table.getOrDefault(nextClass)
if not nextNode.isNil: 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].toPreserve(Ref)])
walk(node, turn, outerValue, event, push(stack, @[outerValue].toPreserve(Ref)))
proc getOrNew[A, B, C](t: var Table[A, TableRef[B, C]], k: A): TableRef[B, C] = proc getOrNew[A, B, C](t: var Table[A, TableRef[B, C]], k: A): TableRef[B, C] =
result = t.getOrDefault(k) 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) result = extendWalk(node, popCount, stepIndex, pat.dbind.pattern, path)
of PatternKind.DCompound: of PatternKind.DCompound:
let let
class = classOf pat
selector: Selector = (popCount, stepIndex,) selector: Selector = (popCount, stepIndex,)
table = node.edges.getOrNew(selector) table = node.edges.getOrNew(selector)
class = classOf pat
result.nextNode = table.getOrDefault(class) result.nextNode = table.getOrDefault(class)
if result.nextNode.isNil: if result.nextNode.isNil:
new result.nextNode new result.nextNode
table[class] = result.nextNode table[class] = result.nextNode
new result.nextNode.continuation new result.nextNode.continuation
for a in node.continuation.cachedAssertions: for a in node.continuation.cache:
var v = projectPath(a, path) var v = projectPath(a, path)
if v.isSome and class == classOf(get v): if v.isSome and class == classOf(get v):
result.nextNode.continuation.cachedAssertions.incl a result.nextNode.continuation.cache.incl a
for i, p in pat.dcompound.pairs: result.popCount = 0
add(path, i) for step, p in pat.dcompound.pairs:
result = extendWalk(result.nextNode, result.popCount, i, p, path) add(path, step)
result = extendWalk(result.nextNode, result.popCount, step, p, path)
discard pop(path) discard pop(path)
inc(result.popCount) inc(result.popCount)
@ -174,83 +224,75 @@ type
Index* = object Index* = object
allAssertions: Bag[Value] allAssertions: Bag[Value]
root: Node root: Node
observerCount: int
proc initIndex*(): Index = proc initIndex*(): Index =
Index(root: Node(continuation: Continuation())) 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) = proc add*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) =
let let
cont = index.root.extend(pattern)
analysis = analyse pattern analysis = analyse pattern
continuation = index.root.extend pattern constValMap = cont.getLeaves(analysis.constPaths)
var constValMap = continuation.leafMap.getOrDefault(analysis.constPaths) leaf = constValMap.getLeaf(analysis.constValues)
if constValMap.isNil: endpoints = leaf.getEndpoints(analysis.capturePaths)
new constValMap # TODO if endpoints.cachedCaptures.len > 0:
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
var captureMap = newTable[seq[Value], Handle]() var captureMap = newTable[seq[Value], Handle]()
for (count, captures) in observerGroup.cachedCaptures: for capture in endpoints.cachedCaptures.items:
captureMap[captures] = publish(turn, observer, captures) captureMap[capture] = publish(turn, observer, capture)
observerGroup.observers[observer] = captureMap endpoints.observers[observer] = captureMap
proc remove*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) = proc remove*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) =
var let
cont = index.root.extend(pattern)
analysis = analyse pattern analysis = analyse pattern
continuation = index.root.extend pattern constValMap = cont.leafMap.getOrDefault(analysis.constPaths)
let constValMap = continuation.leafMap.getOrDefault(analysis.constPaths)
if not constValMap.isNil: if not constValMap.isNil:
let leaf = constValMap.getOrDefault(analysis.constValues) let leaf = constValMap.getOrDefault(analysis.constValues)
if not leaf.isNil: if not leaf.isNil:
let observerGroup = leaf.observerGroups.getOrDefault(analysis.capturePaths) let endpoints = leaf.observerGroups.getOrDefault(analysis.capturePaths)
if not observerGroup.isNil: if not endpoints.isNil:
let captureMap = observerGroup.observers.getOrDefault(observer) var captureMap: TableRef[seq[Value], Handle]
if not captureMap.isNil: if endpoints.observers.pop(observer, captureMap):
for handle in captureMap.values: retract(observer.target, turn, handle) for handle in captureMap.values: retract(turn, handle)
observerGroup.observers.del(observer) if endpoints.observers.len == 0:
if observerGroup.observers.len == 0:
leaf.observerGroups.del(analysis.capturePaths) leaf.observerGroups.del(analysis.capturePaths)
if leaf.isEmpty: if leaf.observerGroups.len == 0:
constValMap.del(analysis.constValues) constValMap.del(analysis.constValues)
if constValMap.len == 0: if constValMap.len == 0:
continuation.leafMap.del(analysis.constPaths) 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) case index.allAssertions.change(outerValue, delta)
of cdAbsentToPresent: of cdAbsentToPresent:
result = true result = true
proc modContinuation(c: Continuation; v: Value) = proc modContinuation(c: Continuation; v: Value) =
c.cachedAssertions.incl(v) c.cache.incl(v)
proc modLeaf(l: Leaf; v: Value) = proc modLeaf(l: Leaf; v: Value) =
l.cachedAssertions.incl(v) l.cache.incl(v)
proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) = 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: for (observer, captureMap) in group.observers.pairs:
let a = vs.toPreserve(Ref) captureMap[vs] = publish(turn, observer, vs.toPreserve(Ref))
captureMap[vs] = publish(turn, observer, a)
# TODO: this handle is coming from the facet? # TODO: this handle is coming from the facet?
modify(index.root, turn, outerValue, addedEvent, modContinuation, modLeaf, modObserver) modify(index.root, turn, outerValue, addedEvent, modContinuation, modLeaf, modObserver)
of cdPresentToAbsent: of cdPresentToAbsent:
result = true result = true
proc modContinuation(c: Continuation; v: Value) = proc modContinuation(c: Continuation; v: Value) =
c.cachedAssertions.excl(v) c.cache.excl(v)
proc modLeaf(l: Leaf; v: Value) = proc modLeaf(l: Leaf; v: Value) =
l.cachedAssertions.excl(v) l.cache.excl(v)
proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) = proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) =
if group.cachedCaptures.change(vs, -1) == cdPresentToAbsent: if group.cachedCaptures.change(vs, -1) == cdPresentToAbsent:
for (observer, captureMap) in group.observers.pairs: for (observer, captureMap) in group.observers.pairs:

View File

@ -1,6 +1,6 @@
# Package # Package
version = "20230713" version = "20230720"
author = "Emery Hemingway" author = "Emery Hemingway"
description = "Syndicated actors for conversational concurrency" description = "Syndicated actors for conversational concurrency"
license = "Unlicense" license = "Unlicense"