github.com/corfe83/mobile@v0.0.0-20220928034243-9edc37f43fac/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  }