SnapshotBuffer: store snapshots in a list sorted by time; it's also responsible for dropping too old snapshots now. makes NT easier.

This commit is contained in:
vis2k 2021-03-17 14:25:36 +08:00
parent 75b8801b67
commit 79fb69c064
3 changed files with 95 additions and 38 deletions

View File

@ -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

View File

@ -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<Snapshot> queue = new Queue<Snapshot>();
// 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<float, Snapshot> list = new SortedList<float, Snapshot>();
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<Snapshot> All() => list.Values;
public void Clear() => list.Clear();
}
}

View File

@ -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<Snapshot> 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));
}