// Copyright 2019 The Ebiten Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build ebitenmobilegobind // +build ebitenmobilegobind // gobind is a wrapper of the original gobind. This command adds extra files like a view controller. package main import ( "flag" "fmt" "io/ioutil" "log" "os" "os/exec" "path/filepath" "strings" "golang.org/x/tools/go/packages" ) var ( lang = flag.String("lang", "", "") outdir = flag.String("outdir", "", "") javaPkg = flag.String("javapkg", "", "") prefix = flag.String("prefix", "", "") bootclasspath = flag.String("bootclasspath", "", "") classpath = flag.String("classpath", "", "") tags = flag.String("tags", "", "") ) var usage = `The Gobind tool generates Java language bindings for Go. For usage details, see doc.go.` func main() { flag.Parse() if err := run(); err != nil { log.Fatal(err) } } func invokeOriginalGobind(lang string) (pkgName string, err error) { cmd := exec.Command("gobind-original", os.Args[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return "", err } cfgtags := strings.Join(strings.Split(*tags, ","), " ") cfg := &packages.Config{} switch lang { case "java": cfg.Env = append(os.Environ(), "GOOS=android") case "objc": cfg.Env = append(os.Environ(), "GOOS=darwin") if cfgtags != "" { cfgtags += " " } cfgtags += "ios" } cfg.BuildFlags = []string{"-tags", cfgtags} pkgs, err := packages.Load(cfg, flag.Args()[0]) if err != nil { return "", err } return pkgs[0].Name, nil } func run() error { writeFile := func(filename string, content string) error { if err := ioutil.WriteFile(filepath.Join(*outdir, filename), []byte(content), 0644); err != nil { return err } return nil } // Add additional files. langs := strings.Split(*lang, ",") for _, lang := range langs { pkgName, err := invokeOriginalGobind(lang) if err != nil { return err } prefixLower := *prefix + pkgName prefixUpper := strings.Title(*prefix) + strings.Title(pkgName) replacePrefixes := func(content string) string { content = strings.ReplaceAll(content, "{{.PrefixUpper}}", prefixUpper) content = strings.ReplaceAll(content, "{{.PrefixLower}}", prefixLower) content = strings.ReplaceAll(content, "{{.JavaPkg}}", *javaPkg) return content } switch lang { case "objc": // iOS if err := writeFile(filepath.Join("src", "gobind", prefixLower+"ebitenviewcontroller_ios.m"), replacePrefixes(objcM)); err != nil { return err } if err := writeFile(filepath.Join("src", "gobind", prefixLower+"ebitenviewcontroller_ios.go"), `package main // #cgo CFLAGS: -DGLES_SILENCE_DEPRECATION import "C"`); err != nil { return err } case "java": // Android dir := filepath.Join(strings.Split(*javaPkg, ".")...) dir = filepath.Join(dir, prefixLower) if err := writeFile(filepath.Join("java", dir, "EbitenView.java"), replacePrefixes(viewJava)); err != nil { return err } if err := writeFile(filepath.Join("java", dir, "EbitenSurfaceView.java"), replacePrefixes(surfaceViewJava)); err != nil { return err } case "go": // Do nothing. default: panic(fmt.Sprintf("unsupported language: %s", lang)) } } return nil } const objcM = `// Code generated by ebitenmobile. DO NOT EDIT. //go:build ios // +build ios #import #import #import #import #import "Ebitenmobileview.objc.h" @interface {{.PrefixUpper}}EbitenViewController : UIViewController @end @implementation {{.PrefixUpper}}EbitenViewController { UIView* metalView_; GLKView* glkView_; bool started_; bool active_; bool error_; CADisplayLink* displayLink_; bool explicitRendering_; } - (UIView*)metalView { if (!metalView_) { metalView_ = [[UIView alloc] init]; metalView_.multipleTouchEnabled = YES; } return metalView_; } - (GLKView*)glkView { if (!glkView_) { glkView_ = [[GLKView alloc] init]; glkView_.multipleTouchEnabled = YES; } return glkView_; } - (void)viewDidLoad { [super viewDidLoad]; if (!started_) { @synchronized(self) { active_ = true; } started_ = true; } if (EbitenmobileviewIsGL()) { self.glkView.delegate = (id)(self); [self.view addSubview: self.glkView]; EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; [self glkView].context = context; [EAGLContext setCurrentContext:context]; } else { [self.view addSubview: self.metalView]; EbitenmobileviewSetUIView((uintptr_t)(self.metalView)); } displayLink_ = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)]; [displayLink_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; EbitenmobileviewSetRenderRequester(self); } - (void)viewWillLayoutSubviews { CGRect viewRect = [[self view] frame]; if (EbitenmobileviewIsGL()) { [[self glkView] setFrame:viewRect]; } else { [[self metalView] setFrame:viewRect]; } } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; CGRect viewRect = [[self view] frame]; EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. // TODO: Notify this to Go world? } - (void)drawFrame{ @synchronized(self) { if (!active_) { return; } if (EbitenmobileviewIsGL()) { [[self glkView] setNeedsDisplay]; } else { [self updateEbiten]; } if (explicitRendering_) { [displayLink_ setPaused:YES]; } } } - (void)glkView:(GLKView*)view drawInRect:(CGRect)rect { @synchronized(self) { [self updateEbiten]; } } - (void)updateEbiten { if (error_) { return; } NSError* err = nil; EbitenmobileviewUpdate(&err); if (err != nil) { [self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:) withObject:err waitUntilDone:NO]; error_ = true; } } - (void)onErrorOnGameUpdate:(NSError*)err { NSLog(@"Error: %@", err); } - (void)updateTouches:(NSSet*)touches { for (UITouch* touch in touches) { if (EbitenmobileviewIsGL()) { if (touch.view != [self glkView]) { continue; } } else { if (touch.view != [self metalView]) { continue; } } CGPoint location = [touch locationInView:touch.view]; EbitenmobileviewUpdateTouchesOnIOS(touch.phase, (uintptr_t)touch, location.x, location.y); } } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { [self updateTouches:touches]; } - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { [self updateTouches:touches]; } - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { [self updateTouches:touches]; } - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { [self updateTouches:touches]; } - (void)suspendGame { NSAssert(started_, @"suspendGame must not be called before viewDidLoad is called"); @synchronized(self) { active_ = false; NSError* err = nil; EbitenmobileviewSuspend(&err); if (err != nil) { [self onErrorOnGameUpdate:err]; } } } - (void)resumeGame { NSAssert(started_, @"resumeGame must not be called before viewDidLoad is called"); @synchronized(self) { active_ = true; NSError* err = nil; EbitenmobileviewResume(&err); if (err != nil) { [self onErrorOnGameUpdate:err]; } } } - (void)setExplicitRenderingMode:(BOOL)explicitRendering { @synchronized(self) { explicitRendering_ = explicitRendering; if (explicitRendering_) { [displayLink_ setPaused:YES]; } } } - (void)requestRenderIfNeeded { @synchronized(self) { if (explicitRendering_) { // Resume the callback temporarily. // This is paused again soon in drawFrame. [displayLink_ setPaused:NO]; } } } @end ` const viewJava = `// Code generated by ebitenmobile. DO NOT EDIT. package {{.JavaPkg}}.{{.PrefixLower}}; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import android.content.Context; import android.hardware.input.InputManager; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.KeyEvent; import android.view.InputDevice; import android.view.MotionEvent; import android.view.ViewGroup; import android.view.WindowManager; import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview; public class EbitenView extends ViewGroup implements InputManager.InputDeviceListener { static class Gamepad { public int deviceId; public ArrayList axes; public ArrayList hats; } // See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L154-L173 static class RangeComparator implements Comparator { @Override public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { int arg0Axis = arg0.getAxis(); int arg1Axis = arg1.getAxis(); if (arg0Axis == MotionEvent.AXIS_GAS) { arg0Axis = MotionEvent.AXIS_BRAKE; } else if (arg0Axis == MotionEvent.AXIS_BRAKE) { arg0Axis = MotionEvent.AXIS_GAS; } if (arg1Axis == MotionEvent.AXIS_GAS) { arg1Axis = MotionEvent.AXIS_BRAKE; } else if (arg1Axis == MotionEvent.AXIS_BRAKE) { arg1Axis = MotionEvent.AXIS_GAS; } return arg0Axis - arg1Axis; } } private static double pxToDp(double x) { return x / Ebitenmobileview.deviceScale(); } public EbitenView(Context context) { super(context); initialize(context); } public EbitenView(Context context, AttributeSet attrs) { super(context, attrs); initialize(context); } private void initialize(Context context) { this.gamepads = new ArrayList(); this.ebitenSurfaceView = new EbitenSurfaceView(getContext()); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); addView(this.ebitenSurfaceView, params); this.inputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE); this.inputManager.registerInputDeviceListener(this, null); for (int id : this.inputManager.getInputDeviceIds()) { this.onInputDeviceAdded(id); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { this.ebitenSurfaceView.layout(0, 0, right - left, bottom - top); double widthInDp = pxToDp(right - left); double heightInDp = pxToDp(bottom - top); Ebitenmobileview.layout(widthInDp, heightInDp); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { Ebitenmobileview.onKeyDownOnAndroid(keyCode, event.getUnicodeChar(), event.getSource(), event.getDeviceId()); return true; } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { Ebitenmobileview.onKeyUpOnAndroid(keyCode, event.getSource(), event.getDeviceId()); return true; } @Override public boolean onTouchEvent(MotionEvent e) { // getActionIndex returns a valid value only for the action whose index is the returned value of getActionIndex (#2220). // See https://developer.android.com/reference/android/view/MotionEvent#getActionMasked(). // For other pointers, treat their actions as MotionEvent.ACTION_MOVE. int touchIndex = e.getActionIndex(); for (int i = 0; i < e.getPointerCount(); i++) { int id = e.getPointerId(i); int x = (int)e.getX(i); int y = (int)e.getY(i); int action = (i == touchIndex) ? e.getActionMasked() : MotionEvent.ACTION_MOVE; Ebitenmobileview.updateTouchesOnAndroid(action, id, (int)pxToDp(x), (int)pxToDp(y)); } return true; } private Gamepad getGamepad(int deviceId) { for (Gamepad gamepad : this.gamepads) { if (gamepad.deviceId == deviceId) { return gamepad; } } return null; } @Override public boolean onGenericMotionEvent(MotionEvent event) { if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) { return super.onGenericMotionEvent(event); } if (event.getAction() != MotionEvent.ACTION_MOVE) { return super.onGenericMotionEvent(event); } // See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L256-L277 Gamepad gamepad = this.getGamepad(event.getDeviceId()); if (gamepad == null) { return true; } int actionPointerIndex = event.getActionIndex(); for (int i = 0; i < gamepad.axes.size(); i++) { InputDevice.MotionRange range = gamepad.axes.get(i); float axisValue = event.getAxisValue(range.getAxis(), actionPointerIndex); float value = (axisValue - range.getMin()) / range.getRange() * 2.0f - 1.0f; Ebitenmobileview.onGamepadAxisChanged(gamepad.deviceId, i, value); } for (int i = 0; i < gamepad.hats.size() / 2; i++) { int hatX = Math.round(event.getAxisValue(gamepad.hats.get(2*i).getAxis(), actionPointerIndex)); int hatY = Math.round(event.getAxisValue(gamepad.hats.get(2*i+1).getAxis(), actionPointerIndex)); Ebitenmobileview.onGamepadHatChanged(gamepad.deviceId, i, hatX, hatY); } return true; } @Override public void onInputDeviceAdded(int deviceId) { InputDevice inputDevice = this.inputManager.getInputDevice(deviceId); // The InputDevice can be null on some deivces (#1342). if (inputDevice == null) { return; } // A fingerprint reader is unexpectedly recognized as a joystick. Skip this (#1542). if (inputDevice.getName().equals("uinput-fpc")) { return; } int sources = inputDevice.getSources(); if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD && (sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) { return; } // See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L182-L216 List ranges = inputDevice.getMotionRanges(); Collections.sort(ranges, new RangeComparator()); Gamepad gamepad = new Gamepad(); gamepad.deviceId = deviceId; gamepad.axes = new ArrayList(); gamepad.hats = new ArrayList(); for (InputDevice.MotionRange range : ranges) { if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) { gamepad.hats.add(range); } else { gamepad.axes.add(range); } } this.gamepads.add(gamepad); String descriptor = inputDevice.getDescriptor(); int vendorId = inputDevice.getVendorId(); int productId = inputDevice.getProductId(); // These values are required to calculate SDL's GUID. int buttonMask = getButtonMask(inputDevice, gamepad.hats.size()/2); int axisMask = getAxisMask(inputDevice); Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), gamepad.axes.size(), gamepad.hats.size()/2, descriptor, vendorId, productId, buttonMask, axisMask); // Initialize the trigger axes values explicitly, or the initial button values would be 0.5 instead of 0. if (gamepad.axes.size() >= 6) { Ebitenmobileview.onGamepadAxisChanged(deviceId, 4, -1); Ebitenmobileview.onGamepadAxisChanged(deviceId, 5, -1); } } // The implementation is copied from SDL: // https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L308 private static int getButtonMask(InputDevice joystickDevice, int nhats) { int buttonMask = 0; int[] keys = new int[] { KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, KeyEvent.KEYCODE_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BUTTON_MODE, KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_BUTTON_THUMBL, KeyEvent.KEYCODE_BUTTON_THUMBR, KeyEvent.KEYCODE_BUTTON_L1, KeyEvent.KEYCODE_BUTTON_R1, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_DPAD_CENTER, // These don't map into any SDL controller buttons directly KeyEvent.KEYCODE_BUTTON_L2, KeyEvent.KEYCODE_BUTTON_R2, KeyEvent.KEYCODE_BUTTON_C, KeyEvent.KEYCODE_BUTTON_Z, KeyEvent.KEYCODE_BUTTON_1, KeyEvent.KEYCODE_BUTTON_2, KeyEvent.KEYCODE_BUTTON_3, KeyEvent.KEYCODE_BUTTON_4, KeyEvent.KEYCODE_BUTTON_5, KeyEvent.KEYCODE_BUTTON_6, KeyEvent.KEYCODE_BUTTON_7, KeyEvent.KEYCODE_BUTTON_8, KeyEvent.KEYCODE_BUTTON_9, KeyEvent.KEYCODE_BUTTON_10, KeyEvent.KEYCODE_BUTTON_11, KeyEvent.KEYCODE_BUTTON_12, KeyEvent.KEYCODE_BUTTON_13, KeyEvent.KEYCODE_BUTTON_14, KeyEvent.KEYCODE_BUTTON_15, KeyEvent.KEYCODE_BUTTON_16, }; int[] masks = new int[] { (1 << 0), // A -> A (1 << 1), // B -> B (1 << 2), // X -> X (1 << 3), // Y -> Y (1 << 4), // BACK -> BACK (1 << 5), // MODE -> GUIDE (1 << 6), // START -> START (1 << 7), // THUMBL -> LEFTSTICK (1 << 8), // THUMBR -> RIGHTSTICK (1 << 9), // L1 -> LEFTSHOULDER (1 << 10), // R1 -> RIGHTSHOULDER (1 << 11), // DPAD_UP -> DPAD_UP (1 << 12), // DPAD_DOWN -> DPAD_DOWN (1 << 13), // DPAD_LEFT -> DPAD_LEFT (1 << 14), // DPAD_RIGHT -> DPAD_RIGHT (1 << 4), // SELECT -> BACK (1 << 0), // DPAD_CENTER -> A (1 << 15), // L2 -> ?? (1 << 16), // R2 -> ?? (1 << 17), // C -> ?? (1 << 18), // Z -> ?? (1 << 20), // 1 -> ?? (1 << 21), // 2 -> ?? (1 << 22), // 3 -> ?? (1 << 23), // 4 -> ?? (1 << 24), // 5 -> ?? (1 << 25), // 6 -> ?? (1 << 26), // 7 -> ?? (1 << 27), // 8 -> ?? (1 << 28), // 9 -> ?? (1 << 29), // 10 -> ?? (1 << 30), // 11 -> ?? (1 << 31), // 12 -> ?? // We're out of room... 0xFFFFFFFF, // 13 -> ?? 0xFFFFFFFF, // 14 -> ?? 0xFFFFFFFF, // 15 -> ?? 0xFFFFFFFF, // 16 -> ?? }; boolean[] hasKeys = joystickDevice.hasKeys(keys); for (int i = 0; i < keys.length; ++i) { if (hasKeys[i]) { buttonMask |= masks[i]; } } // https://github.com/libsdl-org/SDL/blob/47f2373dc13b66c48bf4024fcdab53cd0bdd59bb/src/joystick/android/SDL_sysjoystick.c#L360-L367 if (nhats > 0) { // Add Dpad buttons. buttonMask |= 1 << 11; buttonMask |= 1 << 12; buttonMask |= 1 << 13; buttonMask |= 1 << 14; } return buttonMask; } private static int getAxisMask(InputDevice joystickDevice) { final int SDL_CONTROLLER_AXIS_LEFTX = 0; final int SDL_CONTROLLER_AXIS_LEFTY = 1; final int SDL_CONTROLLER_AXIS_RIGHTX = 2; final int SDL_CONTROLLER_AXIS_RIGHTY = 3; final int SDL_CONTROLLER_AXIS_TRIGGERLEFT = 4; final int SDL_CONTROLLER_AXIS_TRIGGERRIGHT = 5; int naxes = 0; for (InputDevice.MotionRange range : joystickDevice.getMotionRanges()) { if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { if (range.getAxis() != MotionEvent.AXIS_HAT_X && range.getAxis() != MotionEvent.AXIS_HAT_Y) { naxes++; } } } // The variable is_accelerometer seems always false, then skip the checking: // https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L207 int axisMask = 0; if (naxes >= 2) { axisMask |= ((1 << SDL_CONTROLLER_AXIS_LEFTX) | (1 << SDL_CONTROLLER_AXIS_LEFTY)); } if (naxes >= 4) { axisMask |= ((1 << SDL_CONTROLLER_AXIS_RIGHTX) | (1 << SDL_CONTROLLER_AXIS_RIGHTY)); } if (naxes >= 6) { axisMask |= ((1 << SDL_CONTROLLER_AXIS_TRIGGERLEFT) | (1 << SDL_CONTROLLER_AXIS_TRIGGERRIGHT)); } return axisMask; } @Override public void onInputDeviceChanged(int deviceId) { // Do nothing. } @Override public void onInputDeviceRemoved(int deviceId) { // Do not call inputManager.getInputDevice(), which returns null (#1185). Ebitenmobileview.onInputDeviceRemoved(deviceId); this.gamepads.remove(this.getGamepad(deviceId)); } // suspendGame suspends the game. // It is recommended to call this when the application is being suspended e.g., // Activity's onPause is called. public void suspendGame() { this.inputManager.unregisterInputDeviceListener(this); this.ebitenSurfaceView.onPause(); try { Ebitenmobileview.suspend(); } catch (final Exception e) { onErrorOnGameUpdate(e); } } // resumeGame resumes the game. // It is recommended to call this when the application is being resumed e.g., // Activity's onResume is called. public void resumeGame() { this.inputManager.registerInputDeviceListener(this, null); this.ebitenSurfaceView.onResume(); try { Ebitenmobileview.resume(); } catch (final Exception e) { onErrorOnGameUpdate(e); } } // onErrorOnGameUpdate is called on the main thread when an error happens when updating a game. // You can define your own error handler, e.g., using Crashlytics, by overriding this method. protected void onErrorOnGameUpdate(Exception e) { Log.e("Go", e.toString()); } private EbitenSurfaceView ebitenSurfaceView; private InputManager inputManager; private ArrayList gamepads; } ` const surfaceViewJava = `// Code generated by ebitenmobile. DO NOT EDIT. package {{.JavaPkg}}.{{.PrefixLower}}; import android.content.Context; import android.opengl.GLSurfaceView; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.Log; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview; import {{.JavaPkg}}.ebitenmobileview.RenderRequester; import {{.JavaPkg}}.{{.PrefixLower}}.EbitenView; class EbitenSurfaceView extends GLSurfaceView implements RenderRequester { private class EbitenRenderer implements GLSurfaceView.Renderer { private boolean errored_ = false; @Override public void onDrawFrame(GL10 gl) { if (errored_) { return; } try { Ebitenmobileview.update(); } catch (final Exception e) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { onErrorOnGameUpdate(e); } }); errored_ = true; } } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { Ebitenmobileview.onContextLost(); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { } } public EbitenSurfaceView(Context context) { super(context); initialize(); } public EbitenSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } private void initialize() { setEGLContextClientVersion(2); setEGLConfigChooser(8, 8, 8, 8, 0, 0); setRenderer(new EbitenRenderer()); Ebitenmobileview.setRenderRequester(this); } private void onErrorOnGameUpdate(Exception e) { ((EbitenView)getParent()).onErrorOnGameUpdate(e); } @Override public synchronized void setExplicitRenderingMode(boolean explicitRendering) { if (explicitRendering) { setRenderMode(RENDERMODE_WHEN_DIRTY); } else { setRenderMode(RENDERMODE_CONTINUOUSLY); } } @Override public synchronized void requestRenderIfNeeded() { if (getRenderMode() == RENDERMODE_WHEN_DIRTY) { requestRender(); } } } `