function WaitForChild(u,r)local m=u:FindFirstChild(r)if not m then local S=coroutine.running()local H H=u.ChildAdded:Connect(function(u)if u.Name==r then H:Disconnect()m=u ResumeThread(S)end end)YieldThread()end return m end function GetEventHandler(u)local r=EventHandlers[u]if r then return r end local m={Name=u;Folder=EventsFolder,Callbacks={},IncomingQueueErrored=nil}EventHandlers[u]=m if IsServer then local u=Instance.new("\082\101\109\111\116\101\069\118\101\110\116")u.Name=m.Name u.Parent=m.Folder m.Remote=u else FastSpawn(function()m.Queue={}local u=WaitForChild(m.Folder,m.Name)m.Remote=u if#m.Callbacks==0 then m.IncomingQueue={}end u.OnClientEvent:Connect(function(...)if m.IncomingQueue then if#m.IncomingQueue>=2048 then if not m.IncomingQueueErrored then m.IncomingQueueErrored=true FastSpawn(error,string.format("\069\120\104\097\117\115\116\101\100 \114\101\109\111\116\101 \105\110\118\111\099\097\116\105\111\110 \113\117\101\117\101 \102\111\114 \037\115",u:GetFullName()),-1)delay(1,function()m.IncomingQueueErrored=nil end)end if#m.IncomingQueue>=8172 then table.remove(m.IncomingQueue,1)end end ReceiveCounter+=1 table.insert(m.IncomingQueue,table.pack(ReceiveCounter,m,...))return end SafeFireEvent(m,...)end)if not IsStudio then u.Name=""end for u,r in pairs(m.Queue)do r()end m.Queue=nil end)end return m end function GetFunctionHandler(u)local r=FunctionHandlers[u]if r then return r end local m={Name=u;Folder=FunctionsFolder;Callback=nil,IncomingQueueErrored=nil}FunctionHandlers[u]=m if IsServer then local u=Instance.new("\082\101\109\111\116\101\070\117\110\099\116\105\111\110")u.Name=m.Name u.Parent=m.Folder m.Remote=u else FastSpawn(function()m.Queue={}local u=WaitForChild(m.Folder,m.Name)m.Remote=u m.IncomingQueue={}m.OnClientInvoke=function(...)if not m.Callback then if#m.IncomingQueue>=2048 then if not m.IncomingQueueErrored then m.IncomingQueueErrored=true FastSpawn(error,string.format("\069\120\104\097\117\115\116\101\100 \114\101\109\111\116\101 \105\110\118\111\099\097\116\105\111\110 \113\117\101\117\101 \102\111\114 \037\115",u:GetFullName()),-1)delay(1,function()m.IncomingQueueErrored=nil end)end if#m.IncomingQueue>=8172 then table.remove(m.IncomingQueue,1)end end ReceiveCounter+=1 local r=table.pack(ReceiveCounter,m,coroutine.running())table.insert(m.IncomingQueue,r)YieldThread()end return SafeInvokeCallback(m,...)end if not IsStudio then u.Name=""end for u,r in pairs(m.Queue)do r()end m.Queue=nil end)end return m end function AddToQueue(u,r,m)if u.Remote then return r()end u.Queue[#u.Queue+1]=r if m then delay(5,function()if not u.Remote then warn(debug.traceback(("\073\110\102\105\110\105\116\101 \121\105\101\108\100 \112\111\115\115\105\098\108\101 \111\110 \'\037\115\058\087\097\105\116\070\111\114\067\104\105\108\100\040\"\037\115\"\041\'"):format(u.Folder:GetFullName(),u.Name)))end end)end end function ExecuteDeferredHandlers()local u=DeferredHandlers local r={}DeferredHandlers={}for u in pairs(u)do local m=u.IncomingQueue u.IncomingQueue=nil table.move(m,1,#m,#r+1,r)end table.sort(r,function(u,r)return u[1]<r[1]end)for u,r in ipairs(r)do local m=r[2]if m.Callbacks then SafeFireEvent(m,unpack(r,3))else ResumeThread(r[3])end end end local u={MatchParams=function(u,r)r={unpack(r)}local m=1 for u,m in pairs(r)do local S=type(m)=="\115\116\114\105\110\103"and string.split(m,"\124")or m local H={}local P=""for u,r in pairs(S)do local m=(r:gsub("\094\037\115\043","")):gsub("\037\115\043\036","")P..=(#P>0 and" \111\114 "or"")..m H[m:lower()]=true end H._string=P r[u]=H end if IsServer then m=2 table.insert(r,1,false)end local function S(S,...)local H=table.pack(...)if H.n>#r then if IsStudio then warn(("\091\078\101\116\119\111\114\107\093 \073\110\118\097\108\105\100 \110\117\109\098\101\114 \111\102 \112\097\114\097\109\101\116\101\114\115 \116\111 \037\115 \040\037\115 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\115\041"):format(u,(#r-m)+1,(H.n-m)+1))end return end for S=m,#r,1 do local P=typeof(H[S])local n=r[S]if not n[P:lower()]and not n.any then if IsStudio then warn(("\091\078\101\116\119\111\114\107\093 \073\110\118\097\108\105\100 \112\097\114\097\109\101\116\101\114 \037\100 \116\111 \037\115 \040\037\115 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\115\041"):format((S-m)+1,u,n._string,P))end return end end return S(...)end return S end}function combineFn(r,m,...)local S={...}if typeof(m)=="\116\097\098\108\101"then local H=m m=m[1]if H.MatchParams then table.insert(S,u.MatchParams(r.Name,H.MatchParams))end end local function H(...)if LoggingNetwork then local u=...table.insert(LoggingNetwork[u][r.Remote].dataIn,GetParamString(select(2,...)))end local u=1 local function H(r,...)if r~=u then return end u+=1 if r<=#S then return S[r](function(...)return H(r+1,...)end,...)end return m(...)end return H(1,...)end return H end function Network.BindEvents(m,u,r)if typeof(u)=="\116\097\098\108\101"then u,r=nil,u end for r,m in pairs(r)do local S=GetEventHandler(r)if not S then error(("\084\114\105\101\100 \116\111 \098\105\110\100 \099\097\108\108\098\097\099\107 \116\111 \110\111\110-\101\120\105\115\116\105\110\103 \082\101\109\111\116\101\069\118\101\110\116 \037\113"):format(r))end S.Callbacks[#S.Callbacks+1]=combineFn(S,m,u)if IsServer then S.Remote.OnServerEvent:Connect(function(...)SafeFireEvent(S,...)end)else if S.IncomingQueue then DeferredHandlers[S]=true end end end ExecuteDeferredHandlers()end function Network.BindFunctions(m,u,r)if typeof(u)=="\116\097\098\108\101"then u,r=nil,u end for r,m in pairs(r)do local S=GetFunctionHandler(r)if not S then error(("\084\114\105\101\100 \116\111 \098\105\110\100 \099\097\108\108\098\097\099\107 \116\111 \110\111\110-\101\120\105\115\116\105\110\103 \082\101\109\111\116\101\070\117\110\099\116\105\111\110 \037\113"):format(r))end if S.Callback then error(("\084\114\105\101\100 \116\111 \098\105\110\100 \109\117\108\116\105\112\108\101 \099\097\108\108\098\097\099\107\115 \116\111 \116\104\101 \115\097\109\101 \082\101\109\111\116\101\070\117\110\099\116\105\111\110 \040\037\115\041"):format(S.Remote:GetFullName()))end S.Callback=combineFn(S,m,u)if IsServer then S.Remote.OnServerInvoke=function(...)return SafeInvokeCallback(S,...)end else if S.IncomingQueue then DeferredHandlers[S]=true end end end ExecuteDeferredHandlers()end
-- Regular :WaitForChild had issues with order (remoteevents were going through before waitforchild resumed)
function WaitForChild(parent, name)
	local remote = parent:FindFirstChild(name)

	if not remote then
		local thread = coroutine.running()
		local con

		con = parent.ChildAdded:Connect(function(child)
			if child.Name == name then
				con:Disconnect()
				remote = child
				ResumeThread(thread)
			end
		end)

		YieldThread()
	end

	return remote
end

function GetEventHandler(name)
	local handler = EventHandlers[name]
	if handler then
		return handler
	end

	local handler = {
		Name = name,
		Folder = EventsFolder,

		Callbacks = {},
		IncomingQueueErrored = nil,
	}

	EventHandlers[name] = handler

	if IsServer then
		local remote = Instance.new("RemoteEvent")
		remote.Name = handler.Name
		remote.Parent = handler.Folder

		handler.Remote = remote
	else
		FastSpawn(function()
			handler.Queue = {}

			local remote = WaitForChild(handler.Folder, handler.Name)
			handler.Remote = remote

			if #handler.Callbacks == 0 then
				handler.IncomingQueue = {}
			end

			remote.OnClientEvent:Connect(function(...)
				if handler.IncomingQueue then
					if #handler.IncomingQueue >= 2048 then
						if not handler.IncomingQueueErrored then
							handler.IncomingQueueErrored = true
							FastSpawn(
								error,
								string.format("Exhausted remote invocation queue for %s", remote:GetFullName()),
								-1
							)

							delay(1, function()
								handler.IncomingQueueErrored = nil
							end)
						end

						if #handler.IncomingQueue >= 8172 then
							table.remove(handler.IncomingQueue, 1)
						end
					end

					ReceiveCounter += 1
					table.insert(handler.IncomingQueue, table.pack(ReceiveCounter, handler, ...))
					return
				end

				SafeFireEvent(handler, ...)
			end)

			if not IsStudio then
				remote.Name = ""
			end

			for _, fn in pairs(handler.Queue) do
				fn()
			end

			handler.Queue = nil
		end)
	end

	return handler
end

function GetFunctionHandler(name)
	local handler = FunctionHandlers[name]
	if handler then
		return handler
	end

	local handler = {
		Name = name,
		Folder = FunctionsFolder,

		Callback = nil,
		IncomingQueueErrored = nil,
	}

	FunctionHandlers[name] = handler

	if IsServer then
		local remote = Instance.new("RemoteFunction")
		remote.Name = handler.Name
		remote.Parent = handler.Folder

		handler.Remote = remote
	else
		FastSpawn(function()
			handler.Queue = {}

			local remote = WaitForChild(handler.Folder, handler.Name)
			handler.Remote = remote

			handler.IncomingQueue = {}
			handler.OnClientInvoke = function(...)
				if not handler.Callback then
					if #handler.IncomingQueue >= 2048 then
						if not handler.IncomingQueueErrored then
							handler.IncomingQueueErrored = true
							FastSpawn(
								error,
								string.format("Exhausted remote invocation queue for %s", remote:GetFullName()),
								-1
							)

							delay(1, function()
								handler.IncomingQueueErrored = nil
							end)
						end

						if #handler.IncomingQueue >= 8172 then
							table.remove(handler.IncomingQueue, 1)
						end
					end

					ReceiveCounter += 1
					local params = table.pack(ReceiveCounter, handler, coroutine.running())

					table.insert(handler.IncomingQueue, params)
					YieldThread()
				end

				return SafeInvokeCallback(handler, ...)
			end

			if not IsStudio then
				remote.Name = ""
			end

			for _, fn in pairs(handler.Queue) do
				fn()
			end

			handler.Queue = nil
		end)
	end

	return handler
end

function AddToQueue(handler, fn, doWarn)
	if handler.Remote then
		return fn()
	end

	handler.Queue[#handler.Queue + 1] = fn

	if doWarn then
		delay(5, function()
			if not handler.Remote then
				warn(
					debug.traceback(
						("Infinite yield possible on '%s:WaitForChild(\"%s\")'"):format(
							handler.Folder:GetFullName(),
							handler.Name
						)
					)
				)
			end
		end)
	end
end

function ExecuteDeferredHandlers()
	local handlers = DeferredHandlers
	local queue = {}

	DeferredHandlers = {}

	for handler in pairs(handlers) do
		local incoming = handler.IncomingQueue
		handler.IncomingQueue = nil

		table.move(incoming, 1, #incoming, #queue + 1, queue)
	end

	table.sort(queue, function(a, b)
		return a[1] < b[1]
	end)

	for _, v in ipairs(queue) do
		local handler = v[2]

		if handler.Callbacks then
			SafeFireEvent(handler, unpack(v, 3))
		else
			ResumeThread(v[3])
		end
	end
end

local Middleware = {
	MatchParams = function(name, paramTypes)
		paramTypes = { unpack(paramTypes) }
		local paramStart = 1

		for i, v in pairs(paramTypes) do
			local list = type(v) == "string" and string.split(v, "|") or v

			local dict = {}
			local typeListString = ""

			for _, v in pairs(list) do
				local typeString = v:gsub("^%s+", ""):gsub("%s+$", "")

				typeListString ..= (#typeListString > 0 and " or " or "") .. typeString
				dict[typeString:lower()] = true
			end

			dict._string = typeListString
			paramTypes[i] = dict
		end

		if IsServer then
			paramStart = 2
			table.insert(paramTypes, 1, false)
		end

		local function MatchParams(fn, ...)
			local params = table.pack(...)

			if params.n > #paramTypes then
				if IsStudio then
					warn(
						("[Network] Invalid number of parameters to %s (%s expected, got %s)"):format(
							name,
							#paramTypes - paramStart + 1,
							params.n - paramStart + 1
						)
					)
				end
				return
			end

			for i = paramStart, #paramTypes do
				local argType = typeof(params[i])
				local argExpected = paramTypes[i]

				if not argExpected[argType:lower()] and not argExpected.any then
					if IsStudio then
						warn(
							("[Network] Invalid parameter %d to %s (%s expected, got %s)"):format(
								i - paramStart + 1,
								name,
								argExpected._string,
								argType
							)
						)
					end
					return
				end
			end

			return fn(...)
		end

		return MatchParams
	end,
}

function combineFn(handler, final, ...)
	local middleware = { ... }

	if typeof(final) == "table" then
		local info = final
		final = final[1]

		if info.MatchParams then
			table.insert(middleware, Middleware.MatchParams(handler.Name, info.MatchParams))
		end
	end

	local function NetworkHandler(...)
		if LoggingNetwork then
			local client = ...
			table.insert(LoggingNetwork[client][handler.Remote].dataIn, GetParamString(select(2, ...)))
		end

		local currentIndex = 1

		local function runMiddleware(index, ...)
			if index ~= currentIndex then
				return
			end

			currentIndex += 1

			if index <= #middleware then
				return middleware[index](function(...)
					return runMiddleware(index + 1, ...)
				end, ...)
			end

			return final(...)
		end

		return runMiddleware(1, ...)
	end

	return NetworkHandler
end

function Network:BindEvents(pre, callbacks)
	if typeof(pre) == "table" then
		pre, callbacks = nil, pre
	end

	for name, fn in pairs(callbacks) do
		local handler = GetEventHandler(name)
		if not handler then
			error(("Tried to bind callback to non-existing RemoteEvent %q"):format(name))
		end

		handler.Callbacks[#handler.Callbacks + 1] = combineFn(handler, fn, pre)

		if IsServer then
			handler.Remote.OnServerEvent:Connect(function(...)
				SafeFireEvent(handler, ...)
			end)
		else
			if handler.IncomingQueue then
				DeferredHandlers[handler] = true
			end
		end
	end

	ExecuteDeferredHandlers()
end

function Network:BindFunctions(pre, callbacks)
	if typeof(pre) == "table" then
		pre, callbacks = nil, pre
	end

	for name, fn in pairs(callbacks) do
		local handler = GetFunctionHandler(name)
		if not handler then
			error(("Tried to bind callback to non-existing RemoteFunction %q"):format(name))
		end

		if handler.Callback then
			error(
				("Tried to bind multiple callbacks to the same RemoteFunction (%s)"):format(
					handler.Remote:GetFullName()
				)
			)
		end

		handler.Callback = combineFn(handler, fn, pre)

		if IsServer then
			handler.Remote.OnServerInvoke = function(...)
				return SafeInvokeCallback(handler, ...)
			end
		else
			if handler.IncomingQueue then
				DeferredHandlers[handler] = true
			end
		end
	end

	ExecuteDeferredHandlers()
end
