// 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 }) }