diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs
index 5803e79087..d0606897ec 100644
--- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs
@@ -144,10 +144,18 @@ public class NetworkConfig
///
public bool EnableNetworkLogs = true;
- // todo: transitional. For the next release, only Snapshot should remain
- // The booleans allow iterative development and testing in the meantime
+ ///
+ /// Whether or not to enable Snapshot System for variable updates. Currently unsupported.
+ ///
public bool UseSnapshotDelta { get; } = false;
+ ///
+ /// Whether or not to enable Snapshot System for spawn and despawn commands. Working but experimental.
+ ///
public bool UseSnapshotSpawn { get; } = false;
+ ///
+ /// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
+ ///
+ public int SnapshotMaxSpawnUsage { get; } = 1200;
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs b/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs
index b2a509f573..8b5133ea6f 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs
@@ -243,15 +243,14 @@ internal void WriteEntry(NetworkWriter writer, in Entry entry)
writer.WriteUInt16(entry.Length);
}
- internal void WriteSpawn(ClientData clientData, NetworkWriter writer, in SnapshotSpawnCommand spawn)
+ internal ClientData.SentSpawn WriteSpawn(in ClientData clientData, NetworkWriter writer, in SnapshotSpawnCommand spawn)
{
// remember which spawn we sent this connection with which sequence number
// that way, upon ack, we can track what is being ack'ed
- ClientData.SentSpawn s;
- s.ObjectId = spawn.NetworkObjectId;
- s.Tick = spawn.TickWritten;
- s.SequenceNumber = clientData.SequenceNumber;
- clientData.SentSpawns.Add(s);
+ ClientData.SentSpawn sentSpawn;
+ sentSpawn.ObjectId = spawn.NetworkObjectId;
+ sentSpawn.Tick = spawn.TickWritten;
+ sentSpawn.SequenceNumber = clientData.SequenceNumber;
writer.WriteUInt64Packed(spawn.NetworkObjectId);
writer.WriteUInt64Packed(spawn.GlobalObjectIdHash);
@@ -265,20 +264,23 @@ internal void WriteSpawn(ClientData clientData, NetworkWriter writer, in Snapsho
writer.WriteVector3(spawn.ObjectScale);
writer.WriteInt32Packed(spawn.TickWritten);
+
+ return sentSpawn;
}
- internal void WriteDespawn(ClientData clientData, NetworkWriter writer, in SnapshotDespawnCommand despawn)
+ internal ClientData.SentSpawn WriteDespawn(in ClientData clientData, NetworkWriter writer, in SnapshotDespawnCommand despawn)
{
// remember which spawn we sent this connection with which sequence number
// that way, upon ack, we can track what is being ack'ed
- ClientData.SentSpawn s;
- s.ObjectId = despawn.NetworkObjectId;
- s.Tick = despawn.TickWritten;
- s.SequenceNumber = clientData.SequenceNumber;
- clientData.SentSpawns.Add(s);
+ ClientData.SentSpawn sentSpawn;
+ sentSpawn.ObjectId = despawn.NetworkObjectId;
+ sentSpawn.Tick = despawn.TickWritten;
+ sentSpawn.SequenceNumber = clientData.SequenceNumber;
writer.WriteUInt64Packed(despawn.NetworkObjectId);
writer.WriteInt32Packed(despawn.TickWritten);
+
+ return sentSpawn;
}
///
/// Read a received Entry
@@ -605,8 +607,6 @@ internal class SnapshotSystem : INetworkUpdateSystem, IDisposable
internal const ushort SentinelBefore = 0x4246;
internal const ushort SentinelAfter = 0x89CE;
- private const int k_MaxSpawnUsage = 1000; // max bytes to use for the spawn/despawn part
-
private NetworkManager m_NetworkManager = default;
private Snapshot m_Snapshot = default;
@@ -728,8 +728,8 @@ private void SendSnapshot(ulong clientId)
writer.WriteUInt16(SentinelBefore);
WriteBuffer(buffer);
WriteIndex(buffer);
- WriteSpawns(buffer, clientId);
WriteAcks(buffer, clientId);
+ WriteSpawns(buffer, clientId);
writer.WriteUInt16(SentinelAfter);
m_ClientData[clientId].LastReceivedSequence = 0;
@@ -745,8 +745,8 @@ private void WriteSpawns(NetworkBuffer buffer, ulong clientId)
{
var spawnWritten = 0;
var despawnWritten = 0;
+ var overSize = false;
- bool overSize = false;
ClientData clientData = m_ClientData[clientId];
// this is needed because spawns being removed may have reduce the size below LRU position
@@ -777,35 +777,58 @@ private void WriteSpawns(NetworkBuffer buffer, ulong clientId)
for (var j = 0; j < m_Snapshot.NumSpawns && !overSize; j++)
{
var index = clientData.NextSpawnIndex;
+ var savedPosition = writer.GetStream().Position;
if (m_Snapshot.Spawns[index].TargetClientIds.Contains(clientId))
{
- m_Snapshot.WriteSpawn(clientData, writer, in m_Snapshot.Spawns[index]);
- spawnWritten++;
- }
+ var sentSpawn = m_Snapshot.WriteSpawn(clientData, writer, in m_Snapshot.Spawns[index]);
- // limit spawn sizes, compare current pos to very first position we wrote to
- if (writer.GetStream().Position - positionSpawns > k_MaxSpawnUsage)
- {
- overSize = true;
+ // limit spawn sizes, compare current pos to very first position we wrote to
+ if (writer.GetStream().Position - positionSpawns > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage)
+ {
+ overSize = true;
+ // revert back the position to undo the write
+ writer.GetStream().Position = savedPosition;
+ }
+ else
+ {
+ clientData.SentSpawns.Add(sentSpawn);
+ spawnWritten++;
+ }
}
clientData.NextSpawnIndex = (clientData.NextSpawnIndex + 1) % m_Snapshot.NumSpawns;
}
+ // even though we might have a spawn we could not fit, it's possible despawns will fit (they're smaller)
+
+ // todo: this next line is commented for now because there's no check for a spawn command to have been
+ // ack'ed before sending a despawn for the same object.
+ // Uncommenting this line would allow some despawn to be sent while spawns are pending.
+ // As-is it is overly restrictive but allows us to go forward without the spawn/despawn dependency check
+
+ // overSize = false;
for (var j = 0; j < m_Snapshot.NumDespawns && !overSize; j++)
{
var index = clientData.NextDespawnIndex;
+ var savedPosition = writer.GetStream().Position;
if (m_Snapshot.Despawns[index].TargetClientIds.Contains(clientId))
{
- m_Snapshot.WriteDespawn(clientData, writer, in m_Snapshot.Despawns[index]);
- despawnWritten++;
- }
- // limit spawn sizes, compare current pos to very first position we wrote to
- if (writer.GetStream().Position - positionSpawns > k_MaxSpawnUsage)
- {
- overSize = true;
+ var sentDespawn = m_Snapshot.WriteDespawn(clientData, writer, in m_Snapshot.Despawns[index]);
+
+ // limit spawn sizes, compare current pos to very first position we wrote to
+ if (writer.GetStream().Position - positionSpawns > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage)
+ {
+ overSize = true;
+ // revert back the position to undo the write
+ writer.GetStream().Position = savedPosition;
+ }
+ else
+ {
+ clientData.SentSpawns.Add(sentDespawn);
+ despawnWritten++;
+ }
}
clientData.NextDespawnIndex = (clientData.NextDespawnIndex + 1) % m_Snapshot.NumDespawns;
}
@@ -991,8 +1014,8 @@ internal void ReadSnapshot(ulong clientId, Stream snapshotStream)
m_Snapshot.ReadBuffer(reader, snapshotStream);
m_Snapshot.ReadIndex(reader);
- m_Snapshot.ReadSpawns(reader);
m_Snapshot.ReadAcks(clientId, m_ClientData[clientId], reader, GetConnectionRtt(clientId));
+ m_Snapshot.ReadSpawns(reader);
sentinel = reader.ReadUInt16();
if (sentinel != SentinelAfter)