QuickWait - A faster & more accurate wait that supports smaller wait times

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.

Comparision

Minimum possible wait times (rounded)

quickWait: 0.00001 seconds (and lower)
task.wait: 0.003 seconds
wait: 0.03 seconds

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

Results:

quickWait avg: 0.00011618340008499218
task.wait avg: 0.0047561958000003415
wait avg: 0.03249875679999786
quickWait wins!

The script/module

--!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.

Enjoy!

21 Likes

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.

1 Like

Not really, imagine a infinite loop that needs to run fast. This wont deliver any bad results

2 Likes

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.

2 Likes

Imagine a for loop that needs to go through a 2048x2048 grid for a drawing system and register colors.

for x = 1, 2048 do
 for y = 1, 2048 do
  -- code
 end
end

obviously this will crash it is making 4,194,304 iterations.

if we implement a task.wait per grid item it will now take 69,905 seconds because task.wait waits 1/60 of a second without exhausting.

If we implement @Malte0621’s QuickWait it goes down to 3,495 seconds, which is a huge increase still without exhausting.


Still pretty slow but switching it up can fix it to go down to about 5 seconds, still without exhausting

local i
for x = 1, 2048 do
 for y = 1, 2048 do
  -- code
  i += 1
  if (i % 100) == 0 then
   quickWait(0.003)
  end
 end
end
5 Likes

My bad, you’re totally right. The first time I read it I could’ve sworn it was just doing a while loop until time was reached.

3 Likes

Running this would take around 0.01 seconds on virtually any device (I assume you mean in conjunction to some arbitrarily complex logic).


QuickWait just iterates RenderStepped:Wait(), so you might as well use that instead.

The timing is also dependent on the framerate, so you are yeilding for a minimum of 0.003 seconds.

3 Likes

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.

2 Likes

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.

4 Likes

Why so much code also, here is a small function I whipped up and it can wait 0.00001 of a second.

function uwait(time) local start = os.clock() repeat   until os.clock()-start > time end
2 Likes

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.

7 Likes

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.

4 Likes

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 .-.

Updated the code drastically, removing any unnecessary stuff and improving its speed even more (edited it a few times today)

Welcome to another episode of how to crash your CPU with while do loops without a wait or task wait!

1 Like

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)

2 Likes

also, here’s a accuracy test I performed on higher wait times.

local quickWait = require(script:WaitForChild("quickWait"))

local toWait = 5.35

warn("quickWait(" .. toWait .. ") =", quickWait(toWait))
warn("task.wait(" .. toWait .. ") =", task.wait(toWait))
warn("wait(" .. toWait .. ") =", wait(toWait))
quickWait(5.35) = 5.350000000005821
task.wait(5.35) = 5.362722799999574
wait(5.35) = 5.365588700000444
2 Likes

People (like me) have written snippets like these and I kid you not, is ridiculous. You do not need these precise timings.

What would be more better is if you write the code that relies on perfect timing to adjust it internally.

2 Likes

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.

2 Likes

Code shouldn’t need to wait for such an exact time. That’s more or less some sort of design flaw. Is it possible to tell us what the use case is?

3 Likes