From 4f2dba49922d878be833b369714923cd8b59966d Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Mon, 26 Aug 2024 00:23:34 +0200 Subject: [PATCH] Rework RV to trigger on entering world --- FreshShit/_ReactiveValue/event.lua | 681 +++++++++++++++++++++++++++++ FreshShit/_ReactiveValue/export | 2 +- FreshShit/_ReactiveValue/init.lua | 676 ---------------------------- 3 files changed, 682 insertions(+), 677 deletions(-) create mode 100644 FreshShit/_ReactiveValue/event.lua delete mode 100644 FreshShit/_ReactiveValue/init.lua diff --git a/FreshShit/_ReactiveValue/event.lua b/FreshShit/_ReactiveValue/event.lua new file mode 100644 index 0000000..1b73b31 --- /dev/null +++ b/FreshShit/_ReactiveValue/event.lua @@ -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
+ --- Tables can be listened to for changes on any field or a specific field
+ ---### Example usage (value):
+ --- ```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):
+ --- ```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 + --- 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:
+ --- ```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
+ --- test[4][1] = 14 -- test.4.1 changed to 14 + --- ``` + ---### To listen to a specific field of a table do:
+ --- ```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 + ---@field _fieldListeners table> + ---@field _anyFieldListeners table> + ---@field _oneTimeListeners table + ---@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 - 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 - 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 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 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 \ No newline at end of file diff --git a/FreshShit/_ReactiveValue/export b/FreshShit/_ReactiveValue/export index 3d19cdf..f258aec 100644 --- a/FreshShit/_ReactiveValue/export +++ b/FreshShit/_ReactiveValue/export @@ -1 +1 @@ -!T3rdxTnoYFlB6RVnPnKLa829oETSLsPT0Jf6HZ2(UhVgSirjXho2(SSdLTl53(PzKKT8NXoeAHslT0eBPzgnFPrJgz3VB)E9nM23yi8x(hDddSTCO9no(p7D4bhTFFJrUobgw)fT)rD3OVrG7GzuFMLRt)J(T13C917B85JhnIrd6Fe)ZdTyE2KR6r)CqFJh7134kTBsCgmX1)9Uwo8BU3(h1B)t6BmW121NJ5J6I)Dd83BI)ElOx8M6thZXxVR84Kvac5bHSa3Paw(tVHKa(1PZOaujH8RtcSg8rRHbt6BSl)78RoiGdagm(ybe)a(h4TDKLJfBI4Z8pcx1yO7zcy3)LkS03yT1w7fdTiJDCzCip3KpijNBt3EU5ulgZYz8A(0GqFN5VZ2DaXEU5OqheJ8Mgo1Rh04MbWVBZVc1lysR5VBUP4hRrZnBIxCU5oZn3y91Bn3myc1jUjQF885CUMn23331NJ8xP3joGsGUwnALfakYu9DQZW4VmY1FU5fCkCMawEelFMGS5u0q3SGdj9aUyP5mElE(ZNB2aBEJchbjhjSa()nUJp1Rzd4QnIzpMD6WjgX)1y78glQFIhYZuDFU5tNB2nN(qTz0vgrjU1S8qJoBf)I4kk1JP0acxRLW5zZn)sCtb9mpIpz6Ctg1MZCpHcAUZOFGyhsZTDUC(SFPnukZt2K)2XYoUzNDgz4qKyukUnb83wc(CgHYbcEBWUe7mQjuupaDf9MhRSm3KW5oIB25Sa82zVc0bGOYRfZWr9cu5u8bbqKD5PPGr5IYYglc1g1GrNsHBR16LGkb9nHGE5PpNWPNt9VDOVNwpYtbJe6G8w2(UGLW0q7hKwcpzLzjCBQP9KVJ00yHN)GutBT7fAAR9DKM2qRzpi10(L7fAA)Y3tZE6(WmoYhFVqt7XFhPP55E5dsnT5xFVqvlImVNORDURRnL4ORHr)FpivWIK91tbRCyDxrMAh8GuM(SvNi9z31KO0hMs0vOv6ZURzLo(HPv6oRor6o31KOpmTs3zfALUZQ0knkClmeWK(tDYkQkGHNoKXioE1yvpsF8v9WyZG1OuVxrjKydE4d0MAeqRAkBwFvkgeKKUyiWvETQiluGr1Pcgx3yxaxqVklTQDFPMkX5Qc02sUeoh6LwodPFoxxdCC1wcXIDqW4a2Bp3PE20aHtcFYLJPbkG04SeTOrH6XjbZZfKQjSHO5CVreyBexK2g5swmHu(OPOTNTwMcZ)7AybUYjUmxwtf8uoc(ekDkWuxkn3BcXzmD4)c0YWngvOWDDEGFT1MBEWiCCgP3DofnyimM1yhk3)TfJ)TcvS1bvpGDzABXcOOD0CZbiXWbaSl(wbayNBEj12oFaSlmFHNVRhzmjavoCbDKiOe6DjXFiRmXOpDqOpJtNYzF46XWgfhi2BBH0b1)sCdHlHfiVHgTTWw4qCms9znlxSxIyD7ZCCdSgDvZwLF)xBrThkfQnJfUlOx76uHoE75kt5RI7elDkNk1zvXoPuRfoTZP87eOpOYHYFRCevTj2Q7ePP9vc(rGs3OwEsYZ1EPywYvSC4kHyDBKRFBCSuK)A9(wxMKerXGOqjrTYtLUsusDLu6IO3S1GgzqgAZvcpG7LWn0MBWZ1)rJ75MVymyIcLAe4kckOkaJd4d3lTGYBjyc4ABGn3vNeyhGx05cWx1pZKWYNsgY75v0G2i7XhCTa)YIXcv(2gZHiStKdPSb(wNJUqzwtTSj(OhnolAkt6sc8dcyEkX)cWbl4t8Jh8(oD6ijJ3Wbl)Ah8ZC)KMtiGVmyeyJE1aA3nmqagSAJCGK4kY(OhDa34xeLpNWgj8C)brPi9i8Qcg3JEe3BRQDmYOy))btiCapGalbHQ8MdKjqci(uUJfWHdON8KDTVKCf4GgCxFYh2gMxSLS58phG8dpFodjaheiUyp5jkHyONNRFalN2ihzODe7zN7VJSlOwhRAekkHG4Pmhb(efHKquSlqbbVEe0Xb1(FMaHRaJjcSWl5KhBRrd8)yhsKFrArgqzbz1y7WJsRjulvytH2STRJWNCZi3GQjrW2KQA1eGvmGKdXgIsAjrV42qjqcih2iZvea7PZn3Sv8iPKrUO61UbJ8V0LB6Sb)FBEDkEqY5PI5fOaPDQrxULygxspLi5qDEmlft6XmO8YWbqNbUod4Tub6gDA0kcdfWapTRiKRTeEKqC0nfk2sV9BjAV2awRNBLQNpdPRDs2Ffk7kX5rGLV74M)0p1cu0hqqBSiKjJolq4hhTvqVwzJylRGUhh0CM44XuH9axmAFozWfsZhnkYu4bbJt0ZNoZYnKJxQsrzO7Qr5aev(XY7FOJurDerpZG1UBvKuxpg9SocJM5qQvD7kE1LTn6cIIk7rmZiUc(exzIRx5svHiezefzb9oD2(leXyKJb5lK87ZSvlOqY0FMIh0oktG7KSh4)Fy6Uju4BxcusbgI04kdKej1QgqY1H2ZAk9WAps0t4H(1LbNixEH(D0wKNeK)(83LK7MTOGFeQ9)giYq0pwip4sF7RWqtKKGkGPfUWNkNtQezXbJknBMOuRdlfXYdlQ20QsteBOsNkwTKDJwTh3A(d5KBkgn)nAq18IgG8an(W(h1l7W(e6yGYfZjfpiebMcZ3jhKG16L8LRqNjUD(8POOv1qdGdq(iM7m(CgKk2xGTPCjWHSRejXg53aMP2CTHshDBJm22IOY3wP53kFflOxT2o1sOuXuMR8qHXVsYJfpTYDujJKwljdTjLDy7JexN(jv8cRcXP2CJ5ktvXP81w2QVwQ7dY0vOetcr5rKrml5Vp38TWIWhsPEQ1QlcWk5cMRHKpzqVLAqRD0AU9e(hF0E7)d3TPLsdQQR2SZSh6fZZzsffcKWhu7KL2WkkKh7RsXB7HPXYsK9fj7J6Wc9JsMJgAWexHPi2cABsOwBgSiz47ABhf03jAaSKqDQi7yHtHChMROfhC5BmxBnewsWGhH51pbzlghfzjoHOTq3B2(BRR0hfj9(Wzffs7QtaXYHTDkbWgDs4vfpla9asLhxyIGyfg5QLFjeZjb1MDszKgdS4eDguByl3QKAfsEHsHI0vRT0Psm3UDKiz7Kl8BhKZSRG(eeaxtlIi5RusAkSyM(DF5xLThZUrAfhm1sj7tfo0pK5322SP2EZkioVooby756WjPWbb85yENMseC61Ti2Fiv6iK3n3CouyH2NkDeDWbPorQJSKZa8UmLNbOBJvwL2ow)fiBAQJaDl9vJd7cUDIjF(Y1PVF6KhLBJYj1q52USj(j3MfVdUjy0PAvqIcvtVLzgKAsKNNq(aXoikVK3vHSYGaRGuZuPIkRODWCXjoHdJfN9Ksk2bTcepghvAhCR((BJYbTC5GBvSUSQC8rHhZcnBS)N5togawQYu0Qdb8jbaFmpgYLQ8(jr7QQCFsRjgX1QCjAKaSi4QysxkM5QTAbrrdOasJAZC1Y1AgMzeAwmZmEy0QagzjC3iFpNQWOib6qQ)l2GsdJLwaofd)fx8e5j7QFgwEaiiJ9WKCIItXV(jTI7BXvmvXWi5KevqZkxaDlPLvfCTCAClvMDYGgz(NEE0NGj9(NWFEyPGMjiLtr(rD1rlbm1wnTiyDlPPwr0TCkRd(X0AfeL7TK0CbOPQsXuHkx380TARt66eHM45NvC2X0FkALOG8Z9zPvvlv34krO6weRGs4VSLsMJaBjtHyoAHvyHsFRe4aIYTiTrmw)Q0UwihXv6vsMVPCujfVeNvhrovkUsM4IYnkXZG6NClT)Osxk0XJ7MqclZRxm4wUQm)gx155iyQ9dzofdnnJuSEULLv(Daxl)ZBawN6X64vrzT0PmA2QEtcvj8R5ZUcOn3P5koH(5ppJAPKCBOZYort6PIxWCoybJryx0H5rU0PPgS1M3QTUx1seVQE3Cj6BbbsOazvdKOgCk77lSOYMDFUzVJF1XBp38pixbBhTfuBRtXuYZCNcvIMRVm)6(0XW5sqCwaWACN3a4KdmglIFDyAeoyIOe)f0lMjCiB8pPdUMYUPaqYE)YWa15uO5eYSOoB766X7iUt867Jju(VuGtGhFkbzpW1zOfykWfoxkoJebe4mo4r9XcJfcYpbwHdQhXxTRPe8KmG15lhTxe6LAa6cNuHPQdEHBencBGppciXOYKCEMdTqBytscGvODaKNLX4EZITIafeqawzcxsUQSQ0jvvpgTJiRGTdj)T)u3rtv3cIfFMMk9m8I8RZAlNIcS9SegFQD4OmtUyCODkcR68gQDGiWxYUFEj1jDnsJKcGvFv61FneL7XQicAPDGfTSc8b57IsADKVRQ1TkTOKAwFBFtTCQX(3DFX4jFQK3JxfLPUhLIvLR(k282jCRNw5ntgFwWWaSdtaEXZbHyARCBVkzwv1qak4CdJhZoMOOWWJ0InZvz1ZsyEikQhMBu5JzBXNJMby15Nd0pVCd4tJfgqt27AryhGNd6WPrZLk3F6PWZS8XtcGPNG4dut3zfiOmbMX59GljsJMftCsa52Dxfp)gFq4Gen2AO26UsnM0gbrtBciNRemJYQ3ijFNwjobnv25vHoZUzGlxZOsVzfZ7vIhTbjxCV(25g98f)S3CAJeUrB8P8odQIZ4HHG)EoDSLdEkyuxn6e6oJ)b8bNpcK1t0Td49bp4iQJsvQEVWtti24fEEcXwf5niffL47WJ89m9OgNbrSFQtCtmXfFoeXRXnQO(WXhwNsEU4PnVOjQwwe7twsjPzBLWVqYVX9nMg2)nASmSo0ZA057n4QcvY8PS2ys3XrMhKWz8iIc1MqBbT0whbkAWNY0EuEK52CaU9ilNH6jYpkCdmn(YhDITYN0fv4zAAUy7PAC(t1h9dSp)cHNHVrkenYcCT3gdsxPRVifL4Hi)FBD9YQWSxuDs7tN6odyqPLaadRzLnuvpypIRqUbY99mp4QliQ(X(C1jy6O2zPcxKJOyvY3Io6jhK25fnFrwN3t9DfhHZcUog68ceHAEuxgHYRlxEK3XZeiVcoHMRqHGGjSm(mJ5FBScNSrWUo2bcL0Akv)SsuatBW9qhiBkDG096wRCEN0zUxiBsTM0(lx)TFc7Bo79(OVSfzPD6JIEkseDGQRU)QB2S5zg1oKPIO7B4jCqLUbdCTKyO0aPtPT1b72xjDUosACPC7ji0vEu2FRed6pOaUFBgvJqcYvgI3b4MBurpKIg3PIC609OQQqBDNufkl4YMiVszhQFU(h6ICUChXob)v2Rs(m8svO)Y1ft(vo7jpOKNBwv5PH4x5llRJKCzy01Ln)1IjBipurr8ibtgtvtDygIEG)EZs1tRMykkABoXjsfP4QfCaL6haVYoLhXBM4DhAVOlGVgpPNhoAK4ng6B3)W3)6)8q(9XVsc9jB03G5rTTpyit86)KfEo(ce1G3lRp334S921O3zg929eoWcH3nOgWtHpoWna)8YEjFNJ(wkXgExJQaY79PiqmE)(hEi0UqNysdjDXOPN4I)H7qA)JwRl(QoLlKhznw9ojnG67qS)G69T6g)AFJ)Bid2gQ(ghU)R7bVsvTMImv93TPgd5Jr4INjgYmkSt6mfhWX1HcWFkXYP6To6TLA1Apq)d7BKqYHVEwN46FSN61XkaxFodLl7ibeqokFjX(A4Qc5NXENS)(hHSgr1aO6OlW8FTV1Fn38Fhsg6JVrn7XjWlD9h(rFIxFJpg9jUW5e81iRsItThjFt0(YJ7174)agHFMou8EJ9OnGxST2Ue8DIlC8yXxtUH2bwkzi(KrkZvzWlk3uxe0aaEXXV)n)2Et(pV6VE7)aBAFJn609x7SzFJz8r(wB0LF1())) \ No newline at end of file +!S3vJtTnU2()wUPtNf6gYwODE33LPT3TBlBx6Jfyiz7o7W0qmjIGF1Xop)buUDj)T)05ijBzBzBjBNucW2TFGT0rsNVKS05OFd3E4GH9NnS)e4)P)tVOqhBxYW(h9hdoy)d3By)l8Cd7B)FidpC7Dg2p0B8ve)aBp3Hh(pF(lE(Zh2)RhDXfbKWHhs)3tSdM7yDZaYxdh2)PZh2)gPx6tMsR3GBMtjFiwclx7zwHaXOnDqOLFi8pcXs46bDJjr(yboJ9WaYyp3jbdP1DMLTR(L(cBx7Gl1U8qFlk8sp)JMdVpGwr6Jghfe6ndgC)X8jwH0AqUI4YkRhmqg)N2tcVCy)3s)z6thlQB8yJwwAhbhLt8oJrVHNlO8W(BT1w)8eBRPUEbuQTyeLFADUdz3fJMzhey7oDlFsyKV7Ip64n2YzXOlICXwHw0OzZhafEJq4p7sFczE4LBU4JlgX(L9flgTb(WfJEZIr788NV5IrHxsCtkI4xZ9TDd3OZE((E(0g)9YvIsOun3MD2mpbeDtXptCNK8dx45Vy0xO9WRy0AULTFaRBt7rt8YtoSRdcPnUIwIx)6fJ6GfVtHJG0JKGq6FnTNpz(gDGN2jH9mQxpANH9xD2v1yr8RKH8vIQVy0pUy02kQdXjG0ADk2RUsvZiZwXFG9eH6XmsOfvt1IYZwm6Bjff0ZMB5BnBXOaIdL5Ecb0wVI8jlNiIYY5r5Z(LwqUmpDr(BxBNKID2zwtMGDgHI7gq73LtEfJq(abFn46aRmQjuunaDf5INOSSyKfL7WEzp0Mx1tGkaDkvL4kCuxHkNGpWicVk)ygAuUOSSXctTrmyK7PWRLkDn6LG(gtqx)(NB0SZj(lN(3pAw3tqJu6G0s29UGLWSiNhKwcpR1SewMAAp7EKMwq05pi102ATqtBR7rAAtSV6bPM2pTwOP9t3NM907H56iF6AHM2tVhPPn376hKAAlUDTqvlUBUMORDUNNdXYvwdJ8)9Gublw2BMcw506UIm1j8bPm9vTNi9v31KOKhMs0w0k9v31SsN(W0k9nTNi9n31KOpmTsFtlAL(M20knE5w4sat7p1nVOQagE2LmgZX1Jv9e5XN(lJnxRgV17AkHyhWdDGUHuhytdLnpVnfdSUKSyi0J)mDKfcYiQubJRg7c4lKBY3xLEpxt1Y9Mc02s)jCUKRTDNq(Qsxd02QlNIf7GiGs45VZB2ChsiZjHV11tjHcI05SuLOtH6XPjZRzD1rWbIQ4DxybhJyvABwxhK0rkF0u0XZAKPWI)2alWLDNl)tKujpL2GFgLwfy6ZLUV7sl3PKj)pGwhEqPmfWB1P52ARfJ2)cKpeRxEobnOSccSN6sO(3TdO)uHk(YKAaWoh5yhesq7SfJgJDokbGt53oei7IrxtCCutG3cZNm33BU1uRqu5Xd0HIPs08RT8NeuMy2NmoYpG2p5Zor1ZHdsoKD23mPhQFM6fmxgvOpafAxMTYb4yK4hSr5Qfgi239mxVq7lUzJnl)9)QnXzcxOVrIWVIA9wxnQ4YZ1NW3g1Px2TOQuNBf7ut8TZzDMPUsG(Hypx(BHJl9Mi00jEZ6Bf87aH6HrEEunvqPTmNRy7svkX48qPFECSuK)D56AktI3qjKOqjHr7RLSsuADLm6IG3UpUfuO(wtCOkH7t9A4f5qDaq1)rJ9fJ(5PGjleAsGRjiwVGwCmD4ETneomHxcU6g7qD9Xj2(4dD)c476hc40YNynHwZBiHDr2Jp4Qb(d7GGiHVUPukcNC5esWyF7ZrxQb2ZSDS8rpCuw0SaUlkWVi0YZS8)c4Wf8r(N7FCVE94DJpqjl9z7)du)MJU0c8TbJah0lh039IczKbJojxytFz7w5CYyQXp7RcODSlyEY)el0LEc(ugJ7jpH69vuUaRlsMpi8slkHhBbFYcr4Dh6MqxaBpH7zgDOe6zp7ToxBDd4WgCFFYN2fMhDtEXP)7qKFm3NYqcXbb2wbp7zcHy085E(HbkkdFKH2rbV6C)3WRcQ1fOxhfLqW6VgDb4tKTeglb7cuqWNhtDCqT3xTGL3aJjl4d14tMSRuFG(Forw8FGBrgsccZRX2JUQUnGyVclkuMD9Cz(K3i2nOysfSmzIUngzzdi(qSdleysvlQnuQgbKd7K7jmI9Jlg9IntgjLmYzr7wdg5FBBQPZo0F)IBZWdsppvcVafiDZm6ugsAuj9mlohQ3tdYWKEAaeoA4aO3yp3X0skiDNED2mUfkGbE62SLK9sMhjSn2ott8s5Y)sw5LgWs18LzQ5RW(1BsxFrtUnVnpeS89MUX)4FSjOOp2cTXIBm(Q1cz(XrBf0Rv(vWLxqpGsAktC6ucZEGkgDo3A8x4Mps9Ormpi46gN7tUY2lI2UeHIYeV2r5aev(jY7h1r0uhHvZCT62VSiPU8A2Z7imEMdUw1Yv8klB7Snik02JyUrSg(eBnX179iILieBefBb9rz2(pZwJHcdYFMZVpZr8bgCM(Re8GUX7C4Bsxd8VpiB1yk8DlHkziJf34khLyBcMbuYZLmWEg5aJhjYBqI8Z5loH)5fYVr6J(4K8FV4JP5U5dI4NGA)Fawzi6hlIU4sFNBWLMW7cIfmv5h(O9EyLAxFWvLMFNRY8Dyz700LhzCFwOrIfuOBLOEg0OV6JAv)jf7Pvar9buikEvdu6cp(0Ehoi)W)eYuyeWMJkzWWwOkm)hFWcwVxt)8fYvSxRMFfV6vPMbAdqEXMlnjpfYSwyG9jCrqPShVrsm6BatvAU3iUJVDrgCx2Q03vyjSPAfnOwBUBMpPsSgtLYfrlUILlvpDZDujeVVwYo9MwgILpwSD6NfRJOneRsZzQu2kw)Y3lzS83AToiBBrjhNI8uUHnl6)EXOFd(i9jeYCX3YZwaw6pO2anG0lkUudCPu1z5ReC0HVBVhDdNvAn2qxWsZ8hnpHNhWvySGngc1sdYAGfV0iNBYWBhGB3LnBxA4SpIBqKF8M(i1m4gCHBLSnuwLu1CgnBZ0FRJt8IeprIWvV0OQylvo1YAa3rA9ZLFaGDLAynwe5H45cKAyWgxfzHEPL0hk3SZtx2yiEL47b5JkSTTUHw2Ub7MrGStVuEDXCpya0vPRNm1IFzg)IpFJj2ttQx0lJXBcXs2O0qJPn)OwQ1s6luAuKoSXsjTyYB3J3i7M(diFdYHElR)X6aunV4oj9lU4MivZ8V7lh12(m)bYv8IVAKoqMLp9OSFzBdN54s1qSEBYgQ9opxAxkACiDoOpkPmbzpVTLZNYS9g83QCpmkmq)ZS9g9WbPCNuUXspZWhZfEiGoogzxsNi(3GDNtKc2Bk)v9WPS7KAsPVDB23NDZOuwifB1KYYLFJKuwSKteofJotPctfOCYLm3GusI86uYhyTfSWBPGz3vUBpirlylF0k42k6KrREJyO0O6DJPKGQqkq1tAdTozy9p3CuEiT3q4rqllZkV9iW19WgD27R0jldblw(w)ktb8gjGoMNc7rl)9PB22kYEYQrgZ10o0psrwKCAU5nfZCL(6cwWiiishJzUs7HBoMzCZunZmzySzbmYs4UX(Gov0ISnMhosHInOKAXsd0NIPF1bLHkzN57qZdabzIhM0tyCk(JFwkidRoYSkMgPNSqdnlLeAjPLPtBvpnUATJq5Ag((w964)fm53)c(VhwkO5wSYPi)WuD0siJXQPfrRLKMQMnx9uwh)40AfSA3LK0SIMXqPy2LoB6(61UXTTjRuJDFELSlAY3QxPsqaL3Tx6gAWjr6G(80wiLck7tlviWQ5wnQqB0GpCAvlWHgszqHJTO5rfUrno2wz)Ys1M0XHSCnYDi2ESuCKsrfL7uIhIS)szkgehIurUZPUn40uzEhuQrJPr1UoutRODxHaZ4ldpbJoldM99EnLfFpKBQx(qGXrFInIok7LoLZgBwVjZ0QFi57xJMV0Pnl(aeupVL4tuP2INLFIRStXxXCyyaUzf8LEbZTU2DdjAlnpyxzV0Li(f1EJAu3cwGIGK6Uafd4uoRlSOYwTWIrdo69hT7Ir)U1nWXIBdXI7mCl)d8MbroNNpF)79jtH8OGL7cym5tlaKPdtXKoqMM9JgFjlLey9xCN2HD7)z9WVvD7meiDT)LOqrEvSXLwxfxzhpV50kIreG85OcHRmb4ey6FX62W9ARnykqfoxZYPJqliNmMt8Xa5f(4HuTkKiIw(ItT1cZ8cmUKPn7xIMNza6bzwXmrII4f3hHajGUIk2OAK155sYIUWHWecF53(W(3mfpBySuwqGjeIriX1w3uw0dLjknJpXLw44wuFCRYoA09ioQohSknhLr(1zD5tPb2E2mJpXjOuMjxsBiLvK1DEfXjEe6Zz)VUK482GTRsqq93naZ)gLY9GvuhQ2o0I)Sf8IlUQnhp2xMEvtRp6XW4W77QLKbNx4AQXeVxsRX7J3rWNKHvPuFflE3uU5ZQ8MBNLQyya2HPip7EFiPVvUTNwMv6UKGcYdAmnbdybRgMsoobEcR(GuMhSGmkWloS2CSPZzhaTQ7pekNVFJPtRffssxBJ6y7J51D0S45w5Nh(m4oAF6LHW0vW6fet)zhY6zSwgNheEeB76SdyzYi1U7MK57OdcxStJLgI5VBeJjPrq80OqJtvcUIey2irTtRuzaK2oVk0zwZiN65Kk7LgU)APUshsVjcYhFC89Q(zF40oPCN25ZQYLwwUQ0NXNpNm12fZMhXtJZ04RO)deKaqI88uvBFADWeGrKsyzQDLzfjw4kZlsSuXEfY0Js9ZWvDFUAyqUuI1tK5qjDUK8PeFg14I4dPbTCp51SBzFwreLSi2hpuwYY2kHFHD)oRBmnS(70PoSo0dBCEkhEtHkz(KGU4M8JJS5WgCJP6keleDz9LUYnGOp4tcKUctY9Akb39cB3jYhCq8YoWJnGFLrUP6UolYtZ2Nl2EYG8OvE0p258VW8m8DsHOtEIlHcfCxQpVkfLKHi93V826QW8U4442NmZ7kGbLvcamSn02qvCHLKezEJ5NZQk6kli0p9vBpbtpXjzv4h7Wcog1w0X3yss59QArwVJj(ESurTGNJlHUcrOKh16iu(1YLhQsZuO7vqMM2IcbgtOo(mt4F70It2Wyxh5clP0EgroxokGPnEn0bYl4oq2(2nBDEh3z(8OGlnAs7VD73)jSBo7DD0xwvwAN(K4BdJ4edxF)vnB28CJAxRzSv33zoZbv2cm2ZM3cLUq6mAB9WQTI056X7J1YThRJ26RY(7Lyq(cpy92mYGLeOugIVb4M7OPhswH7PjNoBn0vf6L3jvHYtU8BOxPSdXVU9rDrkxUh7eJxXEvuZWlvH(B3wC3x7Dp5bL88f6kp7Z(d1YstKK1HrBkBEvXK7ZtMPyEeJjJBvJjmdwnW)8fLQNQNykE120ohBRizpvzIrLcWurWqLNm6awMEiczS8hKaOQcemni6Ceuu7hDXf2FDy)ZE3B7p4S(dE7jdIHl1FJy5aWKQOSh7tWY2)49o4GH9NqoNwzgoX(B7DWX)6FqFyeaBQ9HlCqARkHxQlgD8bV9V27KZ27Wb7DY(h(HZ(ZJo5G3V4JX6oKmqGA2B1f8cxvWh4BZR8bxPhESM5mEmbxwRb(S2A40AdI3ttWV1MJJRlb8CTo46QEH0wRJZRMEKhzvhlchyn9iJn(263q0wQ(4eB9Vq)VZCX(3m8JvRJmBjINS1fxzRD)ULavQAI3SA1TR6oiU76GLOP4u79DlXNT0SexfA0p7rnAJXd377A0BTwRrV1JA0gJ7U331O)P1An6F6rnAJX3377A0pDTwJ(PpQrBmocFFxJUA8f(oTkDj4o89cD6CyRyTXR477kYvJJXgPqufUgVEOJyi(hFFxh5vlpvKxTUQHqEudXiCwUbQiRRErM(OxeJWT56RI8M1vnKh9Iygoq3avKvHxKIWlAZXn6wa)OndhP1(ZvQlUsxB8L2iz(ZxfI3Sxet1epQnexQxAU0uE5sP5LmvLjjDRGJ1TpEwVSX16MaH0vfwl1Yu1KRtRvxNVIRag9WbB9Xd7sVgeAjCWU1Wd7wbxSxP4JDdWjBdulkdNSBN7cRAF3xT6DnxiUAxF81Ur4SDdWBBnXDBT9(1g4WTw73Pw9OgIp3nfNURpED3k7hSA0(Ve88ooHfYeqRTh(ENIOF)X57uDNLnEFNbLVAmUFNfitAc(FNwyBeoGNg)Mww4bE2bB54cEQ(KaPCt9q9UrmsvLkYy0uL14RTHu1o9T4yECex9BsHN4z5aAWbtHV4Tbhm99KGkEPUjVrj822gBP1rqOj4fNVE1dZPvrNLg(KxTIt7Jx5TSYw(R95h15wg6CLG356Pfvt8pFLQUObUOxLN)QWh9VlI)YXnD5fmMv8vaoQlxcZWtDv1Sb4QUkY1m8vxffncN1vrGIU94vJ76QkrPxW8LId7TjES3g7TRYD3ScCAVGbZYfN2BHDxOa4cZC8Bxndih8bVIXX9Liqc3cm)6aSWAdWW1eV3xPYXAG773TLOfGh8lp0fVgQd1hN4xT6g6Jx8Rtw5TIKUnWt(AO50i8LFfO8uBCM)XPjQbE0VKWL(LecS3ybstXP(LlE1F3MR1q8RVqEylHF9Ts8(OYOsdy3EjGn2RcmYEH(4DV5sV2bV7RRqy5Ib6R5YBJT)Rh(4xdDMgHp(pQRS88nyoo6lFYVzK)PWvFfkLkWxFtbdXsWBFLBNvTWD)CHZMj4VFbyuDH4hRw4XFfa9AfLVs85Viurxno9NU0AHx)PRIwWpP57cOr43FzbJSUriH(Bux9W3)8bhwnW5)wlAfBi()xiM(YpIdzkH3yvQW434MFzhEMzTeI521o03kgrH1CtaRw4uFGwEPb4Ygd8YgiDCmcbMRdsmRx7zEqNvMUG57a4JkgvdaCgI(BLn5CcTupjQbAWkj8ksBwN2UD1SR1ouwyZpjgLZ4)lyXd)l4)E0GqLbrUfnEkY3QRnrjKRXMffr7vKLHMnF7ACm(XP)nuljh(dVA0oQOzRRwrbFQKP7R9YnFJAYkQz3RVj7MCEuMuKCl6GMJvLcl5JuR6BHUesfpD2Qdfkc1CR6lXkqJpe)UQcf0buMCuypP(zhvJ6uyFi7oCuURO4uXPb5qmBVhloIvPQg7yaaBQmf9Idr1i35u3DCAo6w9jBZY6RwllWuiqB8LXTqaKLXZ23GMY6FaWLlpVcX8flXMYeJIsNcDJnBNjRnQ)jn3LbDRsxMqXh0x5ZhxoMLNDPoAo3CXW)R088DLN9qd1Nym)Tb0OGfWjiDDxaNbCuN1DwPjRAAXObh9(J2DXOF36giSCSHC5ygE0DmWToWZNF(B(KPqEgYYHpmh0OfaY4VPPs(ozA3pA8LSu0JnEWtido1UN1d3lJTZqi1u5xIcf5D4gcmXgdplpV5ucGrQKC8yaPddb4yyAEZggJ9CNydMCuH61SCEm0cYvX5eFmXpGpUtzRdxCbw(IOaXcZmrmFxOn)xIMxWa3dY8WzIeR0lUpdb6eDfNSr7iRZZLeIDHdDne(s)9H9rCkgZjyPSsWh8RTUrNOOCzHM)Ah(gYo(Qn0(xyUuR1DXsJG6)uZsvjK)R58KItSm0NlMEDzWpK5BJQGWMVRrn)Bhl3ZArD0g7OTEylFSpwZQET(O0Agh03PSGnioeUVyex(OGsH3hVd3pPawSs7dSADtnTvwJLC7WPMdtWVqQMHDVDL0x1ZxGwM1MUuPcUFxWRjGawqcJP2QtGNWBuqkZswqzg4fhoXo201QeaTU7pekNN)JPtBhfssx7A1b3hV)AIMfVwcE87mdWaSPxgcthdRxsm9UDiRhY6b488WJyBFSDa7gnGAxFtY850bJl25XsdXE9nIXM0ijEzcqJtvsUIeuVrKANOPYywJDMwOZ12HSLAoQvHQ5(6M6kYs9MejhEkfIdxN9Ht7KY9FNpR8o7ivUAYHSVZjtTD5G2x63gFJOKdagvqM9P0yAkCjxn10(wuqhK3otPJ9svnKnwqnRXDWGeUxQQZN)EyOsSVmDrZwZQeh8qfSiXqj8FCy35(QqaP3oDAtrbodt89Ys4nvQ87tc6IhYgYrMdhCeEvCaXgwxwFTRQgw0h9jbsxXDfwmAdS7f2UtKpiV4L5HhJh)kSFt9gISmBOOXw1(hQX9(HmxBSZ5FH5H8oIIyNIBm14ZVrkQjSe6VF5TTTc77IZxjFYmVRagCrswGXVrJDijUG8sIa8X84)OS2vwGB(1Wr7Ra02y5RUQe9oM47XUknQ49P(eRkuvumJuBkS)v9KZQU2moMb(6JwPw3CMyBmxtI8yNv4K(S)6ix4tsSNrKZfZky(JVh7i9fChPBF7MRCzbFsZ5rbxwRfJ9TBV7UqSLNy7(GVEt9yC6tIVn2IV4EQVF82D1zfYTCTMX(Q1oZzoSlQGi()R)h8vGwFpKmFN097XhdTY0dSbYk)RcVRjMvDrAD)YnqdwYxP6iyjaPXogoZbRs9musLTMMQ6(Y1Av3IjF(n(xl2zCYt(OnrnSj6XION7yEplxWvQb33UT6HRX7w6J6js6jVOU6j9t)dLRJuhnKMiWQR4AvlS6ZtA8C8wMWc3s26W0y1e)ZxOL9r9e7XFnhDqWpIKuVTGerNw6H9H5Fcgoyy4W(JJcc9MHrw1W(e4Ulyy)G5ehN9NWlb(WGH9p(G3(x7DYz7D4G9oz)d)Wz)5rNCW7JjWL2tOea(a)jqLIC53UsinOpG1jgWE4V7rl8HBT9ZX34o(sp)JP2g0F4Di5h2ho2hFxlNpr8dOSJHhUZ)1W()V0MY(IBg2)G9(1b0UjX5cE1(LJgm4OFNwn6Gl1aM2tGND0XF4F(Ul)R3)F(T)7H9VW3A2))sn4skkXssmo)meMdWnqIgc4GHGDoixD1pGET8ZjFqEaGkceYiWKgdM0K48dSJpT8bz9UvuMvvxcbwAIPaYqb2bBGUU0YSIutj8mtPKmaQtdmaOZT0KaDKubmUfs4A55xukHxuIfexWHdNf8vRfu1Kt(jMsCHaogjzq05wAoLKjSGuWNFPyiAXzwvQOlyjGn50YmD4kkUGnspdntpJJl4Ya6FmXidbkACao \ No newline at end of file diff --git a/FreshShit/_ReactiveValue/init.lua b/FreshShit/_ReactiveValue/init.lua deleted file mode 100644 index 7534a90..0000000 --- a/FreshShit/_ReactiveValue/init.lua +++ /dev/null @@ -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
---- Tables can be listened to for changes on any field or a specific field
----### Example usage (value):
---- ```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):
---- ```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
---- 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:
---- ```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
---- test[4][1] = 14 -- test.4.1 changed to 14 ---- ``` ----### To listen to a specific field of a table do:
---- ```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 ----@field _fieldListeners table> ----@field _anyFieldListeners table> ----@field _oneTimeListeners table ----@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 - 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 - 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 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 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