Deciding where to drop frames algorithmically

Hey all, this is my first post on the dev forums. Nice to meet you.

This is more of a conceptual problem than an actual scripting problem, so I’m going to refrain from giving code examples and get to what my actual problem is.

I am working on a game that handles all of its logic through scripts on a frame-by-frame basis. It is absolutely essential that this logic is run through close to 60 times a second to keep the intended speed of the game, so I have it programmed it to only handle rendering updates on certain frames (while the logic is performed every frame). This helps free up the workload and maintain a near-consistent frame rate even on lower machines.

The way I do this is by performing a check every RenderStepped. If the frame rate is below 55, I will lower the amount of render updates by 1. This will lead to the game lowering the rate of render updates until the game reaches a stable frame rate.

Currently, the way I decide how to drop render updates is by taking 60 and diving it by the number of missing render frames. Let’s call this variable “firstSplit”. Then, if frame % math.ceil(firstSplit) == 0, rendering will not be performed on that frame.
This works well because it evenly distributes where dropped render updates will be placed, keeping the game as smooth as possible.

Here are some images to visualize this technique:
Gray bars are frames where both logic and rendering are performed.
Red bars are frames where only logic is performed (frames with no render updates)

60 renders a second (perfect performance)

59 renders a second

58 renders a second

57 renders a second

56 renders a second

And here is 30 renders per second.

The problem with going below 30 is my method doesn’t work below that. Does anybody know the math to perform this same type of operation below 30? Let me know in the replies, or if you have any alternative approaches to handling this type of problem, I’d be game to hear that as well.

1 Like

This might help you:

local frames = 60
local dropped = 37
local rendered = frames-dropped
local steprate = rendered/frames

local result = ""

for i = 1, frames do
	if math.floor(i*steprate) == math.floor((i+1)*steprate) then
		result = result.."x"
		result = result.."|"


Here x represents a dropped frame.

dropped = 5: |||||||||||x|||||||||||x|||||||||||x|||||||||||x|||||||||||x
dropped = 27: |x|x|x|x||x|x|x|x||x|x|x|x|x||x|x|x|x||x|x|x|x|x||x|x|x|x||x
dropped = 47: xxx|xxxx|xxx|xxxx|xxxx|xxx|xxxx|xxx|xxxx|xxxx|xxx|xxxx|xxx|x

What I do is I have a step rate - which is the number of frames you render divided by the total number of frames you have. By calling math.floor(i*steprate) you can find which frame number has to be drawn at interval i. If that number is the same as the number you get for the next interval (i+1) then it means you don’t have to update for the next frame so you can ‘drop’ your current one. This should work for any number between 1 and 60, so if you find any issues let me know.

Also, hi! Welcome to the forum.


As an alternative to spiking computation and trying to determine the best places to perform these spikes, I’d look at smoothing out the computations. Can the updates you are doing overflow into the next frame if they take too long? Pausing updates a little sooner rather than stopping two whole frames to perform logic would be much less noticeable. Out of curiosity, what is requiring so much performance? Generally performance heavy calculations should be added to the engine so compiled C++ can handle it. If it should be done in Lua, it could help make a case for LuaJIT. ^.^

1 Like

Thanks! This works perfectly.

Can the updates you are doing overflow into the next frame if they take too long?

They cannot, because every frame is dependent on the updates that occurred in the frame before it. I actually have a control in place just to ensure all the code from one step has completed running before allowing a new step to take place.

what is requiring so much performance?

All collisions and position data is handled in the game logic. What’s conveyed in the actual workspace is really just a visual representation of what’s being handled in my own code.

Generally performance heavy calculations should be added to the engine so compiled C++ can handle it.

Could you send me some reading/videos on this? I wasn’t aware Roblox let you do this type of thing.

Here is some footage of the game in motion (note: this video was uploaded by another user so it may be taken down at any time)

That is no problem, you’d still be running one update after another in a tight loop, but it would just pause where it was in an update and pick it up the next frame. This can be easily done using coroutines.

Right, player developers cannot. However, if you look at the API calls that they have now (GetPartsInRegion3, PathfinderService, math.noise, and so on) many of them were added to perform computationally intensive tasks. Do you think that collisions and position updates are causing the lag? Have you looked into the TweenService? For the collisions, 20 AABB tests shouldn’t cause lag, even on handheld devices. IDK about everyone else here, but I’m interested in hearing what slows down current games so we can all make even better games.

Ah I see what you’re saying. I should be throwing the render updates in a coroutine and just do them when it’s computationally convenient.
I think with a little restructuring this would be feasible, but my current implementation seems to be working a-ok right now so I’m going to stick with it until I run into problems.

For the collisions, 20 AABB tests shouldn’t cause lag, even on handheld devices.

I think the lag is coming from an excessive amount of work calculating when to generate these red arrows that telegraph when an enemy is about to spawn.

The level with the slowdown issues (it’s not the one seen in this video) has significantly more work it needs to do to calculate these arrows than any previous levels I’ve worked on. The code needs to traverse through a table to calculate the total amount of time until the ship spawns in each spawning position. It’s an expensive operation in the level because there’s a lot more table entries it needs to traverse and work with. It’s more a matter of me needing to optimize the code than the engine needing an update for any inherently expensive operations.