detached 616526c

test(renderer): benchmark fragmented damage planning

commit
616526c7729897d6cd4e07aed08780bd354f5c2c
author
brice · · 2026-07-15 07:18 UTC
parent
ce6cefd
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +45 -0
M pkg/renderer/benchmark_test.go +45 -0
@@ -31,6 +31,51 @@ func BenchmarkRendererFullFrameDraw(b *testing.B) {
3131 b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
3232 }
3333
34+func BenchmarkRendererFragmentedDamage(b *testing.B) {
35+ const (
36+ width = 120
37+ height = 40
38+ )
39+
40+ baseline := NewFrame(width, height)
41+ markBenchmarkFrame(&baseline)
42+ frame := NewFrame(width, height)
43+ markBenchmarkFrame(&frame)
44+
45+ // Create 256 overlapping or vertically adjacent text rectangles in reverse
46+ // row/column order. Canonical damage planning must sort and merge these into
47+ // stable per-row spans before emission.
48+ damage := make([]Damage, 0, 256)
49+ for row := 15; row >= 0; row-- {
50+ for col := 15; col >= 0; col-- {
51+ x, y := col*7, row*2
52+ damage = append(damage, Damage{Kind: DamageText, X: x, Y: y, Width: 8, Height: 2})
53+ for dy := range 2 {
54+ for dx := range 8 {
55+ frame.Set(x+dx, y+dy, Cell{Rune: 'z', Style: DefaultStyle()})
56+ }
57+ }
58+ }
59+ }
60+
61+ r := New(Capabilities{})
62+ b.ReportAllocs()
63+ var outBytes int64
64+ for b.Loop() {
65+ // Each iteration starts with the identical pre-update terminal shadow.
66+ r.replaceShadow(baseline)
67+ out, err := r.Draw(frame, damage)
68+ if err != nil {
69+ b.Fatal(err)
70+ }
71+ if len(out) == 0 {
72+ b.Fatal("expected renderer output")
73+ }
74+ outBytes += int64(len(out))
75+ }
76+ b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
77+}
78+
3479 func BenchmarkRendererScrollFastPath(b *testing.B) {
3580 frame := NewFrame(120, 40)
3681 markBenchmarkFrame(&frame)
@@ -31,6 +31,51 @@ func BenchmarkRendererFullFrameDraw(b *testing.B) {
31 b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")31 b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
32 }32 }
33 33
34+func BenchmarkRendererFragmentedDamage(b *testing.B) {
35+ const (
36+ width = 120
37+ height = 40
38+ )
39+
40+ baseline := NewFrame(width, height)
41+ markBenchmarkFrame(&baseline)
42+ frame := NewFrame(width, height)
43+ markBenchmarkFrame(&frame)
44+
45+ // Create 256 overlapping or vertically adjacent text rectangles in reverse
46+ // row/column order. Canonical damage planning must sort and merge these into
47+ // stable per-row spans before emission.
48+ damage := make([]Damage, 0, 256)
49+ for row := 15; row >= 0; row-- {
50+ for col := 15; col >= 0; col-- {
51+ x, y := col*7, row*2
52+ damage = append(damage, Damage{Kind: DamageText, X: x, Y: y, Width: 8, Height: 2})
53+ for dy := range 2 {
54+ for dx := range 8 {
55+ frame.Set(x+dx, y+dy, Cell{Rune: 'z', Style: DefaultStyle()})
56+ }
57+ }
58+ }
59+ }
60+
61+ r := New(Capabilities{})
62+ b.ReportAllocs()
63+ var outBytes int64
64+ for b.Loop() {
65+ // Each iteration starts with the identical pre-update terminal shadow.
66+ r.replaceShadow(baseline)
67+ out, err := r.Draw(frame, damage)
68+ if err != nil {
69+ b.Fatal(err)
70+ }
71+ if len(out) == 0 {
72+ b.Fatal("expected renderer output")
73+ }
74+ outBytes += int64(len(out))
75+ }
76+ b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
77+}
78+
34 func BenchmarkRendererScrollFastPath(b *testing.B) {79 func BenchmarkRendererScrollFastPath(b *testing.B) {
35 frame := NewFrame(120, 40)80 frame := NewFrame(120, 40)
36 markBenchmarkFrame(&frame)81 markBenchmarkFrame(&frame)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close