feat/data-dnd e05cb15

release.go RawHistoryFolder

1package wayland
2
3import (
4	"time"
5
6	"github.com/bnema/go-wayland-bindings/server/wayland"
7	"github.com/bnema/neferwl/internal/ports"
8)
9
10// Buffer release. Outputs read client buffers in place: the renderer
11// samples them when it draws, and an output scanning out a client buffer
12// (ADR 006) shows it until the next flip. A replaced wl_buffer therefore
13// goes back to the client (release) only when the output showing its
14// window reports (ports.OutputPresented) that it has a later content of
15// that window and neither shows nor queues the buffer. Outputs keep only
16// the latest content of a window, so they never draw an older buffer
17// again. heldTimeout guards against an output that stopped reporting
18// (switched away, stalled): a buffer it does not scan out or read on a
19// separate child device is released then.
20
21// heldTimeout releases held buffers of an output that stopped reporting.
22const heldTimeout = 100 * time.Millisecond
23
24type heldBuffer struct {
25	res *wayland.Buffer
26	// sync is the buffer's explicit-sync state: its release point is
27	// signalled instead of wl_buffer.release.
28	sync   syncHold
29	window ports.WindowID
30	id     uint64 // DMABuf ID, 0 for wl_shm
31	after  uint64 // the window's content Seq when the buffer was replaced
32	at     time.Time
33}
34
35// releaseBuffer releases a replaced buffer, or holds it until the output
36// showing its window no longer reads it.
37func (s *Server) releaseBuffer(surf *surface, b *wayland.Buffer, sync syncHold) {
38	// An invisible window is held like a shown one: a scanout plane may
39	// still read its buffer until the flip that hides it.
40	window, name := surf.root().windowID(), s.frameOutput(surf)
41	if !b.Resource.Alive() || window == 0 || name == "" {
42		// Not drawn (unmapped, a cursor): nothing reads it.
43		s.release(b, sync)
44		return
45	}
46	h := heldBuffer{res: b, sync: sync, window: window, at: time.Now()}
47	if d, ok := s.buffers[b.Resource].(*dmabufBuffer); ok {
48		h.id = d.buf.ID
49	}
50	h.after = s.contentSeq[window]
51	s.held = append(s.held, h)
52	// Wake the pacer: it releases the buffer on the next output report.
53	s.wakePacer()
54}
55
56// release gives a buffer back: its release point is signalled when it
57// has one (explicit sync), else wl_buffer.release.
58func (s *Server) release(b *wayland.Buffer, sync syncHold) {
59	if sync.release.set() || sync.acquire != nil {
60		s.releaseSync(sync)
61		return
62	}
63	if b != nil && b.Resource.Alive() {
64		b.SendRelease()
65	}
66}
67
68// releaseBufferSync holds only a buffer's explicit-sync points: the same
69// buffer committed again with new points (clients reuse one buffer).
70func (s *Server) releaseBufferSync(surf *surface, sync syncHold) {
71	if !sync.release.set() && sync.acquire == nil {
72		return
73	}
74	window, name := surf.root().windowID(), s.frameOutput(surf)
75	if window == 0 || name == "" {
76		s.releaseSync(sync)
77		return
78	}
79	after := s.contentSeq[window]
80	s.held = append(s.held, heldBuffer{sync: sync, window: window, after: after, at: time.Now()})
81	s.wakePacer()
82}
83
84// releaseHeld records the output reports and releases the buffers no
85// output reads. Every output reads every window's content, so a buffer
86// waits until no live output shows or queues it and all of them have seen
87// a later content of its window (or heldTimeout passed, without child reads).
88// It reports
89// whether buffers are still held.
90func (s *Server) releaseHeld(now time.Time, reports []ports.OutputPresented) bool {
91	for _, r := range reports {
92		s.reports[r.Output] = r
93	}
94	live := s.heldLive[:0]
95	for name := range s.reports {
96		if s.outputByNameExact(name) == nil {
97			delete(s.reports, name)
98		}
99	}
100	for _, r := range s.reports {
101		live = append(live, r)
102	}
103	// keepHeld only reads live: the scratch holds nothing across calls.
104	s.heldLive = live
105	kept := s.held[:0]
106	for _, h := range s.held {
107		if s.bufferReferenced(h.res) || keepHeld(h, live, now) {
108			kept = append(kept, h)
109			continue
110		}
111		s.release(h.res, h.sync)
112	}
113	clear(s.held[len(kept):])
114	s.held = kept
115	clear(live) // the reports carry maps: do not retain them
116	return len(s.held) > 0
117}
118
119// bufferReferenced checks queued, cached and pending commits on the owner
120// goroutine. Never return a buffer still promised to a later commit.
121func (s *Server) bufferReferenced(b *wayland.Buffer) bool {
122	if b == nil {
123		return false
124	}
125	for _, surf := range s.surfaces {
126		if surf.next.attached && sameBuffer(surf.next.buffer, b) {
127			return true
128		}
129		for _, u := range surf.queue {
130			if u.attached && sameBuffer(u.buffer, b) {
131				return true
132			}
133		}
134	}
135	return false
136}
137
138// keepHeld reports whether an output may still read a held buffer: one
139// scans it out or queued it, a child renderer still reads its content, or
140// one has not got a later content of its window yet and heldTimeout has
141// not passed. Child reads are exempt from the timeout until the render
142// finishes or its device shuts down.
143func keepHeld(h heldBuffer, reports []ports.OutputPresented, now time.Time) bool {
144	seen := true
145	for _, r := range reports {
146		if h.id != 0 && (r.Shown == h.id || r.Queued == h.id) {
147			return true
148		}
149		if seq, ok := r.ChildReads[h.window]; ok && seq <= h.after {
150			return true
151		}
152		if r.Seen[h.window] <= h.after {
153			seen = false
154		}
155	}
156	if len(reports) == 0 {
157		seen = false
158	}
159	return !seen && now.Sub(h.at) < heldTimeout
160}

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