1//go:build linux
2
3package session
4
5import (
6 "time"
7
8 "github.com/bnema/nefergui/internal/render"
9)
10
11// retryInterval bounds how long the owner loop sleeps while a frame is blocked
12// on GPU or buffer readiness that has no event source of its own.
13const retryInterval = 5 * time.Millisecond
14
15// frameSchedule is RunApp's frame state, touched only from the owner loop.
16// A frame is wanted (dirty) until the compositor accepted a commit or the app
17// reported no change. A prepared display list is kept across blocked submits so
18// a retry does not call Draw again. Input, a wake, resize or scale changes ask
19// Draw for a replacement; an unchanged draw retains the unsubmitted frame.
20type frameSchedule struct {
21 dirty bool
22 prepared render.Frame
23 have bool
24 stale bool
25}
26
27// newFrameSchedule starts dirty so the first configured frame is drawn.
28func newFrameSchedule() *frameSchedule { return &frameSchedule{dirty: true} }
29
30// Invalidate asks Draw to refresh the UI before submitting. Keep any held
31// frame until Draw actually replaces it: no-op input must not lose a frame.
32func (f *frameSchedule) Invalidate() {
33 f.dirty = true
34 f.stale = true
35}
36
37// Step attempts one frame. Nothing is drawn unless a frame is wanted and the
38// compositor allows one (ready). Draw runs when no frame is held or it is
39// stale; an unchanged draw keeps a held frame, or goes idle without one.
40// submit reports whether the frame was
41// committed: on false the prepared frame is kept for the next attempt, on true
42// the schedule is clean until the next Invalidate.
43func (f *frameSchedule) Step(
44 ready bool,
45 draw func() (render.Frame, bool, error),
46 submit func(render.Frame) (bool, error),
47) (committed bool, err error) {
48 if !f.dirty || !ready {
49 return false, nil
50 }
51 if !f.have || f.stale {
52 frame, changed, err := draw()
53 if err != nil {
54 return false, err
55 }
56 f.stale = false
57 if changed {
58 f.prepared, f.have = frame, true
59 } else if !f.have {
60 f.dirty = false
61 return false, nil
62 }
63 }
64 committed, err = submit(f.prepared)
65 if err != nil || !committed {
66 return false, err
67 }
68 f.dirty, f.have, f.stale, f.prepared = false, false, false, render.Frame{}
69 return true, nil
70}
71
72// NeedsRetry reports whether the loop must wake itself after retryInterval.
73// gpuPending covers work whose completion only polling can observe. A frame
74// still to be drawn, or one waiting for a frame callback, is woken by events
75// instead, so an idle or compositor-throttled loop never spins.
76func (f *frameSchedule) NeedsRetry(gpuPending, ready bool) bool {
77 return gpuPending || (f.dirty && f.have && ready)
78}