diff --git a/RLBotCS/ManagerTools/CircularBuffer.cs b/RLBotCS/ManagerTools/CircularBuffer.cs index e7042b0..5bafcb5 100644 --- a/RLBotCS/ManagerTools/CircularBuffer.cs +++ b/RLBotCS/ManagerTools/CircularBuffer.cs @@ -2,11 +2,11 @@ namespace RLBotCS.ManagerTools; public class CircularBuffer { - private int _startIndex; - private int _currentIndex; + private int _size = 0; + private int _currentIndex = 0; private readonly T[] _buffer; - public int Count => (_currentIndex - _startIndex + _buffer.Length) % _buffer.Length; + public int Count => _buffer.Length; public CircularBuffer(int capacity) { @@ -16,16 +16,13 @@ public CircularBuffer(int capacity) public void AddLast(T item) { _buffer[_currentIndex] = item; - - // continuously overwrite the oldest item once full + _size = Math.Max(_currentIndex + 1, _size); _currentIndex = (_currentIndex + 1) % _buffer.Length; - if (_currentIndex == _startIndex) - _startIndex = (_startIndex + 1) % _buffer.Length; } public IEnumerable Iter() { - for (int i = _startIndex; i != _currentIndex; i = (i + 1) % _buffer.Length) - yield return _buffer[i]; + for (int i = 0; i < Math.Min(_buffer.Length, _size); i++) + yield return _buffer[(i + _currentIndex) % _buffer.Length]; } } diff --git a/RLBotCS/ManagerTools/PerfMonitor.cs b/RLBotCS/ManagerTools/PerfMonitor.cs index f8bec8c..5de9717 100644 --- a/RLBotCS/ManagerTools/PerfMonitor.cs +++ b/RLBotCS/ManagerTools/PerfMonitor.cs @@ -1,5 +1,6 @@ using Bridge.State; using RLBot.Flat; +using Deltas = (float GameTimeDelta, float ArrivalDelta); namespace RLBotCS.ManagerTools; @@ -25,13 +26,13 @@ public class PerfMonitor B = 0, }; - private readonly CircularBuffer _rlbotSamples = new(_maxSamples); + private readonly CircularBuffer _rlbotSamples = new(_maxSamples); private readonly SortedDictionary> _samples = new(); private float time = 0; - public void AddRLBotSample(float timeDiff) + public void AddRLBotSample(Deltas deltas) { - _rlbotSamples.AddLast(timeDiff); + _rlbotSamples.AddLast(deltas); } public void AddSample(string name, bool gotInput) @@ -49,6 +50,20 @@ public void RemoveBot(string name) _samples.Remove(name); } + public static float GetPercentile(IEnumerable data, float p) + { + var sorted = data.OrderBy(x => x).ToList(); + int n = sorted.Count; + if (n == 0) + return default; + + double rank = p * (n - 1); + int lower = (int)Math.Floor(rank); + int upper = (int)Math.Ceiling(rank); + + return (float)(sorted[lower] + (rank - lower) * (sorted[upper] - sorted[lower])); + } + public void RenderSummary(Rendering rendering, GameState gameState, float deltaTime) { time += deltaTime; @@ -56,11 +71,28 @@ public void RenderSummary(Rendering rendering, GameState gameState, float deltaT return; time = 0; - float averageTimeDiff = _rlbotSamples.Count > 0 ? _rlbotSamples.Iter().Average() : 1; - float timeDiffPercentage = 1 / (120f * averageTimeDiff); + var arrivalDeltas = _rlbotSamples.Iter().Select(t => t.ArrivalDelta); + var gameTimeDeltas = _rlbotSamples.Iter().Select(t => t.GameTimeDelta); + + float averageTickDelta = gameTimeDeltas.Sum() / _maxSamples; + float averageTickRate = 1f / averageTickDelta; + + // Find deltas larger than expected at 60hz, allowing 10% margin + float misses60 = arrivalDeltas.Count(d => (d - (1f / 60f)) > (0.1f / 60f)); + + // Find deltas larger than expected at 120hz, allowing 10% margin + float misses120 = arrivalDeltas.Count(d => (d - (1f / 120f)) > (0.1f / 120f)); - string message = $"RLBot: {timeDiffPercentage * 100:0.0}%"; - bool shouldRender = timeDiffPercentage < 0.9999 || timeDiffPercentage > 1.0001; + string message = $""" + RLBot @ {averageTickRate:0}hz {(1f - misses60 / 120f) * 100f:0}%|{( + 1f - misses120 / 120f + ) * 100f:0}% + p95 {GetPercentile(arrivalDeltas, 0.95f) * 1000f:0.0}ms p99 {GetPercentile( + arrivalDeltas, + 0.99f + ) * 1000f:0.0}ms + """; + bool shouldRender = misses120 > 1; foreach (var (name, samples) in _samples) { diff --git a/RLBotCS/Server/BridgeHandler.cs b/RLBotCS/Server/BridgeHandler.cs index c8feb7e..5c5c5a5 100644 --- a/RLBotCS/Server/BridgeHandler.cs +++ b/RLBotCS/Server/BridgeHandler.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Threading.Channels; using Bridge.Conversion; using Bridge.TCP; @@ -57,6 +58,7 @@ private async Task HandleServer() _context.Logger.LogInformation("Connected to Rocket League"); bool isFirstTick = true; + long lastNewTickTimestamp = Stopwatch.GetTimestamp(); await foreach (var messageClump in _context.Messenger.ReadAllAsync()) { @@ -95,7 +97,15 @@ private async Task HandleServer() _context.ticksSinceMapLoad += 1; if (timeAdvanced) - _context.PerfMonitor.AddRLBotSample(deltaTime); + { + // Only track ticks where time advanced, the api actually sends more + // clumps, but we don't care about those. + float arrivalDeltaTime = (float) + Stopwatch.GetElapsedTime(lastNewTickTimestamp).TotalSeconds; + lastNewTickTimestamp = Stopwatch.GetTimestamp(); + + _context.PerfMonitor.AddRLBotSample((deltaTime, arrivalDeltaTime)); + } ConsiderDistributingPacket(_context, timeAdvanced);