[robloxcritical] Heartbeat executes in between input callbacks and RenderStepped on mobile

(This is critical because one of our games relies on the clearly-defined ordering of tasks within a frame, and as a result key features of it become inaccessible to players who are affected by this bug.)

The mobile device I am testing on is a Nokia X20: Nokia X20 - Full phone specifications

The issue is that heartbeat callbacks are executing after input callbacks, but before rendering callbacks. This only on happens on mobile, it does not happen on desktop (I don’t own a console so cannot test there).

You can see this in the following screenshot of microprofiler dump from my mobile device:

More precisely, it looks like a second input processing stage was added to the frame pipeline (under the “Render” label), which executes before physics and heartbeat but after rendering. (The original input processing stage that is documented is under the “PreRender” label.) Touch inputs on mobile have been moved to this new, second input processing stage.

In summary, on mobile the task scheduler pipeline seems to have become something like (new) input → physics → heartbeat → (old, unused?) input → rendering.

Expected behavior

On all other platforms I have access to, including the mobile emulator in Studio, the ordering of events is always input → rendering → physics → heartbeat. The documentation also makes this clear: Task Scheduler | Roblox Creator Documentation


This is a new feature which is currently only enabled on iOS and Android. It improves input latency and frame rate. What is the problem that you’re seeing?

1 Like

Our game has code that looks like roughly this:

    InputThisFrame = true

   if InputThisFrame then
      -- Do something.

   InputThisFrame = false

This obviously breaks if the Heartbeat callback executes before the RenderStepped callbacks, because it would prevent any of the input handlers from ever seeing any input.

It’s not an unfixable situation - for example we could just prevent Heartbeat from affecting inputs that haven’t been “seen” by RenderStepped callbacks yet. But it still has broken our game until I implement that change to fix it. At least give us a warning beforehand (and ideally a way to distinguish between the two different points in the pipeline where inputs get processed).

P.S. The reason we do this pattern is because we have a lot of different things that affect how inputs should be handled, so just directly using ContextActionService would get pretty cumbersome and be difficult to comprehend. It’s a lot easier to just explicitly the control the ordering of when things happen and how they affect each other in a top-down way.


Thank you for reporting and sharing the details. The feature is temporarily disabled for your place. Please, make the appropriate changes to your place since we are going to reenable it for all places in 2 weeks.

If anybody else observes similar problems, please reach out, we’ll be able to make a temporary exception for your places as well.

1 Like

Will this execution order change always only affect mobile devices? If so, the inconsistency is a bit scary because as a developer I have no way to know about this or test this without explicitly testing on a mobile device. AFAIK Studio cannot simulate mobile differences, and regardless a difference in this would be rather unexpected to need to test for.

This pattern is something I feel is fairly common, as I have something similar in my game, but I was not affected by this change because I was polling the inputs on something other than renderstepped to save on performance.

1 Like

It will be gradually rolled out on all platforms. For now, it is only enabled on Android and iOS.

1 Like

Will the documentation also be changed in this process?

Yes, we will follow this up in the documentation.

However, I personally wouldn’t call relying on the stages order a perfect practice from the stability point of view.


I noticed an issue that broke my game only on mobile, and I found out that touch inputs (tested on IOS) fire after renderstepped

Here is code I used to test it out

local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

UserInputService.InputBegan:Connect(function(InputObject : InputObject, gameProcessedEvent) 
	RunService.RenderStepped:Once(function() print("RenderStepped") end) 
	RunService.Stepped:Once(function() print("Stepped") end)
	RunService.Heartbeat:Once(function() print("Heartbeat") end)

What I got on my pc

What I got on my IPhone 8

Here is the place I used to test it

Idk if it is related, but I hope this can help