Prediction Culling
Introduction
Minimum SDK Version: 1.2.3
Prediction Culling allows developers to save CPU time in games where the player has only a partial view of the game scene.
Quantum prediction and rollbacks, which are time consuming, will only run for important entities that are visible to the local player(s). Leaving anything outside that area to be simulated only once per tick with no rollbacks as soon as the inputs are confirmed from server.
It is safe and simple to activate and, depending on the game, the performance difference can be quite large. Imagine a 30Hz game to constantly rollback ten ticks for every confirmed input (with more players, the predictor eventually misses at least for one of them). This requires the game simulation to be lightweight to be able to run at almost 300Hz (because of the rollbacks). With Prediction Culling enabled the full frames will be simulated at the expected 30Hz all the time while the much smaller prediction area is the only one running within the prediction buffer.
Setting Up Prediction Culling
To enable Prediction Culling enable these settings:


Prediction Culling means the predicted simulation can not be accepted as the final result of a frame because it never advanced the simulation of the full game state. With Expose Verified Status In Simulation
, we guarantee that Quantum will force the re-simulation of verified frames (with that setting exposed) when input confirmations arrives from the server.
Update the prediction area by calling SetPredictionArea()
on every Unity update:
C#
// center is either FPVector2 of FPVector3, radius is an FP.
QuantumRunner.Default.Game.SetPredictionArea(center, radius);
That's it.
What To Expect
Physics And Navmesh Agents
Both physics engines and the NavMesh Agent system are affected by Prediction Culling when enabled. They will only consider and update entities within the visible area on non-verified (prediction) frames.
CPU cycles are saved because no physics broad-phase, narrow-phase collision checks or solver; and no collision avoidance or agent updates are performed for entities with the corresponding Components (DynamicBody, NavMeshAgent) that are outside the area of interest defined by the Prediction Area center point + radius on the local machine.
Iterators
Your own code will also benefit from Prediction Culling as any call to entity iterators on types that include either Transform2D
or Transform3D
will be automatically subject to culling based on their positions.
Essentially whenever a prediction frame is running, a call to GetAllXXX()
will only return entities that are within the limits or the prediction radius, while the same call will return all active instances when simulating the verified frames (after input confirmations arrive).
Manual Culling Control Flags
It is also possible to manually control entity culling (on predicted frames only of course) by using the newly added culling flags (to EntityFlags):
C#
public enum EntityFlags : int {
Active = 1 << 0,
DestroyPending = 1 << 1,
Culled = 1 << 2,
NotCullable = 1 << 3
}
Set the NonCullable
flags on entities at any time to disable the automatic culling:
C#
myEntity->Flags |= EntityFlags.NotCullable;
Manually set an instance to be Culled
(from a single system running before any others that's aware of any transient condition you might want to use for that). Make sure to de-flag them on verified frames from the same system, so you keep a consistent state and do not desync.
C#
// setting entity to be culled by systems after this one:
myEntity->Flags |= EntityFlags.Culled;
// setting entity to not be culled:
myEntity->Flags &= ~EntityFlags.Culled;
Avoiding RNG Issues
Be careful with the use of shared (between entities) RNGSession instances (like the default one stored in Quantum _globals_
). If code that uses RNG sessions is used together with culling, there might be issues when two entities (that have movement affected by RNG) share the same session.
This will never lead to desyncs (because the verified frame is guaranteed deterministic), but there might be some visual jitter because the predicted entity will have it's final position modified from a new RNG value after the verified frame is finally simulated.
The solution is to store isolated RNGSession structs into each entity subject to culling. They can have their seeds injected in any way desired, but the isolation guarantees culling will not change the final positions of predicted entities (unless the rollback actually means that):
chsarp
entitiy Actor[32] {
fields {
RNGSession MyRNG;
}
}
Back to top