app.go (5903B)
1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build linux darwin windows 6 7 package app 8 9 import ( 10 "golang.org/x/mobile/event/lifecycle" 11 "golang.org/x/mobile/event/size" 12 "golang.org/x/mobile/gl" 13 _ "golang.org/x/mobile/internal/mobileinit" 14 ) 15 16 // Main is called by the main.main function to run the mobile application. 17 // 18 // It calls f on the App, in a separate goroutine, as some OS-specific 19 // libraries require being on 'the main thread'. 20 func Main(f func(App)) { 21 main(f) 22 } 23 24 // App is how a GUI mobile application interacts with the OS. 25 type App interface { 26 // Events returns the events channel. It carries events from the system to 27 // the app. The type of such events include: 28 // - lifecycle.Event 29 // - mouse.Event 30 // - paint.Event 31 // - size.Event 32 // - touch.Event 33 // from the golang.org/x/mobile/event/etc packages. Other packages may 34 // define other event types that are carried on this channel. 35 Events() <-chan interface{} 36 37 // Send sends an event on the events channel. It does not block. 38 Send(event interface{}) 39 40 // Publish flushes any pending drawing commands, such as OpenGL calls, and 41 // swaps the back buffer to the screen. 42 Publish() PublishResult 43 44 // TODO: replace filters (and the Events channel) with a NextEvent method? 45 46 // Filter calls each registered event filter function in sequence. 47 Filter(event interface{}) interface{} 48 49 // RegisterFilter registers a event filter function to be called by Filter. The 50 // function can return a different event, or return nil to consume the event, 51 // but the function can also return its argument unchanged, where its purpose 52 // is to trigger a side effect rather than modify the event. 53 RegisterFilter(f func(interface{}) interface{}) 54 } 55 56 // PublishResult is the result of an App.Publish call. 57 type PublishResult struct { 58 // BackBufferPreserved is whether the contents of the back buffer was 59 // preserved. If false, the contents are undefined. 60 BackBufferPreserved bool 61 } 62 63 var theApp = &app{ 64 eventsOut: make(chan interface{}), 65 lifecycleStage: lifecycle.StageDead, 66 publish: make(chan struct{}), 67 publishResult: make(chan PublishResult), 68 } 69 70 func init() { 71 theApp.eventsIn = pump(theApp.eventsOut) 72 theApp.glctx, theApp.worker = gl.NewContext() 73 } 74 75 func (a *app) sendLifecycle(to lifecycle.Stage) { 76 if a.lifecycleStage == to { 77 return 78 } 79 a.eventsIn <- lifecycle.Event{ 80 From: a.lifecycleStage, 81 To: to, 82 DrawContext: a.glctx, 83 } 84 a.lifecycleStage = to 85 } 86 87 type app struct { 88 filters []func(interface{}) interface{} 89 90 eventsOut chan interface{} 91 eventsIn chan interface{} 92 lifecycleStage lifecycle.Stage 93 publish chan struct{} 94 publishResult chan PublishResult 95 96 glctx gl.Context 97 worker gl.Worker 98 } 99 100 func (a *app) Events() <-chan interface{} { 101 return a.eventsOut 102 } 103 104 func (a *app) Send(event interface{}) { 105 a.eventsIn <- event 106 } 107 108 func (a *app) Publish() PublishResult { 109 // gl.Flush is a lightweight (on modern GL drivers) blocking call 110 // that ensures all GL functions pending in the gl package have 111 // been passed onto the GL driver before the app package attempts 112 // to swap the screen buffer. 113 // 114 // This enforces that the final receive (for this paint cycle) on 115 // gl.WorkAvailable happens before the send on endPaint. 116 a.glctx.Flush() 117 a.publish <- struct{}{} 118 return <-a.publishResult 119 } 120 121 func (a *app) Filter(event interface{}) interface{} { 122 for _, f := range a.filters { 123 event = f(event) 124 } 125 return event 126 } 127 128 func (a *app) RegisterFilter(f func(interface{}) interface{}) { 129 a.filters = append(a.filters, f) 130 } 131 132 type stopPumping struct{} 133 134 // pump returns a channel src such that sending on src will eventually send on 135 // dst, in order, but that src will always be ready to send/receive soon, even 136 // if dst currently isn't. It is effectively an infinitely buffered channel. 137 // 138 // In particular, goroutine A sending on src will not deadlock even if goroutine 139 // B that's responsible for receiving on dst is currently blocked trying to 140 // send to A on a separate channel. 141 // 142 // Send a stopPumping on the src channel to close the dst channel after all queued 143 // events are sent on dst. After that, other goroutines can still send to src, 144 // so that such sends won't block forever, but such events will be ignored. 145 func pump(dst chan interface{}) (src chan interface{}) { 146 src = make(chan interface{}) 147 go func() { 148 // initialSize is the initial size of the circular buffer. It must be a 149 // power of 2. 150 const initialSize = 16 151 i, j, buf, mask := 0, 0, make([]interface{}, initialSize), initialSize-1 152 153 srcActive := true 154 for { 155 maybeDst := dst 156 if i == j { 157 maybeDst = nil 158 } 159 if maybeDst == nil && !srcActive { 160 // Pump is stopped and empty. 161 break 162 } 163 164 select { 165 case maybeDst <- buf[i&mask]: 166 buf[i&mask] = nil 167 i++ 168 169 case e := <-src: 170 if _, ok := e.(stopPumping); ok { 171 srcActive = false 172 continue 173 } 174 175 if !srcActive { 176 continue 177 } 178 179 // Allocate a bigger buffer if necessary. 180 if i+len(buf) == j { 181 b := make([]interface{}, 2*len(buf)) 182 n := copy(b, buf[j&mask:]) 183 copy(b[n:], buf[:j&mask]) 184 i, j = 0, len(buf) 185 buf, mask = b, len(b)-1 186 } 187 188 buf[j&mask] = e 189 j++ 190 } 191 } 192 193 close(dst) 194 // Block forever. 195 for range src { 196 } 197 }() 198 return src 199 } 200 201 // TODO: do this for all build targets, not just linux (x11 and Android)? If 202 // so, should package gl instead of this package call RegisterFilter?? 203 // 204 // TODO: does Android need this?? It seems to work without it (Nexus 7, 205 // KitKat). If only x11 needs this, should we move this to x11.go?? 206 func (a *app) registerGLViewportFilter() { 207 a.RegisterFilter(func(e interface{}) interface{} { 208 if e, ok := e.(size.Event); ok { 209 a.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx) 210 } 211 return e 212 }) 213 }