remove SnapshotBuffer. use list directly for easier interpolation.

This commit is contained in:
vis2k 2021-03-17 17:52:39 +08:00
parent 4ed9c6443f
commit 063ae9553a
6 changed files with 40 additions and 210 deletions

View File

@ -3,6 +3,8 @@
// Base class for NetworkTransform and NetworkTransformChild. // Base class for NetworkTransform and NetworkTransformChild.
// => simple unreliable sync without any interpolation for now. // => simple unreliable sync without any interpolation for now.
// => which means we don't need teleport detection either // => which means we don't need teleport detection either
using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace Mirror.Experimental namespace Mirror.Experimental
@ -44,9 +46,26 @@ public abstract class OumuamuaBase : NetworkBehaviour
public int bufferTimeMultiplier = 3; public int bufferTimeMultiplier = 3;
public float bufferTime => sendInterval * bufferTimeMultiplier; public float bufferTime => sendInterval * bufferTimeMultiplier;
// snapshot buffers // snapshots sorted by timestamp
SnapshotBuffer serverBuffer = new SnapshotBuffer(); // in the original article, glenn fiedler drops any snapshots older than
SnapshotBuffer clientBuffer = new SnapshotBuffer(); // 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> serverBuffer = new SortedList<float, Snapshot>();
SortedList<float, Snapshot> clientBuffer = new SortedList<float, Snapshot>();
// insert into snapshot buffer if newer than first entry
static void InsertIfNewEnough(Snapshot snapshot, SortedList<float, Snapshot> buffer)
{
// drop it if it's older than the first snapshot
if (buffer.Count > 0 &&
buffer.Values[0].timestamp > snapshot.timestamp)
return;
// otherwise sort it into the list
buffer.Add(snapshot.timestamp, snapshot);
}
// local authority client sends sync message to server for broadcasting // local authority client sends sync message to server for broadcasting
[Command(channel = Channels.Unreliable)] [Command(channel = Channels.Unreliable)]
@ -56,7 +75,7 @@ void CmdClientToServerSync(Snapshot snapshot)
if (clientAuthority) if (clientAuthority)
{ {
// add to buffer (or drop if older than first element) // add to buffer (or drop if older than first element)
serverBuffer.InsertIfNewEnough(snapshot); InsertIfNewEnough(snapshot, serverBuffer);
} }
} }
@ -68,7 +87,7 @@ void RpcServerToClientSync(Snapshot snapshot)
if (!IsClientWithAuthority) if (!IsClientWithAuthority)
{ {
// add to buffer (or drop if older than first element) // add to buffer (or drop if older than first element)
clientBuffer.InsertIfNewEnough(snapshot); InsertIfNewEnough(snapshot, clientBuffer);
} }
} }
@ -95,7 +114,7 @@ void ApplySnapshot(Snapshot snapshot)
// helper function to apply snapshots. // helper function to apply snapshots.
// we use the same one on server and client. // we use the same one on server and client.
// => called every Update() depending on authority. // => called every Update() depending on authority.
void ApplySnapshots(ref float remoteTime, SnapshotBuffer buffer) void ApplySnapshots(ref float remoteTime, SortedList<float, Snapshot> buffer)
{ {
Debug.Log($"{name} snapshotbuffer={buffer.Count}"); Debug.Log($"{name} snapshotbuffer={buffer.Count}");
@ -120,8 +139,9 @@ void ApplySnapshots(ref float remoteTime, SnapshotBuffer buffer)
if (remoteTime == 0) if (remoteTime == 0)
{ {
// then set it to first snapshot received (if any) // then set it to first snapshot received (if any)
if (buffer.Peek(out Snapshot first)) if (buffer.Count > 0)
{ {
Snapshot first = buffer.Values[0];
remoteTime = first.timestamp; remoteTime = first.timestamp;
Debug.LogWarning("remoteTime initialized to " + first.timestamp); Debug.LogWarning("remoteTime initialized to " + first.timestamp);
} }
@ -134,8 +154,19 @@ void ApplySnapshots(ref float remoteTime, SnapshotBuffer buffer)
// (probably need to speed this up based on buffer size later) // (probably need to speed this up based on buffer size later)
remoteTime += Time.deltaTime; remoteTime += Time.deltaTime;
if (buffer.DequeueIfOldEnough(remoteTime, bufferTime, out Snapshot snapshot)) // apply first snapshot if old enough
ApplySnapshot(snapshot); if (buffer.Count > 0)
{
// snapshot needs to be older than currentTime - bufferTime
float threshold = remoteTime - bufferTime;
Snapshot first = buffer.Values[0];
if (first.timestamp <= threshold)
{
ApplySnapshot(first);
// remove it, now that we have applied it
buffer.RemoveAt(0);
}
}
} }
void Update() void Update()

View File

@ -1,86 +0,0 @@
// the snapshot buffer's job is to hold for example 100ms worth of snapshots.
//
// from the article:
// "What we do is instead of immediately rendering snapshot data received is
// that we buffer snapshots for a short amount of time in an interpolation
// buffer. This interpolation buffer holds on to snapshots for a period of time
// such that you have not only the snapshot you want to render but also,
// statistically speaking, you are very likely to have the next snapshot as
// well."
//
// 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
{
// 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>();
// 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 (list.Count > 0)
{
// snapshot needs to be older than currentTime - bufferTime
float thresholdTime = currentTime - bufferInterval;
// compare time of first entry (oldest snapshot)
Snapshot first = list.Values[0];
if (first.timestamp <= thresholdTime)
{
snapshot = first;
list.RemoveAt(0);
return true;
}
}
snapshot = default;
return false;
}
// peek
public bool Peek(out Snapshot snapshot)
{
if (list.Count > 0)
{
snapshot = list.Values[0];
return true;
}
snapshot = default;
return false;
}
// count queue size independent of time
public int Count => list.Count;
// get all snapshots. useful for testing.
public IList<Snapshot> All() => list.Values;
public void Clear() => list.Clear();
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 889afb8fc95244b0a38f4eefffa7e643
timeCreated: 1615886688

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5eb7a798032d4ae8b5bf22d14a60bcd9
timeCreated: 1615958523

View File

@ -1,106 +0,0 @@
using System.Collections.Generic;
using Mirror.Experimental;
using NUnit.Framework;
namespace Mirror.Tests
{
public class SnapshotBufferTests
{
SnapshotBuffer buffer;
[SetUp]
public void SetUp()
{
buffer = new SnapshotBuffer();
}
[Test]
public void Insert_Empty()
{
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.InsertIfNewEnough(snapshot);
// dequeue at time = 2 with buffer time = 1.5
// in other words, anything older than 0.5 should dequeue (nothing)
bool result = buffer.DequeueIfOldEnough(2, 1.5f, out Snapshot value);
Assert.That(result, Is.False);
}
[Test]
public void Dequeue_OldEnough()
{
// add snapshot at time=1
Snapshot snapshot = new Snapshot{timestamp = 1};
buffer.InsertIfNewEnough(snapshot);
// dequeue at time = 2 with buffer time = 0.5
// in other words, anything older than 1.5 should dequeue
bool result = buffer.DequeueIfOldEnough(2, 0.5f, out Snapshot value);
Assert.That(result, Is.True);
Assert.That(value, Is.EqualTo(snapshot));
}
[Test]
public void Clear()
{
buffer.InsertIfNewEnough(new Snapshot());
buffer.Clear();
Assert.That(buffer.Count, Is.EqualTo(0));
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 9df6ec4155e2423e8b95a49f4d07add4
timeCreated: 1615958523