207 lines
5.0 KiB
GDScript
207 lines
5.0 KiB
GDScript
class_name R
|
|
|
|
static func state(value: Variant, options: Dictionary={}):
|
|
if value is Dictionary:
|
|
return store(value)
|
|
|
|
return RdotState.new(value, options)
|
|
|
|
static func store(value: Dictionary):
|
|
return RdotStore.new(value)
|
|
|
|
static func computed(computation: Callable, options: Dictionary={}):
|
|
return RdotComputed.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 RdotState or value is RdotComputed:
|
|
return _bind_state(target, prop, value, arg1)
|
|
|
|
assert(false, "Invalid arguments to bind, value must be a RdotState, a RdotComputed 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):
|
|
assert(state != null, "target cannot be 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()
|
|
|
|
if callback.is_valid() == false:
|
|
return
|
|
|
|
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 RdotState:
|
|
var node: RdotStateInternal
|
|
var value = null:
|
|
get:
|
|
return do_get()
|
|
set(value):
|
|
do_set(value)
|
|
|
|
func _init(value: Variant, options: Dictionary={}):
|
|
var ref = RdotStateInternal.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 RdotStateInternal.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
|
|
|
|
RdotStateInternal.signalSetFn.call(ref, value)
|
|
|
|
class RdotComputed:
|
|
var node: RdotComputedInternal
|
|
var value = null:
|
|
get:
|
|
return do_get()
|
|
set(value):
|
|
pass
|
|
|
|
func _init(computation: Callable, options: Dictionary={}):
|
|
var ref = RdotComputedInternal.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 RdotComputedInternal.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[RdotState | RComputed]
|
|
func _assertSignals(signals: Array):
|
|
for s in signals:
|
|
assert(s is RdotState or s is RdotComputed, "Watcher expects signals to be RdotState or RdotComputed")
|
|
|
|
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)
|