diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index 63e809bc7c3..0f0e2fad413 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -143,6 +143,8 @@ void GDExtensionManager::load_extensions() { ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, "Error loading extension: " + s); } } + + OS::get_singleton()->load_platform_gdextensions(); } GDExtensionManager *GDExtensionManager::get_singleton() { diff --git a/core/os/os.h b/core/os/os.h index 965dc1f9125..cc5ebe1bc8a 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -328,6 +328,10 @@ public: 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(); virtual ~OS(); }; diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index 54e68997966..bbde652c8d8 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -50,6 +50,15 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p Error err = config->load(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); String entry_symbol = config->get_value("configuration", "entry_symbol"); diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 9c1165bf8ae..e115494cfdb 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -907,6 +907,19 @@ class Godot(private val context: Context) : SensorEventListener { return PermissionsUtil.getGrantedPermissions(getActivity()) } + /** + * Get the list of gdextension modules to register. + */ + @Keep + private fun getGDExtensionConfigFiles(): Array { + val configFiles = mutableSetOf() + for (plugin in pluginRegistry.allPlugins) { + configFiles.addAll(plugin.pluginGDExtensionLibrariesPaths) + } + + return configFiles.toTypedArray() + } + @Keep private fun getCACertificates(): String { return GodotNetUtils.getCACertificates() diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java index 48aa231c7a3..b4f0f44df48 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -57,27 +57,25 @@ import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; /** - * Base class for the Godot Android plugins. + * Base class for Godot Android plugins. *

- * 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: *

- * - The library must have a dependency on the Godot Android library (godot-lib.aar). - * A stable version is available for each release. + * - The library must have a 'compileOnly' dependency on the Godot Android library: `compileOnly "org.godotengine:godot:"` *

- * - The library must include a tag in its manifest file setup as follow: - * + * - The library must include a tag in its Android manifest with the following format: + * * Where: * - '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}. + *

+ * 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 - * 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]/' + * @see Android plugins */ public abstract class GodotPlugin { 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 protected Activity getActivity() { @@ -106,33 +104,16 @@ public abstract class GodotPlugin { /** * Register the plugin with Godot native code. - * - * This method is invoked on the render thread. + *

+ * This method is invoked by the Godot Engine on the render thread. */ public final void onRegisterPluginWithGodotNative() { registeredSignals.putAll( - registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), 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(); + registerPluginWithGodotNative(this, getPluginName(), getPluginSignals())); } private static Map registerPluginWithGodotNative(Object pluginObject, - String pluginName, List pluginMethods, Set pluginSignals, - Set pluginGDExtensionLibrariesPaths) { + String pluginName, Set pluginSignals) { nativeRegisterSingleton(pluginName, pluginObject); Set filteredMethods = new HashSet<>(); @@ -143,14 +124,6 @@ public abstract class GodotPlugin { // 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 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); } - // Get the list of gdextension libraries to register. - if (!pluginGDExtensionLibrariesPaths.isEmpty()) { - nativeRegisterGDExtensionLibraries(pluginGDExtensionLibrariesPaths.toArray(new String[0])); - } - 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. *

- * 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. - * - * Use shouldBeOnTop() to set whether the plugin's {@link View} should be added on top or behind - * the main Godot view. + *

+ * Use {@link GodotPlugin#shouldBeOnTop()} to specify whether the plugin's {@link View} should + * be added on top or behind the main Godot view. * * @see Activity#onCreate(Bundle) * @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; } /** - * 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. + *

+ * This is invoked before {@link GodotPlugin#onGodotMainLoopStarted()}. */ public void onGodotSetupCompleted() {} /** * Invoked on the render thread when the Godot main loop has started. + * + * This is invoked after {@link GodotPlugin#onGodotSetupCompleted()}. */ 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) {} /** - * Called on the GL thread after the surface is created and whenever the OpenGL ES surface size - * changes. + * When using the OpenGL renderer, this is called on the GL thread after the surface is created + * and whenever the OpenGL ES surface size changes. */ 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) {} /** - * 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() {} /** - * Called on the Vulkan thread after the surface is created and whenever the surface size - * changes. + * When using the Vulkan renderer, this is called on the Vulkan thread after the surface is + * created and whenever the surface size changes. */ 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) {} @@ -284,17 +260,6 @@ public abstract class GodotPlugin { @NonNull 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 getPluginMethods() { - return Collections.emptyList(); - } - /** * 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. - * - * The paths must be relative to the 'assets' directory and point to a '*.gdextension' file. + * Returns the paths for the plugin's gdextension libraries (if any). + *

+ * Each returned path must be relative to the 'assets' directory and point to a '*.gdextension' file. */ @NonNull - protected Set getPluginGDExtensionLibrariesPaths() { + public Set getPluginGDExtensionLibrariesPaths() { return Collections.emptySet(); } /** - * Returns whether the plugin's {@link View} returned in onMainCreate() should be placed on - * top of the main Godot view. - * + * Returns whether the plugin's {@link View} returned in + * {@link GodotPlugin#onMainCreate(Activity)} should be placed on top of the main Godot view. + *

* 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. */ @@ -359,7 +324,7 @@ public abstract class GodotPlugin { } emitSignal(getGodot(), getPluginName(), signalInfo, signalArgs); } catch (IllegalArgumentException exception) { - Log.w(TAG, exception.getMessage()); + Log.w(TAG, exception); if (BuildConfig.DEBUG) { throw exception; } @@ -368,7 +333,7 @@ public abstract class GodotPlugin { /** * 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 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. @@ -397,7 +362,7 @@ public abstract class GodotPlugin { godot.runOnRenderThread(() -> nativeEmitSignal(pluginName, signalInfo.getName(), signalArgs)); } catch (IllegalArgumentException exception) { - Log.w(TAG, exception.getMessage()); + Log.w(TAG, exception); if (BuildConfig.DEBUG) { 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); /** - * Used to register gdextension libraries bundled by the plugin. - * @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. + * Used to complete registration of the {@link GodotPlugin} instance's signals. * @param pluginName Name of the plugin * @param signalName Name of the signal to register * @param signalParamTypes Signal parameters types diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java deleted file mode 100644 index 63999a8321c..00000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java +++ /dev/null @@ -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 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 getPluginGDExtensionLibrariesPaths() { - return Collections.emptySet(); - } - - /** - * This is invoked on the render thread when the plugin described by this instance has been - * registered. - */ - default void onPluginRegistered() { - } -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java index c2428de2e14..d81d996c426 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java @@ -52,7 +52,14 @@ import java.util.concurrent.ConcurrentHashMap; public final class GodotPluginRegistry { 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."; + /** + * 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 final ConcurrentHashMap registry; @@ -123,12 +130,12 @@ public final class GodotPluginRegistry { return; } - int godotPluginV1NamePrefixLength = GODOT_PLUGIN_V1_NAME_PREFIX.length(); + int godotPluginV2NamePrefixLength = GODOT_PLUGIN_V2_NAME_PREFIX.length(); for (String metaDataName : metaData.keySet()) { // Parse the meta-data looking for entry with the Godot plugin name prefix. - if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) { - String pluginName = metaDataName.substring(godotPluginV1NamePrefixLength).trim(); - Log.i(TAG, "Initializing Godot plugin " + pluginName); + if (metaDataName.startsWith(GODOT_PLUGIN_V2_NAME_PREFIX)) { + String pluginName = metaDataName.substring(godotPluginV2NamePrefixLength).trim(); + Log.i(TAG, "Initializing Godot v2 plugin " + pluginName); // Retrieve the plugin class full name. 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()); } registry.put(pluginName, pluginHandle); - Log.i(TAG, "Completed initialization for Godot plugin " + pluginHandle.getPluginName()); - } catch (ClassNotFoundException e) { - Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); - } catch (IllegalAccessException e) { - Log.w(TAG, "Unable to load Godot 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); + Log.i(TAG, "Completed initialization for Godot v2 plugin " + pluginHandle.getPluginName()); + } catch (ClassNotFoundException | IllegalAccessException | + InstantiationException | NoSuchMethodException | + InvocationTargetException e) { + Log.w(TAG, "Unable to load Godot v2 plugin " + pluginName, e); } } else { 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) { - 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); } } } diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 79ba2528ba7..a01a74f1fd3 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -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"); _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"); + _get_gdextension_list_config_file = p_env->GetMethodID(godot_class, "getGDExtensionConfigFiles", "()[Ljava/lang/String;"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -264,6 +265,25 @@ Vector GodotJavaWrapper::get_granted_permissions() const { return permissions_list; } +Vector GodotJavaWrapper::get_gdextension_list_config_file() const { + Vector 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(&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 { if (_get_ca_certificates) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index ba42d5dccd6..2ce756807f9 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -59,6 +59,7 @@ private: jmethodID _request_permission = nullptr; jmethodID _request_permissions = nullptr; jmethodID _get_granted_permissions = nullptr; + jmethodID _get_gdextension_list_config_file = nullptr; jmethodID _get_ca_certificates = nullptr; jmethodID _init_input_devices = nullptr; jmethodID _vibrate = nullptr; @@ -102,6 +103,9 @@ public: void begin_benchmark_measure(const String &p_label); void end_benchmark_measure(const String &p_label); void dump_benchmark(const String &benchmark_file); + + // Return the list of gdextensions config file. + Vector get_gdextension_list_config_file() const; }; #endif // JAVA_GODOT_WRAPPER_H diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index c040d8c4c61..2a8c07be832 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -39,6 +39,7 @@ #include "net_socket_android.h" #include "core/config/project_settings.h" +#include "core/extension/gdextension_manager.h" #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" #include "main/main.h" @@ -162,11 +163,39 @@ Vector 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) { String path = p_path; + bool so_file_exists = true; if (!FileAccess::exists(path)) { path = p_path.get_file(); + so_file_exists = false; } 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 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())); if (r_resolved_path != nullptr) { @@ -584,6 +613,10 @@ String OS_Android::get_user_data_dir() const { return "."; } +String OS_Android::get_dynamic_libraries_path() const { + return get_cache_path().path_join("dynamic_libraries"); +} + String OS_Android::get_cache_path() const { if (!cache_dir_cache.is_empty()) { return cache_dir_cache; @@ -791,5 +824,13 @@ Error OS_Android::setup_remote_filesystem(const String &p_server_host, int p_por return err; } +void OS_Android::load_platform_gdextensions() const { + Vector 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() { } diff --git a/platform/android/os_android.h b/platform/android/os_android.h index abcc412588a..f88f3e05186 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -169,9 +169,15 @@ public: virtual void benchmark_end_measure(const String &p_what) override; virtual void benchmark_dump() override; + virtual void load_platform_gdextensions() const 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(); + +private: + // Location where we relocate external dynamic libraries to make them accessible. + String get_dynamic_libraries_path() const; }; #endif // OS_ANDROID_H diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp index 5d48c4e2488..fd60ba4ae71 100644 --- a/platform/android/plugin/godot_plugin_jni.cpp +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -129,31 +129,4 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS 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); -} } diff --git a/platform/android/plugin/godot_plugin_jni.h b/platform/android/plugin/godot_plugin_jni.h index 36a992246d9..baa29a79ea2 100644 --- a/platform/android/plugin/godot_plugin_jni.h +++ b/platform/android/plugin/godot_plugin_jni.h @@ -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_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_nativeRegisterGDExtensionLibraries(JNIEnv *env, jclass clazz, jobjectArray gdextension_paths); } #endif // GODOT_PLUGIN_JNI_H