Repository navigation
Expand file tree
/
Copy pathloop.go
More file actions
140 lines (126 loc) · 3.23 KB
/
Copy pathloop.go
File metadata and controls
140 lines (126 loc) · 3.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package mygo
import (
"sync"
)
// mainLoop is a FIFO of functions waiting to run on the main thread. The
// backend drains it (AppHandler.Dispatch) whenever it has been signaled.
type mainLoop struct {
mu sync.Mutex
queue []func()
closed bool
}
var loop mainLoop
// post schedules fn on the main thread. It reports false when the event
// loop has already shut down and fn will never run.
func (l *mainLoop) post(fn func()) bool {
l.mu.Lock()
if l.closed {
l.mu.Unlock()
return false
}
l.queue = append(l.queue, fn)
first := len(l.queue) == 1
l.mu.Unlock()
if first {
backend().Signal()
}
return true
}
// drain runs queued functions, one at a time, until the queue is empty.
// Main thread only. A function running a loop of its own, as a context
// menu, a modal dialog or await does, runs those queued behind it, which
// it may be waiting for: the backend is signaled again while some wait.
func (l *mainLoop) drain() {
for {
l.mu.Lock()
if len(l.queue) == 0 {
l.mu.Unlock()
return
}
fn := l.queue[0]
l.queue[0] = nil
l.queue = l.queue[1:]
more := len(l.queue) > 0
l.mu.Unlock()
if more {
backend().Signal()
}
fn()
}
}
// stopped reports whether the loop has shut down.
func (l *mainLoop) stopped() bool {
l.mu.Lock()
defer l.mu.Unlock()
return l.closed
}
// shutdown runs whatever is still queued and rejects later posts. The
// queue is closed in the same critical section that finds it empty, so no
// post is accepted without running.
func (l *mainLoop) shutdown() {
for {
l.drain()
l.mu.Lock()
if len(l.queue) == 0 {
l.closed = true
l.mu.Unlock()
return
}
l.mu.Unlock()
}
}
func isMainThread() bool { return backend().IsMainThread() }
// RunOnMain runs fn on the main (UI) thread and waits for it to return.
// Use it to call native APIs directly, e.g. through Window.NativeHandle,
// or to read and change the state a window's Content shows without
// the redraw that Window.Update makes.
// It runs fn right away when called on the main thread, and drops it after
// the application has quit.
func RunOnMain(fn func()) { onMain(fn) }
// postMain runs fn asynchronously on the main thread.
func postMain(fn func()) bool { return loop.post(fn) }
// onMain runs fn on the main thread and waits for it to return. It runs fn
// directly when called on the main thread. After the event loop has shut
// down fn is dropped.
func onMain(fn func()) {
if isMainThread() {
fn()
return
}
done := make(chan struct{})
if !loop.post(func() {
defer close(done)
fn()
}) {
return
}
<-done
}
// onMainValue is onMain for functions returning a value.
func onMainValue[T any](fn func() T) T {
var v T
onMain(func() { v = fn() })
return v
}
// await blocks until a value arrives on ch. On the main thread it keeps
// processing native events so the UI stays responsive and the value can
// actually be produced. Whoever sends on ch must call wake afterwards.
func await[T any](ch <-chan T) T {
if !isMainThread() {
return <-ch
}
for {
select {
case v := <-ch:
return v
default:
}
backend().Step()
}
}
// deliver sends v on a buffered channel consumed by await and wakes the main
// thread in case it is waiting.
func deliver[T any](ch chan<- T, v T) {
ch <- v
backend().Wake()
}