From 2516c5a7980a8c743b6920134660376f2fb71d2b Mon Sep 17 00:00:00 2001 From: atlimit8 Date: Wed, 28 May 2025 04:51:47 -0500 Subject: [PATCH] Collect and show perf sample totals in widget Both the rolling and cumulative active (not paused) means are shown. --- OpenRA.Game/Game.cs | 15 +-- OpenRA.Game/Support/PerfHistory.cs | 4 +- OpenRA.Game/Support/PerfItem.cs | 17 ++- OpenRA.Mods.Common/Widgets/PerfGraphWidget.cs | 113 +++++++++++++++++- 4 files changed, 138 insertions(+), 11 deletions(-) diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index ee58439e50..ff0dd75ad8 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -661,7 +661,7 @@ namespace OpenRA world.Tick(); - PerfHistory.Tick(); + PerfHistory.Tick(!world.Paused); } // Wait until we have done our first world Tick before TickRendering @@ -759,12 +759,13 @@ namespace OpenRA } } - PerfHistory.Items["render"].Tick(); - PerfHistory.Items["batches"].Tick(); - PerfHistory.Items["render_world"].Tick(); - PerfHistory.Items["render_widgets"].Tick(); - PerfHistory.Items["render_flip"].Tick(); - PerfHistory.Items["terrain_lighting"].Tick(); + var isActive = !(worldRenderer?.World.Paused ?? true); + PerfHistory.Items["render"].Tick(isActive); + PerfHistory.Items["batches"].Tick(isActive); + PerfHistory.Items["render_world"].Tick(isActive); + PerfHistory.Items["render_widgets"].Tick(isActive); + PerfHistory.Items["render_flip"].Tick(isActive); + PerfHistory.Items["terrain_lighting"].Tick(isActive); } static void Loop() diff --git a/OpenRA.Game/Support/PerfHistory.cs b/OpenRA.Game/Support/PerfHistory.cs index 39fb458fa4..31b22b02f9 100644 --- a/OpenRA.Game/Support/PerfHistory.cs +++ b/OpenRA.Game/Support/PerfHistory.cs @@ -41,11 +41,11 @@ namespace OpenRA.Support Items[item].Val += x; } - public static void Tick() + public static void Tick(bool gameIsActive = true) { foreach (var item in Items.Values) if (item.HasNormalTick) - item.Tick(); + item.Tick(gameIsActive); } public static void Reset() diff --git a/OpenRA.Game/Support/PerfItem.cs b/OpenRA.Game/Support/PerfItem.cs index c88a7d055c..0d984d50ce 100644 --- a/OpenRA.Game/Support/PerfItem.cs +++ b/OpenRA.Game/Support/PerfItem.cs @@ -22,6 +22,8 @@ namespace OpenRA.Support public double Val = 0.0; int head = 1, tail = 0; public bool HasNormalTick = true; + public double ActiveGameTotal { get; private set; } = 0.0; + public int ActiveGameTotalSamples { get; private set; } = 0; public PerfItem(string name, Color c) { @@ -29,9 +31,15 @@ namespace OpenRA.Support C = c; } - public void Tick() + public void Tick(bool gameIsActive = true) { samples[head++] = Val; + if (gameIsActive) + { + ActiveGameTotal += Val; + ActiveGameTotalSamples++; + } + if (head == samples.Length) head = 0; if (head == tail && ++tail == samples.Length) tail = 0; Val = 0.0; @@ -63,6 +71,11 @@ namespace OpenRA.Support return i == 0 ? sum : sum / i; } + public double ActiveGameTotalAverage() + { + return ActiveGameTotal / ActiveGameTotalSamples; + } + public double LastValue { get @@ -78,6 +91,8 @@ namespace OpenRA.Support head = 1; tail = 0; Val = 0.0; + ActiveGameTotal = 0.0; + ActiveGameTotalSamples = 0; } } } diff --git a/OpenRA.Mods.Common/Widgets/PerfGraphWidget.cs b/OpenRA.Mods.Common/Widgets/PerfGraphWidget.cs index 007d0f1116..c04df9e4e9 100644 --- a/OpenRA.Mods.Common/Widgets/PerfGraphWidget.cs +++ b/OpenRA.Mods.Common/Widgets/PerfGraphWidget.cs @@ -9,7 +9,10 @@ */ #endregion +using System; using System.Linq; +using System.Text; +using OpenRA.Graphics; using OpenRA.Primitives; using OpenRA.Support; using OpenRA.Widgets; @@ -18,6 +21,92 @@ namespace OpenRA.Mods.Common.Widgets { public class PerfGraphWidget : Widget { + readonly int dotWidth; + readonly int nextDotAdvance; + readonly Cache textWidthCache; + readonly SpriteFont font = Game.Renderer.Fonts["Tiny"]; + StringBuilder builder = null; + + public PerfGraphWidget() + { + textWidthCache = new(GetTextWidth); + dotWidth = textWidthCache["."]; + nextDotAdvance = textWidthCache[".."] - dotWidth; + } + + int GetTextWidth(string text) => font.Measure(text).X; + + public void SixCharacterFormatFloat(StringBuilder output, double value) + { + // Try to keep the text at 6 characters long to align columns. + if (double.IsNaN(value)) + { + output.Append("NaN "); + return; + } + + if (value < 0) + { + output.Append("<0 "); + return; + } + + value += 0.000005; // Do normal rounding instead of floor/trucate; + var intValue = (int)value; + if (intValue >= 1000000) + { + output.Append("TooBig"); + return; + } + + var partValue = (int)value; + var digit = partValue / 1000000; + if (intValue >= 1000000) + output.Append((char)(digit + '0')); + + // Append the whole part. + for (var p = 1000000; p >= 10;) + { + var n = p / 10; + + // Skip leading zeros. + if (intValue >= n) + { + partValue -= digit * p; + digit = partValue / n; + output.Append((char)(digit + '0')); + } + + p = n; + } + + // Check for room for the '.'. + if (intValue >= 1000000) + return; + + if (intValue < 100000) + output.Append('.'); + + if (intValue >= 10000) + return; + + // Up to 5 fractional digits may be appended while keeping the total characters at 6. + var fractionValue = (int)(value * 100000) - 100000 * intValue; + for (var p = 10000; ;) + { + digit = fractionValue / p; + output.Append((char)(digit + '0')); + var n = p / 10; + + // Stop if reached 6 characters total. + if (intValue >= n) + return; + + fractionValue -= digit * p; + p = n; + } + } + public override void Draw() { var cr = Game.Renderer.RgbaColorRenderer; @@ -56,9 +145,31 @@ namespace OpenRA.Mods.Common.Widgets } k = 0; + var maxExtraDotSpace = 0; + var maxNameLength = 0; foreach (var item in PerfHistory.Items.Values) { - Game.Renderer.Fonts["Tiny"].DrawText(item.Name, new float2(rect.Left, rect.Top) + new float2(18, 10 * k - 3), Color.White); + maxExtraDotSpace = Math.Max(maxExtraDotSpace, textWidthCache[item.Name]); + maxNameLength = Math.Max(maxNameLength, item.Name.Length); + } + + builder ??= new StringBuilder(Math.Max(20, maxExtraDotSpace + 3)); + maxExtraDotSpace = Math.Max(0, maxExtraDotSpace - dotWidth); // First dot is always printed. + var columnTwoStart = maxExtraDotSpace + 3 * nextDotAdvance + 2; + + foreach (var item in PerfHistory.Items.Values) + { + var nameWidth = textWidthCache[item.Name]; + var dotsNeeded = (maxExtraDotSpace - nameWidth) / nextDotAdvance + 3; // first + 2 extra dots for spacing before numbers. + builder.Append(item.Name); + builder.Append('.', dotsNeeded); + font.DrawText(builder.ToString(), new float2(rect.Left, rect.Top) + new float2(18, 10 * k - 3), Color.White); + builder.Clear(); + SixCharacterFormatFloat(builder, item.Average(Game.Settings.Debug.Samples)); + builder.Append(" : "); + SixCharacterFormatFloat(builder, item.ActiveGameTotalAverage()); + font.DrawText(builder.ToString(), new float2(rect.Left, rect.Top) + new float2(18 + columnTwoStart, 10 * k - 3), Color.White); + builder.Clear(); ++k; } }