diff --git a/platform/android/SCsub b/platform/android/SCsub index 3bc895935193..66c955252bd5 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -28,6 +28,7 @@ android_files = [ "display_server_android.cpp", "plugin/godot_plugin_jni.cpp", "rendering_context_driver_vulkan_android.cpp", + "variant/callable_jni.cpp", ] env_android = env.Clone() diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index c74cef8dd0b3..4a86ed312164 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -58,6 +58,8 @@ class JavaClass : public RefCounted { ARG_TYPE_FLOAT, ARG_TYPE_DOUBLE, ARG_TYPE_STRING, //special case + ARG_TYPE_CHARSEQUENCE, + ARG_TYPE_CALLABLE, ARG_TYPE_CLASS, ARG_ARRAY_BIT = 1 << 16, ARG_NUMBER_CLASS_BIT = 1 << 17, @@ -123,8 +125,12 @@ class JavaClass : public RefCounted { likelihood = 0.5; break; case ARG_TYPE_STRING: + case ARG_TYPE_CHARSEQUENCE: r_type = Variant::STRING; break; + case ARG_TYPE_CALLABLE: + r_type = Variant::CALLABLE; + break; case ARG_TYPE_CLASS: r_type = Variant::OBJECT; break; @@ -163,9 +169,11 @@ class JavaClass : public RefCounted { likelihood = 0.5; break; case ARG_ARRAY_BIT | ARG_TYPE_STRING: + case ARG_ARRAY_BIT | ARG_TYPE_CHARSEQUENCE: r_type = Variant::PACKED_STRING_ARRAY; break; case ARG_ARRAY_BIT | ARG_TYPE_CLASS: + case ARG_ARRAY_BIT | ARG_TYPE_CALLABLE: r_type = Variant::ARRAY; break; } diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 19c18eb96e39..5c5a8be348f8 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -30,7 +30,7 @@ #include "dir_access_jandroid.h" -#include "string_android.h" +#include "jni_utils.h" #include "thread_jandroid.h" #include "core/string/print_string.h" diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index 13ae2150d7e3..9af91924704a 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -35,6 +35,7 @@ import org.godotengine.godot.io.file.FileAccessHandler; import org.godotengine.godot.tts.GodotTTS; import org.godotengine.godot.utils.GodotNetUtils; +import org.godotengine.godot.variant.Callable; import android.app.Activity; import android.content.res.AssetManager; @@ -200,16 +201,26 @@ public static native boolean initialize(Activity activity, * @param p_id Id of the Godot object to invoke * @param p_method Name of the method to invoke * @param p_params Parameters to use for method invocation + * + * @deprecated Use {@link Callable#call(long, String, Object...)} instead. */ - public static native void callobject(long p_id, String p_method, Object[] p_params); + @Deprecated + public static void callobject(long p_id, String p_method, Object[] p_params) { + Callable.call(p_id, p_method, p_params); + } /** * Invoke method |p_method| on the Godot object specified by |p_id| during idle time. * @param p_id Id of the Godot object to invoke * @param p_method Name of the method to invoke * @param p_params Parameters to use for method invocation + * + * @deprecated Use {@link Callable#callDeferred(long, String, Object...)} instead. */ - public static native void calldeferred(long p_id, String p_method, Object[] p_params); + @Deprecated + public static void calldeferred(long p_id, String p_method, Object[] p_params) { + Callable.callDeferred(p_id, p_method, p_params); + } /** * Forward the results from a permission request. diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt b/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt index edb4e7c35782..77f6898ce35f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt @@ -31,6 +31,7 @@ package org.godotengine.godot.plugin import org.godotengine.godot.Godot +import org.godotengine.godot.variant.Callable /** * Provides access to the Android runtime capabilities. @@ -51,13 +52,50 @@ import org.godotengine.godot.Godot * printerr("Unable to retrieve the vibrator service") * else: * printerr("Couldn't find AndroidRuntime singleton") + * + * + * Or it can be used to display an Android native toast from gdscript + * + * var android_runtime = Engine.get_singleton("AndroidRuntime") + * if android_runtime: + * var activity = android_runtime.getActivity() + * + * var toastCallable = func (): + * var ToastClass = JavaClassWrapper.wrap("android.widget.Toast") + * ToastClass.makeText(activity, "This is a test", 1).show() + * + * activity.runOnUiThread(android_runtime.createRunnableFromGodotCallable(toastCallable)) + * else: + * printerr("Unable to access android runtime") */ class AndroidRuntimePlugin(godot: Godot) : GodotPlugin(godot) { override fun getPluginName() = "AndroidRuntime" + /** + * Provides access to the application context to GDScript + */ @UsedByGodot fun getApplicationContext() = activity?.applicationContext + /** + * Provides access to the host activity to GDScript + */ @UsedByGodot override fun getActivity() = super.getActivity() + + /** + * Utility method used to create [Runnable] from Godot [Callable]. + */ + @UsedByGodot + fun createRunnableFromGodotCallable(godotCallable: Callable): Runnable { + return Runnable { godotCallable.call()} + } + + /** + * Utility method used to create [java.util.concurrent.Callable] from Godot [Callable]. + */ + @UsedByGodot + fun createCallableFromGodotCallable(godotCallable: Callable): java.util.concurrent.Callable { + return java.util.concurrent.Callable { godotCallable.call() } + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/variant/Callable.kt b/platform/android/java/lib/src/org/godotengine/godot/variant/Callable.kt new file mode 100644 index 000000000000..b658106ab911 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/variant/Callable.kt @@ -0,0 +1,94 @@ +/**************************************************************************/ +/* Callable.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +package org.godotengine.godot.variant + +import androidx.annotation.Keep + +/** + * Android version of a Godot built-in Callable type representing a method or a standalone function. + */ +@Keep +class Callable private constructor(private val nativeCallablePointer: Long) { + + companion object { + /** + * Invoke method [methodName] on the Godot object specified by [godotObjectId] + */ + @JvmStatic + fun call(godotObjectId: Long, methodName: String, vararg methodParameters: Any): Any? { + return nativeCallObject(godotObjectId, methodName, methodParameters) + } + + /** + * Invoke method [methodName] on the Godot object specified by [godotObjectId] during idle time. + */ + @JvmStatic + fun callDeferred(godotObjectId: Long, methodName: String, vararg methodParameters: Any) { + nativeCallObjectDeferred(godotObjectId, methodName, methodParameters) + } + + @JvmStatic + private external fun nativeCall(pointer: Long, params: Array): Any? + + @JvmStatic + private external fun nativeCallObject(godotObjectId: Long, methodName: String, params: Array): Any? + + @JvmStatic + private external fun nativeCallObjectDeferred(godotObjectId: Long, methodName: String, params: Array) + + @JvmStatic + private external fun releaseNativePointer(nativePointer: Long) + } + + /** + * Calls the method represented by this [Callable]. Arguments can be passed and should match the method's signature. + */ + internal fun call(vararg params: Any): Any? { + if (nativeCallablePointer == 0L) { + return null + } + + return nativeCall(nativeCallablePointer, params) + } + + /** + * Used to provide access to the native callable pointer to the native logic. + */ + private fun getNativePointer() = nativeCallablePointer + + /** Note that [finalize] is deprecated and shouldn't be used, unfortunately its replacement, + * [java.lang.ref.Cleaner], is only available on Android api 33 and higher. + * So we resort to using it for the time being until our min api catches up to api 33. + **/ + protected fun finalize() { + releaseNativePointer(nativeCallablePointer) + } +} diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index 6bedbfd157a2..4a6ae540075a 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -30,7 +30,7 @@ #include "api/java_class_wrapper.h" -#include "string_android.h" +#include "jni_utils.h" #include "thread_jandroid.h" bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret) { @@ -96,11 +96,17 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, arg_expected = Variant::FLOAT; } } break; - case ARG_TYPE_STRING: { + case ARG_TYPE_STRING: + case ARG_TYPE_CHARSEQUENCE: { if (!p_args[i]->is_string()) { arg_expected = Variant::STRING; } } break; + case ARG_TYPE_CALLABLE: { + if (p_args[i]->get_type() != Variant::CALLABLE) { + arg_expected = Variant::CALLABLE; + } + } break; case ARG_TYPE_CLASS: { if (p_args[i]->get_type() != Variant::OBJECT && p_args[i]->get_type() != Variant::NIL) { arg_expected = Variant::OBJECT; @@ -265,12 +271,18 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, argv[i].l = obj; to_free.push_back(obj); } break; - case ARG_TYPE_STRING: { + case ARG_TYPE_STRING: + case ARG_TYPE_CHARSEQUENCE: { String s = *p_args[i]; jstring jStr = env->NewStringUTF(s.utf8().get_data()); argv[i].l = jStr; to_free.push_back(jStr); } break; + case ARG_TYPE_CALLABLE: { + jobject jcallable = callable_to_jcallable(env, *p_args[i]); + argv[i].l = jcallable; + to_free.push_back(jcallable); + } break; case ARG_TYPE_CLASS: { Ref jo = *p_args[i]; if (jo.is_valid()) { @@ -367,7 +379,8 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, to_free.push_back(a); } break; - case ARG_ARRAY_BIT | ARG_TYPE_STRING: { + case ARG_ARRAY_BIT | ARG_TYPE_STRING: + case ARG_ARRAY_BIT | ARG_TYPE_CHARSEQUENCE: { Array arr = *p_args[i]; jobjectArray a = env->NewObjectArray(arr.size(), env->FindClass("java/lang/String"), nullptr); for (int j = 0; j < arr.size(); j++) { @@ -380,6 +393,19 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, argv[i].l = a; to_free.push_back(a); } break; + case ARG_ARRAY_BIT | ARG_TYPE_CALLABLE: { + Array arr = *p_args[i]; + jobjectArray jarr = env->NewObjectArray(arr.size(), env->FindClass("org/godotengine/godot/variant/Callable"), nullptr); + for (int j = 0; j < arr.size(); j++) { + Variant callable = arr[j]; + jobject jcallable = callable_to_jcallable(env, callable); + env->SetObjectArrayElement(jarr, j, jcallable); + to_free.push_back(jcallable); + } + + argv[i].l = jarr; + to_free.push_back(jarr); + } break; case ARG_ARRAY_BIT | ARG_TYPE_CLASS: { argv[i].l = nullptr; } break; @@ -463,7 +489,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, default: { jobject obj; if (method->_constructor) { - obj = env->NewObject(_class, method->method, argv); + obj = env->NewObjectA(_class, method->method, argv); } else if (method->_static) { obj = env->CallStaticObjectMethodA(_class, method->method, argv); } else { @@ -706,6 +732,12 @@ bool JavaClassWrapper::_get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, St } else if (str_type == "java.lang.String") { t |= JavaClass::ARG_TYPE_STRING; strsig += "Ljava/lang/String;"; + } else if (str_type == "java.lang.CharSequence") { + t |= JavaClass::ARG_TYPE_CHARSEQUENCE; + strsig += "Ljava/lang/CharSequence;"; + } else if (str_type == "org.godotengine.godot.variant.Callable") { + t |= JavaClass::ARG_TYPE_CALLABLE; + strsig += "Lorg/godotengine/godot/variant/Callable;"; } else if (str_type == "java.lang.Boolean") { t |= JavaClass::ARG_TYPE_BOOLEAN | JavaClass::ARG_NUMBER_CLASS_BIT; strsig += "Ljava/lang/Boolean;"; @@ -793,6 +825,14 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va var = jstring_to_string((jstring)obj, env); return true; } break; + case ARG_TYPE_CHARSEQUENCE: { + var = charsequence_to_string(env, obj); + return true; + } break; + case ARG_TYPE_CALLABLE: { + var = jcallable_to_callable(env, obj); + return true; + } break; case ARG_TYPE_CLASS: { jclass java_class = env->GetObjectClass(obj); Ref java_class_wrapped = JavaClassWrapper::singleton->wrap_jclass(java_class); @@ -1113,6 +1153,44 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va var = ret; return true; } break; + case ARG_ARRAY_BIT | ARG_TYPE_CHARSEQUENCE: { + Array ret; + jobjectArray arr = (jobjectArray)obj; + + int count = env->GetArrayLength(arr); + + for (int i = 0; i < count; i++) { + jobject o = env->GetObjectArrayElement(arr, i); + if (!o) { + ret.push_back(Variant()); + } else { + String val = charsequence_to_string(env, o); + ret.push_back(val); + } + env->DeleteLocalRef(o); + } + + var = ret; + return true; + } break; + case ARG_ARRAY_BIT | ARG_TYPE_CALLABLE: { + Array ret; + jobjectArray jarr = (jobjectArray)obj; + int count = env->GetArrayLength(jarr); + for (int i = 0; i < count; i++) { + jobject o = env->GetObjectArrayElement(jarr, i); + if (!o) { + ret.push_back(Variant()); + } else { + Callable callable = jcallable_to_callable(env, o); + ret.push_back(callable); + } + env->DeleteLocalRef(o); + } + + var = ret; + return true; + } break; case ARG_ARRAY_BIT | ARG_TYPE_CLASS: { } break; } diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index 0a372641cbb9..3cbe0c142853 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -31,7 +31,7 @@ #ifndef JAVA_GODOT_IO_WRAPPER_H #define JAVA_GODOT_IO_WRAPPER_H -#include "string_android.h" +#include "jni_utils.h" #include "core/math/rect2i.h" #include "core/variant/typed_array.h" diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 5c1e78dcc42e..023832a2d345 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -42,7 +42,6 @@ #include "net_socket_android.h" #include "os_android.h" #include "plugin/godot_plugin_jni.h" -#include "string_android.h" #include "thread_jandroid.h" #include "tts_android.h" @@ -488,51 +487,6 @@ JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(J return env->NewStringUTF(editor_setting_value.utf8().get_data()); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) { - Object *obj = ObjectDB::get_instance(ObjectID(ID)); - ERR_FAIL_NULL(obj); - - String str_method = jstring_to_string(method, env); - - int count = env->GetArrayLength(params); - - Variant *vlist = (Variant *)alloca(sizeof(Variant) * count); - const Variant **vptr = (const Variant **)alloca(sizeof(Variant *) * count); - - for (int i = 0; i < count; i++) { - jobject jobj = env->GetObjectArrayElement(params, i); - ERR_FAIL_NULL(jobj); - memnew_placement(&vlist[i], Variant(_jobject_to_variant(env, jobj))); - vptr[i] = &vlist[i]; - env->DeleteLocalRef(jobj); - } - - Callable::CallError err; - obj->callp(str_method, vptr, count, err); -} - -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) { - Object *obj = ObjectDB::get_instance(ObjectID(ID)); - ERR_FAIL_NULL(obj); - - String str_method = jstring_to_string(method, env); - - int count = env->GetArrayLength(params); - - Variant *args = (Variant *)alloca(sizeof(Variant) * count); - const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * count); - - for (int i = 0; i < count; i++) { - jobject jobj = env->GetObjectArrayElement(params, i); - ERR_FAIL_NULL(jobj); - memnew_placement(&args[i], Variant(_jobject_to_variant(env, jobj))); - argptrs[i] = &args[i]; - env->DeleteLocalRef(jobj); - } - - Callable(obj, str_method).call_deferredp(argptrs, count); -} - JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz) { DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton(); if (ds) { diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 31a7598a7bda..48d91795c7be 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -62,8 +62,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz); JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path); JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(JNIEnv *env, jclass clazz, jstring p_setting_key); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz); diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java_godot_view_wrapper.h index 5f554aa2d60d..83abb2dcc80d 100644 --- a/platform/android/java_godot_view_wrapper.h +++ b/platform/android/java_godot_view_wrapper.h @@ -31,7 +31,7 @@ #ifndef JAVA_GODOT_VIEW_WRAPPER_H #define JAVA_GODOT_VIEW_WRAPPER_H -#include "string_android.h" +#include "jni_utils.h" #include "core/math/vector2.h" diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 512779169a4d..28027fa45bbb 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -32,7 +32,6 @@ #define JAVA_GODOT_WRAPPER_H #include "java_godot_view_wrapper.h" -#include "string_android.h" #include "core/math/color.h" #include "core/templates/list.h" diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp index 4c17d03c602d..561c76e35d2a 100644 --- a/platform/android/jni_utils.cpp +++ b/platform/android/jni_utils.cpp @@ -32,6 +32,57 @@ #include "api/java_class_wrapper.h" +jobject callable_to_jcallable(JNIEnv *p_env, const Variant &p_callable) { + ERR_FAIL_NULL_V(p_env, nullptr); + if (p_callable.get_type() != Variant::CALLABLE) { + return nullptr; + } + + Variant *callable_jcopy = memnew(Variant(p_callable)); + + jclass bclass = p_env->FindClass("org/godotengine/godot/variant/Callable"); + jmethodID ctor = p_env->GetMethodID(bclass, "", "(J)V"); + jobject jcallable = p_env->NewObject(bclass, ctor, reinterpret_cast(callable_jcopy)); + p_env->DeleteLocalRef(bclass); + + return jcallable; +} + +Callable jcallable_to_callable(JNIEnv *p_env, jobject p_jcallable_obj) { + ERR_FAIL_NULL_V(p_env, Callable()); + + const Variant *callable_variant = nullptr; + jclass callable_class = p_env->FindClass("org/godotengine/godot/variant/Callable"); + if (callable_class && p_env->IsInstanceOf(p_jcallable_obj, callable_class)) { + jmethodID get_native_pointer = p_env->GetMethodID(callable_class, "getNativePointer", "()J"); + jlong native_callable = p_env->CallLongMethod(p_jcallable_obj, get_native_pointer); + + callable_variant = reinterpret_cast(native_callable); + } + + p_env->DeleteLocalRef(callable_class); + + ERR_FAIL_NULL_V(callable_variant, Callable()); + return *callable_variant; +} + +String charsequence_to_string(JNIEnv *p_env, jobject p_charsequence) { + ERR_FAIL_NULL_V(p_env, String()); + + String result; + jclass bclass = p_env->FindClass("java/lang/CharSequence"); + if (bclass && p_env->IsInstanceOf(p_charsequence, bclass)) { + jmethodID to_string = p_env->GetMethodID(bclass, "toString", "()Ljava/lang/String;"); + jstring obj_string = (jstring)p_env->CallObjectMethod(p_charsequence, to_string); + + result = jstring_to_string(obj_string, p_env); + p_env->DeleteLocalRef(obj_string); + } + + p_env->DeleteLocalRef(bclass); + return result; +} + jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject) { jvalret v; @@ -100,6 +151,12 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a } break; + case Variant::CALLABLE: { + jobject jcallable = callable_to_jcallable(env, *p_arg); + v.val.l = jcallable; + v.obj = jcallable; + } break; + case Variant::DICTIONARY: { Dictionary dict = *p_arg; jclass dclass = env->FindClass("org/godotengine/godot/Dictionary"); @@ -234,6 +291,10 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { return jstring_to_string((jstring)obj, env); } + if (name == "java.lang.CharSequence") { + return charsequence_to_string(env, obj); + } + if (name == "[Ljava.lang.String;") { jobjectArray arr = (jobjectArray)obj; int stringCount = env->GetArrayLength(arr); @@ -248,6 +309,20 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { return sarr; } + if (name == "[Ljava.lang.CharSequence;") { + jobjectArray arr = (jobjectArray)obj; + int stringCount = env->GetArrayLength(arr); + Vector sarr; + + for (int i = 0; i < stringCount; i++) { + jobject charsequence = env->GetObjectArrayElement(arr, i); + sarr.push_back(charsequence_to_string(env, charsequence)); + env->DeleteLocalRef(charsequence); + } + + return sarr; + } + if (name == "java.lang.Boolean") { jmethodID boolValue = env->GetMethodID(c, "booleanValue", "()Z"); bool ret = env->CallBooleanMethod(obj, boolValue); @@ -370,6 +445,10 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { return ret; } + if (name == "org.godotengine.godot.variant.Callable") { + return jcallable_to_callable(env, obj); + } + Ref generic_object(memnew(JavaObject(JavaClassWrapper::get_singleton()->wrap(name), obj))); env->DeleteLocalRef(c); @@ -389,13 +468,16 @@ Variant::Type get_jni_type(const String &p_type) { { "float", Variant::FLOAT }, { "double", Variant::FLOAT }, { "java.lang.String", Variant::STRING }, + { "java.lang.CharSequence", Variant::STRING }, { "[I", Variant::PACKED_INT32_ARRAY }, { "[J", Variant::PACKED_INT64_ARRAY }, { "[B", Variant::PACKED_BYTE_ARRAY }, { "[F", Variant::PACKED_FLOAT32_ARRAY }, { "[D", Variant::PACKED_FLOAT64_ARRAY }, { "[Ljava.lang.String;", Variant::PACKED_STRING_ARRAY }, + { "[Ljava.lang.CharSequence;", Variant::PACKED_STRING_ARRAY }, { "org.godotengine.godot.Dictionary", Variant::DICTIONARY }, + { "org.godotengine.godot.variant.Callable", Variant::CALLABLE }, { nullptr, Variant::NIL } }; @@ -411,38 +493,3 @@ Variant::Type get_jni_type(const String &p_type) { return Variant::OBJECT; } - -String get_jni_sig(const String &p_type) { - static struct { - const char *name; - const char *sig; - } _type_to_vtype[] = { - { "void", "V" }, - { "boolean", "Z" }, - { "int", "I" }, - { "long", "J" }, - { "float", "F" }, - { "double", "D" }, - { "java.lang.String", "Ljava/lang/String;" }, - { "org.godotengine.godot.Dictionary", "Lorg/godotengine/godot/Dictionary;" }, - { "[I", "[I" }, - { "[J", "[J" }, - { "[B", "[B" }, - { "[F", "[F" }, - { "[D", "[D" }, - { "[Ljava.lang.String;", "[Ljava/lang/String;" }, - { nullptr, "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 "L" + p_type.replace(".", "/") + ";"; -} diff --git a/platform/android/jni_utils.h b/platform/android/jni_utils.h index 631acd1cef58..55e1946c44ab 100644 --- a/platform/android/jni_utils.h +++ b/platform/android/jni_utils.h @@ -31,9 +31,10 @@ #ifndef JNI_UTILS_H #define JNI_UTILS_H -#include "string_android.h" +#include "thread_jandroid.h" #include "core/config/engine.h" +#include "core/string/ustring.h" #include "core/variant/variant.h" #include @@ -52,6 +53,49 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj); Variant::Type get_jni_type(const String &p_type); -String get_jni_sig(const String &p_type); +/** + * Convert a Godot Callable to a org.godotengine.godot.variant.Callable java object. + * @param p_env JNI environment instance + * @param p_callable Callable parameter to convert. If null or invalid type, a null jobject is returned. + * @return org.godotengine.godot.variant.Callable jobject or null + */ +jobject callable_to_jcallable(JNIEnv *p_env, const Variant &p_callable); + +/** + * Convert a org.godotengine.godot.variant.Callable java object to a Godot Callable variant. + * @param p_env JNI environment instance + * @param p_jcallable_obj org.godotengine.godot.variant.Callable java object to convert. + * @return Callable variant + */ +Callable jcallable_to_callable(JNIEnv *p_env, jobject p_jcallable_obj); + +/** + * Converts a java.lang.CharSequence object to a Godot String. + * @param p_env JNI environment instance + * @param p_charsequence java.lang.CharSequence object to convert + * @return Godot String instance. + */ +String charsequence_to_string(JNIEnv *p_env, jobject p_charsequence); + +/** + * Converts JNI jstring to Godot String. + * @param source Source JNI string. If null an empty string is returned. + * @param env JNI environment instance. If null obtained by get_jni_env(). + * @return Godot string instance. + */ +static inline String jstring_to_string(jstring source, JNIEnv *env = nullptr) { + String result; + if (source) { + if (!env) { + env = get_jni_env(); + } + const char *const source_utf8 = env->GetStringUTFChars(source, nullptr); + if (source_utf8) { + result.parse_utf8(source_utf8); + env->ReleaseStringUTFChars(source, source_utf8); + } + } + return result; +} #endif // JNI_UTILS_H diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp index acb18cc5c5c1..3318568b09bc 100644 --- a/platform/android/plugin/godot_plugin_jni.cpp +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -33,7 +33,6 @@ #include "api/java_class_wrapper.h" #include "api/jni_singleton.h" #include "jni_utils.h" -#include "string_android.h" #include "core/config/engine.h" #include "core/error/error_macros.h" diff --git a/platform/android/tts_android.cpp b/platform/android/tts_android.cpp index be85e47972bb..d560f853f82d 100644 --- a/platform/android/tts_android.cpp +++ b/platform/android/tts_android.cpp @@ -32,7 +32,6 @@ #include "java_godot_wrapper.h" #include "os_android.h" -#include "string_android.h" #include "thread_jandroid.h" bool TTS_Android::initialized = false; diff --git a/platform/android/variant/callable_jni.cpp b/platform/android/variant/callable_jni.cpp new file mode 100644 index 000000000000..0ac42a1b6eb5 --- /dev/null +++ b/platform/android/variant/callable_jni.cpp @@ -0,0 +1,117 @@ +/**************************************************************************/ +/* callable_jni.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/**************************************************************************/ + +#include "callable_jni.h" + +#include "jni_utils.h" + +#include "core/error/error_macros.h" +#include "core/object/object.h" + +static Callable _generate_callable(JNIEnv *p_env, jlong p_object_id, jstring p_method_name, jobjectArray p_parameters) { + Object *obj = ObjectDB::get_instance(ObjectID(p_object_id)); + ERR_FAIL_NULL_V(obj, Callable()); + + String str_method = jstring_to_string(p_method_name, p_env); + + int count = p_env->GetArrayLength(p_parameters); + + Variant *args = (Variant *)alloca(sizeof(Variant) * count); + const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * count); + + for (int i = 0; i < count; i++) { + jobject jobj = p_env->GetObjectArrayElement(p_parameters, i); + ERR_FAIL_NULL_V(jobj, Callable()); + memnew_placement(&args[i], Variant(_jobject_to_variant(p_env, jobj))); + argptrs[i] = &args[i]; + p_env->DeleteLocalRef(jobj); + } + + return Callable(obj, str_method).bindp(argptrs, count); +} + +extern "C" { +JNIEXPORT jobject JNICALL Java_org_godotengine_godot_variant_Callable_nativeCall(JNIEnv *p_env, jclass p_clazz, jlong p_native_callable, jobjectArray p_parameters) { + const Variant *callable_variant = reinterpret_cast(p_native_callable); + ERR_FAIL_NULL_V(callable_variant, nullptr); + if (callable_variant->get_type() != Variant::CALLABLE) { + return nullptr; + } + + int count = p_env->GetArrayLength(p_parameters); + + Variant *args = (Variant *)alloca(sizeof(Variant) * count); + const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * count); + + for (int i = 0; i < count; i++) { + jobject jobj = p_env->GetObjectArrayElement(p_parameters, i); + ERR_FAIL_NULL_V(jobj, nullptr); + memnew_placement(&args[i], Variant(_jobject_to_variant(p_env, jobj))); + argptrs[i] = &args[i]; + p_env->DeleteLocalRef(jobj); + } + + Callable callable = *callable_variant; + if (callable.is_valid()) { + Callable::CallError err; + Variant result; + callable.callp(argptrs, count, result, err); + jvalret jresult = _variant_to_jvalue(p_env, result.get_type(), &result, true); + return jresult.obj; + } else { + return nullptr; + } +} + +JNIEXPORT jobject JNICALL Java_org_godotengine_godot_variant_Callable_nativeCallObject(JNIEnv *p_env, jclass p_clazz, jlong p_object_id, jstring p_method_name, jobjectArray p_parameters) { + Callable callable = _generate_callable(p_env, p_object_id, p_method_name, p_parameters); + if (callable.is_valid()) { + Variant result = callable.call(); + jvalret jresult = _variant_to_jvalue(p_env, result.get_type(), &result, true); + return jresult.obj; + } else { + return nullptr; + } +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_variant_Callable_nativeCallObjectDeferred(JNIEnv *p_env, jclass p_clazz, jlong p_object_id, jstring p_method_name, jobjectArray p_parameters) { + Callable callable = _generate_callable(p_env, p_object_id, p_method_name, p_parameters); + if (callable.is_valid()) { + callable.call_deferred(); + } +} + +JNIEXPORT void JNICALL +Java_org_godotengine_godot_variant_Callable_releaseNativePointer(JNIEnv *p_env, jclass clazz, jlong p_native_pointer) { + Variant *variant = reinterpret_cast(p_native_pointer); + ERR_FAIL_NULL(variant); + memdelete(variant); +} +} diff --git a/platform/android/string_android.h b/platform/android/variant/callable_jni.h similarity index 72% rename from platform/android/string_android.h rename to platform/android/variant/callable_jni.h index 3f30b8ec3de4..10d68a471fa7 100644 --- a/platform/android/string_android.h +++ b/platform/android/variant/callable_jni.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* string_android.h */ +/* callable_jni.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,34 +28,16 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef STRING_ANDROID_H -#define STRING_ANDROID_H - -#include "thread_jandroid.h" - -#include "core/string/ustring.h" +#ifndef CALLABLE_JNI_H +#define CALLABLE_JNI_H #include -/** - * Converts JNI jstring to Godot String. - * @param source Source JNI string. If null an empty string is returned. - * @param env JNI environment instance. If null obtained by get_jni_env(). - * @return Godot string instance. - */ -static inline String jstring_to_string(jstring source, JNIEnv *env = nullptr) { - String result; - if (source) { - if (!env) { - env = get_jni_env(); - } - const char *const source_utf8 = env->GetStringUTFChars(source, nullptr); - if (source_utf8) { - result.parse_utf8(source_utf8); - env->ReleaseStringUTFChars(source, source_utf8); - } - } - return result; +extern "C" { +JNIEXPORT jobject JNICALL Java_org_godotengine_godot_variant_Callable_nativeCall(JNIEnv *p_env, jclass p_clazz, jlong p_native_callable, jobjectArray p_parameters); +JNIEXPORT jobject JNICALL Java_org_godotengine_godot_variant_Callable_nativeCallObject(JNIEnv *p_env, jclass p_clazz, jlong p_object_id, jstring p_method_name, jobjectArray p_parameters); +JNIEXPORT void JNICALL Java_org_godotengine_godot_variant_Callable_nativeCallObjectDeferred(JNIEnv *p_env, jclass p_clazz, jlong p_object_id, jstring p_method_name, jobjectArray p_parameters); +JNIEXPORT void JNICALL Java_org_godotengine_godot_variant_Callable_releaseNativePointer(JNIEnv *p_env, jclass clazz, jlong p_native_pointer); } -#endif // STRING_ANDROID_H +#endif // CALLABLE_JNI_H