Coroutine-loop reliance, can it go wrong?

Hey! Recently I have been implementing alot of corountine-heartbeat loops into my game as a replacement of the slow while true do method. To the point that most of my scripts have 1 or even 2 of these loops. However, I am concerned if the thread somehow dies and the loop is killed.

Here is the basic structure of my loop:

coroutine.wrap(function() 
	game["Run Service"].Heartbeat:Connect(function()
		
	end)
end)()

I have some ideas that might secure the loop if the worst comes. But I do not know if it is even needed.

Here is one of my scripts that have 2 loops:

--// UI
local RootGUI = script.Parent

--// UI parts
local SetBar = RootGUI.SettingsBar -- settings
local LevelBar = RootGUI.LevelBar -- levels
local CoinBar = RootGUI.CoinBar -- coins
local SettingsP = script.Parent.SettingsProper

--// just to make sure...
SettingsP.Visible = false

--// Levels
local LevelM = require(game.ReplicatedStorage.ModuleScripts.Levels)
local UserXP = game.ReplicatedStorage.PlayerData:WaitForChild(game.Players.LocalPlayer.UserId).Currency.Xp
local Bar = LevelBar.SelBar
local Levels = LevelM.Levels
local InfText = LevelBar.InfText

--// coin
local CCount = CoinBar.CoinAText
local UserCoin = game.ReplicatedStorage.PlayerData:WaitForChild(game.Players.LocalPlayer.UserId).Currency.Coin

--// Slider
local Slider = require(game.ReplicatedStorage.ModuleScripts.SliderM)


--// Level & coin function main
coroutine.wrap(function()
	game["Run Service"].Heartbeat:Connect(function()
		local xp = UserXP.Value
		local UserLevel = LevelM.ConvertToLevel(xp)
		local toS = 0
		local NextLev = Levels[UserLevel]
		if UserLevel > 1 then
			toS = Levels[UserLevel-1]
		end

		local Spxp = xp-toS
		local SpNextLev = NextLev-toS

		local tot = 1*(Spxp/SpNextLev)
		local perC = math.floor(tot*100)
		InfText.Text = "Level "..UserLevel.." ("..perC.."%)"
		CCount.Text = "₵ "..UserCoin.Value

		Bar:TweenSize(UDim2.fromScale(1,tot),Enum.EasingDirection.Out,Enum.EasingStyle.Linear,.25)
		
		
	end)
end)()

--// Settings

SetBar.Button.MouseButton1Click:Connect(function()
	SettingsP.Visible = not SettingsP.Visible
end)

local PlrMouse = game.Players.LocalPlayer:GetMouse()

local BackgroundMS = SettingsP.AudioSettings.BackgroundM.SlideBase
local SlideConfig1 = {Start=0,End=1,Increment=0.05,DefaultValue=0.8}
local Slider1 = Slider.new(BackgroundMS,SlideConfig1,TweenInfo.new(0.1,Enum.EasingStyle.Linear,Enum.EasingDirection.Out),"X")
Slider.Track(Slider1)

local BSound = game.SoundService.BackgroundSound
--// Settings loop
coroutine.wrap(function() 
	game["Run Service"].Heartbeat:Connect(function()
		for i, audio in pairs(BSound:GetChildren()) do
			if audio.ClassName == "Sound" then
				audio.Volume = Slider.GetValue(Slider1)
			end
			
		end
	end)
end)()

The 2 loops have different purposes, 1 is for updating the amount of currency the player has, and then displaying it on a UI; and the other is just for the settings.

Although it is a vast improvement from while true do loops, I am still sceptical that the loop thread could die.

Yes, it will die, but no, you connected a event to RunService.Heartbeat so it runs every heartbeat.

Also this is very inefficient, you should use events instead (because you can in this case)

what your threads do is to connect an action to the Heartbeat event and die immediately. Because roblox implements the observer pattern I don’t see anything unusual in what you are doing. I would say it’s not necessary to create a thread just to make a connection (although I’m not really an expert on the engine).

What you should worry about is how intensive the actions can be:
if it’s purely lua there are usually no problems. On the other hand, if they involve engine stuff it’s more likely that something will go wrong. For example using Tweens in every game step is almost always a bad idea (you should use math instead).

With the new task library using while loops is even better than using RunService (at least from my point of view). With while true and task.wait you can create a fixed time game loop (this is quite appreciated in online games).

should I do while task.wait() or while game["Run Service"].Heartbeat:Wait() then, since that would be a proper loop also at frame-rate?

according to the api doc both would be the same, which in turn is the same as game["Run Service"].Heartbeat:Connect(function() ....Any difference would be negligible.

What is different is while task.wait(n), with n > dt of course. An additional benefit is that not every step adds tasks, allowing the engine to catch up under throttled conditions.

sorry for the late reply.