Skip to main content

Roblox Physics: Building a Better Sleep System

July 30, 2020

by kleptonaut

Product & Tech

Let’s talk about “sleep” in a physics engine.

The purpose of a sleep system is to stop doing work on bodies that aren’t moving, and start doing work when they will be. The challenge is picking the best heuristic for transitioning between these states. A common approach in other engines is to monitor a body’s velocity, and when velocity is below a given threshold for several frames, the body is put to sleep. It can wake up again by colliding with an awake body, or maybe a handful of other events. Until recently Roblox used a variation of this method as well.

Some known problems with this method are physical interactions where a body may have a near zero velocity for a few frames, but has not ended its current trajectory. We can think of a simple case where a ball is rolling up an inclined plane via its own momentum. Eventually the ball runs out of kinetic energy and reaches the top of its trajectory (having converted it all to potential energy by pushing it up the slope), and rolls back down the slope. However if slow enough, the ball will just fall asleep on the slope at the apex of its trajectory before it can roll down again.

If a developer runs across this sort of edge case in their game, the engine often provides a workaround. They might be able to adjust the thresholds for when something should sleep, sacrificing performance across all interactions. Or maybe they can disable sleep on a specific object all together. Maybe they only want sleep disabled on an object for a certain period of time. Either way it’s up to the developer to discover and work around any sleep edge cases they run across. At Roblox, we wanted to find a way to prevent these bad cases all together, reducing developer workload. The goal is for a sleep system that the developer shouldn’t need to worry about.

In the Roblox engine, rigid bodies are called “assemblies”. It’s a collection of one or more rigidly connected primitive bodies, or “parts”, but this is the object that’s either awake or sleeping.

Assemblies can be connected to other assemblies via simulated joints such as hinges or ropes, making up what’s called a “mechanism”. This is important because assemblies can infer their sleep state based on the state of other assemblies in their mechanism, which we will visit later.

Like other engines, Roblox had been using a traditional heuristic for determining when bodies fall asleep. Each assembly keeps a “history” of its past positions and orientations, and as long as the deviation from the average position stays within the threshold, the assembly stays awake. Essentially if the velocity is below a set value for a few frames, the assembly sleeps. I’ll refer to this value as the velocity threshold.

Awake assemblies are outlined in red. Sleeping assemblies are outlined in black. Static assemblies have no outline.

The example above illustrates the ball getting stuck on a ramp like described earlier. Here, a spinning part pushes the cylinder up a hill, keeping the cylinder motionless for some time. The cylinder’s velocity goes below the velocity threshold for a long enough time to fall asleep. This could be illustrated with a variety of examples, such as a ball bouncing in low gravity, falling asleep in mid-air, or a tall block tipping into place, falling asleep before flush with the ground.

Our goal was to see if there was better information than an assembly’s change in position over the last few frames for determining if it should sleep. Why not follow one of the most basic laws of motion? “An object either remains at rest or continues to move at a constant velocity, unless acted upon by a force.” We know gravity is acting on the cylinder, and it should keep rolling. We want the object to be awake when moving at a constant velocity, or has an unbalanced force acting on it. If either of these cases are true, the body should stay awake. We can grab the last impulse values on an assembly from the physics solver, and check those against a separate impulse threshold.

When visiting the same example again, we see the cylinder correctly roll down the slope. Now, it’s impossible for an assembly to sleep if being acted upon by an unbalanced force. In order for an assembly to fall asleep, it needs to be below the velocity threshold as well as the impulse threshold.

By trying to use this same logic for causing assemblies to wake up, we encounter a small problem. We can’t check the last impulse value of a sleeping assembly, since the whole point of something being asleep is that it’s not in the solver. For that, we still need to rely on contacts and property change events. However, the mechanism relationship can help in a variety of cases. In fact, an assembly’s history is also used to help determine when other assemblies in the same mechanism should wake up.

When a mechanism has an awake assembly, then all immediate neighbors of that assembly are put in a “checking” state (if they aren’t also awake). Assemblies in the “checking” state are still asleep, but check the awake assembly’s history every frame, and if its deviation is above another larger threshold, we just decide that the “checking” assembly should be awake too. This is referred to as the neighbor velocity threshold.

We assume that if one of these assemblies is under a lot of motion, the other assemblies should be awake and moving too. This isn’t always true, so it ends up being a bit more aggressive than necessary. For example, let’s say you have a windmill made of two assemblies: the spinning blades and the base. Ideally, the spinning blades should be awake while the base stays asleep since it’s not moving. However as long as the spinning blades move at a rate above the neighbor velocity threshold, the base will incorrectly stay awake.

This ideal behavior can cause problems with other kinds of mechanisms. You can have one assembly moving just fast enough to stay awake, but slow enough to keep its neighbors in the sleep checking state. If the awake assembly moves to a point that would require it’s sleeping neighbor to move as well, they both get stuck and end up asleep.

Here is a demonstration of that case. We have two blocks connected by a rope in zero gravity. The large block is awake and moving, while the stationary part is “sleeping checking”. That’s correct, until the large block moves far enough to make the rope become taught.

“Sleeping checking” assemblies are outlined in orange.

There’s no way for the sleeping checking assembly to know the rope became taught, as far as it’s concerned, it’s neighbor is still moving slow enough to safely stay asleep. The moving block tugs on the rope but can’t move anymore, so it stops, and goes to sleep.

As you might already expect, a new neighbor impulse threshold could be used to wake sleeping neighbors too. In this rope case, we know that the block undergoes a large change in net force as soon as the rope becomes taught. We use that change in force as an event to wake the “checking” assembly. In the example, we see that once the rope becomes taught, the sleeping part wakes up. All without running additional calculations on the sleeping part itself.

Not only can this be used to wake sleeping assemblies, but it can also keep them asleep longer. We no longer need to assume that an assembly needs to wake just because its awake neighbor is moving pretty fast.

In the end, with just a couple small changes we greatly improve the engine’s sleep system. Rather than using carefully tuned velocity thresholds, Newton’s First Law is directly applied. We now look at forces instead, putting bodies to sleep when they truly are at rest. Tons of edge cases are now handled correctly by the engine, preventing the need for developers to find and fix them independently. Sometimes approaching physics issues by going back to physics fundamentals can work out in the end.

Neither Roblox Corporation nor this blog endorses or supports any company or service. Also, no guarantees or promises are made regarding the accuracy, reliability or completeness of the information contained in this blog.

This blog post was originally published on the Roblox Tech Blog.