Leverage java annotations to simplify the logic used to register the Godot plugin methods.

This commit is contained in:
Fredia Huya-Kouadio 2021-03-16 00:36:31 -07:00
parent 3ddab1c9d1
commit 94df08aae1
9 changed files with 245 additions and 65 deletions

View file

@ -44,7 +44,7 @@ import androidx.fragment.app.FragmentActivity;
* It's also a reference implementation for how to setup and use the {@link Godot} fragment
* within an Android app.
*/
public abstract class FullScreenGodotApp extends FragmentActivity {
public abstract class FullScreenGodotApp extends FragmentActivity implements GodotHost {
@Nullable
private Godot godotFragment;
@ -65,6 +65,8 @@ public abstract class FullScreenGodotApp extends FragmentActivity {
public void onNewIntent(Intent intent) {
if (godotFragment != null) {
godotFragment.onNewIntent(intent);
} else {
super.onNewIntent(intent);
}
}

View file

@ -108,6 +108,7 @@ import java.io.InputStream;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@ -143,6 +144,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
// Used to dispatch events to the main thread.
private final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
private GodotHost godotHost;
private GodotPluginRegistry pluginRegistry;
static private Intent mCurrentIntent;
@ -231,7 +233,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
protected void onGLDrawFrame(GL10 gl) {}
protected void onGLSurfaceChanged(GL10 gl, int width, int height) {} // singletons will always miss first onGLSurfaceChanged call
//protected void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} // singletons won't be ready until first GodotLib.step()
// protected void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} // singletons won't be ready until first GodotLib.step()
public void registerMethods() {}
}
@ -269,6 +271,22 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
public ResultCallback result_callback;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (getParentFragment() instanceof GodotHost) {
godotHost = (GodotHost)getParentFragment();
} else if (getActivity() instanceof GodotHost) {
godotHost = (GodotHost)getActivity();
}
}
@Override
public void onDetach() {
super.onDetach();
godotHost = null;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (result_callback != null) {
@ -299,6 +317,20 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
};
/**
* Invoked on the render thread when the Godot setup is complete.
*/
@CallSuper
protected void onGodotSetupCompleted() {
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
plugin.onGodotSetupCompleted();
}
if (godotHost != null) {
godotHost.onGodotSetupCompleted();
}
}
/**
* Invoked on the render thread when the Godot main loop has started.
*/
@ -307,6 +339,10 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
plugin.onGodotMainLoopStarted();
}
if (godotHost != null) {
godotHost.onGodotMainLoopStarted();
}
}
/**
@ -409,7 +445,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
//deprecated in API 26
// deprecated in API 26
v.vibrate(durationMs);
}
}
@ -464,6 +500,21 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
@CallSuper
protected String[] getCommandLine() {
String[] original = parseCommandLine();
String[] updated;
List<String> hostCommandLine = godotHost != null ? godotHost.getCommandLine() : null;
if (hostCommandLine == null || hostCommandLine.isEmpty()) {
updated = original;
} else {
updated = Arrays.copyOf(original, original.length + hostCommandLine.size());
for (int i = 0; i < hostCommandLine.size(); i++) {
updated[original.length + i] = hostCommandLine.get(i);
}
}
return updated;
}
private String[] parseCommandLine() {
InputStream is;
try {
is = getActivity().getAssets().open("_cl_");
@ -584,7 +635,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE);
pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this);
//check for apk expansion API
// check for apk expansion API
boolean md5mismatch = false;
command_line = getCommandLine();
String main_pack_md5 = null;
@ -642,9 +693,9 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
command_line = new_args.toArray(new String[new_args.size()]);
}
if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) {
//check that environment is ok!
// check that environment is ok!
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//show popup and die
// show popup and die
}
// Build the full path to the app's expansion files

View file

@ -0,0 +1,57 @@
/*************************************************************************/
/* GodotHost.java */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 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. */
/*************************************************************************/
package org.godotengine.godot;
import java.util.Collections;
import java.util.List;
/**
* Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} fragment.
*/
public interface GodotHost {
/**
* Provides a set of command line parameters to setup the engine.
*/
default List<String> getCommandLine() {
return Collections.emptyList();
}
/**
* Invoked on the render thread when the Godot setup is complete.
*/
default void onGodotSetupCompleted() {}
/**
* Invoked on the render thread when the Godot main loop has started.
*/
default void onGodotMainLoopStarted() {}
}

View file

@ -46,6 +46,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -109,31 +110,9 @@ public abstract class GodotPlugin {
* This method is invoked on the render thread.
*/
public final void onRegisterPluginWithGodotNative() {
registeredSignals.putAll(registerPluginWithGodotNative(this, new GodotPluginInfoProvider() {
@NonNull
@Override
public String getPluginName() {
return GodotPlugin.this.getPluginName();
}
@NonNull
@Override
public List<String> getPluginMethods() {
return GodotPlugin.this.getPluginMethods();
}
@NonNull
@Override
public Set<SignalInfo> getPluginSignals() {
return GodotPlugin.this.getPluginSignals();
}
@NonNull
@Override
public Set<String> getPluginGDNativeLibrariesPaths() {
return GodotPlugin.this.getPluginGDNativeLibrariesPaths();
}
}));
registeredSignals.putAll(
registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals(),
getPluginGDNativeLibrariesPaths()));
}
/**
@ -141,23 +120,41 @@ public abstract class GodotPlugin {
*
* This method must be invoked on the render thread.
*/
public static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject, GodotPluginInfoProvider pluginInfoProvider) {
nativeRegisterSingleton(pluginInfoProvider.getPluginName(), pluginObject);
public static void registerPluginWithGodotNative(Object pluginObject,
GodotPluginInfoProvider pluginInfoProvider) {
registerPluginWithGodotNative(pluginObject, pluginInfoProvider.getPluginName(),
Collections.emptyList(), pluginInfoProvider.getPluginSignals(),
pluginInfoProvider.getPluginGDNativeLibrariesPaths());
// Notify that registration is complete.
pluginInfoProvider.onPluginRegistered();
}
private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject,
String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals,
Set<String> pluginGDNativeLibrariesPaths) {
nativeRegisterSingleton(pluginName, pluginObject);
Set<Method> filteredMethods = new HashSet<>();
Class clazz = pluginObject.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
boolean found = false;
for (String s : pluginInfoProvider.getPluginMethods()) {
if (s.equals(method.getName())) {
found = true;
break;
// Check if the method is annotated with {@link UsedByGodot}.
if (method.getAnnotation(UsedByGodot.class) != null) {
filteredMethods.add(method);
} else {
// For backward compatibility, process the methods from the given <pluginMethods> argument.
for (String methodName : pluginMethods) {
if (methodName.equals(method.getName())) {
filteredMethods.add(method);
break;
}
}
}
if (!found)
continue;
}
for (Method method : filteredMethods) {
List<String> ptr = new ArrayList<>();
Class[] paramTypes = method.getParameterTypes();
@ -168,21 +165,20 @@ public abstract class GodotPlugin {
String[] pt = new String[ptr.size()];
ptr.toArray(pt);
nativeRegisterMethod(pluginInfoProvider.getPluginName(), method.getName(), method.getReturnType().getName(), pt);
nativeRegisterMethod(pluginName, method.getName(), method.getReturnType().getName(), pt);
}
// Register the signals for this plugin.
Map<String, SignalInfo> registeredSignals = new HashMap<>();
for (SignalInfo signalInfo : pluginInfoProvider.getPluginSignals()) {
for (SignalInfo signalInfo : pluginSignals) {
String signalName = signalInfo.getName();
nativeRegisterSignal(pluginInfoProvider.getPluginName(), signalName, signalInfo.getParamTypesNames());
nativeRegisterSignal(pluginName, signalName, signalInfo.getParamTypesNames());
registeredSignals.put(signalName, signalInfo);
}
// Get the list of gdnative libraries to register.
Set<String> gdnativeLibrariesPaths = pluginInfoProvider.getPluginGDNativeLibrariesPaths();
if (!gdnativeLibrariesPaths.isEmpty()) {
nativeRegisterGDNativeLibraries(gdnativeLibrariesPaths.toArray(new String[0]));
if (!pluginGDNativeLibrariesPaths.isEmpty()) {
nativeRegisterGDNativeLibraries(pluginGDNativeLibrariesPaths.toArray(new String[0]));
}
return registeredSignals;
@ -235,6 +231,11 @@ public abstract class GodotPlugin {
*/
public boolean onMainBackPressed() { return false; }
/**
* Invoked on the render thread when the Godot setup is complete.
*/
public void onGodotSetupCompleted() {}
/**
* Invoked on the render thread when the Godot main loop has started.
*/
@ -266,8 +267,11 @@ public abstract class GodotPlugin {
/**
* Returns the list of methods to be exposed to Godot.
*
* @deprecated Used the {@link UsedByGodot} annotation instead.
*/
@NonNull
@Deprecated
public List<String> getPluginMethods() {
return Collections.emptyList();
}

View file

@ -32,7 +32,7 @@ package org.godotengine.godot.plugin;
import androidx.annotation.NonNull;
import java.util.List;
import java.util.Collections;
import java.util.Set;
/**
@ -46,17 +46,13 @@ public interface GodotPluginInfoProvider {
@NonNull
String getPluginName();
/**
* Returns the list of methods to be exposed to Godot.
*/
@NonNull
List<String> getPluginMethods();
/**
* Returns the list of signals to be exposed to Godot.
*/
@NonNull
Set<SignalInfo> getPluginSignals();
default Set<SignalInfo> getPluginSignals() {
return Collections.emptySet();
}
/**
* Returns the paths for the plugin's gdnative libraries (if any).
@ -64,5 +60,14 @@ public interface GodotPluginInfoProvider {
* The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file.
*/
@NonNull
Set<String> getPluginGDNativeLibrariesPaths();
default Set<String> getPluginGDNativeLibrariesPaths() {
return Collections.emptySet();
}
/**
* This is invoked on the render thread when the plugin described by this instance has been
* registered.
*/
default void onPluginRegistered() {
}
}

View file

@ -0,0 +1,45 @@
/*************************************************************************/
/* UsedByGodot.java */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 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. */
/*************************************************************************/
package org.godotengine.godot.plugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to indicate a method is being invoked from the Godot game logic.
*
* At runtime, annotated plugin methods are detected and automatically registered.
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface UsedByGodot {}

View file

@ -201,7 +201,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc
}
if (err != OK) {
return; //should exit instead and print the error
return; // should exit instead and print the error
}
java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
@ -252,9 +252,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl
if (step == 1) {
if (!Main::start()) {
return; //should exit instead and print the error
return; // should exit instead and print the error
}
godot_java->on_godot_setup_completed(env);
os_android->main_loop_begin();
godot_java->on_godot_main_loop_started(env);
++step;

View file

@ -74,6 +74,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z");
_vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V");
_get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;");
_on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V");
_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
// get some Activity method pointers...
@ -117,11 +118,21 @@ void GodotJavaWrapper::gfx_init(bool gl2) {
}
void GodotJavaWrapper::on_video_init(JNIEnv *p_env) {
if (_on_video_init)
if (_on_video_init) {
if (p_env == NULL)
p_env = get_jni_env();
p_env->CallVoidMethod(godot_instance, _on_video_init);
p_env->CallVoidMethod(godot_instance, _on_video_init);
}
}
void GodotJavaWrapper::on_godot_setup_completed(JNIEnv *p_env) {
if (_on_godot_setup_completed) {
if (p_env == NULL) {
p_env = get_jni_env();
}
p_env->CallVoidMethod(godot_instance, _on_godot_setup_completed);
}
}
void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) {
@ -129,24 +140,26 @@ void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) {
if (p_env == NULL) {
p_env = get_jni_env();
}
p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started);
}
p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started);
}
void GodotJavaWrapper::restart(JNIEnv *p_env) {
if (_restart)
if (_restart) {
if (p_env == NULL)
p_env = get_jni_env();
p_env->CallVoidMethod(godot_instance, _restart);
p_env->CallVoidMethod(godot_instance, _restart);
}
}
void GodotJavaWrapper::force_quit(JNIEnv *p_env) {
if (_finish)
if (_finish) {
if (p_env == NULL)
p_env = get_jni_env();
p_env->CallVoidMethod(godot_instance, _finish);
p_env->CallVoidMethod(godot_instance, _finish);
}
}
void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) {

View file

@ -63,6 +63,7 @@ private:
jmethodID _is_activity_resumed = 0;
jmethodID _vibrate = 0;
jmethodID _get_input_fallback_mapping = 0;
jmethodID _on_godot_setup_completed = 0;
jmethodID _on_godot_main_loop_started = 0;
jmethodID _get_class_loader = 0;
@ -77,6 +78,7 @@ public:
void gfx_init(bool gl2);
void on_video_init(JNIEnv *p_env = NULL);
void on_godot_setup_completed(JNIEnv *p_env = NULL);
void on_godot_main_loop_started(JNIEnv *p_env = NULL);
void restart(JNIEnv *p_env = NULL);
void force_quit(JNIEnv *p_env = NULL);