forked from andlabs/ui
/
uitask.go
164 lines (144 loc) · 3.85 KB
/
uitask.go
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// 6 july 2014
package ui
import (
"reflect"
"runtime"
"sync"
"unsafe"
)
// Go initializes and runs package ui.
// It returns a non-nil error if initialization fails.
// Otherwise, it will run the event loop and not return until Stop is called.
// Due to platform-specific issues, it must be called from the main OS thread; in general, do not call Go() from anywhere except main() (including any goroutines).
func Go() error {
runtime.LockOSThread()
if err := uiinit(); err != nil {
return err
}
go uiissueloop()
uimsgloop()
return nil
}
// To ensure that Do() and Stop() only do things after Go() has been called, this channel accepts the requests to issue. The issuing is done by uiissueloop() below.
// Notice that this is a pointer ot a function. See Do() below for details.
var issuer = make(chan *func())
// Do performs f on the main loop, as if it were an event handler.
// It waits for f to execute before returning.
// Do cannot be called within event handlers or within Do itself.
func Do(f func()) {
done := make(chan struct{})
defer close(done)
// THIS MUST BE A POINTER.
// Previously, the pointer was constructed within issue().
// This meant that if the Do() was stalled, the garbage collector came in and reused the pointer value too soon!
call := func() {
f()
done <- struct{}{}
}
issuer <- &call
<-done
}
// Stop informs package ui that it should stop.
// Stop then returns immediately.
// Some time after this request is received, Go() will return without performing any final cleanup.
// Stop will not have an effect until any event handlers return.
func Stop() {
// can't send this directly across issuer
go func() {
Do(uistop)
}()
}
func uiissueloop() {
for f := range issuer {
issue(f)
}
}
type event struct {
// All events internally return bool; those that don't will be wrapped around to return a dummy value.
do func() bool
lock sync.Mutex
}
func newEvent() *event {
return &event{
do: func() bool {
return false
},
}
}
func (e *event) set(f func()) {
e.lock.Lock()
defer e.lock.Unlock()
if f == nil {
f = func() {}
}
e.do = func() bool {
f()
return false
}
}
func (e *event) setbool(f func() bool) {
e.lock.Lock()
defer e.lock.Unlock()
if f == nil {
f = func() bool {
return false
}
}
e.do = f
}
// This is the common code for running an event.
// It runs on the main thread without a message pump; it provides its own.
func (e *event) fire() bool {
e.lock.Lock()
defer e.lock.Unlock()
return e.do()
}
// Common code for performing a requested action (ui.Do() or ui.Stop()).
// This should run on the main thread.
// Implementations of issue() should call this.
func perform(fp unsafe.Pointer) {
f := (*func())(fp)
(*f)()
}
// ForeignEvent wraps a channel in such a way that it can be used safely with package ui.
type ForeignEvent struct {
c reflect.Value
e *event
d interface{}
}
// NewForeignEvent creates a new ForeignEvent with the specified channel.
// It panics if the argument is not a receivable channel.
// The returned ForeignEvent assumes ownership of the channel.
// Each time a value is received on the channel, the returned function is invoked on the main thread.
func NewForeignEvent(channel interface{}, handler func(data interface{})) *ForeignEvent {
c := reflect.ValueOf(channel)
t := c.Type()
if t.Kind() != reflect.Chan || (t.ChanDir()&reflect.RecvDir) == 0 {
panic("non-channel or non-receivable channel passed to NewForeignEvent()")
}
fe := &ForeignEvent{
c: c,
e: newEvent(),
}
fe.e.set(func() {
handler(fe.d)
})
go fe.do()
return fe
}
func (fe *ForeignEvent) do() {
for {
v, ok := fe.c.Recv()
if !ok {
break
}
fe.d = v.Interface()
Do(func() {
fe.e.fire()
})
}
}
// Stop ceases all future invocations of the handler passed to NewForeignEvent() on fe; the values read from the channel are merely discarded.
func (fe *ForeignEvent) Stop() {
fe.e.set(nil)
}