Struct ClientServerTickRate
The ClientServerTickRate singleton is used to configure the client and server simulation time step, server packet send rate and other related settings. The singleton entity is automatically created for the clients in the NetworkStreamReceiveSystem first update if not present. On the server, by contrast, the entity is never automatically created and it is up to the user to create the singletong instance if they need to. This behaviour is asymmetric because the client need to have this singleton data synced with the server one. It is like this for compatibility reason and It may be changed in the future. In order to configure these settings you can either:
- Create the entity in a custom Unity.NetCode.ClientServerBootstrap after the worlds has been created.
- On a system, in either the OnCreate or OnUpdate.
- the maximum number of tick per frame
- the maximum number of tick per frame
- tick batching (<`MaxSimulationStepBatchSize`) and others.
Implements
Inherited Members
Namespace: Unity.NetCode
Assembly: Unity.NetCode.dll
Syntax
[Serializable]
public struct ClientServerTickRate : IComponentData, IQueryTypeParameter
Remarks
- Once the client is connected, changes to the ClientServerTickRate are not replicated. If you change the settings are runtime, the same change must be done on both client and server.
- The ClientServerTickRate should never be added to sub-scene with a baker. In case you want to setup the ClientServerTickRate based on some scene settings, we suggest to implement your own component and change the ClientServerTickRate inside a system in your game.
Examples
class MyCustomClientServerBootstrap : ClientServerBootstrap
{
override public void Initialize(string defaultWorld)
{
base.Initialise(defaultWorld);
var customTickRate = new ClientServerTickRate();
//run at 30hz
customTickRate.simulationTickRate = 30;
customTickRate.ResolveDefault();
foreach(var world in World.All)
{
if(world.IsServer())
{
//In this case we only create on the server, but we can do the same also for the client world
var tickRateEntity = world.EntityManager.CreateSingleton(new ClientServerTickRate
{
SimulationTickRate = 30;
});
}
}
}
}
Fields
HandshakeApprovalTimeoutMS
The timeout for the connection handshake and approval procedure.
Note: This is one counter for both states. In other words: The client must complete both Handshake
and Approval before this timeout expires - it's not reset upon entering Approval.
As soon as the client is accepted on the server, the timer will start.
Timeout will occur if the server has not handshaked and approved the connection
within the given duration. The default is 5000ms.
Declaration
[Tooltip("The timeout for the connection handshake and approval procedure. Both must succeed within the allotted time!\n\nDefaults to 0ms (which becomes 5s).")]
[Range(0, 120000)]
public uint HandshakeApprovalTimeoutMS
Field Value
Type | Description |
---|---|
uint |
Remarks
The overall timeout sequence when a client is connecting is:
1. The client goes through the transport-level connection timeout first (max connect attempt * connect timeout).
2. Then, once the UTP connection succeeds, netcode begins the handshake process, where protocol
version RPCs are automatically exchanged.
3. If the client protocol is valid, the server will move the client to either the connected state,
or to the approval state (if approval is enabled via RequireConnectionApproval).
This timeout applies to both the Handshake and Approval elapsed durations. It's a single timer for both.
MaxSimulationStepBatchSize
If the server cannot keep up with the simulation frequency with running MaxSimulationStepsPerFrame
ticks, it is possible to allow each tick to run with a longer delta time in order to keep the game
time updating correctly. This means that instead of running two ticks with delta time N each, the
system will run a single tick with delta time 2*N. It is a less expensive but more inaccurate way
of dealing with server performance spikes, it also requires the game logic to be able to handle it.
Declaration
[Tooltip("Denotes how many individual ticks will be batched together (into a single tick) when recovering from a severe slowdown.\n\nDefault value is 0 (which becomes 4).\n\n<b>Warning: You lose accuracy when batching ticks, and gameplay code must account for it.</b>")]
[Range(0, 16)]
public int MaxSimulationStepBatchSize
Field Value
Type | Description |
---|---|
int |
MaxSimulationStepsPerFrame
If the server cannot keep up with the passing of realtime (i.e. the server is ticking at too low a rate to match the SimulationTickRate), it will perform multiple ticks in a single frame (in an attempt to 'catch up'). This setting puts a limit on how many such updates it can perform in a single frame. Once this limit is reached, the simulation time will update slower than real time. The default value is 1.
Declaration
[Tooltip("Denotes how many fixed-step ticks can be performed on any given Unity frame, when 'catching up', when running too slowly.\n\nDefault value is 0 (which becomes 1).")]
[Range(0, 16)]
public int MaxSimulationStepsPerFrame
Field Value
Type | Description |
---|---|
int |
Remarks
The network tick rate only applies to snapshots, the frequency commands and RPCs is not affected by this setting.
NetworkTickRate
The rate at which the server creates (and sends) a snapshots to each client. This can be lower than than the simulation frequency, which means the server only sends new snapshots to the clients every N frames. Defaults to the SimulationTickRate.
Declaration
[Tooltip("The rate at which the server creates (and sends) a snapshot to each client.\n\nIf zero (the default), this value will be set to the <b>SimulationTickRate</b>, but half (or one third) is often good enough.\n\nThe CPU work performed to build and send snapshots is often the most significant CPU cost in a multiplayer game. Thus, reducing this send-rate can lead to significant CPU savings, but at the expense of gameplay quality (especially when packets are lost to the network).")]
[Min(0)]
public int NetworkTickRate
Field Value
Type | Description |
---|---|
int |
Remarks
The CPU work performed to build and send snapshots (via GhostSendSystem) is often the most significant CPU cost in a multiplayer game. Thus, reducing this send-rate can lead to significant CPU savings, but at the expense of gameplay quality (especially when packets are lost to the network). Note that the server can still send data on every simulation tick, but to different subsets of clients. This is to distribute CPU load over multiple simulation ticks (to avoid CPU spikes). For example, with a NetworkTickRate of 30 and a SimulationTickRate of 60, the server will send snapshots to half of the clients for one tick, and the other half, the next tick. So each client still end up with a packet every 2 simulation ticks, while the server is distributing the CPU load over each tick (via a 'round robin' strategy).
PredictedFixedStepSimulationTickRatio
Multiplier used to calculate the tick rate (i.e. frequency) for the PredictedFixedStepSimulationSystemGroup. The group rate must be an integer multiple of the SimulationTickRate. The default value is 1, meaning that the PredictedFixedStepSimulationSystemGroup run at the same frequency as the prediction loop. The calculated delta is 1.0/(SimulationTickRate*PredictedFixedStepSimulationTickRatio).
Declaration
[Tooltip("Multiplier used to calculate the tick rate (i.e. frequency) for the PredictedFixedStepSimulationSystemGroup.\n\nThe default (and recommendation) is 0 (which becomes 1 i.e. one fixed step per tick), where higher values allow physics to tick more frequently (i.e. at smaller intervals).")]
[Range(0, 8)]
public int PredictedFixedStepSimulationTickRatio
Field Value
Type | Description |
---|---|
int |
SimulationTickRate
The fixed simulation frequency on the server and prediction loop. The client can render at a higher or lower rate than this. Default: 60Hz.
Declaration
[Tooltip("The fixed simulation frequency of the Netcode gameplay simulation. Higher values incur higher CPU costs on both the client and server, especially during client prediction.")]
[Min(1)]
public int SimulationTickRate
Field Value
Type | Description |
---|---|
int |
Remarks
Note: Clients are not locked to this refresh rate (see Partial Ticks documentation). Higher values increase gameplay quality, but incur higher CPU and bandwidth costs. Higher values are particularly expensive on the client, as prediction cost increases.
SnapshotAckMaskCapacity
Netcode needs to store a history of snapshot acknowledgements ("acks") on the server - one per connection. This denotes the size of said history buffer, in bits, and is exposed only to allow further patching of an esoteric issue (see remarks). Default value is 4096 bits (0.5KB), which should prevent this issue in the common case. Previous hardcoded default was 256 bits.
Declaration
[Tooltip("Denotes how many entries the snapshot ack history BitArray stores. Default value: 4096 bits. Min: 1024 bits.\n\nSolves an emergent problem when replicating tens of thousands of relevant static ghosts to a single connection - a case we strongly advise against. See XML doc.")]
public uint SnapshotAckMaskCapacity
Field Value
Type | Description |
---|---|
uint |
Remarks
Due to GhostSendSystem priority queue mechanics, increasing this value may fix errors where:
- Static ghosts never stop resending.
- Static and dynamic ghosts do not correctly find their 'baselines' (i.e. previously send and acked values), when attempting delta-compression.
Per connection, per chunk, netcode stores up to 32 previous snapshots (and thus baselines, and their acks) in a circular/ring buffer (GhostChunkSerializationState and SnapshotHistorySize). This ring-buffer appends an entry every time the chunk is successfully serialized into a snapshot writer.
The problem is: When you have tens of thousands of relevant ghosts for a single connection
(a case we strongly advise against), the priority queue will only "bubble up" a chunk to be resent
after many tens of seconds. You can very loosely approximate the lower bound of this via
(((numGhosts/avgNumGhostsPerChunk)*averageSizeOfChunkInBytes)/transportMTU)/NetworkTickRate
E.g. 100k well optimized ghosts, sent at 30Hz (Simulation 60Hz), is (((100000/40)*1200)/1400)/30 = ~72s
to replicate them all once. I.e. ~4285 simulation ticks will have occurred since the client
was sent the previously sent snapshot.
Thus, when we check the ack buffer ~72 seconds later, the ack has long since been bit-shifted off the end of the 256 tick history buffer. The simplest solution (implemented here) is to store an ack buffer that is considerably larger. It is now 4096 entries by default (i.e. ~1.1 minutes at 60Hz), and 1024 entries at a minimum (~17s at 60Hz), whereas the previous default was 256 (i.e. ~4.26s at 60Hz). This field configures said capacity.
Because we are now able to find acks for snapshots sent over 4.26s ago, this fixed a regression in delta-compression performance (as, previously, the baseline was found, but treated as un-acked, thus unable to be used).
We also previously failed to mark this chunk as having 'no changes' (via isZeroChange
),
as a ghost having 'no change' relies on its current value being compared to any of its acked
baseline values. This means we previously could not early out via CanUseStaticOptimization
(which looks for zero change). As a result, we frequently saw resending of previously acked
static ghosts in these circumstances (at least until the server so happens to try to resend
the same chunk within SnapshotAckMaskCapacity ticks of a previous ack).
Similarly, if you implemented configuration options like MinSendImportance,
we would delay processing of a chunk artificially. If this delay happened to exceed capacity,
the chunk (and its ghosts) can never possibly ack. Thankfully, SnapshotAckMaskCapacity
is now far higher than we'd ever recommend setting MinSendImportance
.
TargetFrameRateMode
If the server is capable of updating more often than the simulation tick rate, it can either
skip the simulation tick for some updates (BusyWait
), or limit the updates using
Application.TargetFrameRate
(Sleep
). Auto
makes it use Sleep
for dedicated server
builds and BusyWait
for client and server builds (as well as the editor).
Declaration
[Tooltip("Denotes how the server should sleep, when determining when it should next tick.\n\nDefaults to <b>Auto</b>, which will use <b>Sleep</b> for dedicated server builds, and <b>BusyWait</b> for client and server builds (as well as the editor).")]
public ClientServerTickRate.FrameRateMode TargetFrameRateMode
Field Value
Type | Description |
---|---|
ClientServerTickRate.FrameRateMode |
Properties
ClampPartialTicksThreshold
On the client, Netcode attempts to align its own fixed step with the render refresh rate, with the goal of reducing Partial ticks, and increasing stability. This setting denotes the window (in %) to snap and align. Defaults to 5 (5%), which is applied each way: I.e. If you're within 5% of the last full tick, or if you're within 5% of the next full tick, we'll clamp. -1 is 'turn clamping off', 0 is 'use default'. Max value is 50 (i.e. 50% each way, leading to full clamping, as it's applied in both directions).
Declaration
[Tooltip("On the client, Netcode attempts to align its own fixed step with the render refresh rate, with the goal of reducing Partial ticks, and increasing stability.\n\nThis setting denotes the window (in %) to snap and align.\n\nDefaults to 5 (5%), which is applied each way.\nI.e. If you're within 5% of the last full tick, or if you're within 5% of the next full tick, we'll clamp. 50 (50%) to always clamp.")]
public int ClampPartialTicksThreshold { readonly get; set; }
Property Value
Type | Description |
---|---|
int |
Remarks
High values will lead to more aggressive alignment, which may be perceivable (as we'll need to shift time further).
PredictedFixedStepSimulationTimeStep
The fixed time used to run the physics simulation. Is always an integer multiple of the SimulationFixedTimeStep.
The value is equal to 1f / (SimulationTickRate * PredictedFixedStepSimulationTickRatio).
Declaration
public float PredictedFixedStepSimulationTimeStep { get; }
Property Value
Type | Description |
---|---|
float |
SendSnapshotsForCatchUpTicks
If the server has to run multiple simulation ticks in the same frame, the server can either send snapshots for all those ticks (true), or just the last one (false).
Declaration
public bool SendSnapshotsForCatchUpTicks { get; set; }
Property Value
Type | Description |
---|---|
bool |
SimulationFixedTimeStep
1f / SimulationTickRate. Think of this as the netcode version of fixedDeltaTime
.
Declaration
public float SimulationFixedTimeStep { get; }
Property Value
Type | Description |
---|---|
float |
Methods
CalculateNetworkSendIntervalOfGhostInTicks(ushort)
Returns the MaxSendRate as a SimulationTickRate interval UNTIL you can resend this chunk.
Declaration
public byte CalculateNetworkSendIntervalOfGhostInTicks(ushort MaxSendRate)
Parameters
Type | Name | Description |
---|---|---|
ushort | MaxSendRate | From the GhostAuthoring. |
Returns
Type | Description |
---|---|
byte | The interval i.e. every nth SimulationTickRate tick. |
CalculateNetworkSendRateInterval()
Helper: Returns 1 when NetworkTickRate is equal to (or close enough - via rounding - to) SimulationTickRate. Returns 2 when half, 3 when 1/3rd etc.
Declaration
public int CalculateNetworkSendRateInterval()
Returns
Type | Description |
---|---|
int | The snapshot send interval. |
ResolveDefaults()
Set all the properties that haven't been changed by the user (or that have invalid ranges) to a proper default value. In particular, this guarantees that both NetworkTickRate and SimulationTickRate are never 0.
Declaration
public void ResolveDefaults()