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 3e5759a
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 208 deletions.
1 change: 1 addition & 0 deletions doc/classes/JavaClassWrapper.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<method name="wrap">
<return type="JavaClass" />
<param index="0" name="name" type="String" />
<param index="1" name="allow_private_methods_access" type="bool" />
<description>
Wraps a class defined in Java, and returns it as a [JavaClass] [Object] type that Godot can interact with.
[b]Note:[/b] This method only works on Android. On every other platform, this method does nothing and returns an empty [JavaClass].
Expand Down
10 changes: 4 additions & 6 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 Expand Up @@ -108,7 +106,7 @@ Ref<JavaClass> JavaObject::get_java_class() const {

JavaClassWrapper *JavaClassWrapper::singleton = nullptr;

Ref<JavaClass> JavaClassWrapper::wrap(const String &) {
Ref<JavaClass> JavaClassWrapper::wrap(const String &, bool) {
return Ref<JavaClass>();
}

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 @@ -270,15 +270,12 @@ class JavaClassWrapper : public Object {
public:
static JavaClassWrapper *get_singleton() { return singleton; }

Ref<JavaClass> wrap(const String &p_class);
Ref<JavaClass> wrap(const String &p_class, bool p_allow_private_methods_access = false);

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

JavaClassWrapper(jobject p_activity = nullptr);
#else
JavaClassWrapper();
Ref<JavaClass> wrap_jclass(jclass p_class, bool p_allow_private_methods_access = false);
#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 @@ -1120,7 +1120,7 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return false;
}

Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class, bool p_allow_private_methods_access) {
String class_name_dots = p_class.replace("/", ".");
if (class_cache.has(class_name_dots)) {
return class_cache[class_name_dots];
Expand Down Expand Up @@ -1175,7 +1175,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {

jint mods = env->CallIntMethod(obj, is_constructor ? Constructor_getModifiers : Method_getModifiers);

if (!(mods & 0x0001)) {
if (!(mods & 0x0001) && (is_constructor || !p_allow_private_methods_access)) {
env->DeleteLocalRef(obj);
continue; //not public bye
}
Expand Down Expand Up @@ -1336,20 +1336,20 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
return java_class;
}

Ref<JavaClass> JavaClassWrapper::wrap_jclass(jclass p_class) {
Ref<JavaClass> JavaClassWrapper::wrap_jclass(jclass p_class, bool p_allow_private_methods_access) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, Ref<JavaClass>());

jstring class_name = (jstring)env->CallObjectMethod(p_class, Class_getName);
String class_name_string = jstring_to_string(class_name, env);
env->DeleteLocalRef(class_name);

return wrap(class_name_string);
return wrap(class_name_string, p_allow_private_methods_access);
}

JavaClassWrapper *JavaClassWrapper::singleton = nullptr;

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

JNIEnv *env = get_jni_env();
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
Loading

0 comments on commit 3e5759a

Please sign in to comment.