From 9e8f23d79a666ddcf7fd7d7932372a9e2117b881 Mon Sep 17 00:00:00 2001 From: swz-git Date: Tue, 10 Mar 2026 02:10:19 +0100 Subject: [PATCH 1/3] fix circularbuffer --- RLBotCS/ManagerTools/CircularBuffer.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) 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]; } } From 0c3a45a2d46e1d66fcfa3e73ff96d6e38936697d Mon Sep 17 00:00:00 2001 From: swz-git Date: Wed, 11 Mar 2026 19:22:05 +0100 Subject: [PATCH 2/3] More detailed perf monitor --- RLBotCS/ManagerTools/PerfMonitor.cs | 46 ++++++++++++++++++++++++----- RLBotCS/Server/BridgeHandler.cs | 11 ++++++- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/RLBotCS/ManagerTools/PerfMonitor.cs b/RLBotCS/ManagerTools/PerfMonitor.cs index f8bec8c..4431059 100644 --- a/RLBotCS/ManagerTools/PerfMonitor.cs +++ b/RLBotCS/ManagerTools/PerfMonitor.cs @@ -1,6 +1,8 @@ using Bridge.State; using RLBot.Flat; +using Deltas = (float GameTimeDelta, float ArrivalDelta); + namespace RLBotCS.ManagerTools; public class PerfMonitor @@ -25,13 +27,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 +51,19 @@ 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); - string message = $"RLBot: {timeDiffPercentage * 100:0.0}%"; - bool shouldRender = timeDiffPercentage < 0.9999 || timeDiffPercentage > 1.0001; + 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 @ {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..951c5c1 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,14 @@ 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); From 4d94956971c96b28f022c2fbb00d4e330987cb99 Mon Sep 17 00:00:00 2001 From: swz-git Date: Wed, 11 Mar 2026 19:37:33 +0100 Subject: [PATCH 3/3] format --- RLBotCS/ManagerTools/PerfMonitor.cs | 24 ++++++++++++------------ RLBotCS/Server/BridgeHandler.cs | 3 ++- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/RLBotCS/ManagerTools/PerfMonitor.cs b/RLBotCS/ManagerTools/PerfMonitor.cs index 4431059..5de9717 100644 --- a/RLBotCS/ManagerTools/PerfMonitor.cs +++ b/RLBotCS/ManagerTools/PerfMonitor.cs @@ -1,6 +1,5 @@ using Bridge.State; using RLBot.Flat; - using Deltas = (float GameTimeDelta, float ArrivalDelta); namespace RLBotCS.ManagerTools; @@ -55,7 +54,8 @@ public static float GetPercentile(IEnumerable data, float p) { var sorted = data.OrderBy(x => x).ToList(); int n = sorted.Count; - if (n == 0) return default; + if (n == 0) + return default; double rank = p * (n - 1); int lower = (int)Math.Floor(rank); @@ -71,7 +71,6 @@ public void RenderSummary(Rendering rendering, GameState gameState, float deltaT return; time = 0; - var arrivalDeltas = _rlbotSamples.Iter().Select(t => t.ArrivalDelta); var gameTimeDeltas = _rlbotSamples.Iter().Select(t => t.GameTimeDelta); @@ -79,19 +78,20 @@ public void RenderSummary(Rendering rendering, GameState gameState, float deltaT 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)); + 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)); + float misses120 = arrivalDeltas.Count(d => (d - (1f / 120f)) > (0.1f / 120f)); 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 - """; + 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 951c5c1..5c5c5a5 100644 --- a/RLBotCS/Server/BridgeHandler.cs +++ b/RLBotCS/Server/BridgeHandler.cs @@ -100,7 +100,8 @@ private async Task HandleServer() { // 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; + float arrivalDeltaTime = (float) + Stopwatch.GetElapsedTime(lastNewTickTimestamp).TotalSeconds; lastNewTickTimestamp = Stopwatch.GetTimestamp(); _context.PerfMonitor.AddRLBotSample((deltaTime, arrivalDeltaTime));