Rework RV to trigger on entering world
This commit is contained in:
681
FreshShit/_ReactiveValue/event.lua
Normal file
681
FreshShit/_ReactiveValue/event.lua
Normal file
@@ -0,0 +1,681 @@
|
||||
-- PLAYER_ENTERING_WORLD
|
||||
function(e)
|
||||
if ReactiveValue then return end
|
||||
|
||||
---@diagnostic disable: missing-return
|
||||
local function dumpTable(table, depth)
|
||||
if (depth > 200) then
|
||||
print("Error: Depth > 200 in dumpTable()")
|
||||
return
|
||||
end
|
||||
for k, v in pairs(table) do
|
||||
if (type(v) == "table") then
|
||||
print(string.rep(" ", depth) .. k .. ":")
|
||||
dumpTable(v, depth + 1)
|
||||
else
|
||||
print(string.rep(" ", depth) .. k .. ": ", v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local metadata = {
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__add = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value + other._value
|
||||
end
|
||||
if otherType == "string" and self._type == otherType then
|
||||
return self._value .. other
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value + other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__mul = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value * other._value
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value * other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__sub = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value - other._value
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value - other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__div = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value / other._value
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value / other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__mod = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value % other._value
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value % other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__pow = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value ^ other._value
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value ^ other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return boolean
|
||||
__eq = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value == other._value
|
||||
end
|
||||
return self._value == other
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return boolean
|
||||
__lt = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value < other._value
|
||||
end
|
||||
return self._value < other
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return boolean
|
||||
__le = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value <= other._value
|
||||
end
|
||||
return self._value <= other
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return boolean
|
||||
__gt = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value > other._value
|
||||
end
|
||||
return self._value > other
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return boolean
|
||||
__ge = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value >= other._value
|
||||
end
|
||||
return self._value >= other
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@return number
|
||||
__len = function(self)
|
||||
if self._type == "table" then
|
||||
return #self._value
|
||||
end
|
||||
if self._type == "string" then
|
||||
return string.len(self._value)
|
||||
end
|
||||
return 0
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@return string
|
||||
__tostring = function(self)
|
||||
return tostring(self._value)
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param key string
|
||||
---@param value any
|
||||
---@return nil
|
||||
__newindex = function(self, key, value)
|
||||
local setupComplete = rawget(self, "_setupComplete")
|
||||
if setupComplete == nil or setupComplete == false then
|
||||
rawset(self, key, value)
|
||||
return
|
||||
end
|
||||
if self._type ~= "table" then
|
||||
rawset(self, key, value)
|
||||
return
|
||||
end
|
||||
|
||||
self._value[key] = value
|
||||
local ChangedKey = { key }
|
||||
|
||||
-- If the value being assigned is a ReactiveValue
|
||||
-- Then listen to changes on it as well
|
||||
-- And propagate those changes upwards
|
||||
if self._recursive and getmetatable(value) == getmetatable(self) then
|
||||
self:_setupListeners(key, value)
|
||||
end
|
||||
|
||||
self:_notify()
|
||||
self:_notifyFieldChanged(ChangedKey)
|
||||
self:_notifyAnyFieldChanged(ChangedKey)
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param key string
|
||||
---@return any|nil
|
||||
__index = function(self, key)
|
||||
local value = rawget(self, key)
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
if rawget(self, "_type") ~= "table" then
|
||||
return nil
|
||||
end
|
||||
local innerTable = rawget(self, "_value")
|
||||
if innerTable ~= nil then
|
||||
return rawget(innerTable, key)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
-- __index = ReactiveValue
|
||||
}
|
||||
|
||||
--- Sadly I could not get @generic to play nice with this class
|
||||
--- I think it's not ready yet, there are issues on github describing similar problems and it is marked as WIP...
|
||||
--- Guess I'll have to live without it for now and specify type of a RV in #type
|
||||
|
||||
---## A type safe value that can be listened to for changes
|
||||
---### **Always use RV:set() for setting primitive values**
|
||||
--- Supports primitive values and tables<br>
|
||||
--- Tables can be listened to for changes on any field or a specific field<br>
|
||||
---### Example usage (value):<br>
|
||||
--- ```lua
|
||||
--- local test = ReactiveValue.new(1)
|
||||
--- test:onChange(function(value)
|
||||
--- print("test changed to " .. value)
|
||||
--- end)
|
||||
--- test:set(2)
|
||||
--- test:set(test + 3)
|
||||
--- ```
|
||||
---### Example usage (table):<br>
|
||||
--- ```lua
|
||||
--- local test = ReactiveValue.new({1, 2, 3})
|
||||
--- test:onAnyFieldChange(function(field, value)
|
||||
--- print(string.format("test.%s changed to %s", table.concat(field, "."), value))
|
||||
--- end)
|
||||
--- test[1] = 4 -- test.1 changed to 4
|
||||
--- test[4] = {1, 2, 3} -- test.4 changed to <table>
|
||||
--- test[4][1] = 14 -- No log(!!) because test[4] is a table and not a ReactiveValue
|
||||
--- ```
|
||||
---### To trigger a callback for `test[4][1]` in the previous example do:<br>
|
||||
--- ```lua
|
||||
--- local test = ReactiveValue.new({1, 2, 3}, true)
|
||||
--- test:onAnyFieldChange(function(field, value)
|
||||
--- print(string.format("test.%s changed to %s", table.concat(field, "."), value))
|
||||
--- end)
|
||||
--- test[1] = 4 -- test.1 changed to 4
|
||||
--- test[4] = {1, 2, 3} -- test.4 changed to <table>
|
||||
--- test[4][1] = 14 -- test.4.1 changed to 14
|
||||
--- ```
|
||||
---### To listen to a specific field of a table do:<br>
|
||||
--- ```lua
|
||||
--- local test = ReactiveValue.new({1, 2, 3}, true)
|
||||
--- test:onFieldChange("1", function(value)
|
||||
--- print("test.1 changed to " .. value)
|
||||
--- end)
|
||||
--- test[1] = 4 -- test.1 changed to 4
|
||||
--- test[4] = {1, 2, 3} -- Does not trigger callback
|
||||
-- ```
|
||||
---@class ReactiveValue
|
||||
---@field _listeners table<function, boolean>
|
||||
---@field _fieldListeners table<string, table<function, boolean>>
|
||||
---@field _anyFieldListeners table<number, table<function, boolean>>
|
||||
---@field _oneTimeListeners table<function, boolean>
|
||||
---@field _value any
|
||||
---@field _type string
|
||||
---@field _recursive boolean?
|
||||
ReactiveValue = {
|
||||
---#### Get the underlying value of a ReactiveValue
|
||||
---@param self ReactiveValue
|
||||
---@return any
|
||||
get = function(self) end,
|
||||
|
||||
---### Set the underlying value of a ReactiveValue triggering listener callbacks
|
||||
---@param self ReactiveValue
|
||||
---@param newValue any
|
||||
set = function(self, newValue) end,
|
||||
|
||||
---## EVENT
|
||||
---### Register a listener that is triggered whenever the underlying value changes
|
||||
--- Returns a function that can be called to undo the callback
|
||||
---@param self ReactiveValue
|
||||
---@param callback fun(value: any, type: string)
|
||||
---@return fun(): nil
|
||||
onChange = function(self, callback) end,
|
||||
|
||||
---## EVENT
|
||||
---### Register a listener that is triggered whenever a specific field of a table changes
|
||||
--- Returns a function that can be called to undo the callback
|
||||
---@param self ReactiveValue
|
||||
---@param field string
|
||||
---@param callback fun(field: string[], value: any, type: string)
|
||||
---@return fun(): nil
|
||||
onFieldChange = function(self, field, callback) end,
|
||||
|
||||
---## EVENT
|
||||
---### Register a listener that is triggered whenever any field of a table changes
|
||||
--- Returns a function that can be called to undo the callback
|
||||
---@param self ReactiveValue
|
||||
---@param callback fun(field: string[], value: any, type: string)
|
||||
---@param depth number? How deep to listen for changes
|
||||
---@return fun(): nil
|
||||
onAnyFieldChange = function(self, callback, depth) end,
|
||||
|
||||
---## EVENT
|
||||
---### Register a listener that is triggered ONCE whenever the underlying value changes
|
||||
--- Returns a function that can be called to undo the callback
|
||||
---@param self ReactiveValue
|
||||
---@param callback fun(value: any, type: string)
|
||||
---@return fun(): nil
|
||||
once = function(self, callback) end,
|
||||
---### Setup listeners for all fields of a table recursively
|
||||
--- This is used to ensure that listeners are notified recursively
|
||||
|
||||
---@param self ReactiveValue
|
||||
_setupAllListenersRecursively = function(self) end,
|
||||
---### Setup listeners for a specific field of a table recursively
|
||||
--- This is used to ensure that listeners are notified recursively
|
||||
|
||||
---@param self ReactiveValue
|
||||
_setupListeners = function(self, key, value, recursive) end,
|
||||
|
||||
---### Notify listeners that the underlying value has changed
|
||||
---@param self ReactiveValue
|
||||
---@return nil
|
||||
---#### Event contains:
|
||||
--- 2. value: any - The new value of the changed field
|
||||
--- 3. type: string - The type of the new value of the changed field
|
||||
_notify = function(self) end,
|
||||
|
||||
---### Notify listeners that a specific field of the underlying value has changed
|
||||
---#### Event contains:
|
||||
--- 1. field: table<string> - A list of keys that lead to the changed field
|
||||
--- 2. value: any - The new value of the changed field
|
||||
--- 3. type: string - The type of the new value of the changed field
|
||||
---@param self ReactiveValue
|
||||
_notifyFieldChanged = function(self, field) end,
|
||||
|
||||
---### Notify listeners that any field of the underlying value has changed
|
||||
---#### Event contains:
|
||||
--- 1. field: table<string> - A list of keys that lead to the changed field
|
||||
--- 2. value: any - The new value of the changed field
|
||||
--- 3. type: string - The type of the new value of the changed field
|
||||
_notifyAnyFieldChanged = function(self, field) end,
|
||||
}
|
||||
---### Constructor
|
||||
---@param initialValue any
|
||||
---@param recursive boolean?
|
||||
---@return ReactiveValue
|
||||
ReactiveValue.new = function(initialValue, recursive)
|
||||
local self = setmetatable({}, metadata)
|
||||
self._listeners = {}
|
||||
self._fieldListeners = {}
|
||||
self._anyFieldListeners = {}
|
||||
self._oneTimeListeners = {}
|
||||
self._value = initialValue
|
||||
self._type = type(initialValue)
|
||||
self._recursive = recursive or false
|
||||
|
||||
---@return any
|
||||
self.get = function(self)
|
||||
return self._value
|
||||
end
|
||||
---@param newValue any
|
||||
self.set = function(self, newValue)
|
||||
if self._value == newValue then
|
||||
return
|
||||
end
|
||||
if type(newValue) ~= self._type then
|
||||
error("Expected " .. self._type .. ", got " .. type(newValue))
|
||||
return
|
||||
end
|
||||
self._value = newValue
|
||||
self:_notify()
|
||||
end
|
||||
self.onChange = function(self, callback)
|
||||
if type(callback) ~= "function" then
|
||||
error("Expected function, got " .. type(callback))
|
||||
return function() end
|
||||
end
|
||||
self._listeners[callback] = true
|
||||
return function()
|
||||
self._listeners[callback] = nil
|
||||
end
|
||||
end
|
||||
self.onFieldChange = function(self, field, callback)
|
||||
if type(callback) ~= "function" then
|
||||
error("Expected function, got " .. type(callback))
|
||||
return function() end
|
||||
end
|
||||
if self._fieldListeners[field] == nil then
|
||||
self._fieldListeners[field] = {}
|
||||
end
|
||||
self._fieldListeners[field][callback] = true
|
||||
return function()
|
||||
self._fieldListeners[field][callback] = nil
|
||||
end
|
||||
end
|
||||
self.onAnyFieldChange = function(self, callback, depth)
|
||||
depth = depth or 99999
|
||||
if type(callback) ~= "function" then
|
||||
error("Expected function, got " .. type(callback))
|
||||
return function() end
|
||||
end
|
||||
if self._anyFieldListeners[depth] == nil then
|
||||
self._anyFieldListeners[depth] = {}
|
||||
end
|
||||
self._anyFieldListeners[depth][callback] = true
|
||||
return function()
|
||||
self._anyFieldListeners[depth][callback] = nil
|
||||
end
|
||||
end
|
||||
self.once = function(self, callback)
|
||||
if type(callback) ~= "function" then
|
||||
error("Expected function, got " .. type(callback))
|
||||
return function() end
|
||||
end
|
||||
self._oneTimeListeners[callback] = true
|
||||
return function()
|
||||
self._oneTimeListeners[callback] = nil
|
||||
end
|
||||
end
|
||||
|
||||
self._setupAllListenersRecursively = function(self)
|
||||
if self._type ~= "table" then
|
||||
return
|
||||
end
|
||||
for key, value in pairs(self._value) do
|
||||
self:_setupListeners(key, value, true)
|
||||
end
|
||||
end
|
||||
---@param key string
|
||||
---@param value any
|
||||
---@param recursive boolean?
|
||||
self._setupListeners = function(self, key, value, recursive)
|
||||
recursive = recursive or false
|
||||
if self._type ~= "table" then
|
||||
return
|
||||
end
|
||||
if getmetatable(value) ~= getmetatable(self) then
|
||||
return
|
||||
end
|
||||
value._recursive = true
|
||||
if value._type == "table" then
|
||||
value:onAnyFieldChange(function(key2)
|
||||
ChangedKey = { key, table.unpack(key2) }
|
||||
self:_notifyFieldChanged(ChangedKey)
|
||||
self:_notifyAnyFieldChanged(ChangedKey)
|
||||
end)
|
||||
else
|
||||
value:onChange(function(newVal)
|
||||
ChangedKey = { key }
|
||||
self:_notifyFieldChanged(ChangedKey)
|
||||
self:_notifyAnyFieldChanged(ChangedKey)
|
||||
end)
|
||||
end
|
||||
|
||||
if recursive then
|
||||
value:_setupAllListenersRecursively()
|
||||
end
|
||||
end
|
||||
|
||||
if recursive then
|
||||
self:_setupAllListenersRecursively()
|
||||
end
|
||||
|
||||
self._notify = function(self)
|
||||
for listener, _ in pairs(self._oneTimeListeners) do
|
||||
-- task.spawn(listener, self._value, self._type)
|
||||
listener(self._value, self._type)
|
||||
self._oneTimeListeners[listener] = nil
|
||||
end
|
||||
for listener, _ in pairs(self._listeners) do
|
||||
-- task.spawn(listener, self._value, self._type)
|
||||
listener(self._value, self._type)
|
||||
end
|
||||
end
|
||||
-- TODO: Maybe implement some sort of regex here or something...
|
||||
-- Such as listening to *.field1 or something
|
||||
-- But this (having to loop over listeners and evaluate some condition) would tank performance
|
||||
-- Compared to a simple lookup
|
||||
-- So I'm not going to do anything about it for now, until I figure out a better way
|
||||
---@param field table<string, string> A list of keys that lead to the changed field
|
||||
---@return nil
|
||||
self._notifyFieldChanged = function(self, field)
|
||||
local value = self._value
|
||||
for _, key in ipairs(field) do
|
||||
value = value[key]
|
||||
end
|
||||
|
||||
local strfield = table.concat(field, ".")
|
||||
if self._fieldListeners[strfield] == nil then
|
||||
return
|
||||
end
|
||||
for listener, _ in pairs(self._fieldListeners[strfield]) do
|
||||
-- task.spawn(listener, value, type(value))
|
||||
listener(value, type(value))
|
||||
end
|
||||
end
|
||||
---@param self ReactiveValue
|
||||
---@param field table<string, string> A list of keys that lead to the changed field
|
||||
---@return nil
|
||||
self._notifyAnyFieldChanged = function(self, field)
|
||||
local value = self._value
|
||||
for _, key in ipairs(field) do
|
||||
value = value[key]
|
||||
end
|
||||
local keyDepth = #field
|
||||
for listenerDepth, listeners in pairs(self._anyFieldListeners) do
|
||||
if listenerDepth >= keyDepth then
|
||||
for listener, _ in pairs(listeners) do
|
||||
-- The reason this also returns type(value) is so that clients don't have to compute type(value)
|
||||
-- I assume some of them might want to do it so computing it once is probably better than having every client compute it for themselves
|
||||
-- task.spawn(listener, field, value, type(value))
|
||||
listener(field, value, type(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self._setupComplete = true
|
||||
return self
|
||||
end
|
||||
|
||||
_G["ReactiveValue"] = ReactiveValue
|
||||
|
||||
-- S -- begintest
|
||||
-- S local invocations = 0
|
||||
-- S -- Integer example
|
||||
-- S local test = ReactiveValue.new(1)
|
||||
-- S test:onChange(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test changed to " .. value)
|
||||
-- S end)
|
||||
-- S test:set(2)
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- String example
|
||||
-- S test = ReactiveValue.new("test")
|
||||
-- S test:onChange(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test changed to " .. value)
|
||||
-- S end)
|
||||
-- S test:set("test2")
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S -- Type safety example
|
||||
-- S local res, err = pcall(test.set, test, 1)
|
||||
-- S assert(res == false)
|
||||
-- S assert(err:find("Expected string, got number"))
|
||||
-- S
|
||||
-- S -- Table example
|
||||
-- S invocations = 0
|
||||
-- S test = ReactiveValue.new({1, 2, 3})
|
||||
-- S local clbk = test:onChange(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test changed to")
|
||||
-- S dumpTable(value, 0)
|
||||
-- S end)
|
||||
-- S test:set({1, 2, 3, 4})
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S -- Callback removal example
|
||||
-- S clbk()
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S -- Any field change example
|
||||
-- S clbk = test:onAnyFieldChange(function(field, value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
|
||||
-- S end)
|
||||
-- S test.Pero = 1
|
||||
-- S test.Pero = nil
|
||||
-- S assert(invocations == 2)
|
||||
-- S clbk()
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S -- Field change example
|
||||
-- S test:onFieldChange("Pero", function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test.Pero changed to " .. value)
|
||||
-- S end)
|
||||
-- S test.Pero = 2
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S -- One time listener example
|
||||
-- S test:once(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test changed to")
|
||||
-- S dumpTable(value, 0)
|
||||
-- S end)
|
||||
-- S test:set({3, 2, 1})
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S -- Table push example
|
||||
-- S test = ReactiveValue.new({})
|
||||
-- S test:onChange(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test changed to")
|
||||
-- S dumpTable(value, 0)
|
||||
-- S end)
|
||||
-- S test:onAnyFieldChange(function(field, value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test." .. table.concat(field, ".") .. " changed to " .. value)
|
||||
-- S end)
|
||||
-- S test[#test + 1] = 4
|
||||
-- S assert(invocations == 2)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S test = ReactiveValue.new({
|
||||
-- S name = "pero",
|
||||
-- S coins = ReactiveValue.new(1)
|
||||
-- S })
|
||||
-- S test.coins:onChange(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test.coins changed to " .. value)
|
||||
-- S end)
|
||||
-- S test.coins:set(2)
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S test = ReactiveValue.new({
|
||||
-- S name = "pero",
|
||||
-- S coins = ReactiveValue.new(1)
|
||||
-- S }, true)
|
||||
-- S test:onAnyFieldChange(function(field, value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
|
||||
-- S end)
|
||||
-- S test.coins:set(2)
|
||||
-- S test.pero2 = ReactiveValue.new({})
|
||||
-- S test.pero2.coins = ReactiveValue.new(1)
|
||||
-- S test.pero2.coins:set(2)
|
||||
-- S assert(invocations == 4)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S test = ReactiveValue.new({
|
||||
-- S name = "pero",
|
||||
-- S coins = ReactiveValue.new({
|
||||
-- S value = ReactiveValue.new(1)
|
||||
-- S })
|
||||
-- S }, true)
|
||||
-- S test:onAnyFieldChange(function(field, value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
|
||||
-- S end)
|
||||
-- S test.coins.value:set(2)
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S test = ReactiveValue.new({}, true)
|
||||
-- S test.coins = ReactiveValue.new({})
|
||||
-- S test.coins.value = ReactiveValue.new(1)
|
||||
-- S test:onAnyFieldChange(function(field, value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
|
||||
-- S end)
|
||||
-- S test.coins.value:set(3)
|
||||
-- S assert(invocations == 1)
|
||||
--S
|
||||
--S invocations = 0
|
||||
--S test = ReactiveValue.new({}, true)
|
||||
--S test:onAnyFieldChange(function(field, value)
|
||||
--S invocations = invocations + 1
|
||||
--S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
|
||||
--S end, 1)
|
||||
--S test.test2 = ReactiveValue.new({}, true)
|
||||
--S test.test2.test3 = ReactiveValue.new(1)
|
||||
--S assert(invocations == 1)
|
||||
--S
|
||||
-- S -- endtest
|
||||
|
||||
-- return ReactiveValue
|
||||
end
|
File diff suppressed because one or more lines are too long
@@ -1,676 +0,0 @@
|
||||
---@diagnostic disable: missing-return
|
||||
local function dumpTable(table, depth)
|
||||
if (depth > 200) then
|
||||
print("Error: Depth > 200 in dumpTable()")
|
||||
return
|
||||
end
|
||||
for k, v in pairs(table) do
|
||||
if (type(v) == "table") then
|
||||
print(string.rep(" ", depth) .. k .. ":")
|
||||
dumpTable(v, depth + 1)
|
||||
else
|
||||
print(string.rep(" ", depth) .. k .. ": ", v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local metadata = {
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__add = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value + other._value
|
||||
end
|
||||
if otherType == "string" and self._type == otherType then
|
||||
return self._value .. other
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value + other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__mul = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value * other._value
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value * other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__sub = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value - other._value
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value - other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__div = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value / other._value
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value / other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__mod = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value % other._value
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value % other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return ReactiveValue|nil
|
||||
__pow = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value ^ other._value
|
||||
end
|
||||
if otherType == "number" and self._type == otherType then
|
||||
return self._value ^ other
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return boolean
|
||||
__eq = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value == other._value
|
||||
end
|
||||
return self._value == other
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return boolean
|
||||
__lt = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value < other._value
|
||||
end
|
||||
return self._value < other
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return boolean
|
||||
__le = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value <= other._value
|
||||
end
|
||||
return self._value <= other
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return boolean
|
||||
__gt = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value > other._value
|
||||
end
|
||||
return self._value > other
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param other ReactiveValue
|
||||
---@return boolean
|
||||
__ge = function(self, other)
|
||||
local otherType = type(other)
|
||||
if otherType == "table" and other._type and other._type == self._type and other._value then
|
||||
return self._value >= other._value
|
||||
end
|
||||
return self._value >= other
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@return number
|
||||
__len = function(self)
|
||||
if self._type == "table" then
|
||||
return #self._value
|
||||
end
|
||||
if self._type == "string" then
|
||||
return string.len(self._value)
|
||||
end
|
||||
return 0
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@return string
|
||||
__tostring = function(self)
|
||||
return tostring(self._value)
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param key string
|
||||
---@param value any
|
||||
---@return nil
|
||||
__newindex = function(self, key, value)
|
||||
local setupComplete = rawget(self, "_setupComplete")
|
||||
if setupComplete == nil or setupComplete == false then
|
||||
rawset(self, key, value)
|
||||
return
|
||||
end
|
||||
if self._type ~= "table" then
|
||||
rawset(self, key, value)
|
||||
return
|
||||
end
|
||||
|
||||
self._value[key] = value
|
||||
local ChangedKey = { key }
|
||||
|
||||
-- If the value being assigned is a ReactiveValue
|
||||
-- Then listen to changes on it as well
|
||||
-- And propagate those changes upwards
|
||||
if self._recursive and getmetatable(value) == getmetatable(self) then
|
||||
self:_setupListeners(key, value)
|
||||
end
|
||||
|
||||
self:_notify()
|
||||
self:_notifyFieldChanged(ChangedKey)
|
||||
self:_notifyAnyFieldChanged(ChangedKey)
|
||||
end,
|
||||
---@param self ReactiveValue
|
||||
---@param key string
|
||||
---@return any|nil
|
||||
__index = function(self, key)
|
||||
local value = rawget(self, key)
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
if rawget(self, "_type") ~= "table" then
|
||||
return nil
|
||||
end
|
||||
local innerTable = rawget(self, "_value")
|
||||
if innerTable ~= nil then
|
||||
return rawget(innerTable, key)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
-- __index = ReactiveValue
|
||||
}
|
||||
|
||||
--- Sadly I could not get @generic to play nice with this class
|
||||
--- I think it's not ready yet, there are issues on github describing similar problems and it is marked as WIP...
|
||||
--- Guess I'll have to live without it for now and specify type of a RV in #type
|
||||
|
||||
---## A type safe value that can be listened to for changes
|
||||
---### **Always use RV:set() for setting primitive values**
|
||||
--- Supports primitive values and tables<br>
|
||||
--- Tables can be listened to for changes on any field or a specific field<br>
|
||||
---### Example usage (value):<br>
|
||||
--- ```lua
|
||||
--- local test = ReactiveValue.new(1)
|
||||
--- test:onChange(function(value)
|
||||
--- print("test changed to " .. value)
|
||||
--- end)
|
||||
--- test:set(2)
|
||||
--- test:set(test + 3)
|
||||
--- ```
|
||||
---### Example usage (table):<br>
|
||||
--- ```lua
|
||||
--- local test = ReactiveValue.new({1, 2, 3})
|
||||
--- test:onAnyFieldChange(function(field, value)
|
||||
--- print(string.format("test.%s changed to %s", table.concat(field, "."), value))
|
||||
--- end)
|
||||
--- test[1] = 4 -- test.1 changed to 4
|
||||
--- test[4] = {1, 2, 3} -- test.4 changed to <table>
|
||||
--- test[4][1] = 14 -- No log(!!) because test[4] is a table and not a ReactiveValue
|
||||
--- ```
|
||||
---### To trigger a callback for `test[4][1]` in the previous example do:<br>
|
||||
--- ```lua
|
||||
--- local test = ReactiveValue.new({1, 2, 3}, true)
|
||||
--- test:onAnyFieldChange(function(field, value)
|
||||
--- print(string.format("test.%s changed to %s", table.concat(field, "."), value))
|
||||
--- end)
|
||||
--- test[1] = 4 -- test.1 changed to 4
|
||||
--- test[4] = {1, 2, 3} -- test.4 changed to <table>
|
||||
--- test[4][1] = 14 -- test.4.1 changed to 14
|
||||
--- ```
|
||||
---### To listen to a specific field of a table do:<br>
|
||||
--- ```lua
|
||||
--- local test = ReactiveValue.new({1, 2, 3}, true)
|
||||
--- test:onFieldChange("1", function(value)
|
||||
--- print("test.1 changed to " .. value)
|
||||
--- end)
|
||||
--- test[1] = 4 -- test.1 changed to 4
|
||||
--- test[4] = {1, 2, 3} -- Does not trigger callback
|
||||
-- ```
|
||||
---@class ReactiveValue
|
||||
---@field _listeners table<function, boolean>
|
||||
---@field _fieldListeners table<string, table<function, boolean>>
|
||||
---@field _anyFieldListeners table<number, table<function, boolean>>
|
||||
---@field _oneTimeListeners table<function, boolean>
|
||||
---@field _value any
|
||||
---@field _type string
|
||||
---@field _recursive boolean?
|
||||
ReactiveValue = {
|
||||
---#### Get the underlying value of a ReactiveValue
|
||||
---@param self ReactiveValue
|
||||
---@return any
|
||||
get = function(self) end,
|
||||
|
||||
---### Set the underlying value of a ReactiveValue triggering listener callbacks
|
||||
---@param self ReactiveValue
|
||||
---@param newValue any
|
||||
set = function(self, newValue) end,
|
||||
|
||||
---## EVENT
|
||||
---### Register a listener that is triggered whenever the underlying value changes
|
||||
--- Returns a function that can be called to undo the callback
|
||||
---@param self ReactiveValue
|
||||
---@param callback fun(value: any, type: string)
|
||||
---@return fun(): nil
|
||||
onChange = function(self, callback) end,
|
||||
|
||||
---## EVENT
|
||||
---### Register a listener that is triggered whenever a specific field of a table changes
|
||||
--- Returns a function that can be called to undo the callback
|
||||
---@param self ReactiveValue
|
||||
---@param field string
|
||||
---@param callback fun(field: string[], value: any, type: string)
|
||||
---@return fun(): nil
|
||||
onFieldChange = function(self, field, callback) end,
|
||||
|
||||
---## EVENT
|
||||
---### Register a listener that is triggered whenever any field of a table changes
|
||||
--- Returns a function that can be called to undo the callback
|
||||
---@param self ReactiveValue
|
||||
---@param callback fun(field: string[], value: any, type: string)
|
||||
---@param depth number? How deep to listen for changes
|
||||
---@return fun(): nil
|
||||
onAnyFieldChange = function(self, callback, depth) end,
|
||||
|
||||
---## EVENT
|
||||
---### Register a listener that is triggered ONCE whenever the underlying value changes
|
||||
--- Returns a function that can be called to undo the callback
|
||||
---@param self ReactiveValue
|
||||
---@param callback fun(value: any, type: string)
|
||||
---@return fun(): nil
|
||||
once = function(self, callback) end,
|
||||
---### Setup listeners for all fields of a table recursively
|
||||
--- This is used to ensure that listeners are notified recursively
|
||||
|
||||
---@param self ReactiveValue
|
||||
_setupAllListenersRecursively = function(self) end,
|
||||
---### Setup listeners for a specific field of a table recursively
|
||||
--- This is used to ensure that listeners are notified recursively
|
||||
|
||||
---@param self ReactiveValue
|
||||
_setupListeners = function(self, key, value, recursive) end,
|
||||
|
||||
---### Notify listeners that the underlying value has changed
|
||||
---@param self ReactiveValue
|
||||
---@return nil
|
||||
---#### Event contains:
|
||||
--- 2. value: any - The new value of the changed field
|
||||
--- 3. type: string - The type of the new value of the changed field
|
||||
_notify = function(self) end,
|
||||
|
||||
---### Notify listeners that a specific field of the underlying value has changed
|
||||
---#### Event contains:
|
||||
--- 1. field: table<string> - A list of keys that lead to the changed field
|
||||
--- 2. value: any - The new value of the changed field
|
||||
--- 3. type: string - The type of the new value of the changed field
|
||||
---@param self ReactiveValue
|
||||
_notifyFieldChanged = function(self, field) end,
|
||||
|
||||
---### Notify listeners that any field of the underlying value has changed
|
||||
---#### Event contains:
|
||||
--- 1. field: table<string> - A list of keys that lead to the changed field
|
||||
--- 2. value: any - The new value of the changed field
|
||||
--- 3. type: string - The type of the new value of the changed field
|
||||
_notifyAnyFieldChanged = function(self, field) end,
|
||||
}
|
||||
---### Constructor
|
||||
---@param initialValue any
|
||||
---@param recursive boolean?
|
||||
---@return ReactiveValue
|
||||
ReactiveValue.new = function(initialValue, recursive)
|
||||
local self = setmetatable({}, metadata)
|
||||
self._listeners = {}
|
||||
self._fieldListeners = {}
|
||||
self._anyFieldListeners = {}
|
||||
self._oneTimeListeners = {}
|
||||
self._value = initialValue
|
||||
self._type = type(initialValue)
|
||||
self._recursive = recursive or false
|
||||
|
||||
---@return any
|
||||
self.get = function(self)
|
||||
return self._value
|
||||
end
|
||||
---@param newValue any
|
||||
self.set = function(self, newValue)
|
||||
if self._value == newValue then
|
||||
return
|
||||
end
|
||||
if type(newValue) ~= self._type then
|
||||
error("Expected " .. self._type .. ", got " .. type(newValue))
|
||||
return
|
||||
end
|
||||
self._value = newValue
|
||||
self:_notify()
|
||||
end
|
||||
self.onChange = function(self, callback)
|
||||
if type(callback) ~= "function" then
|
||||
error("Expected function, got " .. type(callback))
|
||||
return function() end
|
||||
end
|
||||
self._listeners[callback] = true
|
||||
return function()
|
||||
self._listeners[callback] = nil
|
||||
end
|
||||
end
|
||||
self.onFieldChange = function(self, field, callback)
|
||||
if type(callback) ~= "function" then
|
||||
error("Expected function, got " .. type(callback))
|
||||
return function() end
|
||||
end
|
||||
if self._fieldListeners[field] == nil then
|
||||
self._fieldListeners[field] = {}
|
||||
end
|
||||
self._fieldListeners[field][callback] = true
|
||||
return function()
|
||||
self._fieldListeners[field][callback] = nil
|
||||
end
|
||||
end
|
||||
self.onAnyFieldChange = function(self, callback, depth)
|
||||
depth = depth or 99999
|
||||
if type(callback) ~= "function" then
|
||||
error("Expected function, got " .. type(callback))
|
||||
return function() end
|
||||
end
|
||||
if self._anyFieldListeners[depth] == nil then
|
||||
self._anyFieldListeners[depth] = {}
|
||||
end
|
||||
self._anyFieldListeners[depth][callback] = true
|
||||
return function()
|
||||
self._anyFieldListeners[depth][callback] = nil
|
||||
end
|
||||
end
|
||||
self.once = function(self, callback)
|
||||
if type(callback) ~= "function" then
|
||||
error("Expected function, got " .. type(callback))
|
||||
return function() end
|
||||
end
|
||||
self._oneTimeListeners[callback] = true
|
||||
return function()
|
||||
self._oneTimeListeners[callback] = nil
|
||||
end
|
||||
end
|
||||
|
||||
self._setupAllListenersRecursively = function(self)
|
||||
if self._type ~= "table" then
|
||||
return
|
||||
end
|
||||
for key, value in pairs(self._value) do
|
||||
self:_setupListeners(key, value, true)
|
||||
end
|
||||
end
|
||||
---@param key string
|
||||
---@param value any
|
||||
---@param recursive boolean?
|
||||
self._setupListeners = function(self, key, value, recursive)
|
||||
recursive = recursive or false
|
||||
if self._type ~= "table" then
|
||||
return
|
||||
end
|
||||
if getmetatable(value) ~= getmetatable(self) then
|
||||
return
|
||||
end
|
||||
value._recursive = true
|
||||
if value._type == "table" then
|
||||
value:onAnyFieldChange(function(key2)
|
||||
ChangedKey = { key, table.unpack(key2) }
|
||||
self:_notifyFieldChanged(ChangedKey)
|
||||
self:_notifyAnyFieldChanged(ChangedKey)
|
||||
end)
|
||||
else
|
||||
value:onChange(function(newVal)
|
||||
ChangedKey = { key }
|
||||
self:_notifyFieldChanged(ChangedKey)
|
||||
self:_notifyAnyFieldChanged(ChangedKey)
|
||||
end)
|
||||
end
|
||||
|
||||
if recursive then
|
||||
value:_setupAllListenersRecursively()
|
||||
end
|
||||
end
|
||||
|
||||
if recursive then
|
||||
self:_setupAllListenersRecursively()
|
||||
end
|
||||
|
||||
self._notify = function(self)
|
||||
for listener, _ in pairs(self._oneTimeListeners) do
|
||||
-- task.spawn(listener, self._value, self._type)
|
||||
listener(self._value, self._type)
|
||||
self._oneTimeListeners[listener] = nil
|
||||
end
|
||||
for listener, _ in pairs(self._listeners) do
|
||||
-- task.spawn(listener, self._value, self._type)
|
||||
listener(self._value, self._type)
|
||||
end
|
||||
end
|
||||
-- TODO: Maybe implement some sort of regex here or something...
|
||||
-- Such as listening to *.field1 or something
|
||||
-- But this (having to loop over listeners and evaluate some condition) would tank performance
|
||||
-- Compared to a simple lookup
|
||||
-- So I'm not going to do anything about it for now, until I figure out a better way
|
||||
---@param field table<string, string> A list of keys that lead to the changed field
|
||||
---@return nil
|
||||
self._notifyFieldChanged = function(self, field)
|
||||
local value = self._value
|
||||
for _, key in ipairs(field) do
|
||||
value = value[key]
|
||||
end
|
||||
|
||||
local strfield = table.concat(field, ".")
|
||||
if self._fieldListeners[strfield] == nil then
|
||||
return
|
||||
end
|
||||
for listener, _ in pairs(self._fieldListeners[strfield]) do
|
||||
-- task.spawn(listener, value, type(value))
|
||||
listener(value, type(value))
|
||||
end
|
||||
end
|
||||
---@param self ReactiveValue
|
||||
---@param field table<string, string> A list of keys that lead to the changed field
|
||||
---@return nil
|
||||
self._notifyAnyFieldChanged = function(self, field)
|
||||
local value = self._value
|
||||
for _, key in ipairs(field) do
|
||||
value = value[key]
|
||||
end
|
||||
local keyDepth = #field
|
||||
for listenerDepth, listeners in pairs(self._anyFieldListeners) do
|
||||
if listenerDepth >= keyDepth then
|
||||
for listener, _ in pairs(listeners) do
|
||||
-- The reason this also returns type(value) is so that clients don't have to compute type(value)
|
||||
-- I assume some of them might want to do it so computing it once is probably better than having every client compute it for themselves
|
||||
-- task.spawn(listener, field, value, type(value))
|
||||
listener(field, value, type(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self._setupComplete = true
|
||||
return self
|
||||
end
|
||||
|
||||
_G["ReactiveValue"] = ReactiveValue
|
||||
|
||||
-- S -- begintest
|
||||
-- S local invocations = 0
|
||||
-- S -- Integer example
|
||||
-- S local test = ReactiveValue.new(1)
|
||||
-- S test:onChange(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test changed to " .. value)
|
||||
-- S end)
|
||||
-- S test:set(2)
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- String example
|
||||
-- S test = ReactiveValue.new("test")
|
||||
-- S test:onChange(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test changed to " .. value)
|
||||
-- S end)
|
||||
-- S test:set("test2")
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S -- Type safety example
|
||||
-- S local res, err = pcall(test.set, test, 1)
|
||||
-- S assert(res == false)
|
||||
-- S assert(err:find("Expected string, got number"))
|
||||
-- S
|
||||
-- S -- Table example
|
||||
-- S invocations = 0
|
||||
-- S test = ReactiveValue.new({1, 2, 3})
|
||||
-- S local clbk = test:onChange(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test changed to")
|
||||
-- S dumpTable(value, 0)
|
||||
-- S end)
|
||||
-- S test:set({1, 2, 3, 4})
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S -- Callback removal example
|
||||
-- S clbk()
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S -- Any field change example
|
||||
-- S clbk = test:onAnyFieldChange(function(field, value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
|
||||
-- S end)
|
||||
-- S test.Pero = 1
|
||||
-- S test.Pero = nil
|
||||
-- S assert(invocations == 2)
|
||||
-- S clbk()
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S -- Field change example
|
||||
-- S test:onFieldChange("Pero", function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test.Pero changed to " .. value)
|
||||
-- S end)
|
||||
-- S test.Pero = 2
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S -- One time listener example
|
||||
-- S test:once(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test changed to")
|
||||
-- S dumpTable(value, 0)
|
||||
-- S end)
|
||||
-- S test:set({3, 2, 1})
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S -- Table push example
|
||||
-- S test = ReactiveValue.new({})
|
||||
-- S test:onChange(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test changed to")
|
||||
-- S dumpTable(value, 0)
|
||||
-- S end)
|
||||
-- S test:onAnyFieldChange(function(field, value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test." .. table.concat(field, ".") .. " changed to " .. value)
|
||||
-- S end)
|
||||
-- S test[#test + 1] = 4
|
||||
-- S assert(invocations == 2)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S test = ReactiveValue.new({
|
||||
-- S name = "pero",
|
||||
-- S coins = ReactiveValue.new(1)
|
||||
-- S })
|
||||
-- S test.coins:onChange(function(value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test.coins changed to " .. value)
|
||||
-- S end)
|
||||
-- S test.coins:set(2)
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S test = ReactiveValue.new({
|
||||
-- S name = "pero",
|
||||
-- S coins = ReactiveValue.new(1)
|
||||
-- S }, true)
|
||||
-- S test:onAnyFieldChange(function(field, value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
|
||||
-- S end)
|
||||
-- S test.coins:set(2)
|
||||
-- S test.pero2 = ReactiveValue.new({})
|
||||
-- S test.pero2.coins = ReactiveValue.new(1)
|
||||
-- S test.pero2.coins:set(2)
|
||||
-- S assert(invocations == 4)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S test = ReactiveValue.new({
|
||||
-- S name = "pero",
|
||||
-- S coins = ReactiveValue.new({
|
||||
-- S value = ReactiveValue.new(1)
|
||||
-- S })
|
||||
-- S }, true)
|
||||
-- S test:onAnyFieldChange(function(field, value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
|
||||
-- S end)
|
||||
-- S test.coins.value:set(2)
|
||||
-- S assert(invocations == 1)
|
||||
-- S
|
||||
-- S invocations = 0
|
||||
-- S test = ReactiveValue.new({}, true)
|
||||
-- S test.coins = ReactiveValue.new({})
|
||||
-- S test.coins.value = ReactiveValue.new(1)
|
||||
-- S test:onAnyFieldChange(function(field, value)
|
||||
-- S invocations = invocations + 1
|
||||
-- S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
|
||||
-- S end)
|
||||
-- S test.coins.value:set(3)
|
||||
-- S assert(invocations == 1)
|
||||
--S
|
||||
--S invocations = 0
|
||||
--S test = ReactiveValue.new({}, true)
|
||||
--S test:onAnyFieldChange(function(field, value)
|
||||
--S invocations = invocations + 1
|
||||
--S print("test." .. table.concat(field, ".") .. " changed to " .. tostring(value))
|
||||
--S end, 1)
|
||||
--S test.test2 = ReactiveValue.new({}, true)
|
||||
--S test.test2.test3 = ReactiveValue.new(1)
|
||||
--S assert(invocations == 1)
|
||||
--S
|
||||
-- S -- endtest
|
||||
|
||||
-- return ReactiveValue
|
Reference in New Issue
Block a user