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 2e1dc18
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 207 deletions.
8 changes: 3 additions & 5 deletions platform/android/api/api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,11 @@ static JavaClassWrapper *java_class_wrapper = nullptr;

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

GDREGISTER_CLASS(JNISingleton);
GDREGISTER_CLASS(JavaClass);
GDREGISTER_CLASS(JavaObject);
GDREGISTER_CLASS(JavaClassWrapper);
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
4 changes: 1 addition & 3 deletions platform/android/java_godot_lib_jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

#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 @@ -209,8 +208,7 @@ 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);
java_class_wrapper = memnew(JavaClassWrapper);
return true;
}

Expand Down
25 changes: 10 additions & 15 deletions platform/android/plugin/godot_plugin_jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include "godot_plugin_jni.h"

#include "api/java_class_wrapper.h"
#include "api/jni_singleton.h"
#include "jni_utils.h"
#include "string_android.h"
Expand Down Expand Up @@ -57,11 +58,15 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeR

ERR_FAIL_COND_V(jni_singletons.has(singname), false);

JNISingleton *s = (JNISingleton *)ClassDB::instantiate("JNISingleton");
s->set_instance(env->NewGlobalRef(obj));
jni_singletons[singname] = s;
jclass java_class = env->GetObjectClass(obj);
Ref<JavaClass> java_class_wrapped = JavaClassWrapper::get_singleton()->wrap_jclass(java_class);
env->DeleteLocalRef(java_class);

Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
Ref<JavaObject> plugin_object = Ref<JavaObject>(memnew(JavaObject(java_class_wrapped, obj)));
JNISingleton *plugin_singleton = memnew(JNISingleton(plugin_object));
jni_singletons[singname] = plugin_singleton;

Engine::get_singleton()->add_singleton(Engine::Singleton(singname, plugin_singleton));
return true;
}

Expand All @@ -75,26 +80,16 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis
String mname = jstring_to_string(name, env);
String retval = jstring_to_string(ret, env);
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 String rawString = jstring_to_string(string, env);
types.push_back(get_jni_type(rawString));
cs += get_jni_sig(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("Failed getting method ID " + mname);
}

s->add_method(mname, mid, types, get_jni_type(retval));
s->add_method(mname, types, get_jni_type(retval));
}

JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types) {
Expand Down

0 comments on commit 2e1dc18

Please sign in to comment.