app: remove error return from Main

Move runtime.LockOSThread from the portable Main to the
platform-specific android main. For darwin, calling LockOSThread in
Main is too late, there has been enough time for the goroutine
calling it to jump off the initial OS thread. For darwin we call
LockOSThread from an init function, which the runtime keeps on the
first thread. For Android we call LockOSThread only to maintain the
thread-local storage for the GL context that is created later, so
it is safe to call it from func main.

Also remove TODOs about starting a gobind app on Android, which
will be simplified in a followup CL.

Tested on darwin/amd64 and android. Not tested on X11.

Change-Id: I34c56abf8b1292959d4d508bfade287d196c0380
Reviewed-on: https://go-review.googlesource.com/11653
Reviewed-by: Nigel Tao <nigeltao@golang.org>
This commit is contained in:
David Crawshaw 2015-06-27 18:32:29 -04:00
parent 9cc656ecd6
commit d1958fa5cf
5 changed files with 31 additions and 46 deletions

View File

@ -63,6 +63,7 @@ import (
"io" "io"
"log" "log"
"os" "os"
"runtime"
"time" "time"
"unsafe" "unsafe"
@ -274,40 +275,29 @@ func (a *asset) Close() error {
return nil return nil
} }
// TODO(crawshaw): fix up this comment?? func main(f func(App)) {
// notifyInitDone informs Java that the program is initialized. // Preserve this OS thread for the GL context created in windowDraw.
// A NativeActivity will not create a window until this is called. runtime.LockOSThread()
func main(f func(App) error) error {
ctag := C.CString("Go") ctag := C.CString("Go")
cstr := C.CString("app.Run") cstr := C.CString("app.Run")
C.__android_log_write(C.ANDROID_LOG_INFO, ctag, cstr) C.__android_log_write(C.ANDROID_LOG_INFO, ctag, cstr)
C.free(unsafe.Pointer(ctag)) C.free(unsafe.Pointer(ctag))
C.free(unsafe.Pointer(cstr)) C.free(unsafe.Pointer(cstr))
donec := make(chan error, 1) donec := make(chan struct{})
go func() { go func() {
donec <- f(app{}) f(app{})
close(donec)
}() }()
if C.current_native_activity == nil { if C.current_native_activity == nil {
// TODO: Even though c-shared mode doesn't require main to be called <-donec
// now, gobind relies on the main being called. In main, app.Run is return
// called and the start callback initializes Java-Go communication. }
// for w := range windowCreated {
// The problem is if the main exits (because app.Run returns), go if windowDraw(w, queue, donec) {
// runtime exits and kills the app. return
//
// Many things have changed in cgo recently. If we can manage to split
// gobind app, native Go app initialization logic, we may able to
// consider gobind app not to use main of the go package.
//
// TODO: do we need to do what used to be stateStart or stateStop?
select {}
} else {
for w := range windowCreated {
if done, err := windowDraw(w, queue, donec); done {
return err
}
} }
} }
panic("unreachable") panic("unreachable")

View File

@ -8,8 +8,6 @@ package app
import ( import (
"io" "io"
"log"
"runtime"
"golang.org/x/mobile/event" "golang.org/x/mobile/event"
) )
@ -18,11 +16,8 @@ import (
// //
// It calls f on the App, in a separate goroutine, as some OS-specific // It calls f on the App, in a separate goroutine, as some OS-specific
// libraries require being on 'the main thread'. // libraries require being on 'the main thread'.
func Main(f func(App) error) { func Main(f func(App)) {
runtime.LockOSThread() main(f)
if err := main(f); err != nil {
log.Fatal(err)
}
} }
// App is how a GUI mobile application interacts with the OS. // App is how a GUI mobile application interacts with the OS.
@ -152,7 +147,7 @@ func pump(dst chan interface{}) (src chan interface{}) {
// //
// Deprecated: call Main directly instead. // Deprecated: call Main directly instead.
func Run(cb Callbacks) { func Run(cb Callbacks) {
Main(func(a App) error { Main(func(a App) {
var c event.Config var c event.Config
for e := range a.Events() { for e := range a.Events() {
switch e := event.Filter(e).(type) { switch e := event.Filter(e).(type) {
@ -183,7 +178,6 @@ func Run(cb Callbacks) {
} }
} }
} }
return nil
}) })
} }

View File

@ -51,19 +51,17 @@ func init() {
initThreadID = uint64(C.threadID()) initThreadID = uint64(C.threadID())
} }
func main(f func(App) error) error { func main(f func(App)) {
if tid := uint64(C.threadID()); tid != initThreadID { if tid := uint64(C.threadID()); tid != initThreadID {
log.Fatalf("app.Main called on thread %d, but app.init ran on %d", tid, initThreadID) log.Fatalf("app.Main called on thread %d, but app.init ran on %d", tid, initThreadID)
} }
var err error
go func() { go func() {
err = f(app{}) f(app{})
// TODO(crawshaw): trigger runApp to return // TODO(crawshaw): trigger runApp to return
}() }()
C.runApp() C.runApp()
return err
} }
var windowHeight geom.Pt var windowHeight geom.Pt

View File

@ -92,7 +92,7 @@ import (
var firstWindowDraw = true var firstWindowDraw = true
func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan error) (done bool, err error) { func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan struct{}) (done bool) {
C.createEGLWindow(w) C.createEGLWindow(w)
// TODO: is the library or the app responsible for clearing the buffers? // TODO: is the library or the app responsible for clearing the buffers?
@ -136,8 +136,8 @@ func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan error) (don
for { for {
processEvents(queue) processEvents(queue)
select { select {
case err := <-donec: case <-donec:
return true, err return true
case <-windowRedrawNeeded: case <-windowRedrawNeeded:
// Re-query the width and height. // Re-query the width and height.
C.querySurfaceWidthAndHeight() C.querySurfaceWidthAndHeight()
@ -170,7 +170,7 @@ func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan error) (don
} }
case <-windowDestroyed: case <-windowDestroyed:
sendLifecycle(event.LifecycleStageAlive) sendLifecycle(event.LifecycleStageAlive)
return false, nil return false
case <-gl.WorkAvailable: case <-gl.WorkAvailable:
gl.DoWork() gl.DoWork()
case <-endDraw: case <-endDraw:

View File

@ -24,6 +24,7 @@ void swapBuffers(void);
*/ */
import "C" import "C"
import ( import (
"runtime"
"time" "time"
"golang.org/x/mobile/event" "golang.org/x/mobile/event"
@ -31,15 +32,17 @@ import (
"golang.org/x/mobile/gl" "golang.org/x/mobile/gl"
) )
func main(f func(App) error) error { func main(f func(App)) {
runtime.LockOSThread()
C.createWindow() C.createWindow()
// TODO: send lifecycle events when e.g. the X11 window is iconified or moved off-screen. // TODO: send lifecycle events when e.g. the X11 window is iconified or moved off-screen.
sendLifecycle(event.LifecycleStageFocused) sendLifecycle(event.LifecycleStageFocused)
donec := make(chan error, 1) donec := make(chan struct{})
go func() { go func() {
donec <- f(app{}) f(app{})
close(donec)
}() }()
// TODO: can we get the actual vsync signal? // TODO: can we get the actual vsync signal?
@ -49,8 +52,8 @@ func main(f func(App) error) error {
for { for {
select { select {
case err := <-donec: case <-donec:
return err return
case <-gl.WorkAvailable: case <-gl.WorkAvailable:
gl.DoWork() gl.DoWork()
case <-endDraw: case <-endDraw: