Browse Source

Fix LineCanvas.GetMap per-pixel allocations by reusing buffer

Applied the same allocation-free pattern from GetCellMap() to GetMap():
- Reuse List<IntersectionDefinition> buffer instead of LINQ .ToArray()
- Use CollectionsMarshal.AsSpan() to create ReadOnlySpan
- Eliminates 1,920+ allocations per border redraw (80x24)
- Reduces from O(pixels) allocations to 1 allocation total

All unit tests pass (12,055 parallelizable + 1,173 non-parallel)

Co-authored-by: tig <[email protected]>
copilot-swe-agent[bot] 1 week ago
parent
commit
a4245bd091
1 changed files with 12 additions and 4 deletions
  1. 12 4
      Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs

+ 12 - 4
Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs

@@ -211,15 +211,23 @@ public class LineCanvas : IDisposable
     {
     {
         Dictionary<Point, Rune> map = new ();
         Dictionary<Point, Rune> map = new ();
 
 
+        List<IntersectionDefinition> intersectionsBufferList = [];
+
         // walk through each pixel of the bitmap
         // walk through each pixel of the bitmap
         for (int y = inArea.Y; y < inArea.Y + inArea.Height; y++)
         for (int y = inArea.Y; y < inArea.Y + inArea.Height; y++)
         {
         {
             for (int x = inArea.X; x < inArea.X + inArea.Width; x++)
             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);
                 Rune? rune = GetRuneForIntersects (intersects);