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.
So I found a place where I’m polling, and it isn’t immediately obvious to me which way to go with this. With the risk of embarrassing myself , here goes:
I’m using a state machine to manage “scenes” and transitions. This code is part of a module that holds state for a menu screen whose background is provided by a camera flying along paths through the world.
local maxpaths = 5 -- return after maxpaths paths
local pointsTable, duration = GetCamPath() -- load initial values
repeat
local waitstart = tick()
-- call fn that binds cam code to render step (non blocking)
RenderCam(pointsTable, duration)
-- do stuff while waiting
local waitduration = duration
pointsTable, duration = GetCamPath()
maxpaths = maxpaths - 1
-- stall until cam anim ends so next path doesn't stomp on current
-- break immediately if "flying" has changed to false
while wait() do
if tick() - waitstart >= waitduration or not _M.flying then
break
end
end
until (not _M.flying or maxpaths == 0)
Each camera path animation is handled by a function bound to the render step with RunService:BindToRenderStep. That binding step just sets things up and returns. The render step code isn’t blocking, so I added the wait loop to create a pause so that one path doesn’t immediately stomp on the previous one. There is a “flying” flag that is set by an event that is triggered when a button is pressed in the gui and it’s time to transition to another screen/scene. There is also a maxpaths counter that breaks the loop if the user isn’t making a selection, but I may not keep that.
Looking at the advice above, it looks like I want to have another event inside the bound RenderStep code to get rid of the “while wait()” loop. I’m getting confused about how to structure the repeat loop that cycles through each camera path if it’s tied to those events. Chaining things together without a loop seems overly complicated. Would I need to disconnect an event that’s set up inside the bound renderstep function or would that go away cleanly when I unbind the function? This bit of code may need to be reimagined from the ground up, but everything I’m coming up with seems much more complicated than what I have. Is there a case to be made for using wait() in order to avoid complexity?
I ran into two other places in my code where I could directly apply the advice above, and I feel my code is better for it. All this seems like something to aspire to and valuable info for amateurs, though some of the “never use wait” sentiment seems excessive for anyone but the top tier Roblox programmers… who probably already know this stuff.
Theoretically, yes–You should prefer coroutine.wrap or the fast spawn idiom (spawning a BindableEvent with a connection then firing it). There’s no guarantee your spawn will run, even. That being said, I use spawn anyway when timing doesn’t matter and I’ve never had issues with it.
I have no idea if this needs a seperate post but while I was messing around with while loops (for a new project) I experienced that a while wait(0.001) is not accurate (like really not). I tested it by comparing the miliseconds (0.001ms = 1s) * 1000 to a 1 second timer.
wait has a minimum of about 1/30th of a second, but inherently wait isn’t accurate.
If you need accuracy with wait you should probably use the deltaTime returned by wait.
It yielded for about 0.21 seconds even though I input 0.2.
For the best accuracy, you should probably use an accumulator and possibly run the code multiple times every frame.
local Rate = 0.001 -- 1000 times a second
local Accumulated = 0
local RunService = game:GetService"RunService"
RunService.Heartbeat:Connect(function(deltaTime)
Accumulated = Accumulated + deltaTime
while Accumulated >= Rate do
Accumulated = Accumulated - Rate
--block
end
end)
The only way to get almost perfectly accurate wait times that are higher than 0.03 is to wait for a bit less than you want and add a busy wait to get a precise delay. Though this is somewhat intensive so I would avoid at all costs unless it’s indispensable.
local precisewait = function(timetowait)
local starttime = tick()
if timetowait>=0.04 then
wait(timetowait-0.01)
end
while tick()-starttime<timetowait do end
end
I got results that were off by only 0.001 of a second max, but usually they are around 0.0001 or even less.
I advise against ever doing this. There probably won’t ever be an instance where you’ll need to yield down to less than a frame, so using Heartbeat and adding the delta time together is the preferred method for ‘accurately’ yielding. That’s accurate down to a single frame, so it’s probably as accurate as you need.
local Heartbeat = game:GetService("RunService").Heartbeat
local function accurateWait(n)
local elapsed = 0
while elapsed < n do
elapsed = elapsed+Heartbeat:Wait()
end
return elapsed
end
Javascript has promises and async methods. C# has Task<>. From what I know, C++ could utilise the same functionality using CLI.
Yes, we don’t have yet a native form for Lua (that is allowed in the sandbox at least), but using BindableEvents have been tested to be an efficient workaround.
Thank you so much for explaining this. wait() is generally snake oil when it comes to performance and such.
The worse thing is, wait(5) can make the script wait at least 10 seconds if the client is not performing well in terms of FPS. So that’s why I’d prefer anything but wait().
Though we’ve embarrasingly used wait() in our lives, it isn’t like that will be removed any later on. It’s a good start for newcomers, though. They’ll eventually learn to use :Wait().