immersive-home/app/addons/rdot/graph.gd
2024-04-09 15:30:23 +02:00

225 lines
7.0 KiB
GDScript

extends RefCounted
class_name RdotGraph
static var instance: RdotGraph = null
static func getInstance() -> RdotGraph:
if instance == null:
instance = RdotGraph.new()
return instance
var activeConsumer: RdotNode = null
var inNotificationPhase := false
var epoch := 1
var postSignalSetFn := Callable()
var watcherPending := false
var watcher = R.Watcher.new(func(_arg):
if watcherPending:
return
watcherPending=true
var endOfFrame=func():
watcherPending=false
for s in watcher.getPending():
s.do_get()
watcher.watch()
endOfFrame.call_deferred()
)
func setActiveConsumer(consumer: RdotNode) -> RdotNode:
var prev = activeConsumer
activeConsumer = consumer
return prev
func getActiveConsumer() -> RdotNode:
return activeConsumer
func isInNotificationPhase() -> bool:
return inNotificationPhase
func producerAccessed(node: RdotNode):
assert(inNotificationPhase == false, "Signal read during notification phase")
if activeConsumer == null:
return
if activeConsumer.consumerOnSignalRead.is_null() == false:
activeConsumer.consumerOnSignalRead.call(node)
var idx = activeConsumer.nextProducerIndex;
activeConsumer.nextProducerIndex += 1
assertConsumerNode(activeConsumer)
if idx < activeConsumer.producerNode.size()&&activeConsumer.producerNode[idx] != node:
if consumerIsLive(activeConsumer):
var staleProducer = activeConsumer.producerNode[idx]
producerRemoveLiveConsumerAtIndex(staleProducer, activeConsumer.producerIndexOfThis[idx])
if RdotArray.do_get(activeConsumer.producerNode, idx) != node:
RdotArray.do_set(activeConsumer.producerNode, idx, node)
RdotArray.do_set(activeConsumer.producerIndexOfThis, idx, producerAddLiveConsumer(node, activeConsumer, idx) if consumerIsLive(activeConsumer) else 0)
RdotArray.do_set(activeConsumer.producerLastReadVersion, idx, node.version)
func producerIncrementEpoch():
epoch += 1
func producerUpdateValueVersion(node: RdotNode):
if consumerIsLive(node)&&!node.dirty:
return
if !node.dirty&&node.lastCleanEpoch == epoch:
return
if !node.producerMustRecompute(node)&&!consumerPollProducersForChange(node):
node.dirty = false;
node.lastCleanEpoch = epoch
return
if node.producerRecomputeValue.is_null() == false:
node.producerRecomputeValue.call(node)
node.dirty = false
node.lastCleanEpoch = epoch
func producerNotifyConsumers(node: RdotNode):
if node.liveConsumerNode == null:
return
var prev = inNotificationPhase
inNotificationPhase = true
for consumer in node.liveConsumerNode:
if !consumer.dirty:
consumerMarkDirty(consumer)
inNotificationPhase = prev
func producerUpdatesAllowed() -> bool:
return activeConsumer == null||activeConsumer.consumerAllowSignalWrites != false
func consumerMarkDirty(node: RdotNode):
node.dirty = true
producerNotifyConsumers(node)
if node.consumerMarkedDirty.is_null() == false:
node.consumerMarkedDirty.call(node)
func consumerBeforeComputation(node: RdotNode) -> RdotNode:
if node:
node.nextProducerIndex = 0
return setActiveConsumer(node)
func consumerAfterComputation(node: RdotNode, prevConsumer: RdotNode):
setActiveConsumer(prevConsumer)
if node == null||node.producerNode == null||node.producerIndexOfThis == null||node.producerLastReadVersion == null:
return
if consumerIsLive(node):
for i in range(node.nextProducerIndex, node.producerNode.size()):
producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i])
while node.producerNode.size() > node.nextProducerIndex:
node.producerNode.pop_back()
node.producerLastReadVersion.pop_back()
node.producerIndexOfThis.pop_back()
func consumerPollProducersForChange(node: RdotNode) -> bool:
assertConsumerNode(node)
for i in range(node.producerNode.size()):
var producer = node.producerNode[i]
var seenVersion = node.producerLastReadVersion[i]
if seenVersion != producer.version:
return true
producerUpdateValueVersion(producer)
if seenVersion != producer.version:
return true
return false
func consumerDestroy(node: RdotNode):
assertConsumerNode(node)
if consumerIsLive(node):
for i in range(node.producerNode.size()):
producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i])
node.producerNode.clear()
node.producerLastReadVersion.clear()
node.producerIndexOfThis.clear()
if node.liveConsumerNode:
node.liveConsumerNode.clear()
node.liveConsumerIndexOfThis.clear()
static func producerAddLiveConsumer(node: RdotNode, consumer: RdotNode, indexOfThis: int) -> int:
assertProducerNode(node)
assertConsumerNode(node)
if node.liveConsumerNode.size() == 0:
if node.watched.is_null() == false:
node.watched.call(node.wrapper)
for i in range(node.producerNode.size()):
node.producerIndexOfThis[i] = producerAddLiveConsumer(node.producerNode[i], node, i)
node.liveConsumerIndexOfThis.push_back(indexOfThis)
node.liveConsumerNode.push_back(consumer)
return node.liveConsumerNode.size() - 1
static func producerRemoveLiveConsumerAtIndex(node: RdotNode, idx: int):
assertProducerNode(node)
assertConsumerNode(node)
assert(idx < node.liveConsumerNode.size(), "active consumer index %s is out of bounds of %s consumers)" % [idx, node.liveConsumerNode.size()])
if node.liveConsumerNode.size() == 1:
if node.unwatched.is_null() == false:
node.unwatched.call(node.wrapper)
for i in range(node.producerNode.size()):
producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i])
var lastIdx = node.liveConsumerNode.size() - 1
node.liveConsumerNode[idx] = node.liveConsumerNode[lastIdx]
node.liveConsumerIndexOfThis[idx] = node.liveConsumerIndexOfThis[lastIdx]
node.liveConsumerNode.pop_back()
node.liveConsumerIndexOfThis.pop_back()
if idx < node.liveConsumerNode.size():
var idxProducer = node.liveConsumerIndexOfThis[idx]
var consumer = node.liveConsumerNode[idx]
assertConsumerNode(consumer)
consumer.producerIndexOfThis[idxProducer] = idx
static func consumerIsLive(node: RdotNode) -> bool:
return node.consumerIsAlwaysLive||(node.liveConsumerNode != null&&node.liveConsumerNode.size() > 0)
static func assertConsumerNode(node: RdotNode):
if node.producerNode == null:
node.producerNode = []
if node.producerIndexOfThis == null:
node.producerIndexOfThis = []
if node.producerLastReadVersion == null:
node.producerLastReadVersion = []
static func assertProducerNode(node: RdotNode):
if node.liveConsumerNode == null:
node.liveConsumerNode = []
if node.liveConsumerIndexOfThis == null:
node.liveConsumerIndexOfThis = []