Accessing the 240hz Physics Loop

RunService.Stepped runs at the frame rate, same as RenderStepped. The frame rate itself is capped at 60 Hz. If your frame rate drops below 60 Hz, so does Stepped.

After every Stepped we then do multiple physics engine steps. Typically 4 world steps per frame. Sometimes more, sometimes less.

Currently there is some DataModel state that we do not update between those 240 Hz steps that we do update before Lua executes again. Allowing Lua to execute between those 240 steps means doing more work to make sure the DataModel is synchronized before Lua executes more often. There’s a lot of serious consequences to supporting this.

We sometimes talk about adding some kind of physics step callback but we’re being very cautious about this. We know it’s basic functionality in other engines… Here it’s extremely performance sensitive and Lua is often a major bottleneck to achieving 60 Hz on many devices. Once we add something like this we’re stuck supporting it forever. We know it’s useful for some kinds of controllers, but we’re taking our time considering the best way to solve these use cases.

Also comparing the update rates of the old and new solver is a bit of an apples to oranges comparison. Very different algorithms. Even for each of those 240 steps there’s multiple internal solver steps.

18 Likes

I am VERY sure that game:GetService(“RunService”).Stepped:wait() used to run at 240hz if you put it in a while loop. I have tested it myself a couple of months ago because I found this thread:

That example is set private or has been deleted.
Do you have access and if so could you save it and put a link up for us?

As a matter of fact, you have said it yourself, that this piece of code runs at 240hz

while true do
   local totalT,t = game:GetService('RunService').Stepped:wait();
   print(1/t);
end

Here you have said (from my understanding) that Stepped:wait() runs on the Physics step

And on the post right after it, that the Physics step runs at 240hz.

Thus the code written above should output 240.
And it actually has 100% for a while now outputted 240 until the day I wrote this thread, when I initially noticed that it was changed.

1 Like

No. The render loop runs at 60 hz maximum. Physics runs at 240 hz maximum. RunService.Stepped has always been 60 hz maximum.

2 Likes

You’re ignoring what he said the sentence after that.

I think it’s really possible that you’ve misremembered. Stepped has pretty much run at 60hz for as far as I can remember. Two staff which work on the engine + a bunch of other people in the thread have vouched for this.

4 Likes

Please do not take my poor wording (physics step vs internal physics step was a bad choice to elaborate on the topic on my end) for how the underlying code works. Just to give you the benefit of the doubt I launched an older version of studio to verify that the code you posted in fact could never had run at 240Hz.

There is no mechanism in the engine to allow RunService::Stepped to trigger more than the rendering frame rate.

The only way

while true do
   local totalT,t = game:GetService('RunService').Stepped:wait();
   print(1/t);
end

could ever return 240 is if you were playing with a framerate unlocker of some sort, which is very much not supported.

6 Likes

Ok, sorry for using your wording in this context.
Is there any way I could access the older launchers?

How about doing this?

RunService.Stepped:Connect(function()
    for i = 1, 4 do
        -- do stuff
    end
end)

It would simulate 240hz – in a choppy way – but you’d still be getting 240hz mathematically. (Something near it at least, if you consider the code inside the for loop to take time t = 0)

1 Like

That’s what I use but I was wondering if there was any other way to access the 240hz physics step

It is impossible right now.

1 Like

One thing you can do is look at the delta time step value passed as the second parameter to the Stepped callback. That tells you how many steps we’re about to do that frame. Due to various numerical reasons or extreme throttling cases it’s not always 4.

RunService.Stepped:Connect(function(time, step)
    local stepCount = math.round(step / (1/240))
    -- ...
end)

Worth noting this is only accurate for unthrottled bodies. When throttling is in effect some bodies are stepped less frequently, while high priority bodies continue to simulate as fast as possible (the rate given by above code). We don’t currently expose which bodies are in the high priority set directly, but really it’s just Humanoids right now.

7 Likes

I know this is old but I don’t really care.

It really doesn’t justify to resort to hacky tactics like this (which doesn’t actually do 100% of what you claim) just to have a fake glimpse of 240hz step when other engines give you direct or at least a function to sync with the true physics step which makes physics based character controllers far more reliable and efficient. What I’m baffled is that this has been the status quo for years and I’m surprised nobody had commented about this.

There is no excuse for Roblox to not offer a system like this especially when new features such as Shape-Casting giving developers more ways to accurately detect parts and Adaptive Stepping (making it merely possible at the cost of no way of the developer to manually change the HZ per part) yet despite that there’s absolutely no way to make a character controller that has proper and accurate floor/wall detection regardless of whatever Raycast Approach you take because Heartbeat sucks and is almost interchangeable with Renderstep (Hell, Renderstep is better in more physics related cases, pretty ironic.)

After scrolling through dozens of posts from other engineers loosely tying up what was now been completely obsolete 2018 replies of the time, I have come back to ask. Will you guys ever re-consider giving developers the ability to synchronize with the true physics step with all of the new physics related stuff being released?

11 Likes

I’d like to vouch on this as well; in our game, A revamp of our fundamental systems to work on RunService loops makes things like player humanoids impossible to utilize due to the physics step as a whole, and it’s gotten to the point where we’ve considered completely rebuilding Roblox’s physics from the ground up in a separate system, just so that we can hook it up to our engine without any instability.

The fact that we LITERALLY HAVE TO make our own physics system to alleviate this is completely absurd. Roblox has well advanced in improvements and changes over recent years, and the fact that the physics stepping is still being held back like this is baffling to the development side of the platform.

1 Like

If you want some code to run faster than the physics loop I think you just gotta pull off something like:

while script.Parent ~= nil do
 task.wait(1/240)
 -- Code
end

Though personally, if I needed fast moving projectiles for a game I’ll probs just script my own raycast-based physics engine for it.

Raycasts don’t go through walls as far as I know as long as the raycast doesn’t take place inside a wall so theoretically if you implemented bouncing ball physics that entirely rely on raycast you could have it move at “infinite” speed.

1 Like

Im pretty sure you’re able to change the update frequency by changing the Workspace.PhysicsSteppingMethod to Fixed. It defaults to an adaptive timestepping but if you want super accurate physics calculations all the time, you can set it in the workspace property in studio.

1 Like

I know I am late, but if it is lower than 1/60 it will run based on client framerate

That used to be the case with wait() I believe but task.wait() was reported (on release) that it will wait EXACTLY the amount of time you specify it to wait.

The reason I don’t think it runs at the client’s framerate is because the Lua VM can easily execute many millions of instructions per second.
So telling it to wait 1/1000th of a second is practically just “skipping” a bunch of instructions so to speak.

I don’t think the Lua VM is “synced” to the framerate, otherwise a single instruction (like adding 1 + 1) would take a single game frame to complete.

Might test it in studio soon but you can try this code.

local start_time = tick()

task.wait( 1 / 1000 ) -- 1 / 1000th of a second.

local estimated_time = tick() - start_time

print("This code took " .. estimated_time .. " to complete.")

task.wait() is tied to the task scheduler. It will resume after a variable amount of frames, depending on the wait time, until the desired wait time has elapsed. An empty task.wait() or a task.wait() with a very small wait time will wait for a minimum of 1 frame, which is 1/60 of a second, if the fps is not unlocked and the player isn’t lagging

Well, you can try your code and see it for yourself :wink:

(Though, you should put a task.wait() at the start of the script to make sure the code start and ends at the same point in the frame, making the results a lot more consistent, especially if you do it from the command bar)

1 Like

Fellas, we might just make it out alive

5 Likes