There are still some left in the Android Java code, even stuff to swap between GLES1 and GLES2 support from early Godot days... would be good to see some cleanup there too one day. The "graphics/api" option for Android exports is removed, as only GLES 3.0 is supported. It can be readded when GLES 2.0 support comes back. Fixes #13004.
952 lines
28 KiB
C++
952 lines
28 KiB
C++
/*************************************************************************/
|
|
/* godot_android.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2017 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 "engine.h"
|
|
#include "file_access_android.h"
|
|
#include "main/main.h"
|
|
#include "os_android.h"
|
|
#include "project_settings.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) {
|
|
|
|
print_line("attempt to call " + String(p_method));
|
|
|
|
r_error.error = Variant::CallError::CALL_OK;
|
|
|
|
Map<StringName, MethodData>::Element *E = method_map.find(p_method);
|
|
if (!E) {
|
|
|
|
print_line("no exists");
|
|
r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD;
|
|
return Variant();
|
|
}
|
|
|
|
int ac = E->get().argtypes.size();
|
|
if (ac < p_argcount) {
|
|
|
|
print_line("fewargs");
|
|
r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
|
r_error.argument = ac;
|
|
return Variant();
|
|
}
|
|
|
|
if (ac > p_argcount) {
|
|
|
|
print_line("manyargs");
|
|
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;
|
|
}
|
|
}
|
|
|
|
print_line("calling method!!");
|
|
|
|
Variant ret;
|
|
|
|
switch (E->get().ret_type) {
|
|
|
|
case Variant::NIL: {
|
|
|
|
print_line("call void");
|
|
env->CallVoidMethodA(instance, E->get().method, v);
|
|
} break;
|
|
case Variant::BOOL: {
|
|
|
|
ret = env->CallBooleanMethodA(instance, E->get().method, v);
|
|
print_line("call bool");
|
|
} break;
|
|
case Variant::INT: {
|
|
|
|
ret = env->CallIntMethodA(instance, E->get().method, v);
|
|
print_line("call int");
|
|
} 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: {
|
|
|
|
print_line("failure..");
|
|
ERR_FAIL_V(Variant());
|
|
} break;
|
|
}
|
|
|
|
print_line("success");
|
|
|
|
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);
|
|
print_line("INIT VIDEO MODE: " + itos(w) + "," + itos(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() == true) {
|
|
|
|
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);
|
|
|
|
print_line("Singl: " + singname + " Method: " + mname + " RetVal: " + retval);
|
|
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());
|
|
print_line("METHOD: " + mname + " sig: " + cs);
|
|
jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data());
|
|
if (!mid) {
|
|
|
|
print_line("FAILED GETTING METHOID " + mname);
|
|
}
|
|
|
|
s->add_method(mname, mid, types, get_jni_type(retval));
|
|
}
|
|
|
|
#endif
|