Untitled

mail@pastecode.io avatar
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