Untitled
unknown
lua
a year ago
7.8 kB
4
Indexable
Never
local Stream = { Event = {}, Attribute = {}, Changed = {} } setmetatable(Stream.Event, {__index = function(s,i) return "Event/"..i end}) setmetatable(Stream.Attribute, {__index = function(s,i) return "Attribute/"..i end}) setmetatable(Stream.Changed, {__index = function(s,i) return "Changed/"..i end}) -- local CustomListeners = setmetatable({}, { __mode = "k" }) local WeakMap = setmetatable({}, { __mode = "kv" }) local ChangePushRequested = false local RunningComputed = nil local UniqueCounter = 1000 local IsListenBind = false local PendingChanges2 = {} local PendingChanges = {} local StreamValue = { _type = "StreamValue" } StreamValue.__index = StreamValue local StreamComputedValue = { _type = "StreamComputedValue" } StreamComputedValue.__index = StreamComputedValue -- local function isValueObject(value) return typeof(value) == "table" and (value._type == "StreamValue" or value._type == "StreamComputedValue") end local function onValueChanged(value, callback) local uniqueId = UniqueCounter UniqueCounter += 1 value._listeners[uniqueId] = callback WeakMap[uniqueId] = value return function() local value = WeakMap[uniqueId] if value then value._listeners[uniqueId] = nil WeakMap[uniqueId] = nil end end end local function applyValue(target, propertyName, oldValue, value) target[propertyName] = value local targetListeners = CustomListeners[target] if targetListeners then local list = targetListeners[propertyName] if list then for _,fn in list do task.defer(fn, oldValue, value) end end end end local function pushChanges() ChangePushRequested = false PendingChanges, PendingChanges2 = PendingChanges2, PendingChanges for target,oldValue in PendingChanges2 do PendingChanges2[target] = nil if target._value ~= oldValue then for listener,arg in target._listeners do if typeof(listener) == "table" then task.spawn(listener._update, listener) else task.spawn(arg, oldValue, target._value) end end end end end local function requestPushChanges() if not ChangePushRequested then ChangePushRequested = true task.defer(pushChanges) end end -- function StreamValue:get() if RunningComputed then RunningComputed._listening[self] = RunningComputed._evalCounter end return self._value end function StreamValue:set(value) if self._value ~= value then if not PendingChanges[self] then PendingChanges[self] = self._value requestPushChanges() end self._value = value end end -- function StreamComputedValue:get() if RunningComputed then RunningComputed._listening[self] = RunningComputed._evalCounter end return self._value end function StreamComputedValue:_update(firstCall) self._evalCounter = UniqueCounter UniqueCounter += 1 RunningComputed = self local value = self._callback() RunningComputed = nil for target,counter in self._listening do if counter == self._evalCounter then target._listeners[self] = true else target._listeners[self] = nil end end self._evalCounter = nil if self._value ~= value then if not firstCall and not PendingChanges[self] then PendingChanges[self] = self._value requestPushChanges() end self._value = value end end -- function Stream.Value(value) return setmetatable({ _value = value, _listeners = {} }, StreamValue) end function Stream.Computed(callback) local computed = setmetatable({ _callback = callback, _listeners = {}, _listening = {} }, StreamComputedValue) computed:_update(true) return computed end function Stream.Listen(target, args) if isValueObject(target) then return onValueChanged(target, args) end local bound = Stream.Bind(target) return function(propertiesTable) IsListenBind = true return bound(propertiesTable) end end function Stream.New(instance, ...) local newClass if typeof(instance) == "function" then newClass = instance(...) assert(typeof(newClass) == "table", "The constructed class from Stream.New must be a table!") elseif typeof(instance) == "string" then newClass = Instance.new(instance, ...) else error("Stream.New only takes in an instance string or a constructor!") end return function(propertiesTable) local cleanup = Stream.Bind(newClass)(propertiesTable) return newClass, cleanup end end function Stream.Bind(target) local isListenBind = IsListenBind IsListenBind = nil local isTable = typeof(target) == "table" local isInstance = typeof(target) == "Instance" assert(isInstance or isTable, "Stream.Bind only accepts instances or tables!") return function(propertiesTable) local cleanup = {} local targetListeners local listeners local uniqueId for propertyName,value in propertiesTable do if isValueObject(propertyName) then table.insert(cleanup, (onValueChanged(propertyName, value))) continue end if isInstance then if propertyName:sub(1, 6) == "Event/" then table.insert(cleanup, target[propertyName:sub(7)]:Connect(value)) continue elseif propertyName:sub(1, 10) == "Attribute/" then table.insert(cleanup, target:GetAttributeChangedSignal(propertyName:sub(11)):Connect(value)) continue elseif propertyName:sub(1, 8) == "Changed/" then table.insert(cleanup, target:GetPropertyChangedSignal(propertyName:sub(9)):Connect(value)) continue end end if isListenBind then if isInstance then table.insert(cleanup, target:GetPropertyChangedSignal(propertyName):Connect(value)) continue end print(propertyName,value,typeof(value)) if type(value) == "function" then if not targetListeners then targetListeners = CustomListeners[target] if not targetListeners then targetListeners = {} CustomListeners[target] = targetListeners end listeners = {} uniqueId = UniqueCounter UniqueCounter += 1 WeakMap[uniqueId] = target end local list = targetListeners[propertyName] if not list then list = { count = 0 } targetListeners[propertyName] = list end list[uniqueId] = value list.count += 1 table.insert(listeners, propertyName) end else if isValueObject(value) then applyValue(target, propertyName, nil, value:get()) table.insert(cleanup, (onValueChanged(value, function(oldValue, newValue) applyValue(target, propertyName, oldValue, newValue) end))) else print(target,propertyName,value) applyValue(target, propertyName, nil, value) end end end return function() if cleanup then local _cleanup = cleanup cleanup = nil for _,thing in _cleanup do if type(thing) == "function" then task.spawn(thing) else thing:Disconnect() end end if uniqueId then local target = WeakMap[uniqueId] WeakMap[uniqueId] = nil if target then local targetListeners = CustomListeners[target] if targetListeners then for _,key in listeners do local list = targetListeners[key] if list and list[uniqueId] then list[uniqueId] = nil list.count -= 1 if list.count == 0 then targetListeners[key] = nil if next(targetListeners) == nil then CustomListeners[target] = nil end end end end end end listeners = nil end end end end end -- return Stream