From 3d381e6e32c939689a05302a1e2e08d6fe3ef354 Mon Sep 17 00:00:00 2001 From: teinarss Date: Mon, 29 Mar 2021 19:20:07 +0200 Subject: [PATCH] Make SpriteFont.Measure take zero allocations --- OpenRA.Game/Exts.cs | 44 ++++++++++++++++++++++++++++++ OpenRA.Game/Graphics/SpriteFont.cs | 30 +++++++++++--------- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/OpenRA.Game/Exts.cs b/OpenRA.Game/Exts.cs index 8fe5fa5f1a..2f1e49c844 100644 --- a/OpenRA.Game/Exts.cs +++ b/OpenRA.Game/Exts.cs @@ -533,6 +533,50 @@ namespace OpenRA return default(T); } + + public static LineSplitEnumerator SplitLines(this string str, char separator) + { + return new LineSplitEnumerator(str.AsSpan(), separator); + } + } + + public ref struct LineSplitEnumerator + { + ReadOnlySpan str; + readonly char separator; + + public LineSplitEnumerator(ReadOnlySpan str, char separator) + { + this.str = str; + this.separator = separator; + Current = default; + } + + public LineSplitEnumerator GetEnumerator() => this; + + public bool MoveNext() + { + var span = str; + + // Reach the end of the string + if (span.Length == 0) + return false; + + var index = span.IndexOf(separator); + if (index == -1) + { + // The remaining string is an empty string + str = ReadOnlySpan.Empty; + Current = span; + return true; + } + + Current = span.Slice(0, index); + str = span.Slice(index + 1); + return true; + } + + public ReadOnlySpan Current { get; private set; } } public static class Enum diff --git a/OpenRA.Game/Graphics/SpriteFont.cs b/OpenRA.Game/Graphics/SpriteFont.cs index e1dbdbf4ba..6a9136aa5f 100644 --- a/OpenRA.Game/Graphics/SpriteFont.cs +++ b/OpenRA.Game/Graphics/SpriteFont.cs @@ -10,7 +10,6 @@ #endregion using System; -using System.Linq; using OpenRA.Primitives; using OpenRA.Support; @@ -21,7 +20,6 @@ namespace OpenRA.Graphics public int TopOffset { get; private set; } readonly int size; readonly SheetBuilder builder; - readonly Func lineWidth; readonly IFont font; readonly Cache glyphs; readonly Cache<(char C, int Radius), Sprite> contrastGlyphs; @@ -43,10 +41,6 @@ namespace OpenRA.Graphics contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph); dilationElements = new Cache(CreateCircularWeightMap); - // PERF: Cache these delegates for Measure calls. - Func characterWidth = character => glyphs[character].Advance; - lineWidth = line => line.Sum(characterWidth) / deviceScale; - // Pre-cache small font sizes so glyphs are immediately available when we need them if (size <= 24) using (new PerfTimer("Precache {0} {1}px".F(name, size))) @@ -238,16 +232,26 @@ namespace OpenRA.Graphics if (string.IsNullOrEmpty(text)) return new int2(0, size); - var lines = text.Split('\n'); - return new int2((int)Math.Ceiling(MaxLineWidth(lines, lineWidth)), lines.Length * size); + var lines = text.SplitLines('\n'); + + var maxWidth = 0f; + var rows = 0; + foreach (var line in lines) + { + rows++; + maxWidth = Math.Max(maxWidth, LineWidth(line)); + } + + return new int2((int)Math.Ceiling(maxWidth), rows * size); } - static float MaxLineWidth(string[] lines, Func lineWidth) + float LineWidth(ReadOnlySpan line) { - var maxWidth = 0f; - foreach (var line in lines) - maxWidth = Math.Max(maxWidth, lineWidth(line)); - return maxWidth; + var result = 0f; + foreach (var c in line) + result += glyphs[c].Advance; + + return result / deviceScale; } GlyphInfo CreateGlyph(char c)