This is an adaptation of a Twitter thread I made a while ago. This is simply a better formatted and more fleshed out version.
Let’s talk about
wait(n) (although if you are using small
n values to where it’d be barely distinguishable from
wait(), you’re probably going to hit the same issues mentioned in this thread).
We probably learned about
wait() at first as the solution to “why are my infinite loops crashing me?”. It’s a super easy solution and it works! This starts a bad habit of just putting
wait() to fix your code.
In my opinion, wait() has no place in production code.
Common Usages of wait()
Let’s take this piece of code:
local flag = false someAsyncThing(function() -- bla bla bla flag = true -- we're done! end) repeat wait() until flag -- flag is ready!
repeat wait() until is being used to stop code execution until flag is true. This is known as polling (colloquially, you’ll also hear it called busy waiting in circles for higher level software, although they’re not technically the same). Polling in our case is bad because we’re unnecessarily waiting for something to happen instead of just doing code once it happens.
Consider this scenario:
flagis set to false.
someAsyncThingcalls the callback immediately with no yielding.
flagis now true.
repeat wait() until flagis ran. Despite
flagbeing true, we’re still waiting for no reason.
- Code continues.
“Ah, but wait!”, you cry, “I can do this!”
local flag = false someAsyncThing(function() -- bla bla bla flag = true -- we're done! end) while not flag do wait() end -- flag is ready!
Yes, you can. However, you’re still unnecessarily waiting even after
flag is set to true.
Consider this scenario:
- The callback to
someAsyncThingisn’t ran right away.
flagtrue? No? Alright, give me a bit.”
flagis set to true! Can our code run yet?
- “No, sorry, still waiting.”
flagtrue? Yes, it is. Continuing code.”
Our code unnecessarily waited despite it being completely ready to run! The alternative is to simply hook your code up to an event.
If we assume
someAsyncThing will never call before the last line (Roblox Lua is single threaded so unless that’s a defined behavior, it won’t)…
local event = Instance.new("BindableEvent") -- You could use coroutines, but they're a bit finnicky someAsyncThing(function() -- bla bla bla event:Fire() end) event.Event:Wait()
This is the exact same number of lines of code as the
repeat wait() until block. Let’s analyze this code again with the last scenario.
- The callback to
someAsyncThingisn’t ran away, and it’s not documented that it can.
- “Tell me when you’re ready for me to run”
someAsyncThing's callback is called. “Hey, I’m ready for you to run now”
- We instantly get the message, and continue our code.
someAsyncThing can be called immediately in my case!”
In that case, you can do its admittedly uglier cousin:
local event, called = Instance.new("BindableEvent"), false someAsyncThing(function() -- bla bla bla called = true event:Fire() end) if not called then event.Event:Wait() end
Again, this will be ran instantly. No unnecessary polling. Plus, this is milliseconds/seconds saved. Not measly microseconds.
“That’s for my own code, but what if I want to wait for some Roblox code to run.”
You mean like this?
repeat wait() until LocalPlayer.Character
Still no. Roblox has events for everything you could possibly be waiting for (and if it doesn’t make a feature request!). You can yield on an event by calling
:Wait() (no relation).
This will yield the thread until the player’s character is added. And I know, I know, “what if the character already exists”? Feast your eyes on this beauty:
local character = player.Character or player.CharacterAdded:Wait()
Witnessing this level of elegance is dangerous to our feeble human brains. It gets the character and if it doesn’t exist, will wait until it’s added. It will be ran the instant the character is added.
Useful events to yield on in no particular order:
wait() because of a Roblox bug”
We’ve all been there. Sometimes there’s just some weird bug that
wait() seems to fix. Once you’re certain it’s a Roblox bug and there’s no event you can use, you still shouldn’t use
wait(). Have you considered:
These are at least 2x faster than
wait() and I’ve never had an issue where they didn’t work but
“Okay, but why should I care?”
wait() is code smell, meaning using it is the sign of a design problem. Polling is bad when event based programming is staring right at you. Not only is it a design problem, but it has its own issues. A major issue is that
wait() (and also
wait(n)) can wait for MUCH longer than you want it to. The more you call
wait(), the longer it will take.
wait() can easily last seconds, leading to a very laggy game experience. Take your pick on which of those is more important to you.
In summary: you don’t need
:Wait() works basically all of the time, and
Stepped:Wait() fill in the gaps that aren’t filled.