zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

app.go (5941B)


      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 }