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

202 lines
4.9 KiB
GDScript3
Raw Normal View History

2024-04-09 16:30:23 +03:00
class_name R
static func state(value: Variant, options: Dictionary={}):
if value is Dictionary:
return store(value)
2024-04-09 18:11:24 +03:00
return RdotState.new(value, options)
2024-04-09 16:30:23 +03:00
static func store(value: Dictionary):
return RdotStore.new(value)
static func computed(computation: Callable, options: Dictionary={}):
2024-04-09 18:11:24 +03:00
return RdotComputed.new(computation, options)
2024-04-09 16:30:23 +03:00
## 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)
2024-04-09 18:11:24 +03:00
if value is RdotState or value is RdotComputed:
2024-04-09 16:30:23 +03:00
return _bind_state(target, prop, value, arg1)
2024-04-09 18:46:59 +03:00
assert(false, "Invalid arguments to bind, value must be a RdotState, a RdotComputed or a RdotStore")
2024-04-09 16:30:23 +03:00
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])
2024-04-09 18:11:24 +03:00
class RdotState:
var node: RdotStateInternal
2024-04-09 16:30:23 +03:00
var value = null:
get:
return do_get()
set(value):
do_set(value)
func _init(value: Variant, options: Dictionary={}):
2024-04-09 18:11:24 +03:00
var ref = RdotStateInternal.createSignal(value)
2024-04-09 16:30:23 +03:00
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():
2024-04-09 18:11:24 +03:00
return RdotStateInternal.signalGetFn.call(self.node)
2024-04-09 16:30:23 +03:00
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
2024-04-09 18:11:24 +03:00
RdotStateInternal.signalSetFn.call(ref, value)
2024-04-09 16:30:23 +03:00
2024-04-09 18:11:24 +03:00
class RdotComputed:
var node: RdotComputedInternal
2024-04-09 16:30:23 +03:00
var value = null:
get:
return do_get()
set(value):
pass
func _init(computation: Callable, options: Dictionary={}):
2024-04-09 18:11:24 +03:00
var ref = RdotComputedInternal.createdComputed(computation)
2024-04-09 16:30:23 +03:00
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():
2024-04-09 18:11:24 +03:00
return RdotComputedInternal.computedGet(node)
2024-04-09 16:30:23 +03:00
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
2024-04-09 18:11:24 +03:00
## signals: Array[RdotState | RComputed]
2024-04-09 16:30:23 +03:00
func _assertSignals(signals: Array):
for s in signals:
2024-04-09 18:11:24 +03:00
assert(s is RdotState or s is RdotComputed, "Watcher expects signals to be RdotState or RdotComputed")
2024-04-09 16:30:23 +03:00
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)