Avoiding wait() and why

Funny because I raised an issue regarding the use of wait in a Scripting Support thread earlier - mostly the fact that I didn’t want to use it at all. That thread pertained to wait(n) though, not wait without n. I guess this makes up half the answer of what I seek. Convenient timing - hooray.

16 Likes

I almost thought you were going to incentivise not using the actual function entirely at all or even reinventing the wheel.

Until I read the body. But yes I agree people are abusing stuff like this. Especially the one where you poll instead of events. Now polling would be reinventing the wheel.

edit march 2020

hell yeah go ahead and reinvent the wheel here's a better alternative
-- A module script preferably in ReplicatedStorage
local RunService = game:GetService("RunService")

local function wait(n)
    local dt = 0

    while dt < n do
        dt = dt + RunService.Heartbeat:Wait()
    end
    return false, dt
end

return wait
49 Likes

So what I’m getting is that one should avoid loops when possible. I am a large fan of this idea and have to force it on my friend’s scripting.

16 Likes

No, avoid unnecessary loops that an event replicates the behavior of, and never use wait().

22 Likes

While I do agree with your thoughts on wait(), scripts firing BindableEvents to themselves–especially after tightly coupling to the initiating code with an anonymous callback function–well, if the odd, poorly-justified wait() is “code smell”, then such a BindableEvent construct surely is too. BindableEvents are something you generally only want to use for code you don’t want tightly coupled via callbacks or dependency injections, for example to provide communication from closed-source component to software using it, like if you had a slider UI module and you wanted it to generate Changed events that any number of independently-developed bits of code could listen to without the slider having to maintain and iterate over a list of registered callbacks or provide a Lua API interface. BindableEvents in general have a much higher code smell likelihood IMO than wait() statements, since developers use them in some very bad-practice ways, to communicate data between components within their own game that are too difficult to connect properly due to bad code structuring, similar to how global variables are often abused.

30 Likes

*Bookmarked* Great stuff. Thanks for sharing.
Wondering if someone might provide clarification on one point: In an older post about Heartbeat changing to variable freq, zeuxcg says in post 5 that, “…at this point the only way to run logic at fixed frequency is to do a wait() loop.” Do we know if that is still the case?

My understanding is that :Wait() will fire as quickly as it can (in the same variable freq way as the event it’s attached to) while wait() would allow for a loop to be clamped at a slower speed (~ 30 milliseconds, I think). If a fixed/throttled frequency is desired for, let’s say, simplifying your Ai, and wait() turns out to be the right speed, would that be considered a valid production use for it, or is there another way to deal with that? Maybe this is more of a wait(n) example. I’ve been trying to sort out the various use cases for all the clock and wait functions, but it hadn’t yet occurred to me that perhaps wait() had no place at all.

3 Likes

Threads yielded with wait(…) have a chance to resume every other frame, so if you use wait() or wait(0) or wait(n) where n <= 1/30, your thread will resume at most FPS/2 times, or 30Hz if you aren’t dropping frames. So to clarify, it is not a fixed amount of time, everything is tied to frame rate.

22 Likes

Thanks, that’s consistent with what I’ve read. Maybe I’m getting ahead of myself. With other systems I’ve toyed around with, it might have been desirable to clamp an update at 30 ms (which it sounds like wait() would do) for certain things and use a delta in update functions to account for times when even that slower rate wasn’t sufficient. Maybe none of that is relevant to Roblox (I haven’t got around to game Ai yet, and I have no plans to script any physics). So no wait ever? Interesting.

3 Likes

So if I don’t care about micro organization, wait() can still be replaced with Stepped or Heartbeat to be more accurate?

4 Likes

Great post. This pretty much sums it up for me. Event-based programming is far superior when available, and will keep your code running like a finely oiled machine.


If you ever need a continuously-running loop, or even have to do polling for whatever reason, Heartbeat is what I prefer. I use this for polling Gamepad inputs usually, since the event-based inputs for Gamepad are buggy.

21 Likes

So, I’m not finding the post, but I read that using Heartbeat:Wait() (or Stepped, etc) as a loop won’t yield in the way that a wait() loop would. Besides potentially running twice as fast as a wait() loop, are there any practical considerations to note in the event that a slowish script can’t keep up with the Heartbeat? Will it just ignore events that it isn’t ready to handle? [Nevermind, I found the post, and my recollection of it was wrong. A connection to Heartbeat works like you’d expect.]

Maybe I should just run some tests to answer my own goofy questions :grinning:
This thread has help my understanding of a number of things. Wish I could like it more than once. Thanks Kampfkarren and all.

1 Like

If your script can’t keep up with Heartbeat, you’ve got other issues to address. Regardless, I would recommend Connecting to the event, not using :Wait() in a loop:

heartbeat:Connect(function(dt) ... end)

Somewhere in some other thread (Edit: this thread), it was mentioned that this won’t actually spawn a bunch of new threads every heartbeat. It’s smart enough to reuse the same thread. And it’s guaranteed to run every frame.

9 Likes

This is generally what coroutines are used for (coroutine.running, coroutine.yield, coroutine.resume) but they’re a bit finnicky as I mentioned.

What would you recommend instead, it’s at the very least a better solution than wait().

3 Likes

Yes but you’ll still have architectural problems: please don’t just mass replace wait() with Heartbeat:Wait(), it misses the entire point of the post.

3 Likes

The most obvious solution is to have the callback function simply execute the code you need to execute when your flag would have become true (or call another function that does it). Your example is not concrete enough to make it clear why you thing a flag and polling mechanism is required in addition to registering a callback function.

3 Likes

For that small example, yes of course. However I’m referring to situations where that would not be possible (to where an example would be larger and possibly less easy to understand).

5 Likes

Good advice in general. Only instance where I poll is when waiting for my services to load and be exposed (happens only in one script)

2 Likes

What exactly does “waiting for my services to load” mean? Are you using another anti pattern like _G where you can’t yield until it’s added? If not, why can’t your services expose an event that they fire when they load?

2 Likes

I do use _G, though I admit it probably isn’t the best thing to use for my services.
Basically, all my services expose their methods via _G.Services., but I need to make sure _G.Services exists before I can reference it from another script; Hence, I wait() on it.

It’d probably be better to swap to a ModuleScript, but not really on my priority list to do so atm.

Also, an event would require me to still wait for it (albeit I could theoretically swap to a BindableEvent and use roblox’s WaitForChild instead), and it just adds uneeded complexity for one script that polls for atmost 1 second upon player join.

4 Likes

Time to go back into my scripts and fix them all.

11 Likes