app: remove iOS system draw loop

Much like the recent change for OS X, this puts the Go paint loop in
control of drawing onto the screen.

Change-Id: I37321e4bb58869d4c7cafc51951ea64e540d536b
Reviewed-on: https://go-review.googlesource.com/15611
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/app/darwin_armx.go b/app/darwin_armx.go
index 5344e27..0138a42 100644
--- a/app/darwin_armx.go
+++ b/app/darwin_armx.go
@@ -14,11 +14,13 @@
 #include <stdint.h>
 #include <pthread.h>
 #include <UIKit/UIDevice.h>
+#import <GLKit/GLKit.h>
 
 extern struct utsname sysInfo;
 
 void runApp(void);
-void setContext(void* context);
+void makeCurrentContext(GLintptr ctx);
+void swapBuffers(GLintptr ctx);
 uint64_t threadID();
 */
 import "C"
@@ -27,7 +29,6 @@
 	"runtime"
 	"strings"
 	"sync"
-	"unsafe"
 
 	"golang.org/x/mobile/event/lifecycle"
 	"golang.org/x/mobile/event/paint"
@@ -116,6 +117,7 @@
 		PixelsPerPt: pixelsPerPt,
 		Orientation: o,
 	}
+	theApp.eventsIn <- paint.Event{External: true}
 }
 
 // touchIDs is the current active touches. The position in the array
@@ -165,28 +167,50 @@
 	}
 }
 
-var workAvailable <-chan struct{}
+//export lifecycleDead
+func lifecycleDead() { theApp.sendLifecycle(lifecycle.StageDead) }
 
-//export drawgl
-func drawgl(ctx uintptr) {
-	if workAvailable == nil {
-		C.setContext(unsafe.Pointer(ctx))
-		workAvailable = theApp.worker.WorkAvailable()
-		// TODO(crawshaw): not just on process start.
-		theApp.sendLifecycle(lifecycle.StageFocused)
-	}
+//export lifecycleAlive
+func lifecycleAlive() { theApp.sendLifecycle(lifecycle.StageAlive) }
 
-	// TODO(crawshaw): don't send a paint.Event unconditionally. Only send one
-	// if the window actually needs redrawing.
-	theApp.eventsIn <- paint.Event{}
+//export lifecycleVisible
+func lifecycleVisible() { theApp.sendLifecycle(lifecycle.StageVisible) }
+
+//export lifecycleFocused
+func lifecycleFocused() { theApp.sendLifecycle(lifecycle.StageFocused) }
+
+//export startloop
+func startloop(ctx C.GLintptr) {
+	go theApp.loop(ctx)
+}
+
+// loop is the primary drawing loop.
+//
+// After UIKit has captured the initial OS thread for processing UIKit
+// events in runApp, it starts loop on another goroutine. It is locked
+// to an OS thread for its OpenGL context.
+func (a *app) loop(ctx C.GLintptr) {
+	runtime.LockOSThread()
+	C.makeCurrentContext(ctx)
+
+	workAvailable := a.worker.WorkAvailable()
 
 	for {
 		select {
 		case <-workAvailable:
-			theApp.worker.DoWork()
+			a.worker.DoWork()
 		case <-theApp.publish:
+		loop1:
+			for {
+				select {
+				case <-workAvailable:
+					a.worker.DoWork()
+				default:
+					break loop1
+				}
+			}
+			C.swapBuffers(ctx)
 			theApp.publishResult <- PublishResult{}
-			return
 		}
 	}
 }
diff --git a/app/darwin_armx.m b/app/darwin_armx.m
index 9c96986..c86e778 100644
--- a/app/darwin_armx.m
+++ b/app/darwin_armx.m
@@ -15,7 +15,7 @@
 
 struct utsname sysInfo;
 
-@interface GoAppAppController : GLKViewController<UIContentContainer>
+@interface GoAppAppController : GLKViewController<UIContentContainer, GLKViewDelegate>
 @end
 
 @interface GoAppAppDelegate : UIResponder<UIApplicationDelegate>
@@ -25,27 +25,58 @@
 
 @implementation GoAppAppDelegate
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+	lifecycleAlive();
 	self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
 	self.controller = [[GoAppAppController alloc] initWithNibName:nil bundle:nil];
 	self.window.rootViewController = self.controller;
 	[self.window makeKeyAndVisible];
 	return YES;
 }
+
+- (void)applicationDidBecomeActive:(UIApplication * )application {
+	lifecycleFocused();
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+	lifecycleVisible();
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+	lifecycleAlive();
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+	lifecycleDead();
+}
 @end
 
 @interface GoAppAppController ()
 @property (strong, nonatomic) EAGLContext *context;
+@property (strong, nonatomic) GLKView *glview;
 @end
 
 @implementation GoAppAppController
+- (void)viewWillAppear:(BOOL)animated
+{
+	// TODO: replace by swapping out GLKViewController for a UIVIewController.
+	[super viewWillAppear:animated];
+	self.paused = YES;
+}
+
 - (void)viewDidLoad {
 	[super viewDidLoad];
-	self.preferredFramesPerSecond = 60;
 	self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
-	GLKView *view = (GLKView *)self.view;
-	view.context = self.context;
-	view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
-	view.multipleTouchEnabled = true; // TODO expose setting to user.
+	self.glview = (GLKView*)self.view;
+	self.glview.drawableDepthFormat = GLKViewDrawableDepthFormat24;
+	self.glview.multipleTouchEnabled = true; // TODO expose setting to user.
+	self.glview.context = self.context;
+	self.glview.userInteractionEnabled = YES;
+	self.glview.enableSetNeedsDisplay = YES; // only invoked once
+
+	// Do not use the GLKViewController draw loop.
+	self.paused = YES;
+	self.resumeOnDidBecomeActive = NO;
+	self.preferredFramesPerSecond = 0;
 
 	int scale = 1;
 	if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)]) {
@@ -67,8 +98,11 @@
 	}];
 }
 
-- (void)update {
-	drawgl((GoUintptr)self.context);
+- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
+	// Now that we have been asked to do the first draw, disable any
+	// future draw and hand control over to the Go paint.Event cycle.
+	self.glview.enableSetNeedsDisplay = NO;
+	startloop((GLintptr)self.context);
 }
 
 #define TOUCH_TYPE_BEGIN 0 // touch.TypeBegin
@@ -106,7 +140,7 @@
 	}
 }
 
-void setContext(void* context) {
+void makeCurrentContext(GLintptr context) {
 	EAGLContext* ctx = (EAGLContext*)context;
 	if (![EAGLContext setCurrentContext:ctx]) {
 		// TODO(crawshaw): determine how terrible this is. Exit?
@@ -114,6 +148,14 @@
 	}
 }
 
+void swapBuffers(GLintptr context) {
+	__block EAGLContext* ctx = (EAGLContext*)context;
+	dispatch_sync(dispatch_get_main_queue(), ^{
+		[EAGLContext setCurrentContext:ctx];
+		[ctx presentRenderbuffer:GL_RENDERBUFFER];
+	});
+}
+
 uint64_t threadID() {
 	uint64_t id;
 	if (pthread_threadid_np(pthread_self(), &id)) {