local e=nil local function r(r,...)local F=e e=nil r(...)e=F end local function F()while true do r(coroutine.yield())end end local E={}E.__index=E function E.new(e,r)return setmetatable({_connected=true;_signal=e,_fn=r;_next=false},E)end function E.Disconnect(e)e._connected=false if e._signal._handlerListHead==e then e._signal._handlerListHead=e._next else local r=e._signal._handlerListHead while r and r._next~=e do r=r._next end if r then r._next=e._next end end end setmetatable(E,{__index=function(e,r)error(("\065\116\116\101\109\112\116 \116\111 \103\101\116 \067\111\110\110\101\099\116\105\111\110\058\058\037\115 \040\110\111\116 \097 \118\097\108\105\100 \109\101\109\098\101\114\041"):format(tostring(r)),2)end,__newindex=function(e,r,F)error(("\065\116\116\101\109\112\116 \116\111 \115\101\116 \067\111\110\110\101\099\116\105\111\110\058\058\037\115 \040\110\111\116 \097 \118\097\108\105\100 \109\101\109\098\101\114\041"):format(tostring(r)),2)end})local A={}A.__index=A function A.new()return setmetatable({_handlerListHead=false;Event={}},A)end function A.Connect(r,e)local F=E.new(r,e)if r._handlerListHead then F._next=r._handlerListHead r._handlerListHead=F else r._handlerListHead=F end local A=r function F.Fire(r,...)A:Fire(e)end function F.fire(r,...)A:Fire(e)end function F.disconnect(e)e:Disconnect()end function F.wait(e)return A:Wait()end return F end function A.connect(r,e)return r:Connect(e)end function A.DisconnectAll(e)e._handlerListHead=false end function A.Fire(r,...)local E=r._handlerListHead while E do if E._connected then if not e then e=coroutine.create(F)coroutine.resume(e)end task.spawn(e,E._fn,...)end E=E._next end end function A.fire(e,...)e:Fire(...)end function A.Wait(e)local r=coroutine.running()local F F=e:Connect(function(...)F:Disconnect()task.spawn(r,...)end)return coroutine.yield()end function A.wait(e)return e:Wait()end function A.Once(r,e)local F F=r:Connect(function(...)if F._connected then F:Disconnect()end e(...)end)return F end function A.ConnectOnce(r,e)return r:Once(e)end function A.connectOnce(r,e)return r:Once(e)end function A.Destroy(e)e:DisconnectAll()setmetatable(e,nil)end setmetatable(A,{__index=function(e,r)error(("\065\116\116\101\109\112\116 \116\111 \103\101\116 \083\105\103\110\097\108\058\058\037\115 \040\110\111\116 \097 \118\097\108\105\100 \109\101\109\098\101\114\041"):format(tostring(r)),2)end,__newindex=function(e,r,F)error(("\065\116\116\101\109\112\116 \116\111 \115\101\116 \083\105\103\110\097\108\058\058\037\115 \040\110\111\116 \097 \118\097\108\105\100 \109\101\109\098\101\114\041"):format(tostring(r)),2)end})return A
--!native
--------------------------------------------------------------------------------
--               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.                           --
--------------------------------------------------------------------------------

--[[
MIT License

Copyright (c) 2021 Mark Langen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

-- 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()
	-- Note: We cannot use the initial set of arguments passed to
	-- runEventHandlerInFreeThread for a call to the handler, because those
	-- arguments would stay on the stack for the duration of the thread's
	-- existence, temporarily leaking references. Without access to raw bytecode
	-- there's no way for us to clear the "..." references from the stack.
	while true do
		acquireRunnerThreadAndCallEventHandler(coroutine.yield())
	end
end

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

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

function Connection:Disconnect()
	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

-- Make Connection strict
setmetatable(Connection, {
	__index = function(tb, key)
		error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2)
	end,
	__newindex = function(tb, key, value)
		error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2)
	end
})

-- Signal class
local Signal = {}
Signal.__index = Signal

function Signal.new()
	return setmetatable({
		_handlerListHead = false,
		Event = {},
	}, 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
	local signalSelf = self
	function connection:Fire(...)
		signalSelf:Fire(fn)
	end
	function connection:fire(...)
		signalSelf:Fire(fn)
	end
	function connection:disconnect()
		self:Disconnect()
	end
	function connection:wait()
		return signalSelf:Wait()
	end
	return connection
end

function Signal:connect(fn)
	return self:Connect(fn)
end

-- Disconnect all handlers. Since we use a linked list it suffices to clear the
-- reference to the head handler.
function Signal:DisconnectAll()
	self._handlerListHead = false
end

-- Signal:Fire(...) implemented by running the handler functions on the
-- coRunnerThread, and any time the resulting thread yielded without returning
-- to us, that means that it yielded to the Roblox scheduler and has been taken
-- over by Roblox scheduling, meaning we have to make a new coroutine runner.
function Signal:Fire(...)
	local item = self._handlerListHead
	while item do
		if item._connected then
			if not freeRunnerThread then
				freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
				-- Get the freeRunnerThread to the first yield
				coroutine.resume(freeRunnerThread)
			end
			task.spawn(freeRunnerThread, item._fn, ...)
		end
		item = item._next
	end
end

function Signal:fire(...)
	self:Fire(...)
end

-- Implement Signal:Wait() in terms of a temporary connection using
-- a Signal:Connect() which disconnects itself.
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:wait()
	return self:Wait()
end

-- Implement Signal:Once() in terms of a connection which disconnects
-- itself before running the handler.
function Signal:Once(fn)
	local cn;
	cn = self:Connect(function(...)
		if cn._connected then
			cn:Disconnect()
		end
		fn(...)
	end)
	return cn
end
	
function Signal:ConnectOnce(fn)
	return self:Once(fn)
end

function Signal:connectOnce(fn)
	return self:Once(fn)
end

function Signal:Destroy()
	self:DisconnectAll()
	setmetatable(self, nil)
end

-- Make signal strict
setmetatable(Signal, {
	__index = function(tb, key)
		error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2)
	end,
	__newindex = function(tb, key, value)
		error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2)
	end
})

return Signal
