completely remove dynamic adjustment calculation

This commit is contained in:
mischa 2024-09-24 14:39:56 +02:00
parent 143c14471f
commit 22bdb40d4d
7 changed files with 32 additions and 94 deletions

View File

@ -1991,7 +1991,6 @@ public static void OnGUI()
GUILayout.Box($"timeline: {localTimeline:F2}");
GUILayout.Box($"buffer: {snapshots.Count}");
GUILayout.Box($"DriftEMA: {NetworkClient.driftEma.Value:F2}");
GUILayout.Box($"DelTimeEMA: {NetworkClient.deliveryTimeEma.Value:F2}");
GUILayout.Box($"timescale: {localTimescale:F2}");
GUILayout.Box($"BTM: {NetworkClient.bufferTimeMultiplier:F2}"); // current dynamically adjusted multiplier
GUILayout.Box($"RTT: {NetworkTime.rtt * 1000:F0}ms");

View File

@ -47,34 +47,6 @@ public static partial class NetworkClient
// ever add one value.
static ExponentialMovingAverage driftEma;
// dynamic buffer time adjustment //////////////////////////////////////
// dynamically adjusts bufferTimeMultiplier for smooth results.
// to understand how this works, try this manually:
//
// - disable dynamic adjustment
// - set jitter = 0.2 (20% is a lot!)
// - notice some stuttering
// - disable interpolation to see just how much jitter this really is(!)
// - enable interpolation again
// - manually increase bufferTimeMultiplier to 3-4
// ... the cube slows down (blue) until it's smooth
// - with dynamic adjustment enabled, it will set 4 automatically
// ... the cube slows down (blue) until it's smooth as well
//
// note that 20% jitter is extreme.
// for this to be perfectly smooth, set the safety tolerance to '2'.
// but realistically this is not necessary, and '1' is enough.
[Header("Snapshot Interpolation: Dynamic Adjustment")]
[Tooltip("Automatically adjust bufferTimeMultiplier for smooth results.\nSets a low multiplier on stable connections, and a high multiplier on jittery connections.")]
public static bool dynamicAdjustment = true;
[Tooltip("Safety buffer that is always added to the dynamic bufferTimeMultiplier adjustment.")]
public static float dynamicAdjustmentTolerance = 1; // 1 is realistically just fine, 2 is very very safe even for 20% jitter. can be half a frame too. (see above comments)
[Tooltip("Dynamic adjustment is computed over n-second exponential moving average standard deviation.")]
public static int deliveryTimeEmaDuration = 2; // 1-2s recommended to capture average delivery time
static ExponentialMovingAverage deliveryTimeEma; // average delivery time (standard deviation gives average jitter)
// OnValidate: see NetworkClient.cs
// add snapshot & initialize client interpolation time if needed
@ -91,7 +63,6 @@ static void InitTimeInterpolation()
// 1 second holds 'sendRate' worth of values.
// multiplied by emaDuration gives n-seconds.
driftEma = new ExponentialMovingAverage(NetworkServer.sendRate * snapshotSettings.driftEmaDuration);
deliveryTimeEma = new ExponentialMovingAverage(NetworkServer.sendRate);
}
// server sends TimeSnapshotMessage every sendInterval.
@ -129,8 +100,7 @@ public static void OnTimeSnapshot(TimeSnapshot snap)
snapshotSettings.slowdownSpeed,
ref driftEma,
snapshotSettings.catchupNegativeThreshold,
snapshotSettings.catchupPositiveThreshold,
ref deliveryTimeEma);
snapshotSettings.catchupPositiveThreshold);
inserted= true;

View File

@ -83,8 +83,7 @@ public void OnTimeSnapshot(TimeSnapshot snapshot)
NetworkClient.snapshotSettings.slowdownSpeed,
ref driftEma,
NetworkClient.snapshotSettings.catchupNegativeThreshold,
NetworkClient.snapshotSettings.catchupPositiveThreshold,
ref deliveryTimeEma
NetworkClient.snapshotSettings.catchupPositiveThreshold
);
}

View File

@ -152,8 +152,7 @@ public static void InsertAndAdjust<T>(
double slowdownSpeed, // in % [0,1]
ref ExponentialMovingAverage driftEma, // for catchup / slowdown
float catchupNegativeThreshold, // in % of sendInteral (careful, we may run out of snapshots)
float catchupPositiveThreshold, // in % of sendInterval
ref ExponentialMovingAverage deliveryTimeEma) // for dynamic buffer time adjustment
float catchupPositiveThreshold) // in % of sendInterval
where T : Snapshot
{
// first snapshot?
@ -176,33 +175,6 @@ public static void InsertAndAdjust<T>(
// need to handle it silently.
if (InsertIfNotExists(buffer, bufferLimit, snapshot))
{
// dynamic buffer adjustment needs delivery interval jitter
if (buffer.Count >= 2)
{
// note that this is not entirely accurate for scrambled inserts.
//
// we always use the last two, not what we just inserted
// even if we were to use the diff for what we just inserted,
// a scrambled insert would still not be 100% accurate:
// => assume a buffer of AC, with delivery time C-A
// => we then insert B, with delivery time B-A
// => but then technically the first C-A wasn't correct,
// as it would have to be C-B
//
// in practice, scramble is rare and won't make much difference
double previousLocalTime = buffer.Values[buffer.Count - 2].localTime;
double lastestLocalTime = buffer.Values[buffer.Count - 1].localTime;
// this is the delivery time since last snapshot
double localDeliveryTime = lastestLocalTime - previousLocalTime;
// feed the local delivery time to the EMA.
// this is what the original stream did too.
// our final dynamic buffer adjustment is different though.
// we use standard deviation instead of average.
deliveryTimeEma.Add(localDeliveryTime);
}
// adjust timescale to catch up / slow down after each insertion
// because that is when we add new values to our EMA.

View File

@ -82,8 +82,7 @@ public void OnMessage(Snapshot3D snap)
snapshotSettings.slowdownSpeed,
ref driftEma,
snapshotSettings.catchupNegativeThreshold,
snapshotSettings.catchupPositiveThreshold,
ref deliveryTimeEma);
snapshotSettings.catchupPositiveThreshold);
}
void Update()

View File

@ -85,8 +85,7 @@ public void OnMessage(Snapshot3D snap)
snapshotSettings.slowdownSpeed,
ref driftEma,
snapshotSettings.catchupNegativeThreshold,
snapshotSettings.catchupPositiveThreshold,
ref deliveryTimeEma);
snapshotSettings.catchupPositiveThreshold);
}
void Update()

View File

@ -181,8 +181,8 @@ public void InsertTwice()
double localTimescale = 0;
// insert twice
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, snap, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, snap, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, snap, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, snap, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0);
// should only be inserted once
Assert.That(buffer.Count, Is.EqualTo(1));
@ -203,8 +203,8 @@ public void Insert_Sorts()
SimpleSnapshot b = new SimpleSnapshot(3, 0, 43);
// insert in reverse order
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0);
// should be in sorted order
Assert.That(buffer.Count, Is.EqualTo(2));
@ -228,11 +228,11 @@ public void Insert_InitializesLocalTimeline()
SimpleSnapshot b = new SimpleSnapshot(3, 0, 43);
// first insertion should initialize the local timeline to remote time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
// second insertion should not modify the timeline again
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
}
@ -253,13 +253,13 @@ public void Insert_ComputesAverageDrift()
SimpleSnapshot c = new SimpleSnapshot(5, 0, 43);
// insert in order
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
// first insertion initializes localTime to '2'.
@ -285,13 +285,13 @@ public void Insert_ComputesAverageDrift_Scrambled()
SimpleSnapshot c = new SimpleSnapshot(5, 0, 43);
// insert scrambled (not in order)
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
// first insertion initializes localTime to '2'.
@ -322,13 +322,13 @@ public void Insert_ComputesAverageDeliveryInterval()
SimpleSnapshot c = new SimpleSnapshot(5, 6, 43);
// insert in order
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2 - bufferTime)); // initial snapshot - buffer time
// first insertion doesn't compute delivery interval because we need 2 snaps.
@ -358,13 +358,13 @@ public void Insert_ComputesAverageDeliveryInterval_Scrambled()
SimpleSnapshot c = new SimpleSnapshot(5, 6, 43);
// insert in order
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2)); // detect wrong timeline immediately
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2)); // detect wrong timeline immediately
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(2)); // detect wrong timeline immediately
@ -393,13 +393,13 @@ public void Sample()
SimpleSnapshot b = new SimpleSnapshot(20, 0, 43);
SimpleSnapshot c = new SimpleSnapshot(30, 0, 44);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(10-bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(10-bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(10-bufferTime)); // initial snapshot - buffer time
// sample at a time before the first snapshot
@ -437,13 +437,13 @@ public void Step()
SimpleSnapshot b = new SimpleSnapshot(20, 0, 43);
SimpleSnapshot c = new SimpleSnapshot(30, 0, 44);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(10-bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(10-bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(10-bufferTime)); // initial snapshot - buffer time
// step half way to the next snapshot
@ -468,13 +468,13 @@ public void Step_RemovesOld()
SimpleSnapshot c = new SimpleSnapshot(30, 0, 44);
double bufferTime = 30; // don't move timeline until all 3 inserted
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, a, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(10-bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, b, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(10-bufferTime)); // initial snapshot - buffer time
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma);
SnapshotInterpolation.InsertAndAdjust(buffer, bufferLimit, c, ref localTimeline, ref localTimescale, 0, bufferTime, 0.01, 0.01, ref driftEma, 0, 0);
Assert.That(localTimeline, Is.EqualTo(10-bufferTime)); // initial snapshot - buffer time
// step 1.5 snapshots worth, so way past the first one