mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Merge pull request #4436 from gui-cs/copilot/fix-intermediate-heap-allocations
This commit is contained in:
@@ -27,7 +27,7 @@ public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, st
|
||||
readonly get => _grapheme;
|
||||
set
|
||||
{
|
||||
if (GraphemeHelper.GetGraphemes(value).ToArray().Length > 1)
|
||||
if (GraphemeHelper.GetGraphemeCount (value) > 1)
|
||||
{
|
||||
throw new InvalidOperationException ($"Only a single {nameof (Grapheme)} cluster is allowed per Cell.");
|
||||
}
|
||||
|
||||
@@ -46,4 +46,27 @@ public static class GraphemeHelper
|
||||
yield return element;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of grapheme clusters in a string without allocating intermediate collections.
|
||||
/// </summary>
|
||||
/// <param name="text">The string to count graphemes in.</param>
|
||||
/// <returns>The number of grapheme clusters, or 0 if the string is null or empty.</returns>
|
||||
public static int GetGraphemeCount (string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty (text))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator (text);
|
||||
var count = 0;
|
||||
|
||||
while (enumerator.MoveNext ())
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,15 +211,23 @@ public class LineCanvas : IDisposable
|
||||
{
|
||||
Dictionary<Point, Rune> map = new ();
|
||||
|
||||
List<IntersectionDefinition> intersectionsBufferList = [];
|
||||
|
||||
// walk through each pixel of the bitmap
|
||||
for (int y = inArea.Y; y < inArea.Y + inArea.Height; y++)
|
||||
{
|
||||
for (int x = inArea.X; x < inArea.X + inArea.Width; x++)
|
||||
{
|
||||
IntersectionDefinition [] intersects = _lines
|
||||
.Select (l => l.Intersects (x, y))
|
||||
.OfType<IntersectionDefinition> () // automatically filters nulls and casts
|
||||
.ToArray ();
|
||||
intersectionsBufferList.Clear ();
|
||||
foreach (var line in _lines)
|
||||
{
|
||||
if (line.Intersects (x, y) is { } intersect)
|
||||
{
|
||||
intersectionsBufferList.Add (intersect);
|
||||
}
|
||||
}
|
||||
// Safe as long as the list is not modified while the span is in use.
|
||||
ReadOnlySpan<IntersectionDefinition> intersects = CollectionsMarshal.AsSpan(intersectionsBufferList);
|
||||
|
||||
Rune? rune = GetRuneForIntersects (intersects);
|
||||
|
||||
|
||||
@@ -123,11 +123,31 @@ public class TextFormatter
|
||||
}
|
||||
|
||||
string strings = linesFormatted [line];
|
||||
string[] graphemes = GraphemeHelper.GetGraphemes (strings).ToArray ();
|
||||
|
||||
// Use ArrayPool to avoid per-draw allocations
|
||||
int estimatedCount = strings.Length + 10; // Add buffer for grapheme clusters
|
||||
string [] graphemes = ArrayPool<string>.Shared.Rent (estimatedCount);
|
||||
var graphemeCount = 0;
|
||||
|
||||
// When text is justified, we lost left or right, so we use the direction to align.
|
||||
try
|
||||
{
|
||||
foreach (string grapheme in GraphemeHelper.GetGraphemes (strings))
|
||||
{
|
||||
if (graphemeCount >= graphemes.Length)
|
||||
{
|
||||
// Need larger array (rare case for complex text)
|
||||
string [] larger = ArrayPool<string>.Shared.Rent (graphemes.Length * 2);
|
||||
Array.Copy (graphemes, larger, graphemeCount);
|
||||
ArrayPool<string>.Shared.Return (graphemes, clearArray: true);
|
||||
graphemes = larger;
|
||||
}
|
||||
|
||||
int x = 0, y = 0;
|
||||
graphemes [graphemeCount++] = grapheme;
|
||||
}
|
||||
|
||||
// When text is justified, we lost left or right, so we use the direction to align.
|
||||
|
||||
int x = 0, y = 0;
|
||||
|
||||
// Horizontal Alignment
|
||||
if (Alignment is Alignment.End)
|
||||
@@ -214,7 +234,7 @@ public class TextFormatter
|
||||
{
|
||||
if (isVertical)
|
||||
{
|
||||
y = screen.Bottom - graphemes.Length;
|
||||
y = screen.Bottom - graphemeCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -250,7 +270,7 @@ public class TextFormatter
|
||||
{
|
||||
if (isVertical)
|
||||
{
|
||||
int s = (screen.Height - graphemes.Length) / 2;
|
||||
int s = (screen.Height - graphemeCount) / 2;
|
||||
y = screen.Top + s;
|
||||
}
|
||||
else
|
||||
@@ -292,17 +312,17 @@ public class TextFormatter
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!FillRemaining && idx > graphemes.Length - 1)
|
||||
if (!FillRemaining && idx > graphemeCount - 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ((!isVertical
|
||||
&& (current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset
|
||||
|| (idx < graphemes.Length && graphemes [idx].GetColumns () > screen.Width)))
|
||||
|| (idx < graphemeCount && graphemes [idx].GetColumns () > screen.Width)))
|
||||
|| (isVertical
|
||||
&& ((current > start + size + zeroLengthCount && idx > maxScreen.Top + maxScreen.Height - screen.Y)
|
||||
|| (idx < graphemes.Length && graphemes [idx].GetColumns () > screen.Width))))
|
||||
|| (idx < graphemeCount && graphemes [idx].GetColumns () > screen.Width))))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -317,7 +337,7 @@ public class TextFormatter
|
||||
|
||||
if (isVertical)
|
||||
{
|
||||
if (idx >= 0 && idx < graphemes.Length)
|
||||
if (idx >= 0 && idx < graphemeCount)
|
||||
{
|
||||
text = graphemes [idx];
|
||||
}
|
||||
@@ -368,7 +388,7 @@ public class TextFormatter
|
||||
{
|
||||
driver?.Move (current, y);
|
||||
|
||||
if (idx >= 0 && idx < graphemes.Length)
|
||||
if (idx >= 0 && idx < graphemeCount)
|
||||
{
|
||||
text = graphemes [idx];
|
||||
}
|
||||
@@ -428,15 +448,20 @@ public class TextFormatter
|
||||
current += runeWidth;
|
||||
}
|
||||
|
||||
int nextRuneWidth = idx + 1 > -1 && idx + 1 < graphemes.Length
|
||||
int nextRuneWidth = idx + 1 > -1 && idx + 1 < graphemeCount
|
||||
? graphemes [idx + 1].GetColumns ()
|
||||
: 0;
|
||||
|
||||
if (!isVertical && idx + 1 < graphemes.Length && current + nextRuneWidth > start + size)
|
||||
if (!isVertical && idx + 1 < graphemeCount && current + nextRuneWidth > start + size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<string>.Shared.Return (graphemes, clearArray: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -931,10 +956,30 @@ public class TextFormatter
|
||||
}
|
||||
|
||||
string strings = linesFormatted [line];
|
||||
string [] graphemes = GraphemeHelper.GetGraphemes (strings).ToArray ();
|
||||
|
||||
// Use ArrayPool to avoid per-line allocations
|
||||
int estimatedCount = strings.Length + 10; // Add buffer for grapheme clusters
|
||||
string [] graphemes = ArrayPool<string>.Shared.Rent (estimatedCount);
|
||||
var graphemeCount = 0;
|
||||
|
||||
// When text is justified, we lost left or right, so we use the direction to align.
|
||||
int x = 0, y = 0;
|
||||
try
|
||||
{
|
||||
foreach (string grapheme in GraphemeHelper.GetGraphemes (strings))
|
||||
{
|
||||
if (graphemeCount >= graphemes.Length)
|
||||
{
|
||||
// Need larger array (rare case for complex text)
|
||||
string [] larger = ArrayPool<string>.Shared.Rent (graphemes.Length * 2);
|
||||
Array.Copy (graphemes, larger, graphemeCount);
|
||||
ArrayPool<string>.Shared.Return (graphemes, clearArray: true);
|
||||
graphemes = larger;
|
||||
}
|
||||
|
||||
graphemes [graphemeCount++] = grapheme;
|
||||
}
|
||||
|
||||
// When text is justified, we lost left or right, so we use the direction to align.
|
||||
int x = 0, y = 0;
|
||||
|
||||
switch (Alignment)
|
||||
{
|
||||
@@ -1011,7 +1056,7 @@ public class TextFormatter
|
||||
{
|
||||
// Vertical Alignment
|
||||
case Alignment.End when isVertical:
|
||||
y = screen.Bottom - graphemes.Length;
|
||||
y = screen.Bottom - graphemeCount;
|
||||
|
||||
break;
|
||||
case Alignment.End:
|
||||
@@ -1041,7 +1086,7 @@ public class TextFormatter
|
||||
}
|
||||
case Alignment.Center when isVertical:
|
||||
{
|
||||
int s = (screen.Height - graphemes.Length) / 2;
|
||||
int s = (screen.Height - graphemeCount) / 2;
|
||||
y = screen.Top + s;
|
||||
|
||||
break;
|
||||
@@ -1081,22 +1126,22 @@ public class TextFormatter
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!FillRemaining && idx > graphemes.Length - 1)
|
||||
if (!FillRemaining && idx > graphemeCount - 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ((!isVertical
|
||||
&& (current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset
|
||||
|| (idx < graphemes.Length && graphemes [idx].GetColumns () > screen.Width)))
|
||||
|| (idx < graphemeCount && graphemes [idx].GetColumns () > screen.Width)))
|
||||
|| (isVertical
|
||||
&& ((current > start + size + zeroLengthCount && idx > maxScreen.Top + maxScreen.Height - screen.Y)
|
||||
|| (idx < graphemes.Length && graphemes [idx].GetColumns () > screen.Width))))
|
||||
|| (idx < graphemeCount && graphemes [idx].GetColumns () > screen.Width))))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string text = idx >= 0 && idx < graphemes.Length ? graphemes [idx] : " ";
|
||||
string text = idx >= 0 && idx < graphemeCount ? graphemes [idx] : " ";
|
||||
int runeWidth = GetStringWidth (text, TabWidth);
|
||||
|
||||
if (isVertical)
|
||||
@@ -1116,20 +1161,25 @@ public class TextFormatter
|
||||
|
||||
current += isVertical && runeWidth > 0 ? 1 : runeWidth;
|
||||
|
||||
int nextStringWidth = idx + 1 > -1 && idx + 1 < graphemes.Length
|
||||
int nextStringWidth = idx + 1 > -1 && idx + 1 < graphemeCount
|
||||
? graphemes [idx + 1].GetColumns ()
|
||||
: 0;
|
||||
|
||||
if (!isVertical && idx + 1 < graphemes.Length && current + nextStringWidth > start + size)
|
||||
if (!isVertical && idx + 1 < graphemeCount && current + nextStringWidth > start + size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the line's drawn region to the overall region
|
||||
if (lineWidth > 0 && lineHeight > 0)
|
||||
// Add the line's drawn region to the overall region
|
||||
if (lineWidth > 0 && lineHeight > 0)
|
||||
{
|
||||
drawnRegion.Union (new Rectangle (lineX, lineY, lineWidth, lineHeight));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
drawnRegion.Union (new Rectangle (lineX, lineY, lineWidth, lineHeight));
|
||||
ArrayPool<string>.Shared.Return (graphemes, clearArray: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user