Hello developers, I just though I’d post my quickWait function that can wait as small as 0.0001 seconds and lower. I created it for a project where I needed precise small waits. But I am not working on that project that much anymore so I thought I’d share it as a resource for all of you to enjoy.
I have also made somewhat of a “unit test” that tested the average wait times (calling all 3 functions to wait “0.0001” second):
--!native
--!optimize 2
local quickWait = require(script:WaitForChild("quickWait"))
local task_wait = task.wait
local tests = 1000
local waitFor = 0.0001
local t0 = 0
for i = 1,tests do
t0 = t0 + quickWait(waitFor)
end
print("quickWait avg:",t0/tests)
local t1 = 0
for i = 1,tests do
t1 = t1 + task_wait(waitFor)
end
print("task.wait avg:",t1/tests)
local t2 = 0
for i = 1,tests do
t2 = t2 + wait(waitFor)
end
print("wait avg:",t2/tests)
local winner = math.min(t0, t1, t2)
if winner == t0 then
warn("quickWait wins!")
elseif winner == t1 then
warn("task.wait wins!")
else
warn("wait wins!")
end
--!native
--!optimize 2
local RenderStepped = game:GetService("RunService").Heartbeat
local os_clock = os.clock
local t = os_clock()
local task_wait = task.wait
local MAX_TIMEOUT = 0.1 -- Adjust (lower) if you lag/hang the game (usually when called too often)
local function quickWait(waitTime)
if not waitTime or waitTime == 0 then
if os_clock() - t >= MAX_TIMEOUT then
t = os_clock()
return RenderStepped:Wait()
end
return 0
elseif waitTime < 0.001 then
local startTime = os_clock()
while true do
if os_clock() - startTime >= waitTime then
break
end
if os_clock() - t >= MAX_TIMEOUT then
t = os_clock()
RenderStepped:Wait()
end
end
return os_clock() - startTime
else
return task_wait(waitTime)
end
end
return quickWait
NOTE: I completely changed the code on Nov 26th 2024. It now WORKS in infinite loops and is still a lot faster in terms of average wait times.
I think its a neat idea, but I am curious to know what situations you would need this in? I can’t imagine you would need to use this precise of timings for most practical reasons.
Run the following code and let me know how fast your studio crashes. Running infinite loops fast is what you’re supposed to be avoiding.
local i = 1
while true do
i += 1
if i > 100000000000000 then
break
end
end
Iterating through massively large arrays (or small arrays with many operations) produces similar results. Its why a big part of optimization in games is limiting how many times you’re doing an operation on an array of objects per frame.
I mean with logic, and my point is that it will crash regardless of the device and delays will fix it.
No, not exactly it uses os.clock for smaller values, Because of Roblox’s 60 fps cap RenderStepped:Wait() is equal to task.wait() both only able to go 1/60th of a second at their lowest, while QuickWait is 1/300th of a second at its lowest.
It would be better to spread work across multiple frames rather than cramming as much work into a frame as possible. You have the right idea with the modulo 100 thing. However, it would be a better approach to do some amount of iterations until an amount of time passes, let’s say 2 ms, and then yield for a frame. I don’t see the benefit of this “quick wait” thing. Yielding for smaller than a frame is almost equivalent to executing some process multiple times in one frame.
EDIT: you can also implement parallel processing via actors if you want to squeeze as much speed as possible, but that’s a bit unrelated to the original topic.
Although you can “wait” for a very short duration of time with this module, you’re not actually waiting. You’re crashing the program for x amount of time before it resumes, giving it the illusion that it’s waiting. During that millisecond-long wait, this module would run an infinite loop which takes all the processing power thus yielding every other thread until the allotted time has passed.
I really do not understand how something like this is useful, and what use cases would require it. Even if there is a use case, it’s bad practice because it literally relies on the execution speed of the cpu to wait some amount of time, and prevents other threads from running, unlike task.wait() (like @VegetationBush pointed out).
Waiting within a frame (opposed to waiting for the next frame, or some other frame) doesn’t make sense (unless we are talking about the order of execution, and QuickWait will not change the order of execution). If you need to have a wait time smaller than a frame, you should instead see it as doing multiple calculations in a single frame (for a physics engine running at 240hz for example), and time is irrelevant in that case. If time is required for whatever reason, you can just “fake” the passage of time by giving a modified time value to whatever function requires it.
Not sure how I would have “faked” waiting when I was trying to play sounds after each other with precise timing between them. Either way the function bitsplicer mentioned is very neat and I’ve seen similar things before. Their approach would have worked but I didn’t think of it when I made the quickWait function. However, he’s function freezes if its ran for too long, mine does not. Mine does however freeze when its called to many times .-.
Doesn’t crash anymore lol, it runs smoothly even on 10 coroutines running 1000 unit tests. (edit: will crash if you use too low wait times in a while loop or too long for loop though)
I really did need it for what I was making. But I’m providing the code for those who might have the same need I had and not for everyone to use on all their projects as that is clearly not needed.