Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Godot Android plugin re-architecture #80740

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/extension/gdextension_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
4 changes: 4 additions & 0 deletions core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,10 @@ class OS {

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();
};
Expand Down
9 changes: 9 additions & 0 deletions editor/plugins/gdextension_export_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
13 changes: 13 additions & 0 deletions platform/android/java/lib/src/org/godotengine/godot/Godot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
val configFiles = mutableSetOf<String>()
for (plugin in pluginRegistry.allPlugins) {
configFiles.addAll(plugin.pluginGDExtensionLibrariesPaths)
}

return configFiles.toTypedArray()
}

@Keep
private fun getCACertificates(): String {
return GodotNetUtils.getCACertificates()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,25 @@
import javax.microedition.khronos.opengles.GL10;

/**
* Base class for the Godot Android plugins.
* Base class for Godot Android plugins.
* <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>
* - 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:<godotLibVersion>"`
* <p>
* - The library must include a <meta-data> tag in its manifest file setup as follow:
* <meta-data android:name="org.godotengine.plugin.v1.[PluginName]" android:value="[plugin.init.ClassFullName]" />
* - The library must include a <meta-data> tag in its Android manifest with the following format:
* <meta-data android:name="org.godotengine.plugin.v2.[PluginName]" android:value="[plugin.init.ClassFullName]" />
* 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}.
* <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
* 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 <a href="https://docs.godotengine.org/en/stable/tutorials/platform/android/index.html">Android plugins</a>
*/
public abstract class GodotPlugin {
private static final String TAG = GodotPlugin.class.getSimpleName();
Expand All @@ -97,7 +95,7 @@ protected Godot getGodot() {
}

/**
* Provides access to the underlying {@link Activity}.
* Provides access to the hosting {@link Activity}.
*/
@Nullable
protected Activity getActivity() {
Expand All @@ -106,33 +104,16 @@ protected Activity getActivity() {

/**
* Register the plugin with Godot native code.
*
* This method is invoked on the render thread.
* <p>
* 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<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject,
String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals,
Set<String> pluginGDExtensionLibrariesPaths) {
String pluginName, Set<SignalInfo> pluginSignals) {
nativeRegisterSingleton(pluginName, pluginObject);

Set<Method> filteredMethods = new HashSet<>();
Expand All @@ -143,14 +124,6 @@ private static Map<String, SignalInfo> registerPluginWithGodotNative(Object plug
// 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;
}
}
}
}

Expand All @@ -176,23 +149,18 @@ private static Map<String, SignalInfo> registerPluginWithGodotNative(Object plug
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.
* <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.
*
* Use shouldBeOnTop() to set whether the plugin's {@link View} should be added on top or behind
* the main Godot view.
* <p>
* 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.
Expand Down Expand Up @@ -235,44 +203,52 @@ public void onMainDestroy() {}
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() {}

/**
* 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) {}

Expand All @@ -284,17 +260,6 @@ public void onVkSurfaceCreated(Surface surface) {}
@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<String> getPluginMethods() {
return Collections.emptyList();
}

/**
* Returns the list of signals to be exposed to Godot.
*/
Expand All @@ -304,19 +269,19 @@ public Set<SignalInfo> getPluginSignals() {
}

/**
* 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).
* <p>
* Each returned path must be relative to the 'assets' directory and point to a '*.gdextension' file.
*/
@NonNull
protected Set<String> getPluginGDExtensionLibrariesPaths() {
public Set<String> 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.
* <p>
* 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.
*/
Expand Down Expand Up @@ -359,7 +324,7 @@ protected void emitSignal(final String signalName, final Object... signalArgs) {
}
emitSignal(getGodot(), getPluginName(), signalInfo, signalArgs);
} catch (IllegalArgumentException exception) {
Log.w(TAG, exception.getMessage());
Log.w(TAG, exception);
if (BuildConfig.DEBUG) {
throw exception;
}
Expand All @@ -368,7 +333,7 @@ protected void emitSignal(final String signalName, final Object... signalArgs) {

/**
* 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.
Expand Down Expand Up @@ -397,7 +362,7 @@ public static void emitSignal(Godot godot, String pluginName, SignalInfo signalI
godot.runOnRenderThread(() -> nativeEmitSignal(pluginName, signalInfo.getName(), signalArgs));

} catch (IllegalArgumentException exception) {
Log.w(TAG, exception.getMessage());
Log.w(TAG, exception);
if (BuildConfig.DEBUG) {
throw exception;
}
Expand All @@ -420,13 +385,7 @@ public static void emitSignal(Godot godot, String pluginName, SignalInfo signalI
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
Expand Down
Loading
Loading