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