Using RemoteFunction causes error "cannot resume dead coroutine"

I have this Localscript which handles the UI. When the player enters a zone, a textbox comes down. The first script invokes a RemoteFunction to check which type of information to display, but also the text, the color and the Endtime (if a cooldown should be displayed). When the player stands in the zone and the cooldown runs out, the the UIs are switched from the cooldown UI to the UI that basically tells the player he can press E to do whatever I want him to be able to do.
However, the issue is that the code breaks when the RemoteFunction is called on the client-side, but only when it is called through the “SwitchInterfaces” function. The “SwitchInterfaces” function calls the “InterfaceOut” function, that should pull the interface out, which calls the “CreateInterface” which calls the RemoteEvent and here it is where it breaks with the error “cannot resume dead coroutine”. I’ve removed the RemoteFunction and hardcoded the values and it worked, so for some reason the RemoteFunction breaks it. The serverside of the RemoveFunction also works perfectly fine and gives out the correct values if it wouldn’t brake the client-side code.

Note: The StartTime will be fetched from a DataStore in the future. For now I always change it to the current os.time() plus a few seconds to test

The error:
image

Client-side code:

-- VARIABLES

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local CollectionService = game:GetService("CollectionService")
local UserInputService = game:GetService("UserInputService")

local Function_Text = ReplicatedStorage:WaitForChild("Functions"):WaitForChild("Text")
local Function_InterfaceDisplayInfo = ReplicatedStorage:WaitForChild("Functions"):WaitForChild("InterfaceDisplayInfo")

local ZoneHandler = require(ReplicatedStorage.Modules.ZoneHandler)

local UI_Presets = ReplicatedStorage:WaitForChild("UI_Presets")

local StarterGui = Players.LocalPlayer.PlayerGui
local UI_Main = StarterGui:WaitForChild("UI_Main")
local CounterBackground = UI_Main.CounterBackground

local Connections = {}
local Threads = {}

local ZoneName = "UI_Zone"

local Number = 0

-- TWEEN_INFO

local Info_InfoBackground_Out = TweenInfo.new(0.2, Enum.EasingStyle.Back, Enum.EasingDirection.Out)
local Info_InfoBackground_In = TweenInfo.new(0.2, Enum.EasingStyle.Back, Enum.EasingDirection.In)


-- HELPER FUNCTIONS

function SwitchInterfaces(Container)
	local Tween_In = InterfaceIn(CounterBackground.InfoBackground)
	
	Tween_In.Completed:Wait()
	print("switching")
	InterfaceOut(Container)
end

local function CreateCooldown(Type, NewInterface, EndTime)
	local Thread

	Thread = task.spawn(function()
		local InterfaceText = NewInterface.TextLabel.Text

		while true do
			local SecondsBetween = os.difftime(EndTime, os.time())

			local Seconds = SecondsBetween % 60
			local Minutes = math.floor(SecondsBetween % (60 * 60) / 60)
			local Hours = math.floor(SecondsBetween % (60 * 60 * 24) / (60 * 60))
			local Days = math.floor(SecondsBetween % (60 * 60 * 24 * 30) / (60 * 60 * 24))

			if Days >= 1 then
				NewInterface.TextLabel.Text = InterfaceText.."\n("..Days.."d "..Hours.."h "..Minutes.."m "..Seconds.."s)"
			elseif Hours >= 1 then
				NewInterface.TextLabel.Text = InterfaceText.."\n("..Hours.."h "..Minutes.."m "..Seconds.."s)"
			elseif Minutes >= 1 then
				NewInterface.TextLabel.Text = InterfaceText.."\n("..Minutes.." m "..Seconds.."s)"
			elseif Seconds >= 0 then
				NewInterface.TextLabel.Text = InterfaceText.."\n("..Seconds.."s)"
			end

			if Days == 0 and Hours == 0 and Minutes == 0 and Seconds == 0 then
				SwitchInterfaces(Type)
				
				break
			end

			task.wait(0.5)
		end
	end)

	table.insert(Threads, Thread)
end

local function CreateInterface(Container)
	local Type = Container:GetAttribute("Type")
	
	local InfoType, Text, Color, EndTime = Function_InterfaceDisplayInfo:InvokeServer(Type)

	if InfoType == "OnCooldown" then
		local NewInterface = UI_Presets.InfoBackground:Clone()
		NewInterface.TextLabel.Text = Text
		NewInterface.TextLabel.BackgroundColor3 = Color
		NewInterface.Parent = CounterBackground

		CreateCooldown(Container, NewInterface, EndTime)

	elseif InfoType == "OffCooldown" then
		print("off")
		local NewInterface = UI_Presets.InfoBackground_Key:Clone()
		NewInterface.Main.Text = Text
		NewInterface.Main.BackgroundColor3 = Color
		NewInterface.Name = "InfoBackground"
		NewInterface.Parent = CounterBackground

	elseif InfoType == "InfoOnly" then
		local NewInterface = UI_Presets.InfoBackground:Clone()
		NewInterface.TextLabel.Text = Text
		NewInterface.TextLabel.BackgroundColor3 = Color
		NewInterface.Parent = CounterBackground
	end
end


-- MAIN FUNCTIONS

function InterfaceOut(Container)
	CreateInterface(Container)
	local TweenObject = CounterBackground:FindFirstChild("InfoBackground")
	
	if CounterBackground:FindFirstChild("InfoBackground_Key") then
		TweenObject = CounterBackground:FindFirstChild("InfoBackground_Key")
	end
	
	local Tween_Out = TweenService:Create(TweenObject, Info_InfoBackground_Out, {Position = UDim2.fromScale(0.5, 1)})

	Tween_Out:Play()
	print("tween played")
	Tween_Out.Completed:Connect(function()
		Tween_Out:Destroy()
	end)

	if TweenObject:GetChildren()[1]:IsA("TextButton") then
		for _, Button in pairs(TweenObject:GetChildren()) do
			local Connection_UI = Button.MouseButton1Up:Connect(function()
				-- code to interaction
			end)

			table.insert(Connections, Connection_UI)
		end
	end

	local Connection_Key = UserInputService.InputBegan:Connect(function(Input)
		if Input.KeyCode == Enum.KeyCode.E then
			-- code to interaction
		end
	end)

	table.insert(Connections, Connection_Key)
end

function InterfaceIn(Object)
	local Tween_In = TweenService:Create(Object, Info_InfoBackground_In, {Position = UDim2.fromScale(0.5, -0.01)})

	Tween_In:Play()

	for _, Connection in pairs(Connections) do
		Connection:Disconnect()
	end

	Tween_In.Completed:Connect(function()
		Tween_In:Destroy()

		for _, Thread in pairs(Threads) do
			task.cancel(Thread)
		end

		table.clear(Connections)
		table.clear(Threads)

		Object:Destroy()
	end)

	return Tween_In
end

for _, Container in pairs(CollectionService:GetTagged(ZoneName)) do
	Zone = ZoneHandler.new(Container)

	Zone.playerEntered:Connect(function(Player)
		InterfaceOut(Container)
	end)

	Zone.playerExited:Connect(function(Player)
		InterfaceIn(CounterBackground.InfoBackground)
	end)
end

Server-side code:

-- VARIABLES

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")

local UI_Settings = {

	["Types"] = {

		["Red"] = {
			Text_InCountdown = "You can't use the booster.",
			Color_InCountdown = nil,

			Text_OutCountdown = "Use the booster.",
			Color_OutCountdown = Color3.fromRGB(135, 249, 255),

			StartTime = 1717967562, -- Change it to

			CooldownInfo = {

				Seconds = 5,
				Minutes = 0

			}
		}
	}
}

local Function_Text = ReplicatedStorage:WaitForChild("Functions"):WaitForChild("Text")
local Function_InterfaceDisplayInfo = ReplicatedStorage:WaitForChild("Functions"):WaitForChild("InterfaceDisplayInfo")


-- FUNCTIONS

Function_InterfaceDisplayInfo.OnServerInvoke = function(Player, Type)
	local Settings = UI_Settings.Types[Type]
	
	if not UI_Settings.Types[Type] then
		warn("UI-Settings for "..Type.." couldn't be found.")
		
		return
	end

	local InfoType
	local Text
	local Color
	
	local TotalSeconds = 0
	local EndTime = nil

	if Settings.CooldownInfo then
		local StartTime = Settings.StartTime
		
		for Info, Time in pairs(UI_Settings.Types[Type].CooldownInfo) do
			if Info == "Seconds" then
				TotalSeconds = TotalSeconds + Time
			elseif Info == "Minutes" then
				TotalSeconds = TotalSeconds + (Time * 60)
			elseif Info == "Hours" then
				TotalSeconds = TotalSeconds + (Time * 60 * 60)
			elseif Info == "Days" then
				TotalSeconds = TotalSeconds + (Time * 60 * 60 * 24)
			elseif Info == "Months" then
				TotalSeconds = TotalSeconds + (Time * 60 * 60 * 31)
			elseif Info == "Years" then
				TotalSeconds = TotalSeconds + (Time * 60 * 60 * 31 * 12)
			end
		end

		
		if os.difftime(StartTime + TotalSeconds, os.time()) >= 1 then
			Text = Settings.Text_InCountdown or "You forgot to add the text."
			Color = Settings.Color_InCountdown or Color3.fromRGB(50, 131, 255)
			
			InfoType = "OnCooldown"
			
			EndTime = StartTime + TotalSeconds
		else
			
			Text = Settings.Text_OutCountdown or "You forgot to add the text."
			Color = Settings.Color_OutCountdown or Color3.fromRGB(170, 0, 0)
			
			InfoType = "OffCooldown"
		end 
	else
		Text = Settings.Text or "You forgot to add the text."
		Color = Settings.Color or Color3.fromRGB(50, 131, 255)

		InfoType = "InfoOnly"
	end
	
-- here the correct values are always given, even if the client-side code still breaks

	return InfoType, Text, Color, EndTime
end

On which line does the error occur?

I’ve added a screenshot to my original post. Sadly, it’s just this message with no line of code. But I’ve traced to the Remove Function, because when it is called, everything under it, is not being executed.

Someone on Discord managed to help me:

Adding these few lines to the part where I cancel the threads solved it.
If someone sees this and wants to add an explanation as to why this happens and why exactly this solves it, please do so, because I don’t understand it.

if coroutine.status(Thread) ~= "dead" then
				task.wait(0.5)
			else
				break
			end
1 Like

This might help:
https://www.lua.org/pil/9.1.html
I believe your coroutine finishes execution and terminates putting it in the dead state. Calling any subsequent coroutine operations on it will result in an error I believe. This is only speculation though, I am not extremely experienced myself, when it comes to coroutines or the inner workings of the task library.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.