-- Calls fn(...) in a separate thread and returns false if it errors or invoking client leaves the game.
-- Fail state is only checked every 0.5 seconds, so don't expect errors to return immediately

function SafeInvokeCallback(handler, ...)
	local finished = false
	local callbackThread
	local invokeThread
	local result
	
	local function finish(...)
		if not finished then
			finished = true
			result = table.pack(...)
			
			if invokeThread then
				ResumeThread(invokeThread)
			end
		end
	end
	
	FastSpawn(function(...)
		callbackThread = coroutine.running()
		finish(true, handler.Callback(...))
	end, ...)
	
	if not finished then
		local client = IsServer and (...)
		
		coroutine.wrap(function()
			while not finished and coroutine.status(callbackThread) ~= "dead" do
				if IsServer and client.Parent ~= Players then
					break
				end
				
				wait(0.5)
			end
			
			finish(false)
		end)()
	end
	
	if not finished then
		invokeThread = coroutine.running()
		YieldThread()
	end
	
	return unpack(result)
end

function SafeInvoke(timeout, handler, ...)
	local thread = coroutine.running()
	local finished = false
	local result

	coroutine.wrap(function(...)
		if IsServer then
			result = table.pack(pcall(function(...) return handler.Remote:InvokeClient(...) end, ...))
		else
			result = table.pack(pcall(function(...) return handler.Remote:InvokeServer(...) end, ...))
		end

		if not finished then
			finished = true
			ResumeThread(thread)
		end
	end)(...)
	
	if typeof(timeout) == "number" then
		delay(timeout, function()
			if not finished then
				finished = true
				ResumeThread(thread)
			end
		end)
	end

	YieldThread()

	if result and result[1] == true and result[2] == true then
		return true, unpack(result, 3)
	end

	return false
end

function SafeFireEvent(handler, ...)
	local callbacks = handler.Callbacks
	local index = #callbacks
	
	while index > 0 do
		local running = true
		
		FastSpawn(function(...)
			while running and index > 0 do
				local fn = callbacks[index]
				index -= 1
				
				fn(...)
			end
		end, ...)
		
		running = false
	end
end
