Skip to content

Commit

Permalink
perf: Prediction.CorrectHistory removed O(N) insertion. adjusting suc…
Browse files Browse the repository at this point in the history
…cessive values is enough.
  • Loading branch information
miwarnec committed Mar 18, 2024
1 parent a84b786 commit ac87583
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 30 deletions.
13 changes: 9 additions & 4 deletions Assets/Mirror/Core/Prediction/Prediction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,15 @@ public static class Prediction
afterIndex -= 1; // we removed the first value so all indices are off by one now
}

// insert the corrected state into the history, or overwrite if already exists
// SortedList insertions are O(N)!
history[corrected.timestamp] = corrected;
afterIndex += 1; // we inserted the corrected value before the previous index
// PERFORMANCE OPTIMIZATION: avoid O(N) insertion, only readjust all values after.
// the end result is the same since after.delta and after.position are both recalculated.
// it's technically not correct if we were to reconstruct final position from 0..after..end but
// we never do, we only ever iterate from after..end!
//
// insert the corrected state into the history, or overwrite if already exists
// SortedList insertions are O(N)!
// history[corrected.timestamp] = corrected;
// afterIndex += 1; // we inserted the corrected value before the previous index

// the entry behind the inserted one still has the delta from (before, after).
// we need to correct it to (corrected, after).
Expand Down
1 change: 1 addition & 0 deletions Assets/Mirror/Examples/BenchmarkPrediction/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ Predicted:
2024-03-14: 590 FPS Client, 1700 FPS Server // UpdateGhosting() every 4th frame
2024-03-14: 615 FPS Client, 1700 FPS Server // predictedRigidbodyTransform.GetPositionAndRotation()
2024-03-15: 625 FPS Client, 1700 FPS Server // Vector3.MoveTowardsCustom()
2024-03-18: 628 FPS Client, 1700 FPS Server // removed O(N) insertion from CorrectHistory()
55 changes: 29 additions & 26 deletions Assets/Mirror/Tests/Editor/Prediction/PredictionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,10 @@ public void CorrectHistory()
const int historyLimit = 32;
Prediction.CorrectHistory(history, historyLimit, correction, before, after, afterIndex);

// there should be 4 initial + 1 corrected = 5 entries now
Assert.That(history.Count, Is.EqualTo(5));
// PERFORMANCE OPTIMIZATION: nothing is inserted anymore, values are only adjusted.
// there should be 4 initial + 1 corrected = 5 entries now
// Assert.That(history.Count, Is.EqualTo(5));
Assert.That(history.Count, Is.EqualTo(4));

// first entry at t=0 should be unchanged, since we corrected after that one.
Assert.That(history.Keys[0], Is.EqualTo(0));
Expand All @@ -181,42 +183,43 @@ public void CorrectHistory()
Assert.That(history.Values[1].angularVelocity.x, Is.EqualTo(1));
Assert.That(history.Values[1].angularVelocityDelta.x, Is.EqualTo(1));

// third entry at t=1.5 should be the received state.
// absolute values should be the correction, without any deltas since
// server doesn't send those and we don't need them.
Assert.That(history.Keys[2], Is.EqualTo(1.5));
Assert.That(history.Values[2].position.x, Is.EqualTo(1.6f).Within(0.001f));
Assert.That(history.Values[2].positionDelta.x, Is.EqualTo(0));
Assert.That(history.Values[2].velocity.x, Is.EqualTo(1.6f).Within(0.001f));
Assert.That(history.Values[2].velocityDelta.x, Is.EqualTo(0));
Assert.That(history.Values[2].angularVelocity.x, Is.EqualTo(1.6f).Within(0.001f));
Assert.That(history.Values[2].angularVelocityDelta.x, Is.EqualTo(0));
// PERFORMANCE OPTIMIZATION: nothing is inserted anymore, values are only adjusted.
// third entry at t=1.5 should be the received state.
// absolute values should be the correction, without any deltas since
// server doesn't send those and we don't need them.
// Assert.That(history.Keys[2], Is.EqualTo(1.5));
// Assert.That(history.Values[2].position.x, Is.EqualTo(1.6f).Within(0.001f));
// Assert.That(history.Values[2].positionDelta.x, Is.EqualTo(0));
// Assert.That(history.Values[2].velocity.x, Is.EqualTo(1.6f).Within(0.001f));
// Assert.That(history.Values[2].velocityDelta.x, Is.EqualTo(0));
// Assert.That(history.Values[2].angularVelocity.x, Is.EqualTo(1.6f).Within(0.001f));
// Assert.That(history.Values[2].angularVelocityDelta.x, Is.EqualTo(0));

// fourth entry at t=2:
// delta was from t=1.0 @ 1 to t=2.0 @ 2 = 1.0
// we inserted at t=1.5 which is half way between t=1 and t=2.
// the delta at t=1.5 would've been 0.5.
// => the inserted position is at t=1.6
// => add the relative delta of 0.5 = 2.1
Assert.That(history.Keys[3], Is.EqualTo(2.0));
Assert.That(history.Values[3].position.x, Is.EqualTo(2.1).Within(0.001f));
Assert.That(history.Values[3].positionDelta.x, Is.EqualTo(0.5).Within(0.001f));
Assert.That(history.Values[3].velocity.x, Is.EqualTo(2.1).Within(0.001f));
Assert.That(history.Values[3].velocityDelta.x, Is.EqualTo(0.5).Within(0.001f));
Assert.That(history.Values[3].angularVelocity.x, Is.EqualTo(2.1).Within(0.001f));
Assert.That(history.Values[3].angularVelocityDelta.x, Is.EqualTo(0.5));
Assert.That(history.Keys[2], Is.EqualTo(2.0));
Assert.That(history.Values[2].position.x, Is.EqualTo(2.1).Within(0.001f));
Assert.That(history.Values[2].positionDelta.x, Is.EqualTo(0.5).Within(0.001f));
Assert.That(history.Values[2].velocity.x, Is.EqualTo(2.1).Within(0.001f));
Assert.That(history.Values[2].velocityDelta.x, Is.EqualTo(0.5).Within(0.001f));
Assert.That(history.Values[2].angularVelocity.x, Is.EqualTo(2.1).Within(0.001f));
Assert.That(history.Values[2].angularVelocityDelta.x, Is.EqualTo(0.5));

// fifth entry at t=3:
// client moved by a delta of 1 here, and that remains unchanged.
// absolute position was 3.0 but if we apply the delta of 1 to the one before at 2.1,
// we get the new position of 3.1
Assert.That(history.Keys[4], Is.EqualTo(3.0));
Assert.That(history.Values[4].position.x, Is.EqualTo(3.1).Within(0.001f));
Assert.That(history.Values[4].positionDelta.x, Is.EqualTo(1.0).Within(0.001f));
Assert.That(history.Values[4].velocity.x, Is.EqualTo(3.1).Within(0.001f));
Assert.That(history.Values[4].velocityDelta.x, Is.EqualTo(1.0).Within(0.001f));
Assert.That(history.Values[4].angularVelocity.x, Is.EqualTo(3.1).Within(0.001f));
Assert.That(history.Values[4].angularVelocityDelta.x, Is.EqualTo(1.0).Within(0.001f));
Assert.That(history.Keys[3], Is.EqualTo(3.0));
Assert.That(history.Values[3].position.x, Is.EqualTo(3.1).Within(0.001f));
Assert.That(history.Values[3].positionDelta.x, Is.EqualTo(1.0).Within(0.001f));
Assert.That(history.Values[3].velocity.x, Is.EqualTo(3.1).Within(0.001f));
Assert.That(history.Values[3].velocityDelta.x, Is.EqualTo(1.0).Within(0.001f));
Assert.That(history.Values[3].angularVelocity.x, Is.EqualTo(3.1).Within(0.001f));
Assert.That(history.Values[3].angularVelocityDelta.x, Is.EqualTo(1.0).Within(0.001f));
}
}
}

0 comments on commit ac87583

Please sign in to comment.