function WaitForChild(A,J)local X=A:FindFirstChild(J)if not X then local o=coroutine.running()local l l=A.ChildAdded:Connect(function(A)if A.Name==J then l:Disconnect()X=A ResumeThread(o)end end)YieldThread()end return X end function GetEventHandler(A)local J=EventHandlers[A]if J then return J end local X={Name=A;Folder=EventsFolder;Callbacks={},IncomingQueueErrored=nil}EventHandlers[A]=X if IsServer then local A=Instance.new("\082\101\109\111\116\101\069\118\101\110\116")A.Name=X.Name A.Parent=X.Folder X.Remote=A else FastSpawn(function()X.Queue={}local A=WaitForChild(X.Folder,X.Name)X.Remote=A if#X.Callbacks==0 then X.IncomingQueue={}end A.OnClientEvent:Connect(function(...)if X.IncomingQueue then if#X.IncomingQueue>=2048 then if not X.IncomingQueueErrored then X.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",A:GetFullName()),-1)delay(1,function()X.IncomingQueueErrored=nil end)end if#X.IncomingQueue>=8172 then table.remove(X.IncomingQueue,1)end end ReceiveCounter+=1 table.insert(X.IncomingQueue,table.pack(ReceiveCounter,X,...))return end SafeFireEvent(X,...)end)if not IsStudio then A.Name=""end for A,J in pairs(X.Queue)do J()end X.Queue=nil end)end return X end function GetFunctionHandler(A)local J=FunctionHandlers[A]if J then return J end local X={Name=A;Folder=FunctionsFolder,Callback=nil;IncomingQueueErrored=nil}FunctionHandlers[A]=X if IsServer then local A=Instance.new("\082\101\109\111\116\101\070\117\110\099\116\105\111\110")A.Name=X.Name A.Parent=X.Folder X.Remote=A else FastSpawn(function()X.Queue={}local A=WaitForChild(X.Folder,X.Name)X.Remote=A X.IncomingQueue={}X.OnClientInvoke=function(...)if not X.Callback then if#X.IncomingQueue>=2048 then if not X.IncomingQueueErrored then X.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",A:GetFullName()),-1)delay(1,function()X.IncomingQueueErrored=nil end)end if#X.IncomingQueue>=8172 then table.remove(X.IncomingQueue,1)end end ReceiveCounter+=1 local J=table.pack(ReceiveCounter,X,coroutine.running())table.insert(X.IncomingQueue,J)YieldThread()end return SafeInvokeCallback(X,...)end if not IsStudio then A.Name=""end for A,J in pairs(X.Queue)do J()end X.Queue=nil end)end return X end function AddToQueue(A,J,X)if A.Remote then return J()end A.Queue[#A.Queue+1]=J if X then delay(5,function()if not A.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(A.Folder:GetFullName(),A.Name)))end end)end end function ExecuteDeferredHandlers()local A=DeferredHandlers local J={}DeferredHandlers={}for A in pairs(A)do local X=A.IncomingQueue A.IncomingQueue=nil table.move(X,1,#X,#J+1,J)end table.sort(J,function(A,J)return A[1]<J[1]end)for A,J in ipairs(J)do local X=J[2]if X.Callbacks then SafeFireEvent(X,unpack(J,3))else ResumeThread(J[3])end end end local A={MatchParams=function(A,J)J={unpack(J)}local X=1 for A,X in pairs(J)do local o=type(X)=="\115\116\114\105\110\103"and string.split(X,"\124")or X local l={}local P=""for A,J in pairs(o)do local X=(J:gsub("\094\037\115\043","")):gsub("\037\115\043\036","")P..=(#P>0 and" \111\114 "or"")..X l[X:lower()]=true end l._string=P J[A]=l end if IsServer then X=2 table.insert(J,1,false)end local function o(o,...)local l=table.pack(...)if l.n>#J 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(A,(#J-X)+1,(l.n-X)+1))end return end for o=X,#J,1 do local P=typeof(l[o])local u=J[o]if not u[P:lower()]and not u.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((o-X)+1,A,u._string,P))end return end end return o(...)end return o end}function combineFn(J,X,...)local o={...}if typeof(X)=="\116\097\098\108\101"then local l=X X=X[1]if l.MatchParams then table.insert(o,A.MatchParams(J.Name,l.MatchParams))end end local function l(...)if LoggingNetwork then local A=...table.insert(LoggingNetwork[A][J.Remote].dataIn,GetParamString(select(2,...)))end local A=1 local function l(J,...)if J~=A then return end A+=1 if J<=#o then return o[J](function(...)return l(J+1,...)end,...)end return X(...)end return l(1,...)end return l end function Network.BindEvents(X,A,J)if typeof(A)=="\116\097\098\108\101"then A,J=nil,A end for J,X in pairs(J)do local o=GetEventHandler(J)if not o 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(J))end o.Callbacks[#o.Callbacks+1]=combineFn(o,X,A)if IsServer then o.Remote.OnServerEvent:Connect(function(...)SafeFireEvent(o,...)end)else if o.IncomingQueue then DeferredHandlers[o]=true end end end ExecuteDeferredHandlers()end function Network.BindFunctions(X,A,J)if typeof(A)=="\116\097\098\108\101"then A,J=nil,A end for J,X in pairs(J)do local o=GetFunctionHandler(J)if not o 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(J))end if o.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(o.Remote:GetFullName()))end o.Callback=combineFn(o,X,A)if IsServer then o.Remote.OnServerInvoke=function(...)return SafeInvokeCallback(o,...)end else if o.IncomingQueue then DeferredHandlers[o]=true end end end ExecuteDeferredHandlers()end

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
