github.com/onlysumitg/go_mobile@v0.0.3/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  
     7  package app
     8  
     9  import (
    10  	"github.com/onlysumitg/go_mobile/event/lifecycle"
    11  	"github.com/onlysumitg/go_mobile/event/size"
    12  	"github.com/onlysumitg/go_mobile/gl"
    13  	_ "github.com/onlysumitg/go_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 github.com/onlysumitg/go_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  }