local A={}A.Connections={}A.LastIteration=nil local M={}M.Debug=false local J=setmetatable({},{__index=function(A,M)A[M]=game:GetService(M)return A[M]end})function A.set(A)M.Debug=A.Debug or false end function A.wrap(A,...)assert(typeof(A)=="\102\117\110\099\116\105\111\110","\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \'\119\114\097\112\' \111\110\108\121 \097\099\099\101\112\116\115 \102\117\110\099\116\105\111\110\115\044 \103\111\116 \'"..(typeof(A).."\'"))local M=table.unpack({...})local F F=J.RunService.Stepped:Connect(function()F:Disconnect()return A(M)end)end function A.spawn(A)assert(typeof(A)=="\102\117\110\099\116\105\111\110","\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \'\115\112\097\119\110\' \111\110\108\121 \097\099\099\101\112\116\115 \102\117\110\099\116\105\111\110\115\044 \103\111\116 \'"..(typeof(A).."\'"))local F F=J.RunService.Stepped:Connect(function()F:Disconnect()local J,k=pcall(function()return A()end)if not J and M.Debug then warn(k)end end)end function A.delay(M,F)assert(typeof(M)=="\110\117\109\098\101\114"and typeof(F)=="\102\117\110\099\116\105\111\110","\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \'\100\101\108\097\121\' \109\105\115\115\105\110\103 \112\097\114\097\109\101\116\101\114\115\044 \103\111\116 \'"..(typeof(M)..("\' \097\110\100 \'"..(typeof(F).."\'"))))A.wrap(function()local k=os.clock()while M<os.clock()-k do J.RunService.Stepped:Wait()end return A.wrap(F)end)end function A.wait(A)if A then local M=os.clock()while A>os.clock()-M do J.RunService.Stepped:Wait()end end return J.RunService.Stepped:Wait()end function A.Connect(F,J)assert(J~=nil,"\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \'\067\111\110\110\101\099\116\' \109\105\115\115\105\110\103 \112\097\114\097\109\101\116\101\114\115\044 \103\111\116 \102\117\110\099\116\105\111\110 \'"..(typeof(J).."\'"))local k={}function k.Disconnect(A)k=nil if typeof(J)=="\082\066\088\083\099\114\105\112\116\067\111\110\110\101\099\116\105\111\110"then J:Disconnect()elseif typeof(J)=="\116\097\098\108\101"then local A,F=pcall(function()J:Disconnect()end)if not A and M.Debug then warn("\091\068\073\067\069 \077\065\078\065\071\069\082\093\058",F)end end J=nil setmetatable(k,{__index=function()error("\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \065\116\116\101\109\112\116 \116\111 \117\115\101 \100\101\115\116\114\111\121\101\100 \099\111\110\110\101\099\116\105\111\110")end,__newindex=function()error("\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \065\116\116\101\109\112\116 \116\111 \117\115\101 \100\101\115\116\114\111\121\101\100 \099\111\110\110\101\099\116\105\111\110")end})end function k.Fire(M,...)if typeof(J)=="\102\117\110\099\116\105\111\110"then return A.wrap(J,...)else warn("\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \065\116\116\101\109\112\116\101\100 \116\111 \099\097\108\108 \058\070\105\114\101 \111\110 \'"..(typeof(J).."\'"))end end return k end function A.ConnectKey(k,J,F)assert(J~=nil and F~=nil,"\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \'\067\111\110\110\101\099\116\075\101\121\' \109\105\115\115\105\110\103 \112\097\114\097\109\101\116\101\114\115\044 \103\111\116 \107\101\121 \'"..(typeof(J)..("\' \097\110\100 \103\111\116 \102\117\110\099\116\105\111\110 \'"..(typeof(F).."\'"))))if not A.Connections[J]then A.Connections[J]={}end local P={}function P.Disconnect(k)if not A.Connections[J]then return end A.Connections[J][F]=nil if typeof(F)=="\082\066\088\083\099\114\105\112\116\067\111\110\110\101\099\116\105\111\110"then F:Disconnect()elseif typeof(F)=="\116\097\098\108\101"then local A,J=pcall(function()F:Disconnect()end)if not A and M.Debug then warn("\091\068\073\067\069 \077\065\078\065\071\069\082\093\058",J)end end F=nil setmetatable(P,{__index=function()error("\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \065\116\116\101\109\112\116 \116\111 \117\115\101 \100\101\115\116\114\111\121\101\100 \099\111\110\110\101\099\116\105\111\110")end,__newindex=function()error("\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \065\116\116\101\109\112\116 \116\111 \117\115\101 \100\101\115\116\114\111\121\101\100 \099\111\110\110\101\099\116\105\111\110")end})end function P.Fire(M,...)if typeof(F)=="\102\117\110\099\116\105\111\110"then return A.wrap(F,...)else warn("\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \065\116\116\101\109\112\116\101\100 \116\111 \099\097\108\108 \058\070\105\114\101 \111\110 \'"..(typeof(F).."\'"))end end A.Connections[J][F]=P return P end function A.FireKey(J,M,...)assert(M~=nil,"\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \'\070\105\114\101\075\101\121\' \109\105\115\115\105\110\103 \112\097\114\097\109\101\116\101\114\115\044 \103\111\116 \107\101\121 \'"..(typeof(M).."\'"))if A.Connections[M]then for A,M in pairs(A.Connections[M])do M:Fire(...)end end end function A.DisconnectKey(J,M)assert(M~=nil,"\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \'\068\105\115\099\111\110\110\101\099\116\075\101\121\' \109\105\115\115\105\110\103 \112\097\114\097\109\101\116\101\114\115\044 \103\111\116 \107\101\121 \'"..(typeof(M).."\'"))if A.Connections[M]then for J,F in pairs(A.Connections[M])do F:Disconnect()A.Connections[M][J]=nil end A.Connections[M]=nil end end function A.Task(F,M)M=M or 60 assert(typeof(M)=="\110\117\109\098\101\114","\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \084\097\115\107 \115\099\104\101\100\117\108\101\114 \111\110\108\121 \097\099\099\101\112\116\115 \110\117\109\098\101\114\115 \102\111\114 \102\114\097\109\101\115 \112\101\114 \115\101\099\111\110\100\044 \103\111\116 \'"..(typeof(M).."\'"))local k={}k.CodeQueue={}k.UpdateTable={}k.Enable=true k.Sleeping=true k.Paused=false k.UpdateTableEvent=nil local P=os.clock()J.RunService.Stepped:Wait()local function O()A.LastIteration=os.clock()for M=#k.UpdateTable,1,-1 do k.UpdateTable[M+1]=k.UpdateTable[M]>=A.LastIteration-1 and k.UpdateTable[M]or nil end k.UpdateTable[1]=A.LastIteration end local function d()k.UpdateTableEvent=J.RunService.Stepped:Connect(O)while true do if k.Sleeping then break end if not k:Enabled()then break end if M<0 then if#k.CodeQueue>0 then k.CodeQueue[1]()table.remove(k.CodeQueue,1)if not k:Enabled()then break end else k.Sleeping=true break end else local A=os.clock()-P>=1 and#k.UpdateTable or#k.UpdateTable/(os.clock()-P)if A>=M and os.clock()-k.UpdateTable[1]<1/M then if#k.CodeQueue>0 then k.CodeQueue[1]()table.remove(k.CodeQueue,1)if not k:Enabled()then break end else k.Sleeping=true break end elseif k:Enabled()then J.RunService.Stepped:Wait()end end end k.UpdateTableEvent:Disconnect()k.UpdateTableEvent=nil end function k.Enabled(A)return k.Enable end function k.Pause(A)k.Paused=true k.Sleeping=true local M=os.clock()-P>=1 and#k.UpdateTable or#k.UpdateTable/(os.clock()-P)return M end function k.Resume(A)if k.Paused then k.Paused=false k.Sleeping=false d()end local M=os.clock()-P>=1 and#k.UpdateTable or#k.UpdateTable/(os.clock()-P)return M end function k.Wait(A)while not k.Sleeping do J.RunService.Stepped:Wait()end local M=os.clock()-P>=1 and#k.UpdateTable or#k.UpdateTable/(os.clock()-P)return M end function k.Disconnect(A)k.Enable=false k:Pause()k.CodeQueue=nil k.UpdateTable=nil k.UpdateTableEvent:Disconnect()k:Wait()for A in pairs(k)do k[A]=nil end setmetatable(k,{__index=function()error("\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \065\116\116\101\109\112\116 \116\111 \117\115\101 \100\101\115\116\114\111\121\101\100 \116\097\115\107 \115\099\104\101\100\117\108\101\114")end;__newindex=function()error("\091\068\073\067\069 \077\065\078\065\071\069\082\093\058 \065\116\116\101\109\112\116 \116\111 \117\115\101 \100\101\115\116\114\111\121\101\100 \116\097\115\107 \115\099\104\101\100\117\108\101\114")end})end function k.Queue(M,A)if not k.CodeQueue then return end k.CodeQueue[#k.CodeQueue+1]=A if k.Sleeping and not k.Paused then k.Sleeping=false d()end end return k end return A
--[[
	@Author: Gavin "Mullets" Rosenthal
	@Desc: a task manager with scheduling & coroutines
--]]

--[[
[DOCUMENTATION]:
	https://github.com/Mullets-Gavin/DiceManager
	Listed below is a quick glance on the API, visit the link above for proper documentation.
	
	Manager.set(dictionary)
	
	Manager.wrap(function)
	Manager.spawn(function)
	Manager.delay(time,function)
	
	event = Manager:Connect(function)
	event:Disconnect()
	event:Fire()
	
	event = Manager:ConnectKey(key,function)
	event:Disconnect()
	event:Fire()
	
	Manager:DisconnectKey(key)
	
	event = Manager:Task([fps])
	event:Queue(function)
	event:Pause()
	event:Resume()
	event:Disconnect()
--]]

--// logic
local Manager = {}
Manager.Connections = {}
Manager.LastIteration = nil

local Settings = {}
Settings.Debug = false

--// services
local Services = setmetatable({}, {__index = function(cache, serviceName)
	cache[serviceName] = game:GetService(serviceName)
	return cache[serviceName]
end})

--// functions
--[[
	Variations of call:
	
	.set(properties)
	
	properties = {
		['Debug'] = bool
	}
--]]
function Manager.set(properties)
	Settings.Debug = properties['Debug'] or false
end

--[[
	Variations of call:
	
	.wrap(function)
]]--
function Manager.wrap(code,...)
	assert(typeof(code) == 'function',"[DICE MANAGER]: 'wrap' only accepts functions, got '".. typeof(code) .."'")
	local contents = table.unpack({...})
	local event
	event = Services['RunService'].Stepped:Connect(function()
		event:Disconnect()
		return code(contents)
	end)
end

--[[
	Variations of call:
	
	.spawn(function)
]]--
function Manager.spawn(code)
	assert(typeof(code) == 'function',"[DICE MANAGER]: 'spawn' only accepts functions, got '".. typeof(code) .."'")
	local event
	event = Services['RunService'].Stepped:Connect(function()
		event:Disconnect()
		local success,err = pcall(function()
			return code()
		end)
		if not success and Settings.Debug then
			warn(err)
		end
	end)
end

--[[
	Variations of call:
	
	.delay(time,function)
]]--
function Manager.delay(clock,code)
	assert(typeof(clock) == 'number' and typeof(code) == 'function',"[DICE MANAGER]: 'delay' missing parameters, got '".. typeof(clock) .."' and '".. typeof(code) .."'")
	Manager.wrap(function()
		local current = os.clock()
		while clock < os.clock() - current do
			Services['RunService'].Stepped:Wait()
		end
		return Manager.wrap(code)
	end)
end

--[[
	Variations of call:
	
	.wait(time)
--]]
function Manager.wait(clock)
	if clock then
		local current = os.clock()
		while clock > os.clock() - current do
			Services['RunService'].Stepped:Wait()
		end
	end
	return Services['RunService'].Stepped:Wait()
end

--[[
	Variations of call:
	
	:Connect(function)
	:Connect(RBXScriptConnection)
	
	Returns:
	
	control = dictionary
	control:Fire(...)
	control:Disconnect()
]]--
function Manager:Connect(code)
	assert(code ~= nil,"[DICE MANAGER]: 'Connect' missing parameters, got function '".. typeof(code) .."'")
	local control = {}
	
	function control:Disconnect()
		control = nil
		if typeof(code) == 'RBXScriptConnection' then
			code:Disconnect()
		elseif typeof(code) == 'table' then
			local success,err = pcall(function()
				code:Disconnect()
			end)
			if not success and Settings.Debug then
				warn('[DICE MANAGER]:',err)
			end
		end
		code = nil
		setmetatable(control, {
			__index = function()
				error('[DICE MANAGER]: Attempt to use destroyed connection')
			end;
			__newindex = function()
				error('[DICE MANAGER]: Attempt to use destroyed connection')
			end;
		})
	end
	
	function control:Fire(...)
		if typeof(code) == 'function' then
			return Manager.wrap(code,...)
		else
			warn("[DICE MANAGER]: Attempted to call :Fire on '".. typeof(code) .."'")
		end
	end
	
	return control
end

--[[
	Variations of call:
	
	:Connect(key,function)
	:Connect(key,RBXScriptConnection)
	:Connect(key,table)
	
	Returns:
	
	control = dictionary
	control:Fire(...)
	control:Disconnect()
]]--
function Manager:ConnectKey(key,code)
	assert(key ~= nil and code ~= nil,"[DICE MANAGER]: 'ConnectKey' missing parameters, got key '".. typeof(key) .."' and got function '".. typeof(code) .."'")
	if not Manager.Connections[key] then
		Manager.Connections[key] = {}
	end
	local control = {}
	
	function control:Disconnect()
		if not Manager.Connections[key] then return end
		Manager.Connections[key][code] = nil
		if typeof(code) == 'RBXScriptConnection' then
			code:Disconnect()
		elseif typeof(code) == 'table' then
			local success,err = pcall(function()
				code:Disconnect()
			end)
			if not success and Settings.Debug then
				warn('[DICE MANAGER]:',err)
			end
		end
		code = nil
		setmetatable(control, {
			__index = function()
				error('[DICE MANAGER]: Attempt to use destroyed connection')
			end;
			__newindex = function()
				error('[DICE MANAGER]: Attempt to use destroyed connection')
			end;
		})
	end
	
	function control:Fire(...)
		if typeof(code) == 'function' then
			return Manager.wrap(code,...)
		else
			warn("[DICE MANAGER]: Attempted to call :Fire on '".. typeof(code) .."'")
		end
	end
	
	Manager.Connections[key][code] = control
	return control
end

--[[
	Variations of call:
	
	:FireKey(key)
--]]
function Manager:FireKey(key,...)
	assert(key ~= nil,"[DICE MANAGER]: 'FireKey' missing parameters, got key '".. typeof(key) .."'")
	if Manager.Connections[key] then
		for code,control in pairs(Manager.Connections[key]) do
			control:Fire(...)
		end
	end
end

--[[
	Variations of call:
	
	:DisconnectKey(key)
--]]
function Manager:DisconnectKey(key)
	assert(key ~= nil,"[DICE MANAGER]: 'DisconnectKey' missing parameters, got key '".. typeof(key) .."'")
	if Manager.Connections[key] then
		for code,control in pairs(Manager.Connections[key]) do
			control:Disconnect()
			Manager.Connections[key][code] = nil
		end
		Manager.Connections[key] = nil
	end
end

--[[
	Variations of call:
	
	:Task([fps])
	
	Returns:
	
	control = dictionary
	control:Queue(function)
	control:Pause()
	control:Resume()
	control:Disconnect()
--]]
function Manager:Task(targetFPS)
	targetFPS = targetFPS or 60
	assert(typeof(targetFPS) == 'number',"[DICE MANAGER]: Task scheduler only accepts numbers for frames per second, got '".. typeof(targetFPS) .."'")
	
	local control = {}
	control.CodeQueue = {}
	control.UpdateTable = {}
	control.Enable = true
	control.Sleeping = true
	control.Paused = false
	control.UpdateTableEvent = nil
	
	local start = os.clock()
	Services['RunService'].Stepped:Wait()
	
	local function Update()
		Manager.LastIteration = os.clock()
		for index = #control.UpdateTable,1,-1 do
			control.UpdateTable[index + 1] = ((control.UpdateTable[index] >= (Manager.LastIteration - 1)) and control.UpdateTable[index] or nil)
		end
		control.UpdateTable[1] = Manager.LastIteration
	end
	
	local function Loop()
		control.UpdateTableEvent = Services['RunService'].Stepped:Connect(Update)
		while (true) do
			if control.Sleeping then break end
			if not control:Enabled() then break end
			if targetFPS < 0 then
				if (#control.CodeQueue > 0) then
					control.CodeQueue[1]()
					table.remove(control.CodeQueue, 1)
					if not control:Enabled() then
						break
					end
				else
					control.Sleeping = true
					break
				end
			else
				local fps = (((os.clock() - start) >= 1 and #control.UpdateTable) or (#control.UpdateTable / (os.clock() - start)))
				if (fps >= targetFPS and (os.clock() - control.UpdateTable[1]) < (1 / targetFPS)) then
					if (#control.CodeQueue > 0) then
						control.CodeQueue[1]()
						table.remove(control.CodeQueue, 1)
						if not control:Enabled() then
							break
						end
					else
						control.Sleeping = true
						break
					end
				elseif control:Enabled() then
					Services['RunService'].Stepped:Wait()
				end
			end
		end
		control.UpdateTableEvent:Disconnect()
		control.UpdateTableEvent = nil
	end
	
	function control:Enabled()
		return control.Enable
	end
	
	function control:Pause()
		control.Paused = true
		control.Sleeping = true
		local fps = (((os.clock() - start) >= 1 and #control.UpdateTable) or (#control.UpdateTable / (os.clock() - start)))
		return fps
	end
	
	function control:Resume()
		if control.Paused then
			control.Paused = false
			control.Sleeping = false
			Loop()
		end
		local fps = (((os.clock() - start) >= 1 and #control.UpdateTable) or (#control.UpdateTable / (os.clock() - start)))
		return fps
	end
	
	function control:Wait()
		while not control.Sleeping do
			Services['RunService'].Stepped:Wait()
		end
		local fps = (((os.clock() - start) >= 1 and #control.UpdateTable) or (#control.UpdateTable / (os.clock() - start)))
		return fps
	end
	
	function control:Disconnect()
		control.Enable = false
		control:Pause()
		control.CodeQueue = nil
		control.UpdateTable = nil
		control.UpdateTableEvent:Disconnect()
		control:Wait()
		for index in pairs(control) do
			control[index] = nil
		end
		setmetatable(control, {
			__index = function()
				error('[DICE MANAGER]: Attempt to use destroyed task scheduler')
			end;
			__newindex = function()
				error('[DICE MANAGER]: Attempt to use destroyed task scheduler')
			end;
		})
	end
	
	function control:Queue(code)
		if not control.CodeQueue then return end
		control.CodeQueue[#control.CodeQueue + 1] = code
		if (control.Sleeping and not control.Paused) then
			control.Sleeping = false
			Loop()
		end
	end
	
	return control
end

return Manager