Which is better while loops or runservice loops

Hello, I am making a timer but I’m not sure which kind of loop would be generally the best to use for a timer that can be stopped at anytime.
sorry I typed this on mobile

—while loop
local run = true
local debounce = tick()
local Time = 0

while true do
if run == false then —timer stops if run is false
break
end
if tick() >= debounce +1 then — checks if current time is greater than debounce plus a second 
debounce = tick() — changes the debounce to the current time
Time += 1
print(Time)
end
end
—runserivce loop

local RunService = game:GetService(“RunService”)
local debounce = 0
local Time = 0

local function func()
debounce +=1 
if debounce == 60 then — if debounce is 60 then it resets it
debounce = 0
Time += 1
print(Time)
end
end

local connection = RunService.Heartbeat:Connect(func) — to start the heartbeat function

connection:Disconnect() —to stop timer from running

the while loop uses tick as the cooldown and the runservice relies on every 60th frame

So in your opinion which one do you prefer the most?

1 Like

while loop would probably be better in this case, RunService runs on FPS and if your making a timer globally, a simple while loop should do.

1 Like

It depends on your script/situation. Plus, if you’re using While loops, please avoid using ‘Wait’ and use either runservice or task.wait(), they’re much better and more efficient.

I don’t see a wait function call in the while loop, doesn’t that freeze the game?
Second of all, you’ll never need a while loop with a wait function inside it. Otherwise just use RunService’s events. Each event is for a specific purpose, and they’re all synced with the engine internals.
For example RenderStepped is called before rendering the scene, Heartbeat is called after simulation, Stepped is called before physics step.
For example:

  • Use Heartbeat for most frame to frame logic
  • Use Stepped to change physics stuff
  • Use RenderStepped for Camera movement

Find out more here:
Task Scheduler (roblox.com)
Otherwise I only use while loop as a substitution for the for loop when how much steps the loop have to run isn’t clear.

1 Like

True, we’re lucky to have these methods, hopefully they’d completely replace ‘wait’ and we could see it[ wait] gets removed.

2 Likes

I can’t even remember the last time I found a need to use a while loop at all besides for a quick test of something.

the best loop for a timer is no loop

local timerID = 0

local function StartTimer(duration, callback)
	timerID += 1
	local currentTimerID = timerID
	delay(duration, function()
		if currentTimerID ~= timerID then return end
		callback()
	end)
end

local function StopTimer()
	timerID += 1
end

StartTimer(10, function()
	print("This will never be called because we stoped it")
end)

-- stop the previous timer
StopTimer()

StartTimer(10, function()
	print("This will never be called because we started another timer after this timer")
end)

-- overwrite the previous timer
StartTimer(10, function()
	print("Hello World")
end)
2 Likes

The only issue with this is that the timer is only as accurate as the wait() method which isn’t that accurate. That’s why they replaced it with task.wait() which runs off heartbeat. In that case why not just connect your timer to heart beat? Probably less effort too.

I typically do something like this for a timer:

local someTimer = tick() + 5 --5 sec timer
game:GetService("RunService").Heartbeat:Connect(function()
	if(someTimer <= tick())then
		print("Times Up!")
		someTimer = tick() + 5 --reset the timer.
	end
end)

but that’s only when you call wait many times over and over but if you only call it once then the accuracy is not that bad

for instance lets say every time you call wait(1) instead of waiting 1 second it might wait 1.01 seconds

so if you have

while true do
	wait(1)
end

so as each time you wait it may add 0.01 seconds more so it adds up over time
and after its waited a total of 100 times it might be out of sync by 1 hole second

but if you only call wait once like this

wait(100)

then it might wait 100.01 seconds and that’s not that bad

And if you want to do it with a class

local timerClass = {}
timerClass.__index = timerClass

timerClass.New = function()
	local self = setmetatable({}, timerClass)
	self.id = 0
	return self
end

timerClass.Start = function(self, duration, callback)
	self.id += 1
	local currentTimerID = self.id
	delay(duration, function()
		if currentTimerID ~= self.id then return end
		callback()
	end)
end

timerClass.Stop = function(self)
	self.id += 1
end

and to use the class

local timer1 = timerClass.New()
local timer2 = timerClass.New()

timer1:Start(10, function()
	print("Timer1 Ended")
end)

timer2:Start(10, function()
	print("Timer2 Ended")
end)

timer1:Stop()

You should not use RunServer (RenderStepped, Heartbeat, …) to make a timer. In general RunServer should be used only if necessary (as the documentation above says) and a timer does not require so much precision.

For a timer it is typical to use tenths of seconds (0.3s, 0.2s, 0.1s). Smaller numbers would simply not be appreciated by anyone. RunServer’s accuracy is about 0.01666667s (1/60). An unnecessary waste of a valuable resource such as RunServer.

With task.wait() creating a fixed time game loop is as simple as this:

while true do
   task.wait(0.3)
   --your code
end

Note that you don’t need to use tick() or os.clock() either.

If you look at the documentation for task.wait() it is using heartbeat… This essentially means that task.wait() is a timer implemented using heartbeat.
https://developer.roblox.com/en-us/api-reference/lua-docs/task

I’m sorry, but I don’t understand what you are saying. In the documentation it does not say that it is using or is implemented with Heartbeat.

It says there that the thread that was paused by task.wait() is resumed on the next Heartbeat, that is, after the physics simulation, unlike wait(), but which is equivalent to Heartbeat.

Anyway, it would be somewhat strange (and disappointing) if task.wait() was just a macro for Heartbeat without any internal optimization.

If the thread halted by task.wait() resumes on the first heartbeat step after the duration has elapsed, then even if it doesn’t use heart beat itself the result is effectively the same.

In the task scheduler docs it specifies this guideline only for render step and immediately afterward says this:

I see your point, but it’s not effectively the same thing. With Heartbeat a thread is resolved at each step of the game, whereas with task.wait() only one thread is resolved when the time is up.

Note that it talks about wait() which is now deprecated.

Hi @luya_yobanka

The DevForum and Wiki clearly state that task.wait is the same as game:GetService("RunService").Heartbeat:Wait(). Check it out here : Task Library

Ok. But that’s a special case for when the waiting time is zero. This is the coolest feature of task.wait(), or at least for me.

I think RunService because the while loop is going to loop needlessly sometimes. So it’s probably going to use more CPU where the Roblox event should be only getting run when needed. The RunService snippet is probably also better for readability.