938 lines
28 KiB
C++
938 lines
28 KiB
C++
/*************************************************************************/
|
|
/* 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() == 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);
|
|
|
|
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
|