/*************************************************************************/ /* godot_android.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #ifdef ANDROID_NATIVE_ACTIVITY #include "core/engine.h" #include "core/project_settings.h" #include "file_access_android.h" #include "main/main.h" #include "os_android.h" #include <EGL/egl.h> #include <android/log.h> #include <android/sensor.h> #include <android/window.h> #include <android_native_app_glue.h> #include <errno.h> #include <jni.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "godot", __VA_ARGS__)) #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "godot", __VA_ARGS__)) extern "C" { JNIEXPORT void JNICALL Java_org_godotengine_godot_Godot_registerSingleton(JNIEnv *env, jobject obj, jstring name, jobject p_object); JNIEXPORT void JNICALL Java_org_godotengine_godot_Godot_registerMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args); JNIEXPORT jstring JNICALL Java_org_godotengine_godot_Godot_getGlobal(JNIEnv *env, jobject obj, jstring path); }; class JNISingleton : public Object { GDCLASS(JNISingleton, Object); struct MethodData { jmethodID method; Variant::Type ret_type; Vector<Variant::Type> argtypes; }; jobject instance; Map<StringName, MethodData> method_map; JNIEnv *env; public: void update_env(JNIEnv *p_env) { env = p_env; } virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { r_error.error = Variant::CallError::CALL_OK; Map<StringName, MethodData>::Element *E = method_map.find(p_method); if (!E) { r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; return Variant(); } int ac = E->get().argtypes.size(); if (ac < p_argcount) { r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = ac; return Variant(); } if (ac > p_argcount) { r_error.error = Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_error.argument = ac; return Variant(); } for (int i = 0; i < p_argcount; i++) { if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) { r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = i; r_error.expected = E->get().argtypes[i]; } } jvalue *v = NULL; if (p_argcount) { v = (jvalue *)alloca(sizeof(jvalue) * p_argcount); } for (int i = 0; i < p_argcount; i++) { switch (E->get().argtypes[i]) { case Variant::BOOL: { v[i].z = *p_args[i]; } break; case Variant::INT: { v[i].i = *p_args[i]; } break; case Variant::REAL: { v[i].f = *p_args[i]; } break; case Variant::STRING: { String s = *p_args[i]; jstring jStr = env->NewStringUTF(s.utf8().get_data()); v[i].l = jStr; } break; case Variant::STRING_ARRAY: { PoolVector<String> sarray = *p_args[i]; jobjectArray arr = env->NewObjectArray(sarray.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); for (int j = 0; j < sarray.size(); j++) { env->SetObjectArrayElement(arr, j, env->NewStringUTF(sarray[i].utf8().get_data())); } v[i].l = arr; } break; case Variant::INT_ARRAY: { PoolVector<int> array = *p_args[i]; jintArray arr = env->NewIntArray(array.size()); PoolVector<int>::Read r = array.read(); env->SetIntArrayRegion(arr, 0, array.size(), r.ptr()); v[i].l = arr; } break; case Variant::REAL_ARRAY: { PoolVector<float> array = *p_args[i]; jfloatArray arr = env->NewFloatArray(array.size()); PoolVector<float>::Read r = array.read(); env->SetFloatArrayRegion(arr, 0, array.size(), r.ptr()); v[i].l = arr; } break; default: { ERR_FAIL_V(Variant()); } break; } } Variant ret; switch (E->get().ret_type) { case Variant::NIL: { env->CallVoidMethodA(instance, E->get().method, v); } break; case Variant::BOOL: { ret = env->CallBooleanMethodA(instance, E->get().method, v); } break; case Variant::INT: { ret = env->CallIntMethodA(instance, E->get().method, v); } break; case Variant::REAL: { ret = env->CallFloatMethodA(instance, E->get().method, v); } break; case Variant::STRING: { jobject o = env->CallObjectMethodA(instance, E->get().method, v); String singname = env->GetStringUTFChars((jstring)o, NULL); } break; case Variant::STRING_ARRAY: { jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v); int stringCount = env->GetArrayLength(arr); PoolVector<String> sarr; for (int i = 0; i < stringCount; i++) { jstring string = (jstring)env->GetObjectArrayElement(arr, i); const char *rawString = env->GetStringUTFChars(string, 0); sarr.push_back(String(rawString)); } ret = sarr; } break; case Variant::INT_ARRAY: { jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v); int fCount = env->GetArrayLength(arr); PoolVector<int> sarr; sarr.resize(fCount); PoolVector<int>::Write w = sarr.write(); env->GetIntArrayRegion(arr, 0, fCount, w.ptr()); w = PoolVector<int>::Write(); ret = sarr; } break; case Variant::REAL_ARRAY: { jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v); int fCount = env->GetArrayLength(arr); PoolVector<float> sarr; sarr.resize(fCount); PoolVector<float>::Write w = sarr.write(); env->GetFloatArrayRegion(arr, 0, fCount, w.ptr()); w = PoolVector<float>::Write(); ret = sarr; } break; default: { ERR_FAIL_V(Variant()); } break; } return ret; } jobject get_instance() const { return instance; } void set_instance(jobject p_instance) { instance = p_instance; } void add_method(const StringName &p_name, jmethodID p_method, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) { MethodData md; md.method = p_method; md.argtypes = p_args; md.ret_type = p_ret_type; method_map[p_name] = md; } JNISingleton() {} }; //JNIEnv *JNISingleton::env=NULL; static HashMap<String, JNISingleton *> jni_singletons; struct engine { struct android_app *app; OS_Android *os; JNIEnv *jni; ASensorManager *sensorManager; const ASensor *accelerometerSensor; const ASensor *magnetometerSensor; const ASensor *gyroscopeSensor; ASensorEventQueue *sensorEventQueue; bool display_active; bool requested_quit; int animating; EGLDisplay display; EGLSurface surface; EGLContext context; int32_t width; int32_t height; }; /** * Initialize an EGL context for the current display. */ static int engine_init_display(struct engine *engine, bool p_gl2) { // initialize OpenGL ES and EGL /* * Here specify the attributes of the desired configuration. * Below, we select an EGLConfig with at least 8 bits per color * component compatible with on-screen windows */ const EGLint gl2_attribs[] = { // EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 4, EGL_GREEN_SIZE, 4, EGL_RED_SIZE, 4, EGL_ALPHA_SIZE, 0, EGL_DEPTH_SIZE, 16, EGL_STENCIL_SIZE, EGL_DONT_CARE, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; const EGLint gl1_attribs[] = { // EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 4, EGL_GREEN_SIZE, 4, EGL_RED_SIZE, 4, EGL_ALPHA_SIZE, 0, EGL_DEPTH_SIZE, 16, EGL_STENCIL_SIZE, EGL_DONT_CARE, EGL_NONE }; const EGLint *attribs = p_gl2 ? gl2_attribs : gl1_attribs; EGLint w, h, dummy, format; EGLint numConfigs; EGLConfig config; EGLSurface surface; EGLContext context; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, 0, 0); /* Here, the application chooses the configuration it desires. In this * sample, we have a very simplified selection process, where we pick * the first EGLConfig that matches our criteria */ eglChooseConfig(display, attribs, &config, 1, &numConfigs); LOGI("Num configs: %i\n", numConfigs); /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is * guaranteed to be accepted by ANativeWindow_setBuffersGeometry(). * As soon as we picked a EGLConfig, we can safely reconfigure the * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */ eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); ANativeWindow_setBuffersGeometry(engine->app->window, 0, 0, format); //ANativeWindow_setFlags(engine->app->window, 0, 0, format|); surface = eglCreateWindowSurface(display, config, engine->app->window, NULL); const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; context = eglCreateContext(display, config, EGL_NO_CONTEXT, p_gl2 ? context_attribs : NULL); if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { LOGW("Unable to eglMakeCurrent"); return -1; } eglQuerySurface(display, surface, EGL_WIDTH, &w); eglQuerySurface(display, surface, EGL_HEIGHT, &h); //engine->os->set_egl_extensions(eglQueryString(display,EGL_EXTENSIONS)); engine->os->init_video_mode(w, h); engine->display = display; engine->context = context; engine->surface = surface; engine->width = w; engine->height = h; engine->display_active = true; //engine->state.angle = 0; // Initialize GL state. //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); glEnable(GL_CULL_FACE); // glShadeModel(GL_SMOOTH); glDisable(GL_DEPTH_TEST); LOGI("GL Version: %s - %s %s\n", glGetString(GL_VERSION), glGetString(GL_VENDOR), glGetString(GL_RENDERER)); return 0; } static void engine_draw_frame(struct engine *engine) { if (engine->display == NULL) { // No display. return; } // Just fill the screen with a color. //glClearColor(0,1,0,1); //glClear(GL_COLOR_BUFFER_BIT); if (engine->os && engine->os->main_loop_iterate()) { engine->requested_quit = true; return; //should exit instead } eglSwapBuffers(engine->display, engine->surface); } static void engine_term_display(struct engine *engine) { if (engine->display != EGL_NO_DISPLAY) { eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (engine->context != EGL_NO_CONTEXT) { eglDestroyContext(engine->display, engine->context); } if (engine->surface != EGL_NO_SURFACE) { eglDestroySurface(engine->display, engine->surface); } eglTerminate(engine->display); } engine->animating = 0; engine->display = EGL_NO_DISPLAY; engine->context = EGL_NO_CONTEXT; engine->surface = EGL_NO_SURFACE; engine->display_active = false; } /** * Process the next input event. */ static int32_t engine_handle_input(struct android_app *app, AInputEvent *event) { struct engine *engine = (struct engine *)app->userData; if (!engine->os) return 0; switch (AInputEvent_getType(event)) { case AINPUT_EVENT_TYPE_KEY: { int ac = AKeyEvent_getAction(event); switch (ac) { case AKEY_EVENT_ACTION_DOWN: { int32_t code = AKeyEvent_getKeyCode(event); if (code == AKEYCODE_BACK) { //AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled); if (engine->os) engine->os->main_loop_request_quit(); return 1; } } break; case AKEY_EVENT_ACTION_UP: { } break; } } break; case AINPUT_EVENT_TYPE_MOTION: { Vector<OS_Android::TouchPos> touchvec; int pc = AMotionEvent_getPointerCount(event); touchvec.resize(pc); for (int i = 0; i < pc; i++) { touchvec[i].pos.x = AMotionEvent_getX(event, i); touchvec[i].pos.y = AMotionEvent_getY(event, i); touchvec[i].id = AMotionEvent_getPointerId(event, i); } //System.out.printf("gaction: %d\n",event.getAction()); int pidx = (AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> 8; switch (AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK) { case AMOTION_EVENT_ACTION_DOWN: { engine->os->process_touch(0, 0, touchvec); //System.out.printf("action down at: %f,%f\n", event.getX(),event.getY()); } break; case AMOTION_EVENT_ACTION_MOVE: { engine->os->process_touch(1, 0, touchvec); /* for(int i=0;i<event.getPointerCount();i++) { System.out.printf("%d - moved to: %f,%f\n",i, event.getX(i),event.getY(i)); } */ } break; case AMOTION_EVENT_ACTION_POINTER_UP: { engine->os->process_touch(4, pidx, touchvec); //System.out.printf("%d - s.up at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx)); } break; case AMOTION_EVENT_ACTION_POINTER_DOWN: { engine->os->process_touch(3, pidx, touchvec); //System.out.printf("%d - s.down at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx)); } break; case AMOTION_EVENT_ACTION_CANCEL: case AMOTION_EVENT_ACTION_UP: { engine->os->process_touch(2, 0, touchvec); /* for(int i=0;i<event.getPointerCount();i++) { System.out.printf("%d - up! %f,%f\n",i, event.getX(i),event.getY(i)); } */ } break; } return 1; } break; } return 0; } /** * Process the next main command. */ static void _gfx_init(void *ud, bool p_gl2) { struct engine *engine = (struct engine *)ud; engine_init_display(engine, p_gl2); } static void engine_handle_cmd(struct android_app *app, int32_t cmd) { struct engine *engine = (struct engine *)app->userData; // LOGI("**** CMD %i\n",cmd); switch (cmd) { case APP_CMD_SAVE_STATE: // The system has asked us to save our current state. Do so. //engine->app->savedState = malloc(sizeof(struct saved_state)); //*((struct saved_state*)engine->app->savedState) = engine->state; //engine->app->savedStateSize = sizeof(struct saved_state); break; case APP_CMD_CONFIG_CHANGED: case APP_CMD_WINDOW_RESIZED: { if (engine->display_active) { EGLint w, h; eglQuerySurface(engine->display, engine->surface, EGL_WIDTH, &w); eglQuerySurface(engine->display, engine->surface, EGL_HEIGHT, &h); // if (w==engine->os->get_video_mode().width && h==engine->os->get_video_mode().height) // break; engine_term_display(engine); } engine->os->reload_gfx(); engine_draw_frame(engine); engine->animating = 1; } break; case APP_CMD_INIT_WINDOW: //The window is being shown, get it ready. //LOGI("INIT WINDOW"); if (engine->app->window != NULL) { if (engine->os == NULL) { //do initialization here, when there's OpenGL! hackish but the only way engine->os = new OS_Android(_gfx_init, engine); __android_log_print(ANDROID_LOG_INFO, "godot", "pre asdasd setup..."); Error err = Main::setup("apk", 0, NULL); String modules = ProjectSettings::get_singleton()->get("android/modules"); Vector<String> mods = modules.split(",", false); mods.push_back("GodotOS"); __android_log_print(ANDROID_LOG_INFO, "godot", "mod count: %i", mods.size()); if (mods.size()) { jclass activityClass = engine->jni->FindClass("android/app/NativeActivity"); jmethodID getClassLoader = engine->jni->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); jobject cls = engine->jni->CallObjectMethod(app->activity->clazz, getClassLoader); jclass classLoader = engine->jni->FindClass("java/lang/ClassLoader"); jmethodID findClass = engine->jni->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); static JNINativeMethod methods[] = { { "registerSingleton", "(Ljava/lang/String;Ljava/lang/Object;)V", (void *)&Java_org_godotengine_godot_Godot_registerSingleton }, { "registerMethod", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V", (void *)&Java_org_godotengine_godot_Godot_registerMethod }, { "getGlobal", "(Ljava/lang/String;)Ljava/lang/String;", (void *)&Java_org_godotengine_godot_Godot_getGlobal }, }; jstring gstrClassName = engine->jni->NewStringUTF("org/godotengine/godot/Godot"); jclass GodotClass = (jclass)engine->jni->CallObjectMethod(cls, findClass, gstrClassName); __android_log_print(ANDROID_LOG_INFO, "godot", "godot ****^*^*?^*^*class data %x", GodotClass); engine->jni->RegisterNatives(GodotClass, methods, sizeof(methods) / sizeof(methods[0])); for (int i = 0; i < mods.size(); i++) { String m = mods[i]; //jclass singletonClass = engine->jni->FindClass(m.utf8().get_data()); jstring strClassName = engine->jni->NewStringUTF(m.utf8().get_data()); jclass singletonClass = (jclass)engine->jni->CallObjectMethod(cls, findClass, strClassName); __android_log_print(ANDROID_LOG_INFO, "godot", "****^*^*?^*^*class data %x", singletonClass); jmethodID initialize = engine->jni->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lorg/godotengine/godot/Godot$SingletonBase;"); jobject obj = engine->jni->CallStaticObjectMethod(singletonClass, initialize, app->activity->clazz); __android_log_print(ANDROID_LOG_INFO, "godot", "****^*^*?^*^*class instance %x", obj); jobject gob = engine->jni->NewGlobalRef(obj); } } if (!Main::start()) return; //should exit instead and print the error engine->os->main_loop_begin(); } else { //i guess recreate resources? engine->os->reload_gfx(); } engine->animating = 1; engine_draw_frame(engine); } break; case APP_CMD_TERM_WINDOW: // The window is being hidden or closed, clean it up. //LOGI("TERM WINDOW"); engine_term_display(engine); break; case APP_CMD_GAINED_FOCUS: // When our app gains focus, we start monitoring the accelerometer. if (engine->accelerometerSensor != NULL) { ASensorEventQueue_enableSensor(engine->sensorEventQueue, engine->accelerometerSensor); // We'd like to get 60 events per second (in us). ASensorEventQueue_setEventRate(engine->sensorEventQueue, engine->accelerometerSensor, (1000L / 60) * 1000); } // start monitoring gravity if (engine->gravitySensor != NULL) { ASensorEventQueue_enableSensor(engine->sensorEventQueue, engine->gravitySensor); // We'd like to get 60 events per second (in us). ASensorEventQueue_setEventRate(engine->sensorEventQueue, engine->gravitySensor, (1000L / 60) * 1000); } // Also start monitoring the magnetometer. if (engine->magnetometerSensor != NULL) { ASensorEventQueue_enableSensor(engine->sensorEventQueue, engine->magnetometerSensor); // We'd like to get 60 events per second (in us). ASensorEventQueue_setEventRate(engine->sensorEventQueue, engine->magnetometerSensor, (1000L / 60) * 1000); } // And the gyroscope. if (engine->gyroscopeSensor != NULL) { ASensorEventQueue_enableSensor(engine->sensorEventQueue, engine->gyroscopeSensor); // We'd like to get 60 events per second (in us). ASensorEventQueue_setEventRate(engine->sensorEventQueue, engine->gyroscopeSensor, (1000L / 60) * 1000); } engine->animating = 1; break; case APP_CMD_LOST_FOCUS: // When our app loses focus, we stop monitoring the sensors. // This is to avoid consuming battery while not being used. if (engine->accelerometerSensor != NULL) { ASensorEventQueue_disableSensor(engine->sensorEventQueue, engine->accelerometerSensor); } if (engine->gravitySensor != NULL) { ASensorEventQueue_disableSensor(engine->sensorEventQueue, engine->gravitySensor); } if (engine->magnetometerSensor != NULL) { ASensorEventQueue_disableSensor(engine->sensorEventQueue, engine->magnetometerSensor); } if (engine->gyroscopeSensor != NULL) { ASensorEventQueue_disableSensor(engine->sensorEventQueue, engine->gyroscopeSensor); } // Also stop animating. engine->animating = 0; engine_draw_frame(engine); break; } } void android_main(struct android_app *app) { struct engine engine; // Make sure glue isn't stripped. app_dummy(); memset(&engine, 0, sizeof(engine)); app->userData = &engine; app->onAppCmd = engine_handle_cmd; app->onInputEvent = engine_handle_input; engine.app = app; engine.requested_quit = false; engine.os = NULL; engine.display_active = false; FileAccessAndroid::asset_manager = app->activity->assetManager; // Prepare to monitor sensors engine.sensorManager = ASensorManager_getInstance(); engine.accelerometerSensor = ASensorManager_getDefaultSensor(engine.sensorManager, ASENSOR_TYPE_ACCELEROMETER); engine.gravitySensor = ASensorManager_getDefaultSensor(engine.sensorManager, ASENSOR_TYPE_GRAVITY); engine.magnetometerSensor = ASensorManager_getDefaultSensor(engine.sensorManager, ASENSOR_TYPE_MAGNETIC_FIELD); engine.gyroscopeSensor = ASensorManager_getDefaultSensor(engine.sensorManager, ASENSOR_TYPE_GYROSCOPE); engine.sensorEventQueue = ASensorManager_createEventQueue(engine.sensorManager, app->looper, LOOPER_ID_USER, NULL, NULL); ANativeActivity_setWindowFlags(app->activity, AWINDOW_FLAG_FULLSCREEN | AWINDOW_FLAG_KEEP_SCREEN_ON, 0); app->activity->vm->AttachCurrentThread(&engine.jni, NULL); // loop waiting for stuff to do. while (1) { // Read all pending events. int ident; int events; struct android_poll_source *source; // If not animating, we will block forever waiting for events. // If animating, we loop until all events are read, then continue // to draw the next frame of animation. int nullmax = 50; while ((ident = ALooper_pollAll(engine.animating ? 0 : -1, NULL, &events, (void **)&source)) >= 0) { // Process this event. if (source != NULL) { // LOGI("process\n"); source->process(app, source); } else { nullmax--; if (nullmax < 0) break; } // If a sensor has data, process it now. // LOGI("events\n"); if (ident == LOOPER_ID_USER) { if (engine.accelerometerSensor != NULL || engine.magnetometerSensor != NULL || engine.gyroscopeSensor != NULL) { ASensorEvent event; while (ASensorEventQueue_getEvents(engine.sensorEventQueue, &event, 1) > 0) { if (engine.os) { if (event.acceleration != NULL) { engine.os->process_accelerometer(Vector3(event.acceleration.x, event.acceleration.y, event.acceleration.z)); } if (event.magnetic != NULL) { engine.os->process_magnetometer(Vector3(event.magnetic.x, event.magnetic.y, event.magnetic.z)); } if (event.vector != NULL) { engine.os->process_gyroscope(Vector3(event.vector.x, event.vector.y, event.vector.z)); } } } } } // Check if we are exiting. if (app->destroyRequested != 0) { if (engine.os) { engine.os->main_loop_request_quit(); } app->destroyRequested = 0; } if (engine.requested_quit) { engine_term_display(&engine); exit(0); } // LOGI("end\n"); } // LOGI("engine animating? %i\n",engine.animating); if (engine.animating) { //do os render engine_draw_frame(&engine); //LOGI("TERM WINDOW"); } } } JNIEXPORT void JNICALL Java_org_godotengine_godot_Godot_registerSingleton(JNIEnv *env, jobject obj, jstring name, jobject p_object) { String singname = env->GetStringUTFChars(name, NULL); JNISingleton *s = memnew(JNISingleton); s->update_env(env); s->set_instance(env->NewGlobalRef(p_object)); jni_singletons[singname] = s; Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s)); } static Variant::Type get_jni_type(const String &p_type) { static struct { const char *name; Variant::Type type; } _type_to_vtype[] = { { "void", Variant::NIL }, { "boolean", Variant::BOOL }, { "int", Variant::INT }, { "float", Variant::REAL }, { "java.lang.String", Variant::STRING }, { "[I", Variant::INT_ARRAY }, { "[F", Variant::REAL_ARRAY }, { "[Ljava.lang.String;", Variant::STRING_ARRAY }, { NULL, Variant::NIL } }; int idx = 0; while (_type_to_vtype[idx].name) { if (p_type == _type_to_vtype[idx].name) return _type_to_vtype[idx].type; idx++; } return Variant::NIL; } static const char *get_jni_sig(const String &p_type) { static struct { const char *name; const char *sig; } _type_to_vtype[] = { { "void", "V" }, { "boolean", "Z" }, { "int", "I" }, { "float", "F" }, { "java.lang.String", "Ljava/lang/String;" }, { "[I", "[I" }, { "[F", "[F" }, { "[Ljava.lang.String;", "[Ljava/lang/String;" }, { NULL, "V" } }; int idx = 0; while (_type_to_vtype[idx].name) { if (p_type == _type_to_vtype[idx].name) return _type_to_vtype[idx].sig; idx++; } return ""; } JNIEXPORT jstring JNICALL Java_org_godotengine_godot_Godot_getGlobal(JNIEnv *env, jobject obj, jstring path) { String js = env->GetStringUTFChars(path, NULL); return env->NewStringUTF(ProjectSettings::get_singleton()->get(js).operator String().utf8().get_data()); } JNIEXPORT void JNICALL Java_org_godotengine_godot_Godot_registerMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) { String singname = env->GetStringUTFChars(sname, NULL); ERR_FAIL_COND(!jni_singletons.has(singname)); JNISingleton *s = jni_singletons.get(singname); String mname = env->GetStringUTFChars(name, NULL); String retval = env->GetStringUTFChars(ret, NULL); Vector<Variant::Type> types; String cs = "("; int stringCount = env->GetArrayLength(args); for (int i = 0; i < stringCount; i++) { jstring string = (jstring)env->GetObjectArrayElement(args, i); const char *rawString = env->GetStringUTFChars(string, 0); types.push_back(get_jni_type(String(rawString))); cs += get_jni_sig(String(rawString)); } cs += ")"; cs += get_jni_sig(retval); jclass cls = env->GetObjectClass(s->get_instance()); jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data()); if (!mid) { print_line("RegisterMethod: Failed getting method ID: " + mname); } s->add_method(mname, mid, types, get_jni_type(retval)); } #endif