2021-09-01 11:47:21 +00:00
|
|
|
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
|
|
|
|
# SPDX-License-Identifier: Unlicense
|
2021-06-24 15:50:27 +00:00
|
|
|
|
|
|
|
import preserves
|
|
|
|
import options, sets, tables
|
|
|
|
|
|
|
|
type
|
|
|
|
Set = HashSet
|
|
|
|
|
|
|
|
Graph*[SubjectId, ObjectId] = object
|
|
|
|
edgesForward: Table[ObjectId, Set[SubjectId]]
|
|
|
|
edgesReverse: Table[SubjectId, Set[ObjectId]]
|
|
|
|
damagedNodes: Set[ObjectId]
|
|
|
|
currentSubjectId*: Option[SubjectId]
|
|
|
|
|
|
|
|
proc withSubject*[Sid, Oid, T](g: var Graph[Sid, Oid]; sid: Option[Sid];
|
|
|
|
body: proc (): T): T =
|
|
|
|
var oldSid = move g.currentSubjectId
|
|
|
|
g.currentSubjectId = sid
|
|
|
|
try: result = body()
|
|
|
|
finally: g.currentSubjectId = move oldSid
|
|
|
|
|
|
|
|
proc withSubject*[Sid, Oid](g: var Graph[Sid, Oid]; sid: Option[Sid];
|
|
|
|
body: proc ()) =
|
|
|
|
var oldSid = move g.currentSubjectId
|
|
|
|
g.currentSubjectId = sid
|
|
|
|
try: body()
|
|
|
|
finally: g.currentSubjectId = move oldSid
|
|
|
|
|
|
|
|
proc recordObservation*[Sid, Oid](g: var Graph[Sid, Oid]; oid: Oid) =
|
|
|
|
if g.currentSubjectId.isSome:
|
|
|
|
let sid = g.currentSubjectId.get
|
|
|
|
if not g.edgesForward.hasKey(oid):
|
|
|
|
g.edgesForward[oid] = initHashSet[Sid]()
|
|
|
|
g.edgesForward[oid].incl(sid)
|
|
|
|
if not g.edgesReverse.hasKey(sid):
|
|
|
|
g.edgesReverse[sid] = initHashSet[Oid]()
|
|
|
|
g.edgesReverse[sid].incl(oid)
|
|
|
|
# TODO: double lookups here
|
|
|
|
|
|
|
|
proc recordDamage*[Sid, Oid](g: var Graph[Sid, Oid]; oid: Oid) =
|
|
|
|
g.damagedNodes.incl(oid)
|
|
|
|
|
|
|
|
proc forgetSubject*[Sid, Oid](g: var Graph[Sid, Oid]; sid: Sid) =
|
|
|
|
var subjectObjects: Set[Oid]
|
|
|
|
if g.edgesReverse.pop(sid, subjectObjects):
|
|
|
|
for oid in subjectObjects:
|
|
|
|
g.edgesForward.del(oid)
|
|
|
|
|
|
|
|
iterator observersOf[Sid, Oid](g: Graph[Sid, Oid]; oid: Oid): Sid =
|
|
|
|
if g.edgesForward.hasKey(oid):
|
|
|
|
for sid in g.edgesForward[oid]: yield sid
|
|
|
|
|
|
|
|
proc repairDamage*[Sid, Oid](g: var Graph[Sid, Oid]; repairNode: proc (sid: Sid) {.gcsafe.}) =
|
|
|
|
var repairedThisRound: Set[Oid]
|
|
|
|
while true:
|
|
|
|
var workSet = move g.damagedNodes
|
|
|
|
assert(g.damagedNodes.len == 0)
|
|
|
|
|
|
|
|
var alreadyDamaged = workSet * repairedThisRound
|
|
|
|
if alreadyDamaged.len > 0:
|
|
|
|
echo "Cyclic dependencies involving ", alreadyDamaged
|
|
|
|
|
|
|
|
workSet = workSet - repairedThisRound
|
|
|
|
repairedThisRound = repairedThisRound + workSet
|
|
|
|
|
|
|
|
if workSet.len == 0: break
|
|
|
|
|
|
|
|
for oid in workSet:
|
|
|
|
for sid in g.observersOf(oid):
|
|
|
|
g.forgetSubject(sid)
|
|
|
|
g.withSubject(some sid) do: repairNode(sid)
|
|
|
|
|
|
|
|
proc defineObservableProperty*[Sid, Oid](g: var Graph[Sid, Oid]; oid: Oid) =
|
|
|
|
assert(not g.edgesForward.hasKey(oid))
|
|
|
|
g.edgesForward[oid] = initHashSet[Sid]()
|
|
|
|
g.recordDamage(oid)
|
|
|
|
|
|
|
|
proc addSubject*[Sid, Oid](g: var Graph[Sid, Oid]; sid: Sid) =
|
|
|
|
assert(not g.edgesReverse.hasKey(sid))
|
|
|
|
g.edgesReverse[sid] = initHashSet[Oid]()
|