diff --git a/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs b/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs index 661c06318f..78cbb9f6b0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs @@ -472,10 +472,30 @@ internal void ReadSpawns(NetworkReader reader) } } - internal ushort ReadAcks(ulong clientId, ClientData clientData, NetworkReader reader) + internal void ReadAcks(ulong clientId, ClientData clientData, NetworkReader reader, ConnectionRtt connection) { ushort ackSequence = reader.ReadUInt16(); + ushort seqMask = reader.ReadUInt16(); + // process the latest acknowledgment + ProcessSingleAck(ackSequence, clientId, clientData, connection); + + // for each bit in the mask, acknowledge one message before + while (seqMask != 0) + { + ackSequence--; + // extract least bit + if (seqMask % 2 == 1) + { + ProcessSingleAck(ackSequence, clientId, clientData, connection); + } + // move to next bit + seqMask >>= 1; + } + } + + internal void ProcessSingleAck(ushort ackSequence, ulong clientId, ClientData clientData, ConnectionRtt connection) + { // look through the spawns sent foreach (var sent in clientData.SentSpawns) { @@ -529,7 +549,8 @@ internal ushort ReadAcks(ulong clientId, ClientData clientData, NetworkReader re } } - return ackSequence; + // keep track of RTTs, using the sequence number acknowledgement as a marker + connection.NotifyAck(ackSequence, Time.unscaledTime); } /// @@ -564,6 +585,7 @@ internal struct SentSpawn // this struct also stores Despawns, not just Spawns internal ushort SequenceNumber = 0; // the next sequence number to use for this client internal ushort LastReceivedSequence = 0; // the last sequence number received by this client + internal ushort ReceivedSequenceMask = 0; // bitmask of the messages before the last one that we received. internal int NextSpawnIndex = 0; // index of the last spawn sent. Used to cycle through spawns (LRU scheme) internal int NextDespawnIndex = 0; // same as above, but for despawns. @@ -686,8 +708,6 @@ private void SendSnapshot(ulong clientId) m_ConnectionRtts[clientId].NotifySend(m_ClientData[clientId].SequenceNumber, Time.unscaledTime); - // Send the entry index and the buffer where the variables are serialized - var context = m_NetworkManager.MessageQueueContainer.EnterInternalCommandContext( MessageQueueContainer.MessageType.SnapshotData, NetworkChannel.SnapshotExchange, new[] { clientId }, NetworkUpdateLoop.UpdateStage); @@ -698,6 +718,7 @@ private void SendSnapshot(ulong clientId) { var sequence = m_ClientData[clientId].SequenceNumber; + // write the tick and sequence header nonNullContext.NetworkWriter.WriteInt32Packed(m_CurrentTick); nonNullContext.NetworkWriter.WriteUInt16(sequence); @@ -705,6 +726,7 @@ private void SendSnapshot(ulong clientId) using (var writer = PooledNetworkWriter.Get(buffer)) { + // write the snapshot: buffer, index, spawns, despawns writer.WriteUInt16(SentinelBefore); WriteBuffer(buffer); WriteIndex(buffer); @@ -712,6 +734,11 @@ private void SendSnapshot(ulong clientId) WriteAcks(buffer, clientId); writer.WriteUInt16(SentinelAfter); + m_ClientData[clientId].LastReceivedSequence = 0; + + // todo: this is incorrect (well, sub-optimal) + // we should still continue ack'ing past messages, in case this one is dropped + m_ClientData[clientId].ReceivedSequenceMask = 0; m_ClientData[clientId].SequenceNumber++; } } @@ -806,7 +833,9 @@ private void WriteAcks(NetworkBuffer buffer, ulong clientId) { using (var writer = PooledNetworkWriter.Get(buffer)) { + // todo: revisit whether 16-bit is enough for LastReceivedSequence writer.WriteUInt16(m_ClientData[clientId].LastReceivedSequence); + writer.WriteUInt16(m_ClientData[clientId].ReceivedSequenceMask); } } @@ -911,12 +940,6 @@ private void WriteVariableToSnapshot(Snapshot snapshot, INetworkVariable network /// The stream to read from internal void ReadSnapshot(ulong clientId, Stream snapshotStream) { - // poor man packet loss simulation - //if (Random.Range(0, 10) > 5) - //{ - // return; - //} - // todo: temporary hack around bug if (!m_NetworkManager.IsServer) { @@ -937,6 +960,23 @@ internal void ReadSnapshot(ulong clientId, Stream snapshotStream) var sequence = reader.ReadUInt16(); // todo: check we didn't miss any and deal with gaps + + if (m_ClientData[clientId].ReceivedSequenceMask != 0) + { + // since each bit in ReceivedSequenceMask is relative to the last received sequence + // we need to shift all the bits by the difference in sequence + m_ClientData[clientId].ReceivedSequenceMask <<= + (sequence - m_ClientData[clientId].LastReceivedSequence); + } + + if (m_ClientData[clientId].LastReceivedSequence != 0) + { + // because the bit we're adding for the previous ReceivedSequenceMask + // was implicit, it needs to be shift by one less + m_ClientData[clientId].ReceivedSequenceMask += + (ushort)(1 << (ushort)((sequence - 1) - m_ClientData[clientId].LastReceivedSequence)); + } + m_ClientData[clientId].LastReceivedSequence = sequence; var sentinel = reader.ReadUInt16(); @@ -948,10 +988,7 @@ internal void ReadSnapshot(ulong clientId, Stream snapshotStream) m_Snapshot.ReadBuffer(reader, snapshotStream); m_Snapshot.ReadIndex(reader); m_Snapshot.ReadSpawns(reader); - var ackSequence = m_Snapshot.ReadAcks(clientId, m_ClientData[clientId], reader); - - // keep track of RTTs, using the sequence number acknowledgement as a marker - GetConnectionRtt(clientId).NotifyAck(ackSequence, Time.unscaledTime); + m_Snapshot.ReadAcks(clientId, m_ClientData[clientId], reader, GetConnectionRtt(clientId)); sentinel = reader.ReadUInt16(); if (sentinel != SentinelAfter)