v0.4.0 b6be3a8

publish_alloc_test.go RawHistoryFolder

1package core
2
3import (
4	"context"
5	"testing"
6	"time"
7
8	"github.com/bnema/neferwl/internal/ports"
9)
10
11// Allocation budgets of one animation frame (step) on two screens of six
12// windows each, with camera and rect motions (position, fade, dim, scale)
13// running, and on one screen with the overview opening (its cards' scale
14// and fade motions running). Every scene sent is freshly built (immutable
15// once sent), so the floor is not 0. Measured: 43 before presizing the scene
16// windows and separators, 36 after, 32 once the fallback timer is made once
17// and reset per frame (the fallback path, step(ctx, nil), re-arms it; the
18// flip path with another screen animating keeps it and costs the same), then
19// 6 once the layouts are built in the owner's reused buffers (layoutInto:
20// the screens' layout, the workspaces' column, row and tile rects) instead of
21// fresh slices. What is left is the scenes themselves (windows, separators,
22// the scene set) and the screen placements. Leaving entries (a closed window
23// still fading) add none: 4, less than a frame with six moving windows
24// (fewer configure targets). The opening overview costs 18 (the stack
25// items, the row map and the neighbor list it rebuilds every frame; its
26// layouts reuse the buffers too).
27const (
28	publishAllocBudget         = 6
29	publishLeavingAllocBudget  = 4
30	publishOverviewAllocBudget = 18
31)
32
33// publishRig is a Core on the real clock (a mock clock would count its own
34// bookkeeping; the fallback timer is then the system one) with screens
35// screens of six windows each. The Slowdown cap keeps every motion running
36// for seconds, so none settles during a measure even under -race or load.
37func publishRig(t *testing.T, screens int) *Core {
38	t.Helper()
39	var cfg ports.Config
40	cfg.Keyboard.CmdKey = "super"
41	cfg.Layout.MaxColumns = 2
42	cfg.Border.Width = 2
43	cfg.Animations.On = true
44	cfg.Animations.Slowdown = 10
45	c, err := New(cfg, Channels{Scenes: make(chan []ports.Scene, 1), Commands: make(chan ports.ClientCommand, 64)})
46	if err != nil {
47		t.Fatal(err)
48	}
49	next := WindowID(1)
50	for i := range screens {
51		c.addScreen(ports.OutputInfo{Name: string(rune('A' + i)), Width: 300, Height: 200})
52	}
53	for _, sc := range c.screens {
54		if sc.name() == "" {
55			continue
56		}
57		for range 6 {
58			sc.mon.AddWindow(next)
59			next++
60		}
61		sc.arrange()
62	}
63	c.refreshShown()
64	return c
65}
66
67// publishStep is one animation frame of only (nil: the fallback timer's,
68// every screen) whose scene is taken from the channel.
69func publishStep(t *testing.T, c *Core, only *screen) func() {
70	ctx := context.Background()
71	return func() {
72		if err := c.step(ctx, only); err != nil {
73			t.Fatal(err)
74		}
75		select {
76		case <-c.ch.Scenes:
77		default:
78			t.Fatal("no scene published")
79		}
80	}
81}
82
83// startRectMotions gives every window of sc a rect motion on each
84// component: position and size, fade, dim and content scale.
85func startRectMotions(c *Core, sc *screen, t0 time.Time) {
86	sc.rects = make(map[WindowID]rectMotion, 6)
87	sp := viewSpring(100, 0)
88	for _, p := range sc.settledLayout {
89		sc.rects[p.ID] = rectMotion{
90			x: c.spring(sp, t0), y: c.spring(sp, t0),
91			w: c.spring(sp, t0), h: c.spring(sp, t0),
92			dx: 100, dy: 100, dw: 100, dh: 100,
93			fade: c.spring(levelSpring(0.5, 0), t0), df: 0.5,
94			dim: c.spring(levelSpring(0.5, 0), t0), ddim: 0.5,
95			scale: true,
96		}
97	}
98	sc.rectsWS = sc.mon.Current()
99}
100
101// checkRunning fails unless every real screen still has six rect motions
102// and its camera motion.
103func checkRunning(t *testing.T, c *Core, when string) {
104	t.Helper()
105	for _, sc := range c.screens {
106		if sc.name() == "" {
107			continue
108		}
109		if len(sc.rects) != 6 || !sc.mon.Current().motion.on || len(sc.shown) < 6 {
110			t.Fatalf("%s: rects %d camera %v shown %d", when, len(sc.rects), sc.mon.Current().motion.on, len(sc.shown))
111		}
112	}
113}
114
115// TestPublishAllocations pins the cost of publishing an animation frame, on
116// the fallback timer (every screen) and on a page flip of one screen while
117// the other still animates (the flip path keeps the timer).
118func TestPublishAllocations(t *testing.T) {
119	c := publishRig(t, 2)
120	t0 := time.Now()
121	for _, sc := range c.screens {
122		if sc.name() == "" {
123			continue
124		}
125		ws := sc.mon.Current()
126		ws.motion = c.spring(viewSpring(100, 0), t0)
127		ws.shift = 100
128		startRectMotions(c, sc, t0)
129	}
130	for _, tc := range []struct {
131		name string
132		only *screen
133	}{{"fallback", nil}, {"flip", c.screens[1]}} {
134		step := publishStep(t, c, tc.only)
135		step()
136		checkRunning(t, c, "setup")
137		if tc.only != nil && !c.animatingOther(tc.only) {
138			t.Fatal("setup: the other screen does not animate")
139		}
140		n := testing.AllocsPerRun(50, step)
141		t.Logf("%s allocs per step = %v", tc.name, n)
142		checkRunning(t, c, "motions settled during the measure")
143		if n > publishAllocBudget {
144			t.Errorf("%s publish allocs per frame = %v, budget %d", tc.name, n, publishAllocBudget)
145		}
146	}
147}
148
149// TestPublishOverviewAllocations pins the cost of publishing while the
150// overview opens: its cards fly from the full rects (scale and fade motions
151// on every card), which guards the reuse of Core.overviewReal.
152func TestPublishOverviewAllocations(t *testing.T) {
153	c := publishRig(t, 1)
154	sc := c.cur()
155	now := time.Now()
156	shots := c.snapshot(now)
157	sc.mon.ToggleOverview()
158	c.transition(shots, now)
159	step := publishStep(t, c, nil)
160	step()
161	if !sc.mon.ov.open || len(c.overviewReal) != 6 || len(sc.rects) == 0 || !c.animating() {
162		t.Fatalf("setup: overview %v, real layouts %d, rects %d, animating %v", sc.mon.ov.open, len(c.overviewReal), len(sc.rects), c.animating())
163	}
164	n := testing.AllocsPerRun(50, step)
165	t.Logf("overview allocs per step = %v", n)
166	if len(sc.rects) == 0 {
167		t.Fatal("card motions settled during the measure")
168	}
169	if n > publishOverviewAllocBudget {
170		t.Errorf("overview publish allocs per frame = %v, budget %d", n, publishOverviewAllocBudget)
171	}
172}
173
174// TestPublishLeavingAllocations pins the cost of a frame with leaving
175// entries on every screen (two of the six windows closed, fading out, the
176// other four with rect motions): the measured 4, under a frame without
177// them (the leaving entries take no configure target).
178func TestPublishLeavingAllocations(t *testing.T) {
179	c := publishRig(t, 2)
180	t0 := time.Now()
181	for _, sc := range c.screens {
182		if sc.name() == "" {
183			continue
184		}
185		c.refreshShown()
186		layout := sc.settledLayout
187		sc.rects = make(map[WindowID]rectMotion, 6)
188		sp := viewSpring(100, 0)
189		for i, p := range layout {
190			if i < 2 {
191				sc.mon.RemoveWindow(p.ID)
192				c.leaveFrom(sc, p, nil, t0)
193				continue
194			}
195			sc.rects[p.ID] = rectMotion{
196				x: c.spring(sp, t0), y: c.spring(sp, t0),
197				w: c.spring(sp, t0), h: c.spring(sp, t0),
198				dx: 100, dy: 100, dw: 100, dh: 100,
199			}
200		}
201		sc.rectsWS = sc.mon.Current()
202	}
203	step := publishStep(t, c, nil)
204	step()
205	for _, sc := range c.screens {
206		if sc.name() == "" {
207			continue
208		}
209		leaving := 0
210		for _, p := range sc.shown {
211			if p.Leaving {
212				leaving++
213			}
214		}
215		if len(sc.rects) != 6 || leaving != 2 || len(sc.shown) != 6 {
216			t.Fatalf("setup: rects %d leaving %d shown %d", len(sc.rects), leaving, len(sc.shown))
217		}
218	}
219	n := testing.AllocsPerRun(50, step)
220	t.Logf("leaving allocs per step = %v", n)
221	for _, sc := range c.screens {
222		if sc.name() != "" && len(sc.rects) != 6 {
223			t.Fatalf("motions settled during the measure: rects %d", len(sc.rects))
224		}
225	}
226	if n > publishLeavingAllocBudget {
227		t.Errorf("publish allocs per frame with leaving entries = %v, budget %d", n, publishLeavingAllocBudget)
228	}
229}

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 25

Tags 10

↑↓ moveenter openesc close