local l=nil local function w(w,...)local U=l l=nil w(...)l=U end local function U(...)w(...)while true do w(coroutine.yield())end end local b={}b.ClassName="\067\111\110\110\101\099\116\105\111\110"b.__index=b function b.new(l,w)return setmetatable({Connected=true,_signal=l;_fn=w,_next=false},b)end function b.Disconnect(l)if not l.Connected then return end l.Connected=false if l._signal._handlerListHead==l then l._signal._handlerListHead=l._next else local w=l._signal._handlerListHead while w and w._next~=l do w=w._next end if w then w._next=l._next end end end b.Destroy=b.Disconnect local o={}o.ClassName="\083\105\103\110\097\108"o.__index=o function o.new(l)local w=setmetatable({_handlerListHead=false,_proxyHandler=nil},o)if l then l:Add(w,"\068\101\115\116\114\111\121")end return w end function o.Is(l)return type(l)=="\116\097\098\108\101"and getmetatable(l)==o end function o.Connect(w,l)local U=b.new(w,l)if w._handlerListHead then U._next=w._handlerListHead w._handlerListHead=U else w._handlerListHead=U end return U end function o.GetConnections(l)local w={}local U=l._handlerListHead while U do table.insert(w,U)U=U._next end return w end function o.DisconnectAll(l)l._handlerListHead=false end function o.Fire(w,...)local b=w._handlerListHead while b do if b.Connected then if not l then l=coroutine.create(U)end task.spawn(l,b._fn,...)end b=b._next end end function o.FireDeferred(l,...)local w=l._handlerListHead while w do task.defer(w._fn,...)w=w._next end end function o.Wait(l)local w=coroutine.running()local U U=l:Connect(function(...)U:Disconnect()task.spawn(w,...)end)return coroutine.yield()end function o.Destroy(l)l:DisconnectAll()local w=rawget(l,"\095\112\114\111\120\121\072\097\110\100\108\101\114")if w then w:Disconnect()end end return o
-- -----------------------------------------------------------------------------
--               Batched Yield-Safe Signal Implementation                     --
-- This is a Signal class which has effectively identical behavior to a       --
-- normal RBXScriptSignal, with the only difference being a couple extra      --
-- stack frames at the bottom of the stack trace when an error is thrown.     --
-- This implementation caches runner coroutines, so the ability to yield in   --
-- the signal handlers comes at minimal extra cost over a naive signal        --
-- implementation that either always or never spawns a thread.                --
--                                                                            --
-- API:                                                                       --
--   local Signal = require(THIS MODULE)                                      --
--   local sig = Signal.new()                                                 --
--   local connection = sig:Connect(function(arg1, arg2, ...) ... end)        --
--   sig:Fire(arg1, arg2, ...)                                                --
--   connection:Disconnect()                                                  --
--   sig:DisconnectAll()                                                      --
--   local arg1, arg2, ... = sig:Wait()                                       --
--                                                                            --
-- Licence:                                                                   --
--   Licenced under the MIT licence.                                          --
--                                                                            --
-- Authors:                                                                   --
--   stravant - July 31st, 2021 - Created the file.                           --
--   sleitnick - August 3rd, 2021 - Modified for Knit.                        --
-- -----------------------------------------------------------------------------

-- The currently idle thread to run the next handler on
local freeRunnerThread = nil

-- Function which acquires the currently idle handler runner thread, runs the
-- function fn on it, and then releases the thread, returning it to being the
-- currently idle one.
-- If there was a currently idle runner thread already, that's okay, that old
-- one will just get thrown and eventually GCed.
local function acquireRunnerThreadAndCallEventHandler(fn, ...)
	local acquiredRunnerThread = freeRunnerThread
	freeRunnerThread = nil
	fn(...)
	-- The handler finished running, this runner thread is free again.
	freeRunnerThread = acquiredRunnerThread
end

-- Coroutine runner that we create coroutines of. The coroutine can be
-- repeatedly resumed with functions to run followed by the argument to run
-- them with.
local function runEventHandlerInFreeThread(...)
	acquireRunnerThreadAndCallEventHandler(...)
	while true do
		acquireRunnerThreadAndCallEventHandler(coroutine.yield())
	end
end

-- Connection class
local Connection = {}
Connection.ClassName = "Connection"
Connection.__index = Connection

function Connection.new(signal, fn)
	return setmetatable({
		Connected = true;
		_signal = signal;
		_fn = fn;
		_next = false;
	}, Connection)
end

function Connection:Disconnect()
	if not self.Connected then
		return
	end

	self.Connected = false

	-- Unhook the node, but DON'T clear it. That way any fire calls that are
	-- currently sitting on this node will be able to iterate forwards off of
	-- it, but any subsequent fire calls will not hit it, and it will be GCed
	-- when no more fire calls are sitting on it.
	if self._signal._handlerListHead == self then
		self._signal._handlerListHead = self._next
	else
		local prev = self._signal._handlerListHead
		while prev and prev._next ~= self do
			prev = prev._next
		end

		if prev then
			prev._next = self._next
		end
	end
end

Connection.Destroy = Connection.Disconnect

local Signal = {}
Signal.ClassName = "Signal"
Signal.__index = Signal

function Signal.new(Janitor)
	local self = setmetatable({
		_handlerListHead = false;
		_proxyHandler = nil;
	}, Signal)

	if Janitor then
		Janitor:Add(self, "Destroy")
	end

	return self
end

function Signal.Is(obj)
	return type(obj) == "table" and getmetatable(obj) == Signal
end

function Signal:Connect(fn)
	local connection = Connection.new(self, fn)
	if self._handlerListHead then
		connection._next = self._handlerListHead
		self._handlerListHead = connection
	else
		self._handlerListHead = connection
	end
	return connection
end

function Signal:GetConnections()
	local items = {}
	local item = self._handlerListHead
	while item do
		table.insert(items, item)
		item = item._next
	end

	return items
end

function Signal:DisconnectAll()
	self._handlerListHead = false
end

function Signal:Fire(...)
	local item = self._handlerListHead
	while item do
		if item.Connected then
			if not freeRunnerThread then
				freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
			end
			task.spawn(freeRunnerThread, item._fn, ...)
		end
		item = item._next
	end
end

function Signal:FireDeferred(...)
	local item = self._handlerListHead
	while item do
		task.defer(item._fn, ...)
		item = item._next
	end
end

function Signal:Wait()
	local waitingCoroutine = coroutine.running()
	local cn
	cn = self:Connect(function(...)
		cn:Disconnect()
		task.spawn(waitingCoroutine, ...)
	end)
	return coroutine.yield()
end

function Signal:Destroy()
	self:DisconnectAll()
	local proxyHandler = rawget(self, "_proxyHandler")
	if proxyHandler then
		proxyHandler:Disconnect()
	end
end

return Signal
