From 79fb69c06449d910fdc0b796afd8b0512b790285 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 17 Mar 2021 14:25:36 +0800 Subject: [PATCH] SnapshotBuffer: store snapshots in a list sorted by time; it's also responsible for dropping too old snapshots now. makes NT easier. --- .../Experimental/Oumuamua/OumuamuaBase.cs | 27 ++------- .../Experimental/Oumuamua/SnapshotBuffer.cs | 48 +++++++++++---- .../SnapshotBufferTests.cs | 58 +++++++++++++++++-- 3 files changed, 95 insertions(+), 38 deletions(-) diff --git a/Assets/Mirror/Components/Experimental/Oumuamua/OumuamuaBase.cs b/Assets/Mirror/Components/Experimental/Oumuamua/OumuamuaBase.cs index 3bed918bd..962f94431 100644 --- a/Assets/Mirror/Components/Experimental/Oumuamua/OumuamuaBase.cs +++ b/Assets/Mirror/Components/Experimental/Oumuamua/OumuamuaBase.cs @@ -25,14 +25,6 @@ public abstract class OumuamuaBase : NetworkBehaviour float lastClientSendTime; float lastServerSendTime; - // keep track of last received timestamps and throw out any snapshots - // older than that (according to the article). - // TODO seems like any snapshot that still fits in the buffer before we - // process it seems worth keeping? - // TODO consider double for precision over days - float serverLastReceivedTimestamp; - float clientLastReceivedTimestamp; - // snapshot timestamps are _remote_ time // we need to interpolate and calculate buffer lifetimes based on it. // -> we don't know remote's current time @@ -63,13 +55,8 @@ void CmdClientToServerSync(Snapshot snapshot) // apply if in client authority mode if (clientAuthority) { - // newer than most recent received snapshot? - if (snapshot.timestamp > serverLastReceivedTimestamp) - { - // add to buffer - serverBuffer.Enqueue(snapshot); - serverLastReceivedTimestamp = snapshot.timestamp; - } + // add to buffer (or drop if older than first element) + serverBuffer.InsertIfNewEnough(snapshot); } } @@ -80,13 +67,8 @@ void RpcServerToClientSync(Snapshot snapshot) // apply for all objects except local player with authority if (!IsClientWithAuthority) { - // newer than most recent received snapshot? - if (snapshot.timestamp > clientLastReceivedTimestamp) - { - // add to buffer - clientBuffer.Enqueue(snapshot); - clientLastReceivedTimestamp = snapshot.timestamp; - } + // add to buffer (or drop if older than first element) + clientBuffer.InsertIfNewEnough(snapshot); } } @@ -106,7 +88,6 @@ void ApplySnapshots(ref float remoteTime, SnapshotBuffer buffer) { Debug.Log($"{name} snapshotbuffer={buffer.Count}"); - // we buffer snapshots for 'bufferTime' // for example: // * we buffer for 3 x sendInterval = 300ms diff --git a/Assets/Mirror/Components/Experimental/Oumuamua/SnapshotBuffer.cs b/Assets/Mirror/Components/Experimental/Oumuamua/SnapshotBuffer.cs index fcb68778e..58854b5d5 100644 --- a/Assets/Mirror/Components/Experimental/Oumuamua/SnapshotBuffer.cs +++ b/Assets/Mirror/Components/Experimental/Oumuamua/SnapshotBuffer.cs @@ -1,27 +1,52 @@ +// the snapshot buffer's job is to hold for example 100ms worth of snapshots. +// SnapshotBuffer simply wraps a sorted list with some time functions. +// it's nothing special and we could do the time math in NetworkTransform, +// but this way we shield ourselves from life's complexities and it's testable! using System.Collections.Generic; namespace Mirror.Experimental { public class SnapshotBuffer { - Queue queue = new Queue(); + // snapshots sorted by timestamp + // in the original article, glenn fiedler drops any snapshots older than + // the last received snapshot. + // -> instead, we insert into a sorted buffer + // -> the higher the buffer information density, the better + // -> we still drop anything older than the first element in the buffer + SortedList list = new SortedList(); - public void Enqueue(Snapshot snapshot) => queue.Enqueue(snapshot); + // insert a snapshot if it's new enough. + // sorts it into the right position. + public void InsertIfNewEnough(Snapshot snapshot) + { + // drop it if it's older than the first snapshot + if (list.Count > 0 && + list.Values[0].timestamp > snapshot.timestamp) + { + return; + } + + // otherwise sort it into the list + list.Add(snapshot.timestamp, snapshot); + } // dequeue the first snapshot if it's older enough. // for example, currentTime = 100, bufferInterval = 0.3 // so any snapshot before time = 99.7 public bool DequeueIfOldEnough(float currentTime, float bufferInterval, out Snapshot snapshot) { - if (queue.Count > 0) + if (list.Count > 0) { // snapshot needs to be older than currentTime - bufferTime float thresholdTime = currentTime - bufferInterval; - // peek and compare time - if (queue.Peek().timestamp <= thresholdTime) + // compare time of first entry (oldest snapshot) + Snapshot first = list.Values[0]; + if (first.timestamp <= thresholdTime) { - snapshot = queue.Dequeue(); + snapshot = first; + list.RemoveAt(0); return true; } } @@ -32,9 +57,9 @@ public bool DequeueIfOldEnough(float currentTime, float bufferInterval, out Snap // peek public bool Peek(out Snapshot snapshot) { - if (queue.Count > 0) + if (list.Count > 0) { - snapshot = queue.Peek(); + snapshot = list.Values[0]; return true; } snapshot = default; @@ -42,8 +67,11 @@ public bool Peek(out Snapshot snapshot) } // count queue size independent of time - public int Count => queue.Count; + public int Count => list.Count; - public void Clear() => queue.Clear(); + // get all snapshots. useful for testing. + public IList All() => list.Values; + + public void Clear() => list.Clear(); } } diff --git a/Assets/Mirror/Tests/Editor/SnapshotInterpolation/SnapshotBufferTests.cs b/Assets/Mirror/Tests/Editor/SnapshotInterpolation/SnapshotBufferTests.cs index 899bda211..a0f791be1 100644 --- a/Assets/Mirror/Tests/Editor/SnapshotInterpolation/SnapshotBufferTests.cs +++ b/Assets/Mirror/Tests/Editor/SnapshotInterpolation/SnapshotBufferTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Mirror.Experimental; using NUnit.Framework; @@ -14,18 +15,65 @@ public void SetUp() } [Test] - public void Enqueue() + public void Insert_Empty() { - buffer.Enqueue(new Snapshot()); + buffer.InsertIfNewEnough(new Snapshot()); Assert.That(buffer.Count, Is.EqualTo(1)); } + [Test] + public void Insert_NotNewEnough() + { + // insert snapshot at time = 1 + buffer.InsertIfNewEnough(new Snapshot{timestamp = 1}); + Assert.That(buffer.Count, Is.EqualTo(1)); + + // insert snapshot at time = 0.5 (too old, should be dropped) + buffer.InsertIfNewEnough(new Snapshot{timestamp = 0.5f}); + Assert.That(buffer.Count, Is.EqualTo(1)); + } + + [Test] + public void Insert_NewEnough() + { + // insert snapshot at time = 1 + buffer.InsertIfNewEnough(new Snapshot{timestamp = 1}); + Assert.That(buffer.Count, Is.EqualTo(1)); + + // insert snapshot at time = 1.5 (newer than first = ok) + buffer.InsertIfNewEnough(new Snapshot{timestamp = 1.5f}); + Assert.That(buffer.Count, Is.EqualTo(2)); + } + + [Test] + public void Insert_Inbetween() + { + // insert snapshot at time = 1 + Snapshot first = new Snapshot{timestamp = 1}; + buffer.InsertIfNewEnough(first); + + // insert snapshot at time = 2 + Snapshot last = new Snapshot{timestamp = 2}; + buffer.InsertIfNewEnough(last); + + // insert snapshot at time = 1.5 (inbetween) + Snapshot between = new Snapshot{timestamp = 1.5f}; + buffer.InsertIfNewEnough(between); + + // check if sorted properly + IList all = buffer.All(); + Assert.That(all.Count, Is.EqualTo(3)); + Assert.That(all[0], Is.EqualTo(first)); + Assert.That(all[1], Is.EqualTo(between)); + Assert.That(all[2], Is.EqualTo(last)); + } + [Test] public void Dequeue_NotOldEnough() { // add snapshot at time=1 Snapshot snapshot = new Snapshot{timestamp = 1}; - buffer.Enqueue(snapshot); + buffer.InsertIfNewEnough(snapshot); // dequeue at time = 2 with buffer time = 1.5 // in other words, anything older than 0.5 should dequeue (nothing) @@ -38,7 +86,7 @@ public void Dequeue_OldEnough() { // add snapshot at time=1 Snapshot snapshot = new Snapshot{timestamp = 1}; - buffer.Enqueue(snapshot); + buffer.InsertIfNewEnough(snapshot); // dequeue at time = 2 with buffer time = 0.5 // in other words, anything older than 1.5 should dequeue @@ -50,7 +98,7 @@ public void Dequeue_OldEnough() [Test] public void Clear() { - buffer.Enqueue(new Snapshot()); + buffer.InsertIfNewEnough(new Snapshot()); buffer.Clear(); Assert.That(buffer.Count, Is.EqualTo(0)); }