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