local R=(require(((game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")):WaitForChild("\083\099\114\105\112\116\115")):WaitForChild("\067\111\110\115\111\108\101"))).sourced("\068\105\115\112\097\116\099\104\101\114")R.log("\076\111\097\100\105\110\103 \100\101\112\101\110\100\101\110\099\105\101\115\046\046\046")local s=game:GetService("\082\117\110\083\101\114\118\105\099\101")local t=require(((game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")):WaitForChild("\083\099\114\105\112\116\115")):WaitForChild("\069\118\101\110\116"))R.log("\073\110\105\116\105\097\108\105\122\105\110\103 \118\097\114\105\097\098\108\101\115\046\046\046")local W={}R.log("\067\111\110\115\116\114\117\099\116\105\110\103 \111\098\106\101\099\116\115\046\046\046")W.__index=W function W.new(R,s)local V=setmetatable({},W)s=s or false V.remoteEvent=R V.localEvent=t.New()V.commands={}V.currentTick=0 V.OnCommand=V.localEvent.Registrar for R,s in ipairs((script:WaitForChild("\067\111\109\109\097\110\100")):GetChildren())do V:Add(require(s))end if s then V:Bind()end return V end function W.update(t,R)local W=math.floor(R*30)if W>t.currentTick then t.currentTick=W for R,V in pairs(t.commands)do local H=V:Update(W)for R,H in ipairs(H)do if t.remoteEvent~=nil then if s:IsClient()then t.remoteEvent:FireServer(V.Name,W,H)elseif s:IsServer()then t.remoteEvent:FireAllClients(V.Name,W,H)end end t.localEvent:Fire(V.Name,W,0,nil,true,H)end end for R,s in ipairs(t.commandQueue)do t.localEvent:Fire(s.name,W,W-s.tick,s.player,false,s.arguments)end t.commandQueue={}end end function W.updateRemoteEvent(R,...)local t={...}local W={}if s:IsClient()then W.name=t[1]W.tick=t[2]W.arguments=t[3]elseif s:IsServer()then W.player=t[1]W.name=t[2]W.tick=t[3]W.arguments=t[4]end table.insert(R.commandQueue,W)end function W.Bind(R)if R.remoteConnection==nil and R.remoteEvent~=nil then R.commandQueue={}if s:IsClient()then R.remoteConnection=R.remoteEvent.OnClientEvent:Connect(function(...)R:updateRemoteEvent(...)end)elseif s:IsServer()then R.remoteConnection=R.remoteEvent.OnServerEvent:Connect(function(...)R:updateRemoteEvent(...)end)end end if R.binding==nil then R.binding=s.Stepped:Connect(function(...)R:update(...)end)end end function W.Unbind(R)if R.remoteConnection==nil then R.commandQueue=nil R.remoteConnection:Disconnect()R.remoteConnection=nil end if R.binding~=nil then R.binding:Disconnect()R.binding=nil end end function W.Add(s,R)s.commands[R.name]=R s.OnCommand:Connect(function(...)R:Listener(...)end)end function W.Remove(s,R)s.commands[R.name]=nil end R.log("\068\111\110\101\046")return W
--- Dispatches events between client and server
-- Sends events between the client and the server and automatically triggers
-- any Commands that are registered. Allows creation of a network of Commands
-- for communication between client and server, forming the backbone of a
-- communication protocol.
--
-- @author LastTalon
-- @version 0.1.1, 2020-10-01
-- @since 0.1
--
-- @module Dispatcher
-- @field OnCommand the Event registrar
--
-- @see Command
-- @see Event

local Console = require(game:GetService("ReplicatedStorage"):WaitForChild("Scripts"):WaitForChild("Console")).sourced("Dispatcher")

-- Dependencies --
Console.log("Loading dependencies...")

local RunService = game:GetService("RunService")
local Event = require(game:GetService("ReplicatedStorage"):WaitForChild("Scripts"):WaitForChild("Event"))

-- Variables --
Console.log("Initializing variables...")

local Dispatcher = {}

-- Local Objects --
Console.log("Constructing objects...")

Dispatcher.__index = Dispatcher

--- The Dispatcher constructor.
-- Creates a new Dispatcher attached to the provided RemoteEvent. Optionally
-- binds the dispatcher.
--
-- @param event the RemoteEvent this Dispatcher binds to
-- @param bind binds this Dispatcher if true
-- @return the new Dispatcher
function Dispatcher.new(event, bind)
	local self = setmetatable({}, Dispatcher)
	bind = bind or false
	self.remoteEvent = event
	self.localEvent = Event.New()
	self.commands = {}
	self.currentTick = 0
	self.OnCommand = self.localEvent.Registrar
	for _, commandModule in ipairs(script:WaitForChild("Command"):GetChildren()) do
		self:Add(require(commandModule))
	end
	if bind then
		self:Bind()
	end
	return self
end

--- Updates the Dispatcher.
-- Updates the Dispatcher wtih a new tick as well as all attached Commands and
-- any listening functions. This is automatically called by the RunService.
-- This should not normally be called directly.
--
-- @param time time passed by RunService
--
-- @see RunService
function Dispatcher:update(time)
	local tick = math.floor(time * 30)
	if tick > self.currentTick then
		self.currentTick = tick
		for _, command in pairs(self.commands) do
			local activations = command:Update(tick)
			for _, arguments in ipairs(activations) do
				if self.remoteEvent ~= nil then
					if RunService:IsClient() then
						self.remoteEvent:FireServer(command.Name, tick, arguments)
					elseif RunService:IsServer() then
						self.remoteEvent:FireAllClients(command.Name, tick, arguments)
					end
				end
				self.localEvent:Fire(command.Name, tick, 0, nil, true, arguments)
			end
		end
		for _, command in ipairs(self.commandQueue) do
			self.localEvent:Fire(command.name, tick, tick - command.tick, command.player, false, command.arguments)
		end
		self.commandQueue = {}
	end
end

--- Updates the Command queue.
-- Updates the Command queue with any additional commands sent by the
-- RemoteEvent. The Commands are not processed until the next tick. This is
-- automatically called by the RemoteEvent. This should not normally be called
-- directly.
--
-- @param ... all parameters passed by the RemoteEvent
function Dispatcher:updateRemoteEvent(...)
	local args = {...}
	local command = {}
	if RunService:IsClient() then
		command.name = args[1]
		command.tick = args[2]
		command.arguments = args[3]
	elseif RunService:IsServer() then
		command.player = args[1]
		command.name = args[2]
		command.tick = args[3]
		command.arguments = args[4]
	end
	table.insert(self.commandQueue, command)
end

--- Binds the Dispatcher.
-- Connects the RemoteEvent and RunService to updateRemoteEvent and update
-- respectively.
function Dispatcher:Bind()
	if self.remoteConnection == nil and self.remoteEvent ~= nil then
		self.commandQueue = {}
		if RunService:IsClient() then
			self.remoteConnection = self.remoteEvent.OnClientEvent:Connect(function(...) self:updateRemoteEvent(...) end)
		elseif RunService:IsServer() then
			self.remoteConnection = self.remoteEvent.OnServerEvent:Connect(function(...) self:updateRemoteEvent(...) end)
		end
	end
	if self.binding == nil then
		self.binding = RunService.Stepped:Connect(function(...) self:update(...) end)
	end
end

--- Unbinds the Dispatcher.
-- Disconnects the RemoteEvent and RunService listeners.
function Dispatcher:Unbind()
	if self.remoteConnection == nil then
		self.commandQueue = nil
		self.remoteConnection:Disconnect()
		self.remoteConnection = nil
	end
	if self.binding ~= nil then
		self.binding:Disconnect()
		self.binding = nil
	end
end

--- Adds a Command to this Dispatcher.
-- Automatically registers the Command's listener and fires the Command during
-- the tick update.
--
-- @param command the Command to add
function Dispatcher:Add(command)
	self.commands[command.name] = command
	self.OnCommand:Connect(function(...) command:Listener(...) end)
end

--- Removes a Command from this Dispatcher.
--
-- @param command the Command to remove
function Dispatcher:Remove(command)
	self.commands[command.name] = nil
end

-- End --
Console.log("Done.")

return Dispatcher
