/
GodotPlugin.java
443 lines (386 loc) · 15.1 KB
/
GodotPlugin.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
/**************************************************************************/
/* GodotPlugin.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 org.godotengine.godot.BuildConfig;
import org.godotengine.godot.Godot;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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;
import java.util.concurrent.ConcurrentHashMap;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* Base class for the Godot Android plugins.
* <p>
* A Godot Android plugin is a regular Android library packaged as an aar archive file with the following caveats:
* <p>
* - The library must have a dependency on the Godot Android library (godot-lib.aar).
* A stable version is available for each release.
* <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]" />
* Where:
* - 'PluginName' is the name of the plugin.
* - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin class
* extending {@link GodotPlugin}.
*
* 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]/'
*/
public abstract class GodotPlugin {
private static final String TAG = GodotPlugin.class.getSimpleName();
private final Godot godot;
private final ConcurrentHashMap<String, SignalInfo> registeredSignals = new ConcurrentHashMap<>();
public GodotPlugin(Godot godot) {
this.godot = godot;
}
/**
* Provides access to the Godot engine.
*/
protected Godot getGodot() {
return godot;
}
/**
* Provides access to the underlying {@link Activity}.
*/
@Nullable
protected Activity getActivity() {
return godot.getActivity();
}
/**
* Register the plugin with Godot native code.
*
* This method is invoked 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();
}
private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject,
String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals,
Set<String> pluginGDExtensionLibrariesPaths) {
nativeRegisterSingleton(pluginName, pluginObject);
Set<Method> filteredMethods = new HashSet<>();
Class<?> clazz = pluginObject.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
// 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;
}
}
}
}
for (Method method : filteredMethods) {
List<String> ptr = new ArrayList<>();
Class<?>[] paramTypes = method.getParameterTypes();
for (Class<?> c : paramTypes) {
ptr.add(c.getName());
}
String[] pt = new String[ptr.size()];
ptr.toArray(pt);
nativeRegisterMethod(pluginName, method.getName(), method.getReturnType().getName(), pt);
}
// Register the signals for this plugin.
Map<String, SignalInfo> registeredSignals = new HashMap<>();
for (SignalInfo signalInfo : pluginSignals) {
String signalName = signalInfo.getName();
nativeRegisterSignal(pluginName, signalName, signalInfo.getParamTypesNames());
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
* {@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
* hierarchy.
*
* Use shouldBeOnTop() to set 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.
*/
@Nullable
public View onMainCreate(Activity activity) {
return null;
}
/**
* @see Activity#onActivityResult(int, int, Intent)
*/
public void onMainActivityResult(int requestCode, int resultCode, Intent data) {
}
/**
* @see Activity#onRequestPermissionsResult(int, String[], int[])
*/
public void onMainRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
}
/**
* @see Activity#onPause()
*/
public void onMainPause() {}
/**
* @see Activity#onResume()
*/
public void onMainResume() {}
/**
* @see Activity#onDestroy()
*/
public void onMainDestroy() {}
/**
* @see Activity#onBackPressed()
*/
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.
*/
public void onGodotMainLoopStarted() {}
/**
* 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.
*/
public void onGLSurfaceChanged(GL10 gl, int width, int height) {}
/**
* 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.
*/
public void onVkDrawFrame() {}
/**
* 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.
*/
public void onVkSurfaceCreated(Surface surface) {}
/**
* Returns the name of the plugin.
* <p>
* This value must match the one listed in the plugin '<meta-data>' manifest entry.
*/
@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.
*/
@NonNull
public Set<SignalInfo> getPluginSignals() {
return Collections.emptySet();
}
/**
* Returns the paths for the plugin's gdextension libraries.
*
* The paths must be relative to the 'assets' directory and point to a '*.gdextension' file.
*/
@NonNull
protected 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.
*
* 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.
*/
public boolean shouldBeOnTop() {
return true;
}
/**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
*
* @param action the action to run on the UI thread
*/
protected void runOnUiThread(Runnable action) {
godot.runOnUiThread(action);
}
/**
* Queue the specified action to be run on the render thread.
*
* @param action the action to run on the render thread
*/
protected void runOnRenderThread(Runnable action) {
godot.runOnRenderThread(action);
}
/**
* Emit a registered Godot signal.
* @param signalName Name of the signal to emit. It will be validated against the set of registered signals.
* @param signalArgs Arguments used to populate the emitted signal. The arguments will be validated against the {@link SignalInfo} matching the registered signalName parameter.
*/
protected void emitSignal(final String signalName, final Object... signalArgs) {
try {
// Check that the given signal is among the registered set.
SignalInfo signalInfo = registeredSignals.get(signalName);
if (signalInfo == null) {
throw new IllegalArgumentException(
"Signal " + signalName + " is not registered for this plugin.");
}
emitSignal(getGodot(), getPluginName(), signalInfo, signalArgs);
} catch (IllegalArgumentException exception) {
Log.w(TAG, exception.getMessage());
if (BuildConfig.DEBUG) {
throw exception;
}
}
}
/**
* Emit a Godot signal.
* @param godot
* @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.
*/
public static void emitSignal(Godot godot, String pluginName, SignalInfo signalInfo, final Object... signalArgs) {
try {
if (signalInfo == null) {
throw new IllegalArgumentException("Signal must be non null.");
}
// Validate the arguments count.
Class<?>[] signalParamTypes = signalInfo.getParamTypes();
if (signalArgs.length != signalParamTypes.length) {
throw new IllegalArgumentException(
"Invalid arguments count. Should be " + signalParamTypes.length + " but is " + signalArgs.length);
}
// Validate the argument's types.
for (int i = 0; i < signalParamTypes.length; i++) {
if (!signalParamTypes[i].isInstance(signalArgs[i])) {
throw new IllegalArgumentException(
"Invalid type for argument #" + i + ". Should be of type " + signalParamTypes[i].getName());
}
}
godot.runOnRenderThread(() -> nativeEmitSignal(pluginName, signalInfo.getName(), signalArgs));
} catch (IllegalArgumentException exception) {
Log.w(TAG, exception.getMessage());
if (BuildConfig.DEBUG) {
throw exception;
}
}
}
/**
* Used to setup a {@link GodotPlugin} instance.
* @param p_name Name of the instance.
*/
private static native void nativeRegisterSingleton(String p_name, Object object);
/**
* Used to complete registration of the {@link GodotPlugin} instance's methods.
* @param p_sname Name of the instance
* @param p_name Name of the method to register
* @param p_ret Return type of the registered method
* @param p_params Method parameters types
*/
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.
* @param pluginName Name of the plugin
* @param signalName Name of the signal to register
* @param signalParamTypes Signal parameters types
*/
private static native void nativeRegisterSignal(String pluginName, String signalName, String[] signalParamTypes);
/**
* Used to emit signal by {@link GodotPlugin} instance.
* @param pluginName Name of the plugin
* @param signalName Name of the signal to emit
* @param signalParams Signal parameters
*/
private static native void nativeEmitSignal(String pluginName, String signalName, Object[] signalParams);
}