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)}, Options{})
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().view.motion.on || len(sc.shown) < 6 {
110			t.Fatalf("%s: rects %d camera %v shown %d", when, len(sc.rects), sc.mon.Current().view.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.view.motion = c.spring(viewSpring(100, 0), t0)
127		ws.view.off = 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// TestPublishStashSlideAllocations pins the cost of publishing a frame while
150// the stash slides: windows laid out at a fractional offset, each with a
151// partial veil, with the same budget as any other animation frame.
152func TestPublishStashSlideAllocations(t *testing.T) {
153	c := publishRig(t, 1)
154	sc := c.screens[0]
155	ws := sc.mon.Current()
156	for _, id := range []WindowID{2, 3, 4, 5} {
157		ws.FocusID(id)
158		ws.ToggleWindowStash()
159	}
160	sc.arrange()
161	c.refreshShown()
162	ws.stashView.off = 0.5
163	ws.stashView.motion = c.spring(ws.stashSpring(0.5, 0), time.Now())
164	step := publishStep(t, c, nil)
165	step()
166	if !ws.stashView.motion.on || !ws.stashShown() {
167		t.Fatal("setup: no stash slide on screen")
168	}
169	var partial int
170	for _, p := range sc.shown {
171		if p.Veil > 0 && p.Veil < 1 {
172			partial++
173		}
174	}
175	if partial == 0 {
176		t.Fatal("setup: no window draws a partial veil")
177	}
178	n := testing.AllocsPerRun(50, step)
179	t.Logf("stash slide allocs per step = %v", n)
180	if !ws.stashView.motion.on {
181		t.Fatal("the slide settled during the measure")
182	}
183	if n > publishAllocBudget {
184		t.Errorf("stash slide publish allocs per frame = %v, budget %d", n, publishAllocBudget)
185	}
186}
187
188// TestPublishOverviewAllocations pins the cost of publishing while the
189// overview opens: its cards fly from the full rects (scale and fade motions
190// on every card), which guards the reuse of Core.overviewReal. The current
191// workspace is scroll or cascade, alone or between two workspaces with
192// windows (their slots and stacks are laid out every frame too).
193func TestPublishOverviewAllocations(t *testing.T) {
194	for _, overflow := range []Overflow{OverflowScroll, OverflowCascade} {
195		for _, neighbors := range []bool{false, true} {
196			name := string(overflow) + "/alone"
197			if neighbors {
198				name = string(overflow) + "/neighbors"
199			}
200			t.Run(name, func(t *testing.T) {
201				c := publishRig(t, 1)
202				sc := c.cur()
203				m := sc.mon
204				if neighbors {
205					// Windows 1..6 are on workspace 0: move to 1 with a
206					// workspace on each side.
207					m.Focus(1)
208					for id := WindowID(10); id < 16; id++ {
209						m.AddWindow(id)
210					}
211					m.Focus(2)
212					for id := WindowID(20); id < 26; id++ {
213						m.AddWindow(id)
214					}
215					m.Focus(1)
216				}
217				m.Current().Overflow = overflow
218				sc.arrange()
219				c.refreshShown()
220				now := time.Now()
221				shots := c.snapshot(now)
222				m.ToggleOverview()
223				c.transition(shots, now)
224				step := publishStep(t, c, nil)
225				step()
226				want, slots := 6, 1
227				if neighbors {
228					want, slots = 18, 3
229				}
230				if rows, _ := m.overviewRows(); len(rows) != slots {
231					t.Fatalf("setup: %d overview slots, want %d", len(rows), slots)
232				}
233				if !m.ov.open || m.overviewAxis() != m.Current().policy().workspace || len(c.overviewReal) != want || len(sc.rects) == 0 || !c.animating() {
234					t.Fatalf("setup: overview %v, real layouts %d (want %d), rects %d, animating %v", m.ov.open, len(c.overviewReal), want, len(sc.rects), c.animating())
235				}
236				n := testing.AllocsPerRun(50, step)
237				t.Logf("%s overview allocs per step = %v", name, n)
238				if len(sc.rects) == 0 {
239					t.Fatal("card motions settled during the measure")
240				}
241				if n > publishOverviewAllocBudget {
242					t.Errorf("overview publish allocs per frame = %v, budget %d", n, publishOverviewAllocBudget)
243				}
244			})
245		}
246	}
247}
248
249// TestPublishLeavingAllocations pins the cost of a frame with leaving
250// entries on every screen (two of the six windows closed, fading out, the
251// other four with rect motions): the measured 4, under a frame without
252// them (the leaving entries take no configure target).
253func TestPublishLeavingAllocations(t *testing.T) {
254	c := publishRig(t, 2)
255	t0 := time.Now()
256	for _, sc := range c.screens {
257		if sc.name() == "" {
258			continue
259		}
260		c.refreshShown()
261		layout := sc.settledLayout
262		sc.rects = make(map[WindowID]rectMotion, 6)
263		sp := viewSpring(100, 0)
264		for i, p := range layout {
265			if i < 2 {
266				sc.mon.RemoveWindow(p.ID)
267				c.leaveFrom(sc, p, nil, t0)
268				continue
269			}
270			sc.rects[p.ID] = rectMotion{
271				x: c.spring(sp, t0), y: c.spring(sp, t0),
272				w: c.spring(sp, t0), h: c.spring(sp, t0),
273				dx: 100, dy: 100, dw: 100, dh: 100,
274			}
275		}
276		sc.rectsWS = sc.mon.Current()
277	}
278	step := publishStep(t, c, nil)
279	step()
280	for _, sc := range c.screens {
281		if sc.name() == "" {
282			continue
283		}
284		leaving := 0
285		for _, p := range sc.shown {
286			if p.Leaving {
287				leaving++
288			}
289		}
290		if len(sc.rects) != 6 || leaving != 2 || len(sc.shown) != 6 {
291			t.Fatalf("setup: rects %d leaving %d shown %d", len(sc.rects), leaving, len(sc.shown))
292		}
293	}
294	n := testing.AllocsPerRun(50, step)
295	t.Logf("leaving allocs per step = %v", n)
296	for _, sc := range c.screens {
297		if sc.name() != "" && len(sc.rects) != 6 {
298			t.Fatalf("motions settled during the measure: rects %d", len(sc.rects))
299		}
300	}
301	if n > publishLeavingAllocBudget {
302		t.Errorf("publish allocs per frame with leaving entries = %v, budget %d", n, publishLeavingAllocBudget)
303	}
304}

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 26

Tags 10

↑↓ moveenter openesc close