Godot Android plugin re-architecture

This commit is contained in:
Fredia Huya-Kouadio 2023-07-17 12:11:37 -07:00
parent fa3428ff25
commit 8cc7739197
13 changed files with 169 additions and 207 deletions

View File

@ -143,6 +143,8 @@ void GDExtensionManager::load_extensions() {
ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, "Error loading extension: " + s); ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, "Error loading extension: " + s);
} }
} }
OS::get_singleton()->load_platform_gdextensions();
} }
GDExtensionManager *GDExtensionManager::get_singleton() { GDExtensionManager *GDExtensionManager::get_singleton() {

View File

@ -328,6 +328,10 @@ public:
virtual PreferredTextureFormat get_preferred_texture_format() const; virtual PreferredTextureFormat get_preferred_texture_format() const;
// Load GDExtensions specific to this platform.
// This is invoked by the GDExtensionManager after loading GDExtensions specified by the project.
virtual void load_platform_gdextensions() const {}
OS(); OS();
virtual ~OS(); virtual ~OS();
}; };

View File

@ -50,6 +50,15 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
Error err = config->load(p_path); Error err = config->load(p_path);
ERR_FAIL_COND_MSG(err, "Failed to load GDExtension file: " + p_path); ERR_FAIL_COND_MSG(err, "Failed to load GDExtension file: " + p_path);
// Check whether this GDExtension should be exported.
bool android_aar_plugin = config->get_value("configuration", "android_aar_plugin", false);
if (android_aar_plugin && p_features.has("android")) {
// The gdextension configuration and Android .so files will be provided by the Android aar
// plugin it's part of, so we abort here.
skip();
return;
}
ERR_FAIL_COND_MSG(!config->has_section_key("configuration", "entry_symbol"), "Failed to export GDExtension file, missing entry symbol: " + p_path); ERR_FAIL_COND_MSG(!config->has_section_key("configuration", "entry_symbol"), "Failed to export GDExtension file, missing entry symbol: " + p_path);
String entry_symbol = config->get_value("configuration", "entry_symbol"); String entry_symbol = config->get_value("configuration", "entry_symbol");

View File

@ -907,6 +907,19 @@ class Godot(private val context: Context) : SensorEventListener {
return PermissionsUtil.getGrantedPermissions(getActivity()) return PermissionsUtil.getGrantedPermissions(getActivity())
} }
/**
* Get the list of gdextension modules to register.
*/
@Keep
private fun getGDExtensionConfigFiles(): Array<String> {
val configFiles = mutableSetOf<String>()
for (plugin in pluginRegistry.allPlugins) {
configFiles.addAll(plugin.pluginGDExtensionLibrariesPaths)
}
return configFiles.toTypedArray()
}
@Keep @Keep
private fun getCACertificates(): String { private fun getCACertificates(): String {
return GodotNetUtils.getCACertificates() return GodotNetUtils.getCACertificates()

View File

@ -57,27 +57,25 @@ import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL10;
/** /**
* Base class for the Godot Android plugins. * Base class for Godot Android plugins.
* <p> * <p>
* A Godot Android plugin is a regular Android library packaged as an aar archive file with the following caveats: * A Godot Android plugin is an Android library with the following requirements:
* <p> * <p>
* - The library must have a dependency on the Godot Android library (godot-lib.aar). * - The library must have a 'compileOnly' dependency on the Godot Android library: `compileOnly "org.godotengine:godot:<godotLibVersion>"`
* A stable version is available for each release.
* <p> * <p>
* - The library must include a <meta-data> tag in its manifest file setup as follow: * - The library must include a <meta-data> tag in its Android manifest with the following format:
* <meta-data android:name="org.godotengine.plugin.v1.[PluginName]" android:value="[plugin.init.ClassFullName]" /> * <meta-data android:name="org.godotengine.plugin.v2.[PluginName]" android:value="[plugin.init.ClassFullName]" />
* Where: * Where:
* - 'PluginName' is the name of the plugin. * - 'PluginName' is the name of the plugin.
* - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin class * - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin init class
* extending {@link GodotPlugin}. * extending {@link GodotPlugin}.
* <p>
* A Godot Android plugin can also define and provide c/c++ gdextension libraries, which will be
* automatically bundled by the aar build system.
* GDExtension ('*.gdextension') config files must be located in the project 'assets' directory and
* their paths specified by {@link GodotPlugin#getPluginGDExtensionLibrariesPaths()}.
* *
* A plugin can also define and provide c/c++ gdextension libraries and nativescripts for the target * @see <a href="https://docs.godotengine.org/en/stable/tutorials/platform/android/index.html">Android plugins</a>
* app/game to leverage.
* The shared library for the gdextension library will be automatically bundled by the aar build
* system.
* Godot '*.gdextension' resource files must however be manually defined in the project
* 'assets' directory. The recommended path for these resources in the 'assets' directory should be:
* 'godot/plugin/v1/[PluginName]/'
*/ */
public abstract class GodotPlugin { public abstract class GodotPlugin {
private static final String TAG = GodotPlugin.class.getSimpleName(); private static final String TAG = GodotPlugin.class.getSimpleName();
@ -97,7 +95,7 @@ public abstract class GodotPlugin {
} }
/** /**
* Provides access to the underlying {@link Activity}. * Provides access to the hosting {@link Activity}.
*/ */
@Nullable @Nullable
protected Activity getActivity() { protected Activity getActivity() {
@ -106,33 +104,16 @@ public abstract class GodotPlugin {
/** /**
* Register the plugin with Godot native code. * Register the plugin with Godot native code.
* * <p>
* This method is invoked on the render thread. * This method is invoked by the Godot Engine on the render thread.
*/ */
public final void onRegisterPluginWithGodotNative() { public final void onRegisterPluginWithGodotNative() {
registeredSignals.putAll( registeredSignals.putAll(
registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals(), registerPluginWithGodotNative(this, getPluginName(), getPluginSignals()));
getPluginGDExtensionLibrariesPaths()));
}
/**
* Register the plugin with Godot native code.
*
* This method must be invoked on the render thread.
*/
public static void registerPluginWithGodotNative(Object pluginObject,
GodotPluginInfoProvider pluginInfoProvider) {
registerPluginWithGodotNative(pluginObject, pluginInfoProvider.getPluginName(),
Collections.emptyList(), pluginInfoProvider.getPluginSignals(),
pluginInfoProvider.getPluginGDExtensionLibrariesPaths());
// Notify that registration is complete.
pluginInfoProvider.onPluginRegistered();
} }
private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject, private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject,
String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals, String pluginName, Set<SignalInfo> pluginSignals) {
Set<String> pluginGDExtensionLibrariesPaths) {
nativeRegisterSingleton(pluginName, pluginObject); nativeRegisterSingleton(pluginName, pluginObject);
Set<Method> filteredMethods = new HashSet<>(); Set<Method> filteredMethods = new HashSet<>();
@ -143,14 +124,6 @@ public abstract class GodotPlugin {
// Check if the method is annotated with {@link UsedByGodot}. // Check if the method is annotated with {@link UsedByGodot}.
if (method.getAnnotation(UsedByGodot.class) != null) { if (method.getAnnotation(UsedByGodot.class) != null) {
filteredMethods.add(method); 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;
}
}
} }
} }
@ -176,23 +149,18 @@ public abstract class GodotPlugin {
registeredSignals.put(signalName, signalInfo); registeredSignals.put(signalName, signalInfo);
} }
// Get the list of gdextension libraries to register.
if (!pluginGDExtensionLibrariesPaths.isEmpty()) {
nativeRegisterGDExtensionLibraries(pluginGDExtensionLibrariesPaths.toArray(new String[0]));
}
return registeredSignals; return registeredSignals;
} }
/** /**
* Invoked once during the Godot Android initialization process after creation of the * Invoked once during the initialization process after creation of the
* {@link org.godotengine.godot.GodotRenderView} view. * {@link org.godotengine.godot.GodotRenderView} view.
* <p> * <p>
* The plugin can return a non-null {@link View} layout in order to add it to the Godot view * The plugin can return a non-null {@link View} layout which will be added to the Godot view
* hierarchy. * hierarchy.
* * <p>
* Use shouldBeOnTop() to set whether the plugin's {@link View} should be added on top or behind * Use {@link GodotPlugin#shouldBeOnTop()} to specify whether the plugin's {@link View} should
* the main Godot view. * be added on top or behind the main Godot view.
* *
* @see Activity#onCreate(Bundle) * @see Activity#onCreate(Bundle)
* @return the plugin's view to be included; null if no views should be included. * @return the plugin's view to be included; null if no views should be included.
@ -235,44 +203,52 @@ public abstract class GodotPlugin {
public boolean onMainBackPressed() { return false; } public boolean onMainBackPressed() { return false; }
/** /**
* Invoked on the render thread when the Godot setup is complete. * Invoked on the render thread when set up of the Godot engine is complete.
* <p>
* This is invoked before {@link GodotPlugin#onGodotMainLoopStarted()}.
*/ */
public void onGodotSetupCompleted() {} public void onGodotSetupCompleted() {}
/** /**
* Invoked on the render thread when the Godot main loop has started. * Invoked on the render thread when the Godot main loop has started.
*
* This is invoked after {@link GodotPlugin#onGodotSetupCompleted()}.
*/ */
public void onGodotMainLoopStarted() {} public void onGodotMainLoopStarted() {}
/** /**
* Invoked once per frame on the GL thread after the frame is drawn. * When using the OpenGL renderer, this is invoked once per frame on the GL thread after the
* frame is drawn.
*/ */
public void onGLDrawFrame(GL10 gl) {} public void onGLDrawFrame(GL10 gl) {}
/** /**
* Called on the GL thread after the surface is created and whenever the OpenGL ES surface size * When using the OpenGL renderer, this is called on the GL thread after the surface is created
* changes. * and whenever the OpenGL ES surface size changes.
*/ */
public void onGLSurfaceChanged(GL10 gl, int width, int height) {} public void onGLSurfaceChanged(GL10 gl, int width, int height) {}
/** /**
* Called on the GL thread when the surface is created or recreated. * When using the OpenGL renderer, this is called on the GL thread when the surface is created
* or recreated.
*/ */
public void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} public void onGLSurfaceCreated(GL10 gl, EGLConfig config) {}
/** /**
* Invoked once per frame on the Vulkan thread after the frame is drawn. * When using the Vulkan renderer, this is invoked once per frame on the Vulkan thread after
* the frame is drawn.
*/ */
public void onVkDrawFrame() {} public void onVkDrawFrame() {}
/** /**
* Called on the Vulkan thread after the surface is created and whenever the surface size * When using the Vulkan renderer, this is called on the Vulkan thread after the surface is
* changes. * created and whenever the surface size changes.
*/ */
public void onVkSurfaceChanged(Surface surface, int width, int height) {} public void onVkSurfaceChanged(Surface surface, int width, int height) {}
/** /**
* Called on the Vulkan thread when the surface is created or recreated. * When using the Vulkan renderer, this is called on the Vulkan thread when the surface is
* created or recreated.
*/ */
public void onVkSurfaceCreated(Surface surface) {} public void onVkSurfaceCreated(Surface surface) {}
@ -284,17 +260,6 @@ public abstract class GodotPlugin {
@NonNull @NonNull
public abstract String getPluginName(); public abstract String getPluginName();
/**
* 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();
}
/** /**
* Returns the list of signals to be exposed to Godot. * Returns the list of signals to be exposed to Godot.
*/ */
@ -304,19 +269,19 @@ public abstract class GodotPlugin {
} }
/** /**
* Returns the paths for the plugin's gdextension libraries. * Returns the paths for the plugin's gdextension libraries (if any).
* * <p>
* The paths must be relative to the 'assets' directory and point to a '*.gdextension' file. * Each returned path must be relative to the 'assets' directory and point to a '*.gdextension' file.
*/ */
@NonNull @NonNull
protected Set<String> getPluginGDExtensionLibrariesPaths() { public Set<String> getPluginGDExtensionLibrariesPaths() {
return Collections.emptySet(); return Collections.emptySet();
} }
/** /**
* Returns whether the plugin's {@link View} returned in onMainCreate() should be placed on * Returns whether the plugin's {@link View} returned in
* top of the main Godot view. * {@link GodotPlugin#onMainCreate(Activity)} should be placed on top of the main Godot view.
* * <p>
* Returning false causes the plugin's {@link View} to be placed behind, which can be useful * Returning false causes the plugin's {@link View} to be placed behind, which can be useful
* when used with transparency in order to let the Godot view handle inputs. * when used with transparency in order to let the Godot view handle inputs.
*/ */
@ -359,7 +324,7 @@ public abstract class GodotPlugin {
} }
emitSignal(getGodot(), getPluginName(), signalInfo, signalArgs); emitSignal(getGodot(), getPluginName(), signalInfo, signalArgs);
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {
Log.w(TAG, exception.getMessage()); Log.w(TAG, exception);
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
throw exception; throw exception;
} }
@ -368,7 +333,7 @@ public abstract class GodotPlugin {
/** /**
* Emit a Godot signal. * Emit a Godot signal.
* @param godot * @param godot Godot instance
* @param pluginName Name of the Godot plugin the signal will be emitted from. The plugin must already be registered with the Godot engine. * @param pluginName Name of the Godot plugin the signal will be emitted from. The plugin must already be registered with the Godot engine.
* @param signalInfo Information about the signal to emit. * @param signalInfo Information about the signal to emit.
* @param signalArgs Arguments used to populate the emitted signal. The arguments will be validated against the given {@link SignalInfo} parameter. * @param signalArgs Arguments used to populate the emitted signal. The arguments will be validated against the given {@link SignalInfo} parameter.
@ -397,7 +362,7 @@ public abstract class GodotPlugin {
godot.runOnRenderThread(() -> nativeEmitSignal(pluginName, signalInfo.getName(), signalArgs)); godot.runOnRenderThread(() -> nativeEmitSignal(pluginName, signalInfo.getName(), signalArgs));
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {
Log.w(TAG, exception.getMessage()); Log.w(TAG, exception);
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
throw exception; throw exception;
} }
@ -420,13 +385,7 @@ public abstract class GodotPlugin {
private static native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params); private static native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params);
/** /**
* Used to register gdextension libraries bundled by the plugin. * Used to complete registration of the {@link GodotPlugin} instance's signals.
* @param gdextensionPaths Paths to the libraries relative to the 'assets' directory.
*/
private static native void nativeRegisterGDExtensionLibraries(String[] gdextensionPaths);
/**
* Used to complete registration of the {@link GodotPlugin} instance's methods.
* @param pluginName Name of the plugin * @param pluginName Name of the plugin
* @param signalName Name of the signal to register * @param signalName Name of the signal to register
* @param signalParamTypes Signal parameters types * @param signalParamTypes Signal parameters types

View File

@ -1,72 +0,0 @@
/**************************************************************************/
/* GodotPluginInfoProvider.java */
/**************************************************************************/
/* 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.plugin;
import androidx.annotation.NonNull;
import java.util.Collections;
import java.util.Set;
/**
* Provides the set of information expected from a Godot plugin.
*/
public interface GodotPluginInfoProvider {
/**
* Returns the name of the plugin.
*/
@NonNull
String getPluginName();
/**
* Returns the list of signals to be exposed to Godot.
*/
@NonNull
default Set<SignalInfo> getPluginSignals() {
return Collections.emptySet();
}
/**
* Returns the paths for the plugin's gdextension libraries (if any).
*
* The paths must be relative to the 'assets' directory and point to a '*.gdextension' file.
*/
@NonNull
default Set<String> getPluginGDExtensionLibrariesPaths() {
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

@ -52,7 +52,14 @@ import java.util.concurrent.ConcurrentHashMap;
public final class GodotPluginRegistry { public final class GodotPluginRegistry {
private static final String TAG = GodotPluginRegistry.class.getSimpleName(); private static final String TAG = GodotPluginRegistry.class.getSimpleName();
/**
* Prefix used for version 1 of the Godot plugin, compatible with Godot 3.x
*/
private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1."; private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1.";
/**
* Prefix used for version 2 of the Godot plugin, compatible with Godot 4.x
*/
private static final String GODOT_PLUGIN_V2_NAME_PREFIX = "org.godotengine.plugin.v2.";
private static GodotPluginRegistry instance; private static GodotPluginRegistry instance;
private final ConcurrentHashMap<String, GodotPlugin> registry; private final ConcurrentHashMap<String, GodotPlugin> registry;
@ -123,12 +130,12 @@ public final class GodotPluginRegistry {
return; return;
} }
int godotPluginV1NamePrefixLength = GODOT_PLUGIN_V1_NAME_PREFIX.length(); int godotPluginV2NamePrefixLength = GODOT_PLUGIN_V2_NAME_PREFIX.length();
for (String metaDataName : metaData.keySet()) { for (String metaDataName : metaData.keySet()) {
// Parse the meta-data looking for entry with the Godot plugin name prefix. // Parse the meta-data looking for entry with the Godot plugin name prefix.
if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) { if (metaDataName.startsWith(GODOT_PLUGIN_V2_NAME_PREFIX)) {
String pluginName = metaDataName.substring(godotPluginV1NamePrefixLength).trim(); String pluginName = metaDataName.substring(godotPluginV2NamePrefixLength).trim();
Log.i(TAG, "Initializing Godot plugin " + pluginName); Log.i(TAG, "Initializing Godot v2 plugin " + pluginName);
// Retrieve the plugin class full name. // Retrieve the plugin class full name.
String pluginHandleClassFullName = metaData.getString(metaDataName); String pluginHandleClassFullName = metaData.getString(metaDataName);
@ -148,25 +155,22 @@ public final class GodotPluginRegistry {
"Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName()); "Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName());
} }
registry.put(pluginName, pluginHandle); registry.put(pluginName, pluginHandle);
Log.i(TAG, "Completed initialization for Godot plugin " + pluginHandle.getPluginName()); Log.i(TAG, "Completed initialization for Godot v2 plugin " + pluginHandle.getPluginName());
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException | IllegalAccessException |
Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); InstantiationException | NoSuchMethodException |
} catch (IllegalAccessException e) { InvocationTargetException e) {
Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); Log.w(TAG, "Unable to load Godot v2 plugin " + pluginName, e);
} catch (InstantiationException e) {
Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
} catch (NoSuchMethodException e) {
Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
} catch (InvocationTargetException e) {
Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
} }
} else { } else {
Log.w(TAG, "Invalid plugin loader class for " + pluginName); Log.w(TAG, "Invalid plugin loader class for " + pluginName);
} }
} else if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) {
String v1PluginName = metaDataName.substring(GODOT_PLUGIN_V1_NAME_PREFIX.length()).trim();
Log.w(TAG, "Godot 4 does not support Godot 3 (v1) plugin: " + v1PluginName);
} }
} }
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Unable load Godot Android plugins from the manifest file.", e); Log.e(TAG, "Unable load Godot Android v2 plugins from the manifest file.", e);
} }
} }
} }

View File

@ -79,6 +79,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_begin_benchmark_measure = p_env->GetMethodID(godot_class, "nativeBeginBenchmarkMeasure", "(Ljava/lang/String;)V"); _begin_benchmark_measure = p_env->GetMethodID(godot_class, "nativeBeginBenchmarkMeasure", "(Ljava/lang/String;)V");
_end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(Ljava/lang/String;)V"); _end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(Ljava/lang/String;)V");
_dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V"); _dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V");
_get_gdextension_list_config_file = p_env->GetMethodID(godot_class, "getGDExtensionConfigFiles", "()[Ljava/lang/String;");
} }
GodotJavaWrapper::~GodotJavaWrapper() { GodotJavaWrapper::~GodotJavaWrapper() {
@ -264,6 +265,25 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const {
return permissions_list; return permissions_list;
} }
Vector<String> GodotJavaWrapper::get_gdextension_list_config_file() const {
Vector<String> config_file_list;
if (_get_gdextension_list_config_file) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, config_file_list);
jobject config_file_list_object = env->CallObjectMethod(godot_instance, _get_gdextension_list_config_file);
jobjectArray *arr = reinterpret_cast<jobjectArray *>(&config_file_list_object);
jsize len = env->GetArrayLength(*arr);
for (int i = 0; i < len; i++) {
jstring j_config_file = (jstring)env->GetObjectArrayElement(*arr, i);
String config_file = jstring_to_string(j_config_file, env);
config_file_list.push_back(config_file);
env->DeleteLocalRef(j_config_file);
}
}
return config_file_list;
}
String GodotJavaWrapper::get_ca_certificates() const { String GodotJavaWrapper::get_ca_certificates() const {
if (_get_ca_certificates) { if (_get_ca_certificates) {
JNIEnv *env = get_jni_env(); JNIEnv *env = get_jni_env();

View File

@ -59,6 +59,7 @@ private:
jmethodID _request_permission = nullptr; jmethodID _request_permission = nullptr;
jmethodID _request_permissions = nullptr; jmethodID _request_permissions = nullptr;
jmethodID _get_granted_permissions = nullptr; jmethodID _get_granted_permissions = nullptr;
jmethodID _get_gdextension_list_config_file = nullptr;
jmethodID _get_ca_certificates = nullptr; jmethodID _get_ca_certificates = nullptr;
jmethodID _init_input_devices = nullptr; jmethodID _init_input_devices = nullptr;
jmethodID _vibrate = nullptr; jmethodID _vibrate = nullptr;
@ -102,6 +103,9 @@ public:
void begin_benchmark_measure(const String &p_label); void begin_benchmark_measure(const String &p_label);
void end_benchmark_measure(const String &p_label); void end_benchmark_measure(const String &p_label);
void dump_benchmark(const String &benchmark_file); void dump_benchmark(const String &benchmark_file);
// Return the list of gdextensions config file.
Vector<String> get_gdextension_list_config_file() const;
}; };
#endif // JAVA_GODOT_WRAPPER_H #endif // JAVA_GODOT_WRAPPER_H

View File

@ -39,6 +39,7 @@
#include "net_socket_android.h" #include "net_socket_android.h"
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
#include "core/extension/gdextension_manager.h"
#include "drivers/unix/dir_access_unix.h" #include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h" #include "drivers/unix/file_access_unix.h"
#include "main/main.h" #include "main/main.h"
@ -162,11 +163,39 @@ Vector<String> OS_Android::get_granted_permissions() const {
Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) { Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
String path = p_path; String path = p_path;
bool so_file_exists = true;
if (!FileAccess::exists(path)) { if (!FileAccess::exists(path)) {
path = p_path.get_file(); path = p_path.get_file();
so_file_exists = false;
} }
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
if (!p_library_handle && so_file_exists) {
// The library may be on the sdcard and thus inaccessible. Try to copy it to the internal
// directory.
uint64_t so_modified_time = FileAccess::get_modified_time(p_path);
String dynamic_library_path = get_dynamic_libraries_path().path_join(String::num_uint64(so_modified_time));
String internal_path = dynamic_library_path.path_join(p_path.get_file());
bool internal_so_file_exists = FileAccess::exists(internal_path);
if (!internal_so_file_exists) {
Ref<DirAccess> da_ref = DirAccess::create_for_path(p_path);
if (da_ref.is_valid()) {
Error create_dir_result = da_ref->make_dir_recursive(dynamic_library_path);
if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) {
internal_so_file_exists = da_ref->copy(path, internal_path) == OK;
}
}
}
if (internal_so_file_exists) {
p_library_handle = dlopen(internal_path.utf8().get_data(), RTLD_NOW);
if (p_library_handle) {
path = internal_path;
}
}
}
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror())); ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
if (r_resolved_path != nullptr) { if (r_resolved_path != nullptr) {
@ -584,6 +613,10 @@ String OS_Android::get_user_data_dir() const {
return "."; return ".";
} }
String OS_Android::get_dynamic_libraries_path() const {
return get_cache_path().path_join("dynamic_libraries");
}
String OS_Android::get_cache_path() const { String OS_Android::get_cache_path() const {
if (!cache_dir_cache.is_empty()) { if (!cache_dir_cache.is_empty()) {
return cache_dir_cache; return cache_dir_cache;
@ -791,5 +824,13 @@ Error OS_Android::setup_remote_filesystem(const String &p_server_host, int p_por
return err; return err;
} }
void OS_Android::load_platform_gdextensions() const {
Vector<String> extension_list_config_file = godot_java->get_gdextension_list_config_file();
for (String config_file_path : extension_list_config_file) {
GDExtensionManager::LoadStatus err = GDExtensionManager::get_singleton()->load_extension(config_file_path);
ERR_CONTINUE_MSG(err == GDExtensionManager::LOAD_STATUS_FAILED, "Error loading platform extension: " + config_file_path);
}
}
OS_Android::~OS_Android() { OS_Android::~OS_Android() {
} }

View File

@ -169,9 +169,15 @@ public:
virtual void benchmark_end_measure(const String &p_what) override; virtual void benchmark_end_measure(const String &p_what) override;
virtual void benchmark_dump() override; virtual void benchmark_dump() override;
virtual void load_platform_gdextensions() const override;
virtual bool _check_internal_feature_support(const String &p_feature) override; virtual bool _check_internal_feature_support(const String &p_feature) override;
OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion); OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion);
~OS_Android(); ~OS_Android();
private:
// Location where we relocate external dynamic libraries to make them accessible.
String get_dynamic_libraries_path() const;
}; };
#endif // OS_ANDROID_H #endif // OS_ANDROID_H

View File

@ -129,31 +129,4 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS
singleton->emit_signalp(StringName(signal_name), args, count); singleton->emit_signalp(StringName(signal_name), args, count);
} }
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDExtensionLibraries(JNIEnv *env, jclass clazz, jobjectArray gdextension_paths) {
int gdextension_count = env->GetArrayLength(gdextension_paths);
if (gdextension_count == 0) {
return;
}
// Retrieve the current list of gdextension libraries.
Array singletons;
if (ProjectSettings::get_singleton()->has_setting("gdextension/singletons")) {
singletons = GLOBAL_GET("gdextension/singletons");
}
// Insert the libraries provided by the plugin
for (int i = 0; i < gdextension_count; i++) {
jstring relative_path = (jstring)env->GetObjectArrayElement(gdextension_paths, i);
String path = "res://" + jstring_to_string(relative_path, env);
if (!singletons.has(path)) {
singletons.push_back(path);
}
env->DeleteLocalRef(relative_path);
}
// Insert the updated list back into project settings.
ProjectSettings::get_singleton()->set("gdextension/singletons", singletons);
}
} }

View File

@ -39,7 +39,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jclass clazz, jstring sname, jstring name, jstring ret, jobjectArray args); JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jclass clazz, jstring sname, jstring name, jstring ret, jobjectArray args);
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); 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);
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params); JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params);
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDExtensionLibraries(JNIEnv *env, jclass clazz, jobjectArray gdextension_paths);
} }
#endif // GODOT_PLUGIN_JNI_H #endif // GODOT_PLUGIN_JNI_H