Skip to content

Commit b5b40de

Browse files
authored
fix: snapshot system. last fixes for release (#1129)
* feat: snapshot. MTT-1087 first part. separating the side effect (storing the sent info) from preparing the buffer to send * feat: snapshot. MTT-1087 second part. Writing SnapshotMessage that fit in the specified limit, instead of stopping once the limit is busted * feat: snapshot. MTT-1087 part 3. Writing spawns after acks, because spawns are limited in message size * feat: snapshot. MTT-1087 part 4. Using max spawn message size from NetworkConfig * feat: snapshot. mtt-1087. Forgotten , for performance * feat: snapshot. MTT-1089 pessimistic resend control. spawn part * feat: snapshot. MTT-1089 pessimistic resend control. despawn part * fix: snapshot. Cleaning up our clientData.SentSpawns data structure. Otherwise it grows unbound * feat: snapshot. Last fixes for release. Prepares for MTT-1089, but leaves it disabled * feat: snapshot. Last fixes for release. PR review comments * style: snapshot. Coding standards * style: snapshot. Coding standards
1 parent 1bbe95f commit b5b40de

File tree

2 files changed

+64
-17
lines changed

2 files changed

+64
-17
lines changed

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -394,17 +394,15 @@ private void OnDestroy()
394394

395395
private SnapshotDespawnCommand GetDespawnCommand()
396396
{
397-
SnapshotDespawnCommand command;
397+
var command = new SnapshotDespawnCommand();
398398
command.NetworkObjectId = NetworkObjectId;
399-
command.TickWritten = default; // value will be set internally by SnapshotSystem
400-
command.TargetClientIds = default;
401399

402400
return command;
403401
}
404402

405403
private SnapshotSpawnCommand GetSpawnCommand()
406404
{
407-
SnapshotSpawnCommand command;
405+
var command = new SnapshotSpawnCommand();
408406
command.NetworkObjectId = NetworkObjectId;
409407
command.OwnerClientId = OwnerClientId;
410408
command.IsPlayerObject = IsPlayerObject;
@@ -426,8 +424,6 @@ private SnapshotSpawnCommand GetSpawnCommand()
426424
command.ObjectPosition = transform.position;
427425
command.ObjectRotation = transform.rotation;
428426
command.ObjectScale = transform.localScale;
429-
command.TickWritten = default; // value will be set internally by SnapshotSystem
430-
command.TargetClientIds = default;
431427

432428
return command;
433429
}

com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ internal struct SnapshotDespawnCommand
3535
// snapshot internal
3636
internal int TickWritten;
3737
internal List<ulong> TargetClientIds;
38+
internal int TimesWritten;
3839
}
3940

4041
internal struct SnapshotSpawnCommand
@@ -57,6 +58,7 @@ internal struct SnapshotSpawnCommand
5758
// snapshot internal
5859
internal int TickWritten;
5960
internal List<ulong> TargetClientIds;
61+
internal int TimesWritten;
6062
}
6163

6264
// A table of NetworkVariables that constitutes a Snapshot.
@@ -191,6 +193,11 @@ internal void AddSpawn(SnapshotSpawnCommand command)
191193
command.TargetClientIds = GetClientList();
192194
}
193195

196+
// todo: store, for each client, the spawn not ack'ed yet,
197+
// to prevent sending despawns to them.
198+
// for clientData in client list
199+
// clientData.SpawnSet.Add(command.NetworkObjectId);
200+
194201
// todo:
195202
// this 'if' might be temporary, but is needed to help in debugging
196203
// or maybe it stays
@@ -302,7 +309,7 @@ internal Entry ReadEntry(NetworkReader reader)
302309

303310
internal SnapshotSpawnCommand ReadSpawn(NetworkReader reader)
304311
{
305-
SnapshotSpawnCommand command;
312+
var command = new SnapshotSpawnCommand();
306313

307314
command.NetworkObjectId = reader.ReadUInt64Packed();
308315
command.GlobalObjectIdHash = (uint)reader.ReadUInt64Packed();
@@ -315,18 +322,16 @@ internal SnapshotSpawnCommand ReadSpawn(NetworkReader reader)
315322
command.ObjectScale = reader.ReadVector3();
316323

317324
command.TickWritten = reader.ReadInt32Packed();
318-
command.TargetClientIds = default;
319325

320326
return command;
321327
}
322328

323329
internal SnapshotDespawnCommand ReadDespawn(NetworkReader reader)
324330
{
325-
SnapshotDespawnCommand command;
331+
var command = new SnapshotDespawnCommand();
326332

327333
command.NetworkObjectId = reader.ReadUInt64Packed();
328334
command.TickWritten = reader.ReadInt32Packed();
329-
command.TargetClientIds = default;
330335

331336
return command;
332337
}
@@ -499,8 +504,11 @@ internal void ReadAcks(ulong clientId, ClientData clientData, NetworkReader read
499504
internal void ProcessSingleAck(ushort ackSequence, ulong clientId, ClientData clientData, ConnectionRtt connection)
500505
{
501506
// look through the spawns sent
502-
foreach (var sent in clientData.SentSpawns)
507+
for (int index = 0; index < clientData.SentSpawns.Count; /*no increment*/)
503508
{
509+
// needless copy, but I didn't find a way around
510+
ClientData.SentSpawn sent = clientData.SentSpawns[index];
511+
504512
// for those with the sequence number being ack'ed
505513
if (sent.SequenceNumber == ackSequence)
506514
{
@@ -548,6 +556,16 @@ internal void ProcessSingleAck(ushort ackSequence, ulong clientId, ClientData cl
548556
}
549557
}
550558
}
559+
560+
// remove current `sent`, by moving last over,
561+
// as it was acknowledged.
562+
// skip incrementing index
563+
clientData.SentSpawns[index] = clientData.SentSpawns[clientData.SentSpawns.Count - 1];
564+
clientData.SentSpawns.RemoveAt(clientData.SentSpawns.Count - 1);
565+
}
566+
else
567+
{
568+
index++;
551569
}
552570
}
553571

@@ -741,6 +759,38 @@ private void SendSnapshot(ulong clientId)
741759
}
742760
}
743761

762+
// Checks if a given SpawnCommand should be written to a Snapshot Message
763+
// Performs exponential back off. To write a spawn a second time
764+
// two ticks must have gone by. To write it a third time, four ticks, etc...
765+
// This prioritize commands that have been re-sent less than others
766+
private bool ShouldWriteSpawn(in SnapshotSpawnCommand spawnCommand)
767+
{
768+
if (m_CurrentTick < spawnCommand.TickWritten)
769+
{
770+
return false;
771+
}
772+
773+
// 63 as we can't shift more than that.
774+
var diff = Math.Min(63, m_CurrentTick - spawnCommand.TickWritten);
775+
776+
// -1 to make the first resend immediate
777+
return (1 << diff) > (spawnCommand.TimesWritten - 1);
778+
}
779+
780+
private bool ShouldWriteDespawn(in SnapshotDespawnCommand despawnCommand)
781+
{
782+
if (m_CurrentTick < despawnCommand.TickWritten)
783+
{
784+
return false;
785+
}
786+
787+
// 63 as we can't shift more than that.
788+
var diff = Math.Min(63, m_CurrentTick - despawnCommand.TickWritten);
789+
790+
// -1 to make the first resend immediate
791+
return (1 << diff) > (despawnCommand.TimesWritten - 1);
792+
}
793+
744794
private void WriteSpawns(NetworkBuffer buffer, ulong clientId)
745795
{
746796
var spawnWritten = 0;
@@ -779,7 +829,8 @@ private void WriteSpawns(NetworkBuffer buffer, ulong clientId)
779829
var index = clientData.NextSpawnIndex;
780830
var savedPosition = writer.GetStream().Position;
781831

782-
if (m_Snapshot.Spawns[index].TargetClientIds.Contains(clientId))
832+
// todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns
833+
if (m_Snapshot.Spawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteSpawn(m_Snapshot.Spawns[index])*/)
783834
{
784835
var sentSpawn = m_Snapshot.WriteSpawn(clientData, writer, in m_Snapshot.Spawns[index]);
785836

@@ -792,6 +843,7 @@ private void WriteSpawns(NetworkBuffer buffer, ulong clientId)
792843
}
793844
else
794845
{
846+
m_Snapshot.Spawns[index].TimesWritten++;
795847
clientData.SentSpawns.Add(sentSpawn);
796848
spawnWritten++;
797849
}
@@ -805,15 +857,15 @@ private void WriteSpawns(NetworkBuffer buffer, ulong clientId)
805857
// ack'ed before sending a despawn for the same object.
806858
// Uncommenting this line would allow some despawn to be sent while spawns are pending.
807859
// As-is it is overly restrictive but allows us to go forward without the spawn/despawn dependency check
808-
809860
// overSize = false;
810861

811862
for (var j = 0; j < m_Snapshot.NumDespawns && !overSize; j++)
812863
{
813864
var index = clientData.NextDespawnIndex;
814865
var savedPosition = writer.GetStream().Position;
815866

816-
if (m_Snapshot.Despawns[index].TargetClientIds.Contains(clientId))
867+
// todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns
868+
if (m_Snapshot.Despawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteDespawn(m_Snapshot.Despawns[index])*/)
817869
{
818870
var sentDespawn = m_Snapshot.WriteDespawn(clientData, writer, in m_Snapshot.Despawns[index]);
819871

@@ -826,6 +878,7 @@ private void WriteSpawns(NetworkBuffer buffer, ulong clientId)
826878
}
827879
else
828880
{
881+
m_Snapshot.Despawns[index].TimesWritten++;
829882
clientData.SentSpawns.Add(sentDespawn);
830883
despawnWritten++;
831884
}
@@ -955,16 +1008,14 @@ internal void ReadSnapshot(ulong clientId, Stream snapshotStream)
9551008
clientId = m_NetworkManager.ServerClientId;
9561009
}
9571010

958-
int snapshotTick = default;
959-
9601011
using var reader = PooledNetworkReader.Get(snapshotStream);
9611012
// make sure we have a ClientData entry for each client
9621013
if (!m_ClientData.ContainsKey(clientId))
9631014
{
9641015
m_ClientData.Add(clientId, new ClientData());
9651016
}
9661017

967-
snapshotTick = reader.ReadInt32Packed();
1018+
var snapshotTick = reader.ReadInt32Packed();
9681019
var sequence = reader.ReadUInt16();
9691020

9701021
if (sequence >= m_ClientData[clientId].LastReceivedSequence)

0 commit comments

Comments
 (0)