HistoryBounds data structure - wip

This commit is contained in:
mischa 2023-07-24 20:24:46 +08:00
parent 6395b9e637
commit e344091c33
2 changed files with 42 additions and 23 deletions

View File

@ -8,30 +8,50 @@
namespace Mirror namespace Mirror
{ {
public static class HistoryBounds public class HistoryBounds
{
public readonly Queue<Bounds> history; // TODO not public
public int Count => history.Count;
public readonly int limit;
public HistoryBounds(int limit)
{
// initialize queue with maximum capacity to avoid runtime resizing
this.limit = limit;
history = new Queue<Bounds>(limit);
}
public void Reset()
{
history.Clear();
// TODO reset total etc.
}
}
public static class HistoryBoundsAlgo
{ {
// insert current bounds into history. returns new total bounds. // insert current bounds into history. returns new total bounds.
// Queue.Dequeue() always has the oldest bounds. // Queue.Dequeue() always has the oldest bounds.
public static Bounds Insert( public static Bounds Insert(
Queue<Bounds> history, HistoryBounds history,
int limit,
Bounds bounds) Bounds bounds)
{ {
// optimization: only insert if // optimization: only insert if
// remove oldest if limit reached // remove oldest if limit reached
if (history.Count >= limit) if (history.Count >= history.limit)
history.Dequeue(); history.history.Dequeue();
// insert the new bounds // insert the new bounds
history.Enqueue(bounds); history.history.Enqueue(bounds);
// summarize total bounds. // summarize total bounds.
// starting at latest bounds, not at 'new Bounds' because that would // starting at latest bounds, not at 'new Bounds' because that would
// encapsulate (0,0) too. // encapsulate (0,0) too.
// TODO make this not be O(N) // TODO make this not be O(N)
Bounds total = bounds; Bounds total = bounds;
foreach (Bounds b in history) foreach (Bounds b in history.history)
total.Encapsulate(b); total.Encapsulate(b);
return total; return total;

View File

@ -6,13 +6,8 @@ namespace Mirror.Tests.LagCompensationTests
{ {
public class HistoryBoundsTests public class HistoryBoundsTests
{ {
Queue<Bounds> history;
[SetUp] [SetUp]
public void SetUp() public void SetUp() {}
{
history = new Queue<Bounds>();
}
// helper function to construct (min, max) bounds // helper function to construct (min, max) bounds
public static Bounds MinMax(Vector3 min, Vector3 max) public static Bounds MinMax(Vector3 min, Vector3 max)
@ -34,6 +29,8 @@ public static Bounds MinMax(float min, float max) =>
[TestCase(10_000, 64, 8)] [TestCase(10_000, 64, 8)]
public void Benchmark(int iterations, int insertions, int limit) public void Benchmark(int iterations, int insertions, int limit)
{ {
HistoryBounds history = new HistoryBounds(limit);
// always use the same seed so we get the same test. // always use the same seed so we get the same test.
Random.InitState(0); Random.InitState(0);
@ -42,13 +39,13 @@ public void Benchmark(int iterations, int insertions, int limit)
{ {
// each test captures 'insertions' bounds, // each test captures 'insertions' bounds,
// with a history of 'limit' bounds. // with a history of 'limit' bounds.
history.Clear(); history.Reset();
for (int i = 0; i < insertions; ++i) for (int i = 0; i < insertions; ++i)
{ {
float min = Random.Range(-1, 1); float min = Random.Range(-1, 1);
float max = Random.Range(min, 1); float max = Random.Range(min, 1);
Bounds bounds = MinMax(min, max); Bounds bounds = MinMax(min, max);
Bounds total = HistoryBounds.Insert(history, limit, bounds); Bounds total = HistoryBoundsAlgo.Insert(history, bounds);
} }
} }
} }
@ -58,30 +55,31 @@ public void Benchmark(int iterations, int insertions, int limit)
public void Insert_Basic() public void Insert_Basic()
{ {
const int limit = 3; const int limit = 3;
HistoryBounds history = new HistoryBounds(limit);
// insert initial [-1, 1]. // insert initial [-1, 1].
// should calculate new bounds == initial. // should calculate new bounds == initial.
Bounds total = HistoryBounds.Insert(history, limit, MinMax(-1, 1)); Bounds total = HistoryBoundsAlgo.Insert(history, MinMax(-1, 1));
Assert.That(history.Count, Is.EqualTo(1)); Assert.That(history.Count, Is.EqualTo(1));
Assert.That(total, Is.EqualTo(MinMax(-1, 1))); Assert.That(total, Is.EqualTo(MinMax(-1, 1)));
// insert [0, 2] // insert [0, 2]
// should calculate new bounds == [-1, 2]. // should calculate new bounds == [-1, 2].
total = HistoryBounds.Insert(history, limit, MinMax(0, 2)); total = HistoryBoundsAlgo.Insert(history, MinMax(0, 2));
Assert.That(history.Count, Is.EqualTo(2)); Assert.That(history.Count, Is.EqualTo(2));
Assert.That(total, Is.EqualTo(MinMax(-1, 2))); Assert.That(total, Is.EqualTo(MinMax(-1, 2)));
// insert one that's smaller than current bounds [-.5, 0] // insert one that's smaller than current bounds [-.5, 0]
// history needs to contain it even if smaller, because once the oldest // history needs to contain it even if smaller, because once the oldest
// largest one gets removed, this one matters too. // largest one gets removed, this one matters too.
total = HistoryBounds.Insert(history, limit, MinMax(-0.5f, 0)); total = HistoryBoundsAlgo.Insert(history, MinMax(-0.5f, 0));
Assert.That(history.Count, Is.EqualTo(3)); Assert.That(history.Count, Is.EqualTo(3));
Assert.That(total, Is.EqualTo(MinMax(-1, 2))); Assert.That(total, Is.EqualTo(MinMax(-1, 2)));
// insert more than 'limit': [0, 0] // insert more than 'limit': [0, 0]
// the oldest one [-1, 1] should be discarded. // the oldest one [-1, 1] should be discarded.
// new bounds should be [-0.5, 2] // new bounds should be [-0.5, 2]
total = HistoryBounds.Insert(history, limit, MinMax(0, 0)); total = HistoryBoundsAlgo.Insert(history, MinMax(0, 0));
Assert.That(history.Count, Is.EqualTo(3)); Assert.That(history.Count, Is.EqualTo(3));
Assert.That(total, Is.EqualTo(MinMax(-0.5f, 2))); Assert.That(total, Is.EqualTo(MinMax(-0.5f, 2)));
} }
@ -92,28 +90,29 @@ public void Insert_Basic()
public void Insert_Revisit() public void Insert_Revisit()
{ {
const int limit = 3; const int limit = 3;
HistoryBounds history = new HistoryBounds(limit);
// insert initial [-1, 1]. // insert initial [-1, 1].
// should calculate new bounds == initial. // should calculate new bounds == initial.
Bounds total = HistoryBounds.Insert(history, limit, MinMax(-1, 1)); Bounds total = HistoryBoundsAlgo.Insert(history, MinMax(-1, 1));
Assert.That(history.Count, Is.EqualTo(1)); Assert.That(history.Count, Is.EqualTo(1));
Assert.That(total, Is.EqualTo(MinMax(-1, 1))); Assert.That(total, Is.EqualTo(MinMax(-1, 1)));
// insert [0, 2] // insert [0, 2]
// should calculate new bounds == [-1, 2]. // should calculate new bounds == [-1, 2].
total = HistoryBounds.Insert(history, limit, MinMax(0, 2)); total = HistoryBoundsAlgo.Insert(history, MinMax(0, 2));
Assert.That(history.Count, Is.EqualTo(2)); Assert.That(history.Count, Is.EqualTo(2));
Assert.That(total, Is.EqualTo(MinMax(-1, 2))); Assert.That(total, Is.EqualTo(MinMax(-1, 2)));
// visit [-1, 1] again // visit [-1, 1] again
total = HistoryBounds.Insert(history, limit, MinMax(-1, 1)); total = HistoryBoundsAlgo.Insert(history, MinMax(-1, 1));
Assert.That(history.Count, Is.EqualTo(3)); Assert.That(history.Count, Is.EqualTo(3));
Assert.That(total, Is.EqualTo(MinMax(-1, 2))); Assert.That(total, Is.EqualTo(MinMax(-1, 2)));
// insert beyond limit. // insert beyond limit.
// oldest one [-1, 1] should be removed. // oldest one [-1, 1] should be removed.
// total should still include it because we revisited [1, 1]. // total should still include it because we revisited [1, 1].
total = HistoryBounds.Insert(history, limit, MinMax(0, 0)); total = HistoryBoundsAlgo.Insert(history, MinMax(0, 0));
Assert.That(history.Count, Is.EqualTo(3)); Assert.That(history.Count, Is.EqualTo(3));
Assert.That(total, Is.EqualTo(MinMax(-1, 2))); Assert.That(total, Is.EqualTo(MinMax(-1, 2)));
} }