Decrease the minimum time in wait() to 1/60th of a second

As of now, wait() will yield for whatever amount of time you input, but will not go any lower than 0.03, or about 1/30th of a second.

Should the need for shorter delays arise, I resort to RunService.Heartbeat:Wait() in Scripts (or RenderStepped:Wait() in LocalScripts). Unfortunately this is locked to 1/60th of a second (0.016 seconds) and cannot be changed due to vertical sync being forced on.

If wait()'s minimum value were changed to 0.016, I think that would prove to have some benefits. A very notable use would be where you rely on a fast loop (Specifically where running the loop with no delay would cause freezes/crashes), but don’t want it to run at 1/60th of a second because it may lag at that rate, and don’t want it to run at 1/30th of a second because that may be too slow.

A bit of a note/edit: I don’t think this would work entirely, I’m not sure. If vertical sync is on then wouldn’t it be impossible to wait at an increment less than 1/60th of a second?

18 Likes

The “minimum wait time” is due to wait() resumes being performed only once per two frames (i.e. that’s why it is ~1/30, every other frame). RenderStepped/Heartbeat/Stepped connections are run on every frame instead, this is why waits on those events resume in ~1/60s instead of ~1/30.

I don’t know if there is a reason why they can’t do wait() resumes on every frame instead, but even if they do that you still won’t be able to wait for times that aren’t multiples of the time per frame, since the resumes happen at frames and not between them. In other words, you won’t be able to do wait(1/40) and have it resume exactly 1/40 second later in a 60FPS setting, because that time would fall between frames. It would instead resume the next frame after that 1/40 second wait.


Can you explain what you are doing exactly where updating at 30 updates/second is too slow and 60 updates/second is too fast? Maybe you can vary the work done per update instead, rather than varying the update rate?

wait() currentlly runs before every other stepped signal (roughly), but has a warm-up time where it doesn’t resume in the first 0.5 seconds of the game.
considering how widely people currently abuse use it, changing that behavior would probably break stuff.

Not if you just yield until the next resumable point in the same frame

while true do
	DoStuff()
	RenderStepped:Wait()
	DoStuff()
	Stepped:Wait()
	DoStuff()
	Heartbeat:Wait()
end

Use RunService.Heartbeat:Wait() and then add in a tick() check if you wish to run your code at a specific time interval.
If it “lags too much”, it’s caused by what you’re doing inside the loop, not the loop going “too fast”. If it’s lagging too much, even if you do that thing every 2 seconds, you’ll get a lag spike still.

1 Like

Only in some situations. An example of a situation where it would be too laggy if repeated quickly is creating a 3D table for terrain editing:

local t = {}
for x = 1, 32 do
t[x] = {}
for y = 1, 32 do
t[x][y] = {}
end
end

If I did that with no waits, then I would get an insane amount of lag, where if I did wait it would run a bit more smoothly at the cost of taking longer to form.

In your code example here, you could decide to update say 100 entries per frame, or higher/lower depending on the performance of the client. Then you can just run it on every frame and set that number as required. The update rate would be the same, but the amount of work per update would be variable.

Again, RunService.Heartbeat:wait() does exactly what you’re asking. I don’t think we can change the behavior of wait() at this point.

1 Like

I don’t know if this will be of any help for your use case, but for the terrain generation experiments I’ve been doing I’ve been using a mechanism of adaptive rate governing. Basically, I pick a reasonable time budget that is some fraction of a 1/60-second frame, and I run the core loop I’m going to iterate some very conservative number of times (just once in the example below), and measure how long it takes. From this I get a rate in terms of iterations per second. I then adjust the number of iterations towards how many the measured rate indicates I can do in the budgeted time. If the operation you’re iterating runs in a fairly consistent amount of time per iteration, this works well and will quickly converge on a good number of iterations for the time budget.

local iterations = 1
local timeBudget = 0.01
local prevValue = 0

local function OnHeartbeat(delta)
	
	local t0 = tick()
	for i = 1,iterations do

		-- Code you want to rate limit goes here, with math.sin() being a trivial example under test		
		local x = math.sin(tick())
	
	end
	local t = tick()-t0
	
	iterations = math.ceil(0.9 * iterations + 0.1 * iterations * timeBudget / t)
	
	if (iterations ~= prevValue) then
		print(t,",",iterations)
	end
	prevValue = iterations
end

game:GetService("RunService").Heartbeat:Connect(OnHeartbeat)

Notice how I only let it correct by 10% each time. This running average mechanism makes it tolerant to transients and variance in the calculation times. If the correction step was just

iterations = math.ceil(iterations * timeBudget / t)

it would be unstable and oscillate, some damping is needed.

Also, this is probably obvious, but the function/code block you govern needs to take considerably less time to run than your time budget, otherwise you have to break it down into smaller operations for this approach make sense.

4 Likes

In addition to what @AllYourBlox posted, I made a task scheduler which may help. You can throttle repeated operations to run at any given frame speed, so if this is in Studio and you’re okay with walking away from your computer you might throttle it to 10 frames/s, whereas an in-game implementation would throttle to the 60 frame/s (or the normal update rate if it’s slower than that)

Is there a way to use wait() frame independent. I have a problem in my game where when the fps is low the custom movement gets slows as well since it relies on render step. I want it to run fast regardless of frame rate :frowning:

To fix that I believe you have to speed up the movement based on the time between frames. Say you’re moving 1 stud per second and the last frame was 1/10th of a second ago (10fps). Move 1/10th of a stud in that frame. If it’s 30fps, move 1/30th of a stud, etc.

So basically increase the movement increment depending on the frame rate?

Yes, on a frame by frame basis.

1 Like

Well that was easy
speed=(60/(fps/desired))

1 Like

If you’re doing updates on Heartbeat or RenderStepped, by connecting to the RunService events (not BindToRenderStep), the function you register as the event listener is passed a time delta, which is the time elapsed since the last event, in seconds. It’s common to use this for framerate-independent animation updates. For example, if you want a block to move at 4 studs per second, on each heartbeat or RenderStepped event (whichever is appropriate) you would update the position by (4 * delta) studs. Typically this involves multiplying the time delta by a scalar speed and unit direction vector, or by a vector that represents velocity all-inclusive of speed and direction. You can also do this manually in a loop with a wait() by managing the time since the last update yourself, using tick().

In some cases, particularly with very long duration animations, or animations where multiple things have to stay in sync or arrive at the same place at the same time, even this is not good enough, since you can accumulate a tiny amount of error from rounding per frame. In this case, what is done instead is using a formula to directly compute position from an initial position, total elapsed time, and a position function that is a function of time. For something moving in a straight line at constant speed, it would look like p(t) = p0 + v*t, where p0 is the initial position, i.e. p(t0), v is the constant velocity and t is the total elapsed time since the thing was at position p0. Total elapsed time t is always evaluated as tick() - t0, not by accumulating deltas. This is how accumulated error is avoided.

2 Likes