Skip to content

Commit

Permalink
Godot Android plugin re-architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
m4gr3d committed Sep 4, 2023
1 parent fa3428f commit 8cc7739
Show file tree
Hide file tree
Showing 13 changed files with 169 additions and 207 deletions.
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

0 comments on commit 8cc7739

Please sign in to comment.