Frames
Introduction
Quantum's predict-rollback architecture allows to mitigate latency. Quantum always rolls-back and re-simulates frames. It is a necessity for determinism and involves the validation of player input by the server. Once the server has either confirmed the player input or overwritten/replaced it (only in cases were the input did not reach the server in time), the validated input of all players for a given frame is sent to the clients. Once the validated input is received, the last verified frame advances using the confirmed input.
N.B.: A player's own input will be rolled back if it has not reached the server in time or could not be validated.
Types of Frame
Quantum differentiates between two types of frame:
- verified; and,
- predicted.
Verified
A Verified frame is a trusted simulation frame. A verified frame is guaranteed to be deterministic and identical on all client simulations. The verified simulation only simulates the next verified frame once it has received the server-confirmed inputs; as such, it moves forward proportional to RTT/2 from the server.
A frame is verified if both of the following condition are both true:
- the input from ALL players is confirmed by the server for this tick; and,
- all previous ticks it follows are verified.
A partial tick confirmation where the input from only a subset of player has been validated by the server will not result in a verified tick/frame.
Predicted
Contrary to verified frames, predicted frames do not require server-confirmed input. This means the predicted frame advances with prediction as soon as the simulation has accumulated enough delta time in the local session.
The Unity-side API offers access to various versions of the predicted frame, see the API explanation below.
Predicted: the simulation "head", based on the synchronised clock.PredictedPrevious(predicted - 1): used for main clock-aliasing interpolation (most views will use this to stay smooth, as Unity's local clock may slightly drift from the main server clock. Quantum runs from a separate clock, in sync with the server clock - smoothly corrected).PreviousUpdatePredicted: this is the exact frame that was the "Predicted/Head" the last timeSession.Updatewas called (with the "corrected" data in it). Used for error correction interpolation (most of the time there will be no error).
In Quantum 3.1 PredictedPrevious and PreviousUpdatePredicted are only separate frame copies when the legacy frame copies are enabled, otherwise they return the Verified frame.
Enable them with SessionRunner.Arguments.IsLegacyFrameCopiesEnabled (or IsLegacyFrameCopiesEnabled on QuantumRunnerLocalDebug).
View interpolation does not need them, it uses the transform history of the entity views.
API
The concept of Verified and Predicted frames exists in both the simulation and the view, albeit with a slightly different API.
Simulation
In the simulation, one can access the state of the currently simulated frame via the Frame class.
| Property | Type | Description |
|---|---|---|
IsVerified |
bool |
Returns true if the frame is deterministic across all clients and uses server-confirmed input. |
IsPredicted |
bool |
Returns true if the frame is a locally predicted one. |
View
In the view, the verified and predicted frames are made available via QuantumRunner.Default.Game.Frames.
| Property | Description |
|---|---|
Verified |
Trusted simulation frame, identical across all clients. |
Predicted |
The local simulation "head" based on the synced Quantum clock. Can differ between clients. |
PredictedPrevious |
Predicted - 1. Used for main clock-aliasing interpolation, most views will use this to stay smooth. As Unity's local clock may slightly drift from the main server clock, Quantum runs from a separate clock which is in sync with the server clock - smoothly corrected. |
PreviousUpdatePredicted |
The re-simulated version of the frame that had been the "Predicted/Head" frame when the last time Session.Update was called. This is necessary in case of rollbacks in order to "correct" data held by it. It is used by the View for error-correction in the interpolation - this is a safety measure and rarely ever necessary. |
In Quantum 3.1 PredictedPrevious and PreviousUpdatePredicted return the Verified frame unless the legacy frame copies are enabled, see the note above.
Predicted Frame Copy Mode (Quantum 3.1)
Before predicting, the predicted frame copies the component data from the verified frame.
SimulationConfig.Entities.CopyMode configures how:
Full(default): copies all component blocks.Diff: only copies the blocks that were write-accessed after the last copy, which is cheaper when only few components change per tick.
Which Frame To Read In The View
- Read the
Predictedframe for everything visual, for example positions, animations and effects. It shows the latest state, which may still change after a rollback. - Read the
Verifiedframe for anything that must not roll back, for example a score display, UI that should not flicker or a sound that must play exactly once.
Frame Time
Frame.Number is the tick of the frame.
Frame.DeltaTime is the time of one tick, 1 / Simulation Rate in seconds.
In Quantum 3.1 Frame.DeltaTime is scaled by Frame.GameTimeScale and Frame.UnscaledDeltaTime returns the unscaled value.
For timers and slow motion see Frame Timer and Pausing.
Using Frame.User
It is possible to extend the Frame by adding data to Frame.User.cs. However, in doing so it will also be necessary to implement the corresponding initialization, allocation and serialization methods used by the frame.
C#
partial void InitUser() // Initialize the Data
partial void SerializeUser(FrameSerializer serializer) // De/Serialize the Data
partial void CopyFromUser(Frame frame) // Copy to next Frame
partial void AllocUser() // Allocate space
partial void FreeUser() // Free allocated space
partial void DumpFrameUser(ref String dump) // Add custom data to frame dumps (e.g. on checksum errors)
NOTE: Adding an excessive amount of data to the frame will impact performance (de/serialization), as well as affect late joins.
Example
This is a very simple example which does not require manual memory allocation.
C#
namespace Quantum {
using System;
unsafe partial class Frame {
public byte[] Grid => _grid;
private byte[] _grid;
partial void InitUser() {
_grid = new byte[RuntimeConfig.GridSize];
}
partial void SerializeUser(FrameSerializer serializer) {
serializer.Stream.SerializeArrayLength<Byte>(ref _grid);
for (int i = 0; i < Grid.Length; i++) {
serializer.Stream.Serialize(ref Grid[i]);
}
}
partial void CopyFromUser(Frame frame) {
Array.Copy(frame._grid, _grid, frame._grid.Length);
}
}
}
Using FrameContext.User
Data in Frame.User is copied, serialized and rolled back with every frame.
Data that is shared by all frames of a session and never rolled back belongs into the frame context instead.
Extend it in Assets/QuantumUser/Simulation/FrameContext.User.cs and access it in the simulation via frame.Context.
C#
partial void ConstructUser(Args args) // Called once when the frame context is created
partial void DisposeUser() // Called when the frame context is disposed
The frame context is not part of the game state, it is not serialized for late joiners and not restored on rollbacks. Only store data that every client derives the same way and that does not change during the simulation, for example caches or lookup structures built from assets. Writing gameplay state into the frame context will cause desyncs.
Last updated on
Back to top