Avoiding wait() and why

It’s not really related to FPS. A client rendering at 60Hz can still have delayed resumes on yielded/newly created threads. It’s just a matter of how many yielded threads you are trying to resume at a certain moment. There’s a quotum on how much can be resumed every 2 frames.

6 Likes

i know that typically WaitForChild is used when scripts are waiting for something (i.e. a new part called “Specific” to be created and have the parent of Workspace, or when the client-side needs to load its entities) but i want to be specific.

when can i effectively use WaitForChild? should i avoid it like the plague as well?
i noticed in a lot of my older scripts, i tend to use this almost religiously-- it’s everywhere. knowing from this post that i’m likely not supposed to rely on waiting, i want to be able to clean that up a bit so my code is more efficient.
i usually avoid wait() itself pretty good, since in general now that i understand events it’s much easier for me to avoid. but i wonder if WaitForChild falls under this?

2 Likes

Nothing wrong with WaitForChild, no.

5 Likes

I don’t use wait function. especially when i need precise control over the flow of the script. for example if i have a tween running, and i want to pause the next frame a button or something got triggered. i can do it easily using a loop and a variable that counts how much time have elapsed. accumulating Render delta.

2 Likes

So what do I do at times when I actually need wait? For example when I use it to wait a certain amount before setting something like a debounce or something (there is no event so :Wait() won’t work in this case)? will I have to make my own version of wait() using a loop and checking how much time has elapsed with render delta?

You said wait() is inconsistent with how long it actually waits? if so, should i make my own version of wait() as a custom wait? then why does wait() even exist?

3 Likes

Implementing a custom wait is pretty easy.

local function realWait(t)
	local total = 0
	repeat
		total = total + RunService.Heartbeat:Wait()
	until total >= t
end
4 Likes

Hi I Tried Your Method Doing

local TickAmount = tick()
realWait(1)
print(tick() - TickAmount)

And I Got

0.26110482215881

I Waited Less Than 1 Second.

It Is Not Accurate At All. I Have Read Upon Other Posts Too And All The Implementations Seem To Be Similar But They Are Too Inaccurate. Thank You For Your Time.

2 Likes

Not sure where you’re getting that from. Here’s me putting it in a command line.

> RunService = game:GetService("RunService")
> function realWait(t)
	local total = 0
	repeat
		total = total + RunService.Heartbeat:Wait()
	until total >= t
end
> t = tick() realWait(1) print(tick() - t)
0.9992048740387
> t = tick() realWait(1) print(tick() - t)
1.0140511989594
> t = tick() realWait(1) print(tick() - t)
1.0150332450867

Note also that tick() is not actually guaranteed to be correct. I don’t know if it’s written anyway, but I’ve talked to engineers that caution against using it as a measure of time.

3 Likes

Hi Again, I Tried A Different Waiting Time.

local t = tick()
realWait(5)
print(tick() - t)

I Got

I Don’t Think It Is Due To

tick

Because I Have Used It In Other Places In My Code And It Works Reliably. I Now Waited Even More.

However At Least It Is More Accurate Than

wait

For Me.

Using The Delta Time Return Value Of Wait (For 5 Seconds) I Got

7.1054225999997

2.1 Extra Seconds.

2 Likes

Time this with a stopwatch, and not through Roblox. Let me know the results, this code is extremely basic and battle tested.

2 Likes

I did some similar tests and, I think it’s because heartbeat throttles when the game first starts, this should settle down after a few seconds.

Module Script

local RunService = game:GetService("RunService")
local Heartbeat = RunService.Heartbeat

return function(n)
    n = n or 0
    local Delta = 0
    repeat
        Delta = Delta + Heartbeat:Wait()
    until Delta >= n
    return Delta
end

Script

local wait = require(location to module script)

while true do
    local Tick = tick()
    wait(1)
    print(tick() - Tick)
end

Output

2.1537821292877
1.0095615386963
1.007693529129
1.0085482597351
1.0157716274261
1.0159933567047
6 Likes

So currently I have these 2 functions:

function RoundController:WaitForPlayers()
	while #Players:GetPlayers() < PLAYERS_REQUIRED do
		wait()
	end
end

function RoundController:StartIntermission()
	local beginTime = os.time()

	while (os.time() - beginTime) < INTERMISSION_LENGTH do
		wait()
	end
end

I’m assuming the proper way to fix them would be like this:

function RoundController:WaitForPlayers()
	local yield = Instance.new("BindableEvent")
	
	local x = Players.PlayerAdded:Connect(function()
		if #Players:GetPlayers() > PLAYERS_REQUIRED then
			yield:Fire()
		end
	end)
	
	yield.Event:Wait()
	x:Disconnect()
end

function RoundController:StartIntermission()
	local yield = Instance.new("BindableEvent")

	delay(INTERMISSION_LENGTH, function()
		yield:Fire()
	end)

	yield.Event:Wait()
end

please tell me if I did this the correct way, because I don’t want to make my code worse. thanks

1 Like

What you have looks fine, but change > PLAYERS_REQUIRED to >= PLAYERS_REQUIRED

3 Likes

Try this to see the accurate time of how much time has elapsed

local RunService = game:GetService("RunService")

local function RealWait(n)
	local DeltaTime = 0
	repeat
		DeltaTime = DeltaTime + RunService.HeartBeat:Wait()
	until not n or DeltaTime > n
	return DeltaTime -- return the time elapsed
end

print(wait(1))
print(RealWait(1))

It’s more accurate then tick()

2 Likes

How big of a concern is this? There’s several places in my code where I use tick() to measure time differences, for instance the custom Wait() function that I currently use:

local function Wait(n)
    local now = tick()
    n = n or 0
    repeat game:GetService("RunService").Heartbeat:Wait()
    until tick()-now >= n
    return tick()-now
end

Is this a really bad practice? Should I start using the return of Heartbeat/Stepped instead of tick(), or is the difference not noticeable enough to matter?

2 Likes

I’m not sure, I do it too, but I’m just relaying what I’ve heard from engineers.

2 Likes

Tick is always correct relative to tick calls made locally, and will work perfectly for testing wait times in this thread. What you probably heard is that tick is not necessarily close to the actual standard unix timestamp, and can vary between the server and clients.

4 Likes

I have a question about this. Let’s say in a game, you give players a certain amount of money per second, or something to that effect. Would you use Heartbeat for this? Something like this:

local function checkLastTime(last, thresh)
	local currentTime = tick()
	if currentTime >= last + thresh then
		return true
	end
	return false
end

local lastPoll = 0
rs.Heartbeat:Connect(function(dt)
    if not checkLastTime(lastPoll, 1) then return end
    lastPoll = tick()
    givePlayersMoney()
end)

As opposed to:

while true do
    givePlayersMoney()
    wait(1)
end

Obviously both options work, but is there any impact in doing it one way vs the other? I don’t like the idea of permanent loops but I also don’t like the extra and isolated Heartbeat connection. But I’m probably just overthinking it. I don’t care about insane precision, but it should be about every second.

1 Like

You can (and arguably should) store a reference to the connection, then disconnect it whenever you are done.

local givePlayerMoneyConnection = rs.Heartbeat:Connect(function(dt)
    if not checkLastTime(lastPoll, 1) then return end
    lastPoll = tick()
    givePlayersMoney()
end)

and later disconnect the event connection:

givePlayerMoneyConnection:Disconnect()

OTOH, if the while loop works for your needs and you’re more comfortable with it then feel free to stick with that until it doesn’t work or you are ready to switch to event connections.

1 Like

I’m aware. But, it won’t ever be done. In that scenario, players should continue receiving money every second forever, so the connection would never need to be disconnected.

1 Like