Prediction.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. // standalone, easy to test algorithms for prediction
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace Mirror
  5. {
  6. // prediction may capture Rigidbody3D/2D/etc. state
  7. // have a common interface.
  8. public interface PredictedState
  9. {
  10. double timestamp { get; }
  11. // predicted states should have absolute and delta values, for example:
  12. // Vector3 position;
  13. // Vector3 positionDelta; // from last to here
  14. // when inserting a correction between this one and the one before,
  15. // we need to adjust the delta:
  16. // positionDelta *= multiplier;
  17. void AdjustDeltas(float multiplier);
  18. }
  19. public static class Prediction
  20. {
  21. // get the two states closest to a given timestamp.
  22. // those can be used to interpolate the exact state at that time.
  23. public static bool Sample<T>(
  24. SortedList<double, T> history,
  25. double timestamp, // current server time
  26. out T before,
  27. out T after,
  28. out double t) // interpolation factor
  29. {
  30. before = default;
  31. after = default;
  32. t = 0;
  33. // can't sample an empty history
  34. // interpolation needs at least two entries.
  35. // can't Lerp(A, A, 1.5). dist(A, A) * 1.5 is always 0.
  36. if (history.Count < 2) {
  37. return false;
  38. }
  39. // older than oldest
  40. if (timestamp < history.Keys[0]) {
  41. return false;
  42. }
  43. // iterate through the history
  44. // TODO this needs to be faster than O(N)
  45. // search around that area.
  46. // should be O(1) most of the time, unless sampling was off.
  47. KeyValuePair<double, T> prev = new KeyValuePair<double, T>();
  48. foreach (KeyValuePair<double, T> entry in history) {
  49. // exact match?
  50. if (timestamp == entry.Key) {
  51. before = entry.Value;
  52. after = entry.Value;
  53. t = Mathd.InverseLerp(entry.Key, entry.Key, timestamp);
  54. return true;
  55. }
  56. // did we check beyond timestamp? then return the previous two.
  57. if (entry.Key > timestamp) {
  58. before = prev.Value;
  59. after = entry.Value;
  60. t = Mathd.InverseLerp(prev.Key, entry.Key, timestamp);
  61. return true;
  62. }
  63. // remember the last
  64. prev = entry;
  65. }
  66. return false;
  67. }
  68. // when receiving a correction from the server, we want to insert it
  69. // into the client's state history.
  70. // -> if there's already a state at timestamp, replace
  71. // -> otherwise insert and adjust the next state's delta
  72. // TODO test coverage
  73. public static void InsertCorrection<T>(
  74. SortedList<double, T> stateHistory,
  75. int stateHistoryLimit,
  76. T corrected, // corrected state with timestamp
  77. T before, // state in history before the correction
  78. T after) // state in history after the correction
  79. where T: PredictedState
  80. {
  81. // respect the limit
  82. // TODO unit test to check if it respects max size
  83. if (stateHistory.Count >= stateHistoryLimit)
  84. stateHistory.RemoveAt(0);
  85. // insert the corrected state into the history, or overwrite if already exists
  86. stateHistory[corrected.timestamp] = corrected;
  87. // the entry behind the inserted one still has the delta from (before, after).
  88. // we need to correct it to (corrected, after).
  89. //
  90. // for example:
  91. // before: (t=1.0, delta=10, position=10)
  92. // after: (t=3.0, delta=20, position=30)
  93. //
  94. // then we insert:
  95. // corrected: (t=2.5, delta=__, position=25)
  96. //
  97. // previous delta was from t=1.0 to t=3.0 => 2.0
  98. // inserted delta is from t=2.5 to t=3.0 => 0.5
  99. // multiplier is 0.5 / 2.0 = 0.25
  100. // multiply 'after.delta(20)' by 0.25 to get the new 'after.delta(5)
  101. //
  102. // so the new history is:
  103. // before: (t=1.0, delta=10, position=10)
  104. // corrected: (t=2.5, delta=__, position=25)
  105. // after: (t=3.0, delta= 5, position=__)
  106. //
  107. // so when we apply the correction, the new after.position would be:
  108. // corrected.position(25) + after.delta(5) = 30
  109. //
  110. double previousDeltaTime = after.timestamp - before.timestamp; // 3.0 - 1.0 = 2.0
  111. double correctedDeltaTime = after.timestamp - corrected.timestamp; // 3.0 - 2.5 = 0.5
  112. double multiplier = correctedDeltaTime / previousDeltaTime; // 0.5 / 2.0 = 0.25
  113. // recalculate 'after.delta' with the multiplier
  114. after.AdjustDeltas((float)multiplier);
  115. // write the adjusted 'after' value into the history buffer
  116. stateHistory[after.timestamp] = after;
  117. }
  118. }
  119. }