668 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			668 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
local function Init()
 | 
						|
    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
 | 
						|
 | 
						|
    -- 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
 | 
						|
end
 | 
						|
 | 
						|
local frame = CreateFrame("Frame")
 | 
						|
frame:RegisterEvent("PLAYER_LOGIN")
 | 
						|
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
 | 
						|
frame:RegisterEvent("GUILD_ROSTER_UPDATE")
 | 
						|
frame:SetScript("OnEvent", function(self, event, ...)
 | 
						|
    Init()
 | 
						|
end)
 | 
						|
Init()
 |