add Rdot
This commit is contained in:
parent
586ea20d1a
commit
6690b9b195
201
app/addons/rdot/Rdot.gd
Normal file
201
app/addons/rdot/Rdot.gd
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
class_name R
|
||||||
|
|
||||||
|
static func state(value: Variant, options: Dictionary={}):
|
||||||
|
if value is Dictionary:
|
||||||
|
return store(value)
|
||||||
|
|
||||||
|
return State.new(value, options)
|
||||||
|
|
||||||
|
static func store(value: Dictionary):
|
||||||
|
return RdotStore.new(value)
|
||||||
|
|
||||||
|
static func computed(computation: Callable, options: Dictionary={}):
|
||||||
|
return Computed.new(computation, options)
|
||||||
|
|
||||||
|
## DIY overloading of
|
||||||
|
## bind(target, prop, value)
|
||||||
|
## bind(target, prop, value, watch_signal)
|
||||||
|
## bind(target, prop, store, key, watch_signal)
|
||||||
|
static func bind(target, prop, value, arg1=null, arg2=null):
|
||||||
|
if value is RdotStore:
|
||||||
|
return _bind_store(target, prop, value, arg1, arg2)
|
||||||
|
|
||||||
|
if value is State or value is Computed:
|
||||||
|
return _bind_state(target, prop, value, arg1)
|
||||||
|
|
||||||
|
assert(false, "Invalid arguments to bind, value must be a R.State or a RdotStore")
|
||||||
|
|
||||||
|
static func _bind_store(target, prop, store: RdotStore, key, watch_signal=null):
|
||||||
|
store._access_property(key)
|
||||||
|
|
||||||
|
return _bind_state(target, prop, store._proxied_value[key], watch_signal)
|
||||||
|
|
||||||
|
static func _bind_state(target, prop, value, watch_signal=null):
|
||||||
|
var graph = RdotGraph.getInstance()
|
||||||
|
|
||||||
|
var watch_c = func(new_value):
|
||||||
|
value.value = new_value
|
||||||
|
|
||||||
|
var c = computed(func(_arg):
|
||||||
|
var oldValue=target.get(prop)
|
||||||
|
|
||||||
|
if oldValue != value.value:
|
||||||
|
target.set(prop, value.value)
|
||||||
|
)
|
||||||
|
|
||||||
|
if watch_signal:
|
||||||
|
watch_signal.connect(watch_c)
|
||||||
|
|
||||||
|
graph.watcher.watch([c])
|
||||||
|
c.do_get()
|
||||||
|
|
||||||
|
return func():
|
||||||
|
graph.watcher.unwatch([c])
|
||||||
|
if watch_signal:
|
||||||
|
watch_signal.disconnect(watch_c)
|
||||||
|
|
||||||
|
static func effect(callback: Callable):
|
||||||
|
var graph = RdotGraph.getInstance()
|
||||||
|
|
||||||
|
var deconstructor := Callable()
|
||||||
|
var c = computed(func(_arg):
|
||||||
|
if !deconstructor.is_null():
|
||||||
|
deconstructor.call()
|
||||||
|
|
||||||
|
var result=callback.call(_arg)
|
||||||
|
|
||||||
|
if result is Callable:
|
||||||
|
deconstructor=result
|
||||||
|
)
|
||||||
|
|
||||||
|
graph.watcher.watch([c])
|
||||||
|
c.do_get()
|
||||||
|
|
||||||
|
return func():
|
||||||
|
if !deconstructor.is_null():
|
||||||
|
deconstructor.call()
|
||||||
|
|
||||||
|
graph.watcher.unwatch([c])
|
||||||
|
|
||||||
|
class State:
|
||||||
|
var node: RdotState
|
||||||
|
var value = null:
|
||||||
|
get:
|
||||||
|
return do_get()
|
||||||
|
set(value):
|
||||||
|
do_set(value)
|
||||||
|
|
||||||
|
func _init(value: Variant, options: Dictionary={}):
|
||||||
|
var ref = RdotState.createSignal(value)
|
||||||
|
var node = ref[1]
|
||||||
|
self.node = node
|
||||||
|
node.wrapper = self
|
||||||
|
|
||||||
|
if !options.is_empty():
|
||||||
|
var equals = options.get("equals")
|
||||||
|
|
||||||
|
if !equals.is_null():
|
||||||
|
node.equals = equals
|
||||||
|
|
||||||
|
func do_get():
|
||||||
|
return RdotState.signalGetFn.call(self.node)
|
||||||
|
|
||||||
|
func do_set(value: Variant):
|
||||||
|
var graph = RdotGraph.getInstance()
|
||||||
|
|
||||||
|
assert(graph.isInNotificationPhase() == false, "Writes to signals not permitted during Watcher callback")
|
||||||
|
|
||||||
|
var ref = self.node
|
||||||
|
|
||||||
|
RdotState.signalSetFn.call(ref, value)
|
||||||
|
|
||||||
|
class Computed:
|
||||||
|
var node: RdotComputed
|
||||||
|
var value = null:
|
||||||
|
get:
|
||||||
|
return do_get()
|
||||||
|
set(value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
func _init(computation: Callable, options: Dictionary={}):
|
||||||
|
var ref = RdotComputed.createdComputed(computation)
|
||||||
|
var node = ref[1]
|
||||||
|
node.consumerAllowSignalWrites = true
|
||||||
|
self.node = node
|
||||||
|
node.wrapper = self
|
||||||
|
|
||||||
|
if options:
|
||||||
|
var equals = options.get("equals")
|
||||||
|
|
||||||
|
if !equals.is_null():
|
||||||
|
node.equals = equals
|
||||||
|
|
||||||
|
func do_get():
|
||||||
|
return RdotComputed.computedGet(node)
|
||||||
|
|
||||||
|
class Watcher:
|
||||||
|
var node: RdotNode
|
||||||
|
|
||||||
|
func _init(notify: Callable):
|
||||||
|
var node = RdotNode.new()
|
||||||
|
node.wrapper = self
|
||||||
|
node.consumerMarkedDirty = notify
|
||||||
|
node.consumerIsAlwaysLive = true
|
||||||
|
node.consumerAllowSignalWrites = false
|
||||||
|
node.producerNode = []
|
||||||
|
self.node = node
|
||||||
|
|
||||||
|
## signals: Array[RState | RComputed]
|
||||||
|
func _assertSignals(signals: Array):
|
||||||
|
for s in signals:
|
||||||
|
assert(s is State or s is Computed, "Watcher expects signals to be RState or RComputed")
|
||||||
|
|
||||||
|
func watch(signals:=[]):
|
||||||
|
_assertSignals(signals)
|
||||||
|
|
||||||
|
var graph = RdotGraph.getInstance()
|
||||||
|
|
||||||
|
var node = self.node
|
||||||
|
node.dirty = false
|
||||||
|
var prev = graph.setActiveConsumer(node)
|
||||||
|
for s in signals:
|
||||||
|
graph.producerAccessed(s.node)
|
||||||
|
|
||||||
|
graph.setActiveConsumer(prev)
|
||||||
|
|
||||||
|
func unwatch(signals: Array):
|
||||||
|
_assertSignals(signals)
|
||||||
|
|
||||||
|
var graph = RdotGraph.getInstance()
|
||||||
|
|
||||||
|
var node = self.node
|
||||||
|
graph.assertConsumerNode(node)
|
||||||
|
|
||||||
|
var indicesToShift = []
|
||||||
|
for i in range(node.producerNode.size()):
|
||||||
|
if signals.has(node.producerNode[i].wrapper):
|
||||||
|
graph.producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i])
|
||||||
|
indicesToShift.append(i)
|
||||||
|
|
||||||
|
for idx in indicesToShift:
|
||||||
|
var lastIdx = node.producerNode.size() - 1
|
||||||
|
node.producerNode[idx] = node.producerNode[lastIdx]
|
||||||
|
node.producerIndexOfThis[idx] = node.producerIndexOfThis[lastIdx]
|
||||||
|
|
||||||
|
node.producerNode.pop_back()
|
||||||
|
node.producerIndexOfThis.pop_back()
|
||||||
|
node.nextProducerIndex -= 1
|
||||||
|
|
||||||
|
if idx < node.producerNode.size():
|
||||||
|
var idxConsumer = node.producerNode[idx]
|
||||||
|
var producer = node.producerNode[idx]
|
||||||
|
|
||||||
|
graph.assertProducerNode(producer)
|
||||||
|
|
||||||
|
producer.liveConsumerIndexOfThis[idxConsumer] = idx
|
||||||
|
|
||||||
|
## Returns Array[RComputed]
|
||||||
|
func getPending() -> Array:
|
||||||
|
var node = self.node
|
||||||
|
|
||||||
|
return node.producerNode.filter(func(n): return n.dirty).map(func(n): return n.wrapper)
|
13
app/addons/rdot/array.gd
Normal file
13
app/addons/rdot/array.gd
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
class_name RdotArray
|
||||||
|
|
||||||
|
static func do_set(array: Array, index: int, value):
|
||||||
|
if index >= array.size():
|
||||||
|
array.resize(index + 1)
|
||||||
|
|
||||||
|
array[index] = value
|
||||||
|
|
||||||
|
static func do_get(array: Array, index: int):
|
||||||
|
if index >= array.size():
|
||||||
|
return null
|
||||||
|
|
||||||
|
return array[index]
|
64
app/addons/rdot/computed.gd
Normal file
64
app/addons/rdot/computed.gd
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
extends RdotNode
|
||||||
|
class_name RdotComputed
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
SET = 0,
|
||||||
|
UNSET = 1,
|
||||||
|
COMPUTING = 2,
|
||||||
|
ERRORED = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
var value: Variant = null
|
||||||
|
var state: State = State.UNSET
|
||||||
|
var error = null
|
||||||
|
var computation := Callable()
|
||||||
|
var equal := func(this, a, b): return a == b
|
||||||
|
|
||||||
|
static func computedGet(node: RdotComputed) -> Variant:
|
||||||
|
var graph := RdotGraph.getInstance()
|
||||||
|
|
||||||
|
graph.producerUpdateValueVersion(node)
|
||||||
|
graph.producerAccessed(node)
|
||||||
|
|
||||||
|
assert(node.state != State.ERRORED, "Error in computed.")
|
||||||
|
|
||||||
|
return node.value
|
||||||
|
|
||||||
|
static func createdComputed(computation: Callable):
|
||||||
|
var node = RdotComputed.new()
|
||||||
|
node.computation = computation
|
||||||
|
|
||||||
|
var computed = func():
|
||||||
|
return computedGet(node)
|
||||||
|
|
||||||
|
return [computed, node]
|
||||||
|
|
||||||
|
func producerMustRecompute(node: RdotNode) -> bool:
|
||||||
|
return node.state == State.UNSET or node.state == State.COMPUTING
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
self.dirty = true
|
||||||
|
self.producerRecomputeValue = func(node: RdotNode):
|
||||||
|
assert(node.state != State.COMPUTING, "Detected cycle in computations.")
|
||||||
|
|
||||||
|
var graph := RdotGraph.getInstance()
|
||||||
|
|
||||||
|
var oldState = node.state
|
||||||
|
var oldValue = node.value
|
||||||
|
node.state = State.COMPUTING
|
||||||
|
|
||||||
|
var prevConsumer = graph.consumerBeforeComputation(node)
|
||||||
|
var newValue = node.computation.call(node.wrapper)
|
||||||
|
var oldOk = oldState != State.UNSET&&oldState != State.ERRORED
|
||||||
|
var wasEqual = oldOk&&node.equal.call(node.wrapper, oldValue, newValue)
|
||||||
|
|
||||||
|
graph.consumerAfterComputation(node, prevConsumer)
|
||||||
|
|
||||||
|
if wasEqual:
|
||||||
|
node.value = oldValue
|
||||||
|
node.state = oldState
|
||||||
|
return
|
||||||
|
|
||||||
|
node.value = newValue
|
||||||
|
node.state = State.SET
|
||||||
|
node.version += 1
|
225
app/addons/rdot/graph.gd
Normal file
225
app/addons/rdot/graph.gd
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
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 = []
|
38
app/addons/rdot/node.gd
Normal file
38
app/addons/rdot/node.gd
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
extends RefCounted
|
||||||
|
class_name RdotNode
|
||||||
|
|
||||||
|
var version := 0
|
||||||
|
var lastCleanEpoch := 0
|
||||||
|
var dirty := false
|
||||||
|
|
||||||
|
## Array[RdotNode] | null
|
||||||
|
var producerNode = null
|
||||||
|
|
||||||
|
## Array[int] | null
|
||||||
|
var producerLastReadVersion = null
|
||||||
|
|
||||||
|
## Array[int] | null
|
||||||
|
var producerIndexOfThis = null
|
||||||
|
var nextProducerIndex := 0
|
||||||
|
|
||||||
|
## Array[RdotNode] | null
|
||||||
|
var liveConsumerNode = null
|
||||||
|
|
||||||
|
## Array[int] | null
|
||||||
|
var liveConsumerIndexOfThis = null
|
||||||
|
var consumerAllowSignalWrites := false
|
||||||
|
var consumerIsAlwaysLive := false
|
||||||
|
|
||||||
|
var watched: Callable = Callable()
|
||||||
|
var unwatched: Callable = Callable()
|
||||||
|
var wrapper
|
||||||
|
|
||||||
|
func producerMustRecompute(node: RdotNode) -> bool:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var producerRecomputeValue: Callable = Callable()
|
||||||
|
var consumerMarkedDirty: Callable = Callable()
|
||||||
|
var consumerOnSignalRead: Callable = Callable()
|
||||||
|
|
||||||
|
# func _to_string():
|
||||||
|
# return "RdotNode {\n" + ",\n\t".join(get_property_list().map(func(dict): return dict.name + ": " + str(get(dict.name)))) + "\n}"
|
50
app/addons/rdot/state.gd
Normal file
50
app/addons/rdot/state.gd
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
extends RdotNode
|
||||||
|
class_name RdotState
|
||||||
|
|
||||||
|
var equal: Callable = func(this, a, b): a == b
|
||||||
|
var value: Variant = null
|
||||||
|
|
||||||
|
static func createSignal(initialValue: Variant):
|
||||||
|
var node = RdotState.new()
|
||||||
|
node.value = initialValue
|
||||||
|
|
||||||
|
var getter = func():
|
||||||
|
RdotGraph.getInstance().producerAccessed(node)
|
||||||
|
return node.value
|
||||||
|
|
||||||
|
return [getter, node]
|
||||||
|
|
||||||
|
static func setPostSignalSetFn(fn: Callable) -> Callable:
|
||||||
|
var graph := RdotGraph.getInstance()
|
||||||
|
var prev = graph.postSignalSetFn
|
||||||
|
graph.postSignalSetFn = fn
|
||||||
|
return prev
|
||||||
|
|
||||||
|
static func signalGetFn(this: RdotState):
|
||||||
|
RdotGraph.getInstance().producerAccessed(this)
|
||||||
|
return this.value
|
||||||
|
|
||||||
|
static func signalSetFn(node: RdotState, newValue: Variant):
|
||||||
|
var graph := RdotGraph.getInstance()
|
||||||
|
|
||||||
|
assert(graph.producerUpdatesAllowed())
|
||||||
|
|
||||||
|
if !node.equal.call(node.wrapper, node.value, newValue):
|
||||||
|
node.value = newValue
|
||||||
|
signalValueChanged(node)
|
||||||
|
|
||||||
|
static func signalUpdateFn(node: RdotState, updater: Callable):
|
||||||
|
var graph := RdotGraph.getInstance()
|
||||||
|
|
||||||
|
assert(graph.producerUpdatesAllowed())
|
||||||
|
|
||||||
|
signalSetFn(node, updater.call(node.value))
|
||||||
|
|
||||||
|
static func signalValueChanged(node: RdotState):
|
||||||
|
var graph := RdotGraph.getInstance()
|
||||||
|
|
||||||
|
node.version += 1
|
||||||
|
graph.producerIncrementEpoch()
|
||||||
|
graph.producerNotifyConsumers(node)
|
||||||
|
if !graph.postSignalSetFn.is_null():
|
||||||
|
graph.postSignalSetFn.call()
|
37
app/addons/rdot/store.gd
Normal file
37
app/addons/rdot/store.gd
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
extends Object
|
||||||
|
class_name RdotStore
|
||||||
|
|
||||||
|
var _proxied_value = {}
|
||||||
|
var _property_list = []
|
||||||
|
|
||||||
|
func _init(initial_value: Dictionary={}):
|
||||||
|
_proxied_value = initial_value
|
||||||
|
|
||||||
|
_property_list = _proxied_value.keys().map(func(key):
|
||||||
|
return {
|
||||||
|
"name": key,
|
||||||
|
"type": typeof(_proxied_value[key])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func _get(property):
|
||||||
|
_access_property(property)
|
||||||
|
|
||||||
|
if _proxied_value[property] is RdotStore:
|
||||||
|
return _proxied_value[property]
|
||||||
|
|
||||||
|
return _proxied_value[property].value
|
||||||
|
|
||||||
|
func _set(property, value):
|
||||||
|
_access_property(property)
|
||||||
|
|
||||||
|
_proxied_value[property].value = value
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _access_property(property):
|
||||||
|
if (_proxied_value[property] is R.State) == false:
|
||||||
|
_proxied_value[property] = R.state(_proxied_value[property])
|
||||||
|
|
||||||
|
func _get_property_list():
|
||||||
|
return _property_list
|
Loading…
Reference in New Issue
Block a user