From d7a45fdfe15abb8d74b78e149beacaf717b57cb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 19:20:11 +0000 Subject: [PATCH] enhance: only visit visible links and dots when rendering the commit graph `Views.CommitGraph.Render` iterated `Links` and `Dots` from the first item on every frame, skipping everything above the viewport, which is O(commits) per frame when scrolled to the bottom of a long history. Both lists are ordered by Y (one dot per commit row, each link ends half a row below its commit), so binary search the first visible item instead. `Paths` keep the linear scan since a path that starts far above the viewport can still cross it. The grayed pens are now created once in `Models.CommitGraph.SetPens` (and the dot fill pen is cached until `DotBrush` changes) instead of being allocated on every frame. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KCxH1zwDXLd9kfAAkcf6PZ --- src/Models/CommitGraph.cs | 4 ++++ src/Views/CommitGraph.cs | 41 +++++++++++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/Models/CommitGraph.cs b/src/Models/CommitGraph.cs index 916a79b1bb..19b9f11914 100644 --- a/src/Models/CommitGraph.cs +++ b/src/Models/CommitGraph.cs @@ -20,6 +20,8 @@ public enum CommitGraphHighlighting public class CommitGraph { public static List Pens { get; } = []; + public static Pen GrayedCurvePen { get; private set; } = null; + public static Pen GrayedDotPen { get; private set; } = null; public static void SetDefaultPens(double thickness = 2) { @@ -33,6 +35,8 @@ public static void SetPens(List colors, double thickness) foreach (var c in colors) Pens.Add(new Pen(c.ToUInt32(), thickness)); + GrayedCurvePen = new Pen(new SolidColorBrush(Colors.Gray, 0.4), thickness); + GrayedDotPen = new Pen(Brushes.Gray, thickness); s_penCount = colors.Count; } diff --git a/src/Views/CommitGraph.cs b/src/Views/CommitGraph.cs index 88a8217c6b..aa2f415707 100644 --- a/src/Views/CommitGraph.cs +++ b/src/Views/CommitGraph.cs @@ -1,4 +1,7 @@ -using Avalonia; +using System; +using System.Collections.Generic; + +using Avalonia; using Avalonia.Controls; using Avalonia.Media; @@ -64,6 +67,9 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang { base.OnPropertyChanged(change); + if (change.Property == DotBrushProperty) + _dotFillPen = null; + if (change.Property == GraphProperty || change.Property == LayoutProperty || change.Property == DotBrushProperty) @@ -72,10 +78,13 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang private void DrawCurves(DrawingContext context, Models.CommitGraph graph, double top, double bottom, double rowHeight) { - var grayedPen = new Pen(new SolidColorBrush(Colors.Gray, 0.4), Models.CommitGraph.Pens[0].Thickness); + var grayedPen = Models.CommitGraph.GrayedCurvePen; - foreach (var link in graph.Links) + // Links are ordered by their end (each link ends half a row below the commit it starts from). + var links = graph.Links; + for (var idx = FindFirstVisible(links, static l => l.End.Y, top, rowHeight); idx < links.Count; idx++) { + var link = links[idx]; var startY = link.Start.Y * rowHeight; var endY = link.End.Y * rowHeight; @@ -168,11 +177,14 @@ private void DrawCurves(DrawingContext context, Models.CommitGraph graph, double private void DrawAnchors(DrawingContext context, Models.CommitGraph graph, double top, double bottom, double rowHeight) { var dotFill = DotBrush; - var dotFillPen = new Pen(dotFill, 2); - var grayedPen = new Pen(Brushes.Gray, Models.CommitGraph.Pens[0].Thickness); + var dotFillPen = _dotFillPen ??= new Pen(dotFill, 2); + var grayedPen = Models.CommitGraph.GrayedDotPen; - foreach (var dot in graph.Dots) + // Dots are ordered by their center (one dot per commit row). + var dots = graph.Dots; + for (var idx = FindFirstVisible(dots, static d => d.Center.Y, top, rowHeight); idx < dots.Count; idx++) { + var dot = dots[idx]; var center = new Point(dot.Center.X, dot.Center.Y * rowHeight); if (center.Y < top) @@ -199,7 +211,24 @@ private void DrawAnchors(DrawingContext context, Models.CommitGraph graph, doubl } } + private static int FindFirstVisible(List items, Func getBottom, double top, double rowHeight) + { + var lo = 0; + var hi = items.Count; + while (lo < hi) + { + var mid = lo + (hi - lo) / 2; + if (getBottom(items[mid]) * rowHeight < top) + lo = mid + 1; + else + hi = mid; + } + + return lo; + } + private Models.CommitGraph _graph = null; private Models.CommitGraphLayout _layout = null; + private Pen _dotFillPen = null; } }