Skip to content

Commit

Permalink
Remove the restriction on supported types for Godot Android plugins
Browse files Browse the repository at this point in the history
The Android plugin implementation is updated to use `JavaClassWrapper` which was fixed in godotengine#96182, thus removing the limitation on supported types.

Note that `JavaClassWrapper` has also been updated in order to only provide access to public methods and constructor to GDScript.
  • Loading branch information
m4gr3d committed Sep 26, 2024
1 parent f7c567e commit f504985
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 218 deletions.
12 changes: 1 addition & 11 deletions platform/android/api/api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,19 @@

#include "core/config/engine.h"

#if !defined(ANDROID_ENABLED)
static JavaClassWrapper *java_class_wrapper = nullptr;
#endif

void register_android_api() {
#if !defined(ANDROID_ENABLED)
// On Android platforms, the `java_class_wrapper` instantiation and the
// `JNISingleton` registration occurs in
// `platform/android/java_godot_lib_jni.cpp#Java_org_godotengine_godot_GodotLib_setup`
java_class_wrapper = memnew(JavaClassWrapper); // Dummy
java_class_wrapper = memnew(JavaClassWrapper);
GDREGISTER_CLASS(JNISingleton);
#endif

GDREGISTER_CLASS(JavaClass);
GDREGISTER_CLASS(JavaObject);
GDREGISTER_CLASS(JavaClassWrapper);
Engine::get_singleton()->add_singleton(Engine::Singleton("JavaClassWrapper", JavaClassWrapper::get_singleton()));
}

void unregister_android_api() {
#if !defined(ANDROID_ENABLED)
memdelete(java_class_wrapper);
#endif
}

void JavaClass::_bind_methods() {
Expand Down
9 changes: 3 additions & 6 deletions platform/android/api/java_class_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ class JavaClassWrapper : public Object {
#ifdef ANDROID_ENABLED
RBMap<String, Ref<JavaClass>> class_cache;
friend class JavaClass;
jmethodID Class_getDeclaredConstructors;
jmethodID Class_getDeclaredMethods;
jmethodID Class_getConstructors;
jmethodID Class_getMethods;
jmethodID Class_getFields;
jmethodID Class_getName;
jmethodID Class_getSuperclass;
Expand Down Expand Up @@ -274,11 +274,8 @@ class JavaClassWrapper : public Object {

#ifdef ANDROID_ENABLED
Ref<JavaClass> wrap_jclass(jclass p_class);

JavaClassWrapper(jobject p_activity = nullptr);
#else
JavaClassWrapper();
#endif
JavaClassWrapper();
};

#endif // JAVA_CLASS_WRAPPER_H
197 changes: 24 additions & 173 deletions platform/android/api/jni_singleton.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,193 +31,53 @@
#ifndef JNI_SINGLETON_H
#define JNI_SINGLETON_H

#include "java_class_wrapper.h"

#include "core/config/engine.h"
#include "core/variant/variant.h"

#ifdef ANDROID_ENABLED
#include "jni_utils.h"
#endif

class JNISingleton : public Object {
GDCLASS(JNISingleton, Object);

#ifdef ANDROID_ENABLED
struct MethodData {
jmethodID method;
Variant::Type ret_type;
Vector<Variant::Type> argtypes;
};

jobject instance;
RBMap<StringName, MethodData> method_map;
#endif
Ref<JavaObject> wrapped_object;

public:
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
#ifdef ANDROID_ENABLED
RBMap<StringName, MethodData>::Element *E = method_map.find(p_method);

// Check the method we're looking for is in the JNISingleton map and that
// the arguments match.
bool call_error = !E || E->get().argtypes.size() != p_argcount;
if (!call_error) {
for (int i = 0; i < p_argcount; i++) {
if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) {
call_error = true;
break;
if (wrapped_object.is_valid()) {
RBMap<StringName, MethodData>::Element *E = method_map.find(p_method);

// Check the method we're looking for is in the JNISingleton map and that
// the arguments match.
bool call_error = !E || E->get().argtypes.size() != p_argcount;
if (!call_error) {
for (int i = 0; i < p_argcount; i++) {
if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) {
call_error = true;
break;
}
}
}
}

if (call_error) {
// The method is not in this map, defaulting to the regular instance calls.
return Object::callp(p_method, p_args, p_argcount, r_error);
}

ERR_FAIL_NULL_V(instance, Variant());

r_error.error = Callable::CallError::CALL_OK;

jvalue *v = nullptr;

if (p_argcount) {
v = (jvalue *)alloca(sizeof(jvalue) * p_argcount);
}

JNIEnv *env = get_jni_env();

int res = env->PushLocalFrame(16);

ERR_FAIL_COND_V(res != 0, Variant());

List<jobject> to_erase;
for (int i = 0; i < p_argcount; i++) {
jvalret vr = _variant_to_jvalue(env, E->get().argtypes[i], p_args[i]);
v[i] = vr.val;
if (vr.obj) {
to_erase.push_back(vr.obj);
if (!call_error) {
return wrapped_object->callp(p_method, p_args, p_argcount, r_error);
}
}

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) == JNI_TRUE;
} break;
case Variant::INT: {
ret = env->CallIntMethodA(instance, E->get().method, v);
} break;
case Variant::FLOAT: {
ret = env->CallFloatMethodA(instance, E->get().method, v);
} break;
case Variant::STRING: {
jobject o = env->CallObjectMethodA(instance, E->get().method, v);
ret = jstring_to_string((jstring)o, env);
env->DeleteLocalRef(o);
} break;
case Variant::PACKED_STRING_ARRAY: {
jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v);

ret = _jobject_to_variant(env, arr);

env->DeleteLocalRef(arr);
} break;
case Variant::PACKED_INT32_ARRAY: {
jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v);

int fCount = env->GetArrayLength(arr);
Vector<int> sarr;
sarr.resize(fCount);

int *w = sarr.ptrw();
env->GetIntArrayRegion(arr, 0, fCount, w);
ret = sarr;
env->DeleteLocalRef(arr);
} break;
case Variant::PACKED_INT64_ARRAY: {
jlongArray arr = (jlongArray)env->CallObjectMethodA(instance, E->get().method, v);

int fCount = env->GetArrayLength(arr);
Vector<int64_t> sarr;
sarr.resize(fCount);

int64_t *w = sarr.ptrw();
env->GetLongArrayRegion(arr, 0, fCount, w);
ret = sarr;
env->DeleteLocalRef(arr);
} break;
case Variant::PACKED_FLOAT32_ARRAY: {
jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v);

int fCount = env->GetArrayLength(arr);
Vector<float> sarr;
sarr.resize(fCount);

float *w = sarr.ptrw();
env->GetFloatArrayRegion(arr, 0, fCount, w);
ret = sarr;
env->DeleteLocalRef(arr);
} break;
case Variant::PACKED_FLOAT64_ARRAY: {
jdoubleArray arr = (jdoubleArray)env->CallObjectMethodA(instance, E->get().method, v);

int fCount = env->GetArrayLength(arr);
Vector<double> sarr;
sarr.resize(fCount);

double *w = sarr.ptrw();
env->GetDoubleArrayRegion(arr, 0, fCount, w);
ret = sarr;
env->DeleteLocalRef(arr);
} break;
case Variant::DICTIONARY: {
jobject obj = env->CallObjectMethodA(instance, E->get().method, v);
ret = _jobject_to_variant(env, obj);
env->DeleteLocalRef(obj);

} break;
case Variant::OBJECT: {
jobject obj = env->CallObjectMethodA(instance, E->get().method, v);
ret = _jobject_to_variant(env, obj);
env->DeleteLocalRef(obj);
} break;
default: {
env->PopLocalFrame(nullptr);
ERR_FAIL_V(Variant());
} break;
}

while (to_erase.size()) {
env->DeleteLocalRef(to_erase.front()->get());
to_erase.pop_front();
}

env->PopLocalFrame(nullptr);

return ret;
#else // ANDROID_ENABLED

// Defaulting to the regular instance calls.
return Object::callp(p_method, p_args, p_argcount, r_error);
#endif
}

#ifdef ANDROID_ENABLED
jobject get_instance() const {
return instance;
Ref<JavaObject> get_wrapped_object() const {
return wrapped_object;
}

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) {
void add_method(const StringName &p_name, 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;
Expand All @@ -232,24 +92,15 @@ class JNISingleton : public Object {
ADD_SIGNAL(mi);
}

#endif
JNISingleton() {}

JNISingleton() {
#ifdef ANDROID_ENABLED
instance = nullptr;
#endif
JNISingleton(const Ref<JavaObject> &p_wrapped_object) {
wrapped_object = p_wrapped_object;
}

~JNISingleton() {
#ifdef ANDROID_ENABLED
method_map.clear();
if (instance) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);

env->DeleteGlobalRef(instance);
}
#endif
wrapped_object.unref();
}
};

Expand Down
10 changes: 5 additions & 5 deletions platform/android/java_class_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1132,10 +1132,10 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
jclass bclass = env->FindClass(class_name_dots.replace(".", "/").utf8().get_data());
ERR_FAIL_NULL_V(bclass, Ref<JavaClass>());

jobjectArray constructors = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredConstructors);
jobjectArray constructors = (jobjectArray)env->CallObjectMethod(bclass, Class_getConstructors);
ERR_FAIL_NULL_V(constructors, Ref<JavaClass>());

jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredMethods);
jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, Class_getMethods);
ERR_FAIL_NULL_V(methods, Ref<JavaClass>());

Ref<JavaClass> java_class = memnew(JavaClass);
Expand Down Expand Up @@ -1349,15 +1349,15 @@ Ref<JavaClass> JavaClassWrapper::wrap_jclass(jclass p_class) {

JavaClassWrapper *JavaClassWrapper::singleton = nullptr;

JavaClassWrapper::JavaClassWrapper(jobject p_activity) {
JavaClassWrapper::JavaClassWrapper() {
singleton = this;

JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);

jclass bclass = env->FindClass("java/lang/Class");
Class_getDeclaredConstructors = env->GetMethodID(bclass, "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;");
Class_getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
Class_getConstructors = env->GetMethodID(bclass, "getConstructors", "()[Ljava/lang/reflect/Constructor;");
Class_getMethods = env->GetMethodID(bclass, "getMethods", "()[Ljava/lang/reflect/Method;");
Class_getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;");
Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
Class_getSuperclass = env->GetMethodID(bclass, "getSuperclass", "()Ljava/lang/Class;");
Expand Down
8 changes: 0 additions & 8 deletions platform/android/java_godot_lib_jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@
#include "java_godot_lib_jni.h"

#include "android_input_handler.h"
#include "api/java_class_wrapper.h"
#include "api/jni_singleton.h"
#include "dir_access_jandroid.h"
#include "display_server_android.h"
#include "file_access_android.h"
Expand Down Expand Up @@ -65,7 +63,6 @@
#include <android/native_window_jni.h>
#include <unistd.h>

static JavaClassWrapper *java_class_wrapper = nullptr;
static OS_Android *os_android = nullptr;
static AndroidInputHandler *input_handler = nullptr;
static GodotJavaWrapper *godot_java = nullptr;
Expand Down Expand Up @@ -97,9 +94,6 @@ static void _terminate(JNIEnv *env, bool p_restart = false) {
// Unregister android plugins
unregister_plugins_singletons();

if (java_class_wrapper) {
memdelete(java_class_wrapper);
}
if (input_handler) {
delete input_handler;
}
Expand Down Expand Up @@ -209,8 +203,6 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env

TTS_Android::setup(p_godot_tts);

java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
GDREGISTER_CLASS(JNISingleton);
return true;
}

Expand Down
Loading

0 comments on commit f504985

Please sign in to comment.