How does NetworkTimeSystem adjust PredictingTick in network jitter?
I want to know about the logic of commandAgeAdjustment
, thank you!
// Check which slot in the circular buffer of command age adjustments the current data should go in
int curSlot = (int)(ack.LastReceivedSnapshotByLocal % commandAgeAdjustment.Length);
// If we moved to a new slot, clear the data between previous and new
if (curSlot != commandAgeAdjustmentSlot)
{
for (int i = (commandAgeAdjustmentSlot + 1) % commandAgeAdjustment.Length;
i != (curSlot+1) % commandAgeAdjustment.Length;
i = (i+1) % commandAgeAdjustment.Length)
{
commandAgeAdjustment[i] = 0;
}
commandAgeAdjustmentSlot = curSlot;
}
float commandAge = ack.ServerCommandAge / 256.0f + kTargetCommandSlack;
NetDebug.cmdAgeBefore = commandAge;
// round down to whole ticks performed in one rtt
int rttInTicks = (int)(((uint) estimatedRTT * (uint) tickRate.SimulationTickRate) / 1000);
if (rttInTicks > commandAgeAdjustment.Length)
rttInTicks = commandAgeAdjustment.Length;
for (int i = 0; i < rttInTicks; ++i)
commandAge -= commandAgeAdjustment[(commandAgeAdjustment.Length+commandAgeAdjustmentSlot-i) % commandAgeAdjustment.Length];
NetDebug.cmdAgeAfter = commandAge;
float predictionTimeScale = 1f;
if (math.abs(commandAge) < 10)
{
predictionTimeScale = math.clamp(1.0f + 0.1f * commandAge, .9f, 1.1f);
subPredictTargetTick += deltaTicks * predictionTimeScale;
uint pdiff = (uint) subPredictTargetTick;
subPredictTargetTick -= pdiff;
predictTargetTick += pdiff;
}
else
{
uint curPredict = latestSnapshotEstimate + kTargetCommandSlack +
((uint) estimatedRTT * (uint) tickRate.SimulationTickRate + 999) / 1000;
float predictDelta = (float)((int)curPredict - (int)predictTargetTick) - deltaTicks;
if (math.abs(predictDelta) > 10)
{
//Attention! this may can rollback in case we have an high difference in estimate (about 10 ticks greater)
//and predictDelta is negative (client is too far ahead)
if (predictDelta < 0.0f)
{
UnityEngine.Debug.LogError($"Large serverTick prediction error. Server tick rollback to {curPredict} delta: {predictDelta}");
}
predictTargetTick = curPredict;
subPredictTargetTick = 0;
for (int i = 0; i < commandAgeAdjustment.Length; ++i)
commandAgeAdjustment[i] = 0;
}
else
{
predictionTimeScale = math.clamp(1.0f + 0.1f * predictDelta, .9f, 1.1f);
subPredictTargetTick += deltaTicks * predictionTimeScale;
uint pdiff = (uint) subPredictTargetTick;
subPredictTargetTick -= pdiff;
predictTargetTick += pdiff;
}
}
commandAgeAdjustment[commandAgeAdjustmentSlot] += deltaTicks * (predictionTimeScale - 1.0f);
@timjohansson can you answer above question? I have no clue to the code above:(
The commandAge is calculated on the server and sent to the client. It tracks how much older the received commands were compared to the current tick being processed on the server. If the commands on average arrives two ticks before they are needed on the server commandAge will be -2.
The client uses the latest received commandAge from the server to scale delta time and get commandAge closer to the target. So if commandAge is -2.5 for example the commands are arriving a bit earlier than they have to on the server, the client will then run at a lower speed for a bit so it is not as far ahead of the serer and commandAge gets closer to -2.
Because the commandAge is calculated on the server and sent to the client we have to wait one full RTT from adjusting until we see any update to commandAge on the client. To compensate for that we track an approximation of how much we have tried to adapt the commandAge during one full RTT (that’s the commandAgeAdjustment circular buffer). So if commandAge is -2.5 but we have already made an accumulated time adjustment equivalent of +0.5 ticks over the last RTT*framRate ticks we assume it is really -2 now but we have not received the updated value from the server yet.
The only thing we do to deal with jitter is kTargetCommandSlack which is set to 2. That means we try to make sure commands arrive 2 ticks before they are needed on the server on average. As long as your jitter is less than 2 ticks, ~33ms with a 60Hz simulation it will be fine.
It should be possible to make kTargetCommandSlack dynamic and based on the jitter we calculate as part of the RTT calculations to make it deal better with really high jitter, but that is not something we are planning to work on right now.