forked from golang/mobile
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathapp.go
214 lines (182 loc) · 5.8 KB
/
app.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux || darwin || windows
// +build linux darwin windows
package app
import (
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/gl"
_ "golang.org/x/mobile/internal/mobileinit"
)
// Main is called by the main.main function to run the mobile application.
//
// It calls f on the App, in a separate goroutine, as some OS-specific
// libraries require being on 'the main thread'.
func Main(f func(App)) {
main(f)
}
// App is how a GUI mobile application interacts with the OS.
type App interface {
// Events returns the events channel. It carries events from the system to
// the app. The type of such events include:
// - lifecycle.Event
// - mouse.Event
// - paint.Event
// - size.Event
// - touch.Event
// from the golang.org/x/mobile/event/etc packages. Other packages may
// define other event types that are carried on this channel.
Events() <-chan interface{}
// Send sends an event on the events channel. It does not block.
Send(event interface{})
// Publish flushes any pending drawing commands, such as OpenGL calls, and
// swaps the back buffer to the screen.
Publish() PublishResult
// TODO: replace filters (and the Events channel) with a NextEvent method?
// Filter calls each registered event filter function in sequence.
Filter(event interface{}) interface{}
// RegisterFilter registers a event filter function to be called by Filter. The
// function can return a different event, or return nil to consume the event,
// but the function can also return its argument unchanged, where its purpose
// is to trigger a side effect rather than modify the event.
RegisterFilter(f func(interface{}) interface{})
}
// PublishResult is the result of an App.Publish call.
type PublishResult struct {
// BackBufferPreserved is whether the contents of the back buffer was
// preserved. If false, the contents are undefined.
BackBufferPreserved bool
}
var theApp = &app{
eventsOut: make(chan interface{}),
lifecycleStage: lifecycle.StageDead,
publish: make(chan struct{}),
publishResult: make(chan PublishResult),
}
func init() {
theApp.eventsIn = pump(theApp.eventsOut)
theApp.glctx, theApp.worker = gl.NewContext()
}
func (a *app) sendLifecycle(to lifecycle.Stage) {
if a.lifecycleStage == to {
return
}
a.eventsIn <- lifecycle.Event{
From: a.lifecycleStage,
To: to,
DrawContext: a.glctx,
}
a.lifecycleStage = to
}
type app struct {
filters []func(interface{}) interface{}
eventsOut chan interface{}
eventsIn chan interface{}
lifecycleStage lifecycle.Stage
publish chan struct{}
publishResult chan PublishResult
glctx gl.Context
worker gl.Worker
}
func (a *app) Events() <-chan interface{} {
return a.eventsOut
}
func (a *app) Send(event interface{}) {
a.eventsIn <- event
}
func (a *app) Publish() PublishResult {
// gl.Flush is a lightweight (on modern GL drivers) blocking call
// that ensures all GL functions pending in the gl package have
// been passed onto the GL driver before the app package attempts
// to swap the screen buffer.
//
// This enforces that the final receive (for this paint cycle) on
// gl.WorkAvailable happens before the send on endPaint.
a.glctx.Flush()
a.publish <- struct{}{}
return <-a.publishResult
}
func (a *app) Filter(event interface{}) interface{} {
for _, f := range a.filters {
event = f(event)
}
return event
}
func (a *app) RegisterFilter(f func(interface{}) interface{}) {
a.filters = append(a.filters, f)
}
type stopPumping struct{}
// pump returns a channel src such that sending on src will eventually send on
// dst, in order, but that src will always be ready to send/receive soon, even
// if dst currently isn't. It is effectively an infinitely buffered channel.
//
// In particular, goroutine A sending on src will not deadlock even if goroutine
// B that's responsible for receiving on dst is currently blocked trying to
// send to A on a separate channel.
//
// Send a stopPumping on the src channel to close the dst channel after all queued
// events are sent on dst. After that, other goroutines can still send to src,
// so that such sends won't block forever, but such events will be ignored.
func pump(dst chan interface{}) (src chan interface{}) {
src = make(chan interface{})
go func() {
// initialSize is the initial size of the circular buffer. It must be a
// power of 2.
const initialSize = 16
i, j, buf, mask := 0, 0, make([]interface{}, initialSize), initialSize-1
srcActive := true
for {
maybeDst := dst
if i == j {
maybeDst = nil
}
if maybeDst == nil && !srcActive {
// Pump is stopped and empty.
break
}
select {
case maybeDst <- buf[i&mask]:
buf[i&mask] = nil
i++
case e := <-src:
if _, ok := e.(stopPumping); ok {
srcActive = false
continue
}
if !srcActive {
continue
}
// Allocate a bigger buffer if necessary.
if i+len(buf) == j {
b := make([]interface{}, 2*len(buf))
n := copy(b, buf[j&mask:])
copy(b[n:], buf[:j&mask])
i, j = 0, len(buf)
buf, mask = b, len(b)-1
}
buf[j&mask] = e
j++
}
}
close(dst)
// Block forever.
for range src {
}
}()
return src
}
// TODO: do this for all build targets, not just linux (x11 and Android)? If
// so, should package gl instead of this package call RegisterFilter??
//
// TODO: does Android need this?? It seems to work without it (Nexus 7,
// KitKat). If only x11 needs this, should we move this to x11.go??
func (a *app) registerGLViewportFilter() {
a.RegisterFilter(func(e interface{}) interface{} {
if e, ok := e.(size.Event); ok {
a.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx)
}
return e
})
}