226 lines
6.4 KiB
GDScript
226 lines
6.4 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 = []
|