immersive-home/app/addons/rdot/Rdot.gd

204 lines
4.9 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()
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)