Commit 6f762ad7 by Rémi Verschelde Committed by GitHub

Merge pull request #10422 from endragor/gdnative-android

Make GDNative work on Android
parents 51066fcd 5ccdeccb
......@@ -1112,10 +1112,20 @@ bool Main::start() {
test = args[i + 1];
} else if (args[i] == "--export") {
editor = true; //needs editor
_export_preset = args[i + 1];
if (i + 1 < args.size()) {
_export_preset = args[i + 1];
} else {
ERR_PRINT("Export preset name not specified");
return false;
}
} else if (args[i] == "--export-debug") {
editor = true; //needs editor
_export_preset = args[i + 1];
if (i + 1 < args.size()) {
_export_preset = args[i + 1];
} else {
ERR_PRINT("Export preset name not specified");
return false;
}
export_debug = true;
} else {
// The parameter does not match anything known, don't skip the next argument
......
......@@ -40,7 +40,7 @@
const String init_symbol = "godot_gdnative_init";
const String terminate_symbol = "godot_gdnative_terminate";
String GDNativeLibrary::platform_names[NUM_PLATFORMS] = {
String GDNativeLibrary::platform_names[NUM_PLATFORMS + 1] = {
"X11_32bit",
"X11_64bit",
"Windows_32bit",
......@@ -48,11 +48,15 @@ String GDNativeLibrary::platform_names[NUM_PLATFORMS] = {
"OSX",
"Android",
"iOS",
"WebAssembly"
"iOS_32bit",
"iOS_64bit",
"WebAssembly",
""
};
String GDNativeLibrary::platform_lib_ext[NUM_PLATFORMS] = {
String GDNativeLibrary::platform_lib_ext[NUM_PLATFORMS + 1] = {
"so",
"so",
"dll",
......@@ -60,21 +64,30 @@ String GDNativeLibrary::platform_lib_ext[NUM_PLATFORMS] = {
"dylib",
"so",
"dylib",
"dylib",
"wasm",
"wasm"
""
};
// TODO(karroffel): make this actually do the right thing.
GDNativeLibrary::Platform GDNativeLibrary::current_platform =
#if defined(X11_ENABLED)
X11_64BIT;
(sizeof(void *) == 8 ? X11_64BIT : X11_32BIT);
#elif defined(WINDOWS_ENABLED)
WINDOWS_64BIT;
(sizeof(void *) == 8 ? WINDOWS_64BIT : WINDOWS_32BIT);
#elif defined(OSX_ENABLED)
OSX;
#elif defined(IPHONE_ENABLED)
(sizeof(void *) == 8 ? IOS_64BIT : IOS_32BIT);
#elif defined(ANDROID_ENABLED)
ANDROID;
#elif defined(JAVASCRIPT_ENABLED)
WASM;
#else
X11_64BIT; // need a sensible default..
NUM_PLATFORMS;
#endif
GDNativeLibrary::GDNativeLibrary()
......@@ -151,7 +164,10 @@ String GDNativeLibrary::get_library_path(StringName p_platform) const {
}
String GDNativeLibrary::get_active_library_path() const {
return library_paths[GDNativeLibrary::current_platform];
if (GDNativeLibrary::current_platform != NUM_PLATFORMS) {
return library_paths[GDNativeLibrary::current_platform];
}
return "";
}
GDNative::GDNative() {
......
......@@ -48,11 +48,17 @@ class GDNativeLibrary : public Resource {
// NOTE(karroffel): I heard OSX 32 bit is dead, so 64 only
OSX,
// TODO(karroffel): all different android versions and archs
// Android .so files must be located in directories corresponding to Android ABI names:
// https://developer.android.com/ndk/guides/abis.html
// Android runtime will select the matching library depending on the device.
// The value here must simply point to the .so name, for example:
// "res://libmy_gdnative.so" or "libmy_gdnative.so",
// while in the project the actual paths can be "lib/android/armeabi-v7a/libmy_gdnative.so",
// "lib/android/arm64-v8a/libmy_gdnative.so".
ANDROID,
// TODO(karroffe): all different iOS versions and archs
IOS,
IOS_32BIT,
IOS_64BIT,
// TODO(karroffel): figure out how to deal with web stuff at all...
WASM,
......@@ -64,10 +70,9 @@ class GDNativeLibrary : public Resource {
};
static String platform_names[NUM_PLATFORMS];
static String platform_lib_ext[NUM_PLATFORMS];
static String platform_names[NUM_PLATFORMS + 1];
static String platform_lib_ext[NUM_PLATFORMS + 1];
// TODO(karroffel): make this actually do something lol.
static Platform current_platform;
String library_paths[NUM_PLATFORMS];
......
......@@ -37,7 +37,7 @@ extern "C" {
#include <stdint.h>
#define GODOT_ARRAY_SIZE 8
#define GODOT_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_ARRAY_TYPE_DEFINED
......
......@@ -36,7 +36,7 @@ extern "C" {
#include <stdint.h>
#define GODOT_DICTIONARY_SIZE 8
#define GODOT_DICTIONARY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_DICTIONARY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_DICTIONARY_TYPE_DEFINED
......
......@@ -56,7 +56,7 @@ extern "C" {
#define GDAPI GDCALLINGCONV
#endif
#else
#define GDCALLINGCONV __attribute__((sysv_abi))
#define GDCALLINGCONV __attribute__((sysv_abi, visibility("default")))
#define GDAPI GDCALLINGCONV
#endif
......
......@@ -36,7 +36,7 @@ extern "C" {
#include <stdint.h>
#define GODOT_NODE_PATH_SIZE 8
#define GODOT_NODE_PATH_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_NODE_PATH_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_NODE_PATH_TYPE_DEFINED
......
......@@ -38,7 +38,7 @@ extern "C" {
/////// PoolByteArray
#define GODOT_POOL_BYTE_ARRAY_SIZE 8
#define GODOT_POOL_BYTE_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_BYTE_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_BYTE_ARRAY_TYPE_DEFINED
......@@ -49,7 +49,7 @@ typedef struct {
/////// PoolIntArray
#define GODOT_POOL_INT_ARRAY_SIZE 8
#define GODOT_POOL_INT_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_INT_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_INT_ARRAY_TYPE_DEFINED
......@@ -60,7 +60,7 @@ typedef struct {
/////// PoolRealArray
#define GODOT_POOL_REAL_ARRAY_SIZE 8
#define GODOT_POOL_REAL_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_REAL_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_REAL_ARRAY_TYPE_DEFINED
......@@ -71,7 +71,7 @@ typedef struct {
/////// PoolStringArray
#define GODOT_POOL_STRING_ARRAY_SIZE 8
#define GODOT_POOL_STRING_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_STRING_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_STRING_ARRAY_TYPE_DEFINED
......@@ -82,7 +82,7 @@ typedef struct {
/////// PoolVector2Array
#define GODOT_POOL_VECTOR2_ARRAY_SIZE 8
#define GODOT_POOL_VECTOR2_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_VECTOR2_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_VECTOR2_ARRAY_TYPE_DEFINED
......@@ -93,7 +93,7 @@ typedef struct {
/////// PoolVector3Array
#define GODOT_POOL_VECTOR3_ARRAY_SIZE 8
#define GODOT_POOL_VECTOR3_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_VECTOR3_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_VECTOR3_ARRAY_TYPE_DEFINED
......@@ -104,7 +104,7 @@ typedef struct {
/////// PoolColorArray
#define GODOT_POOL_COLOR_ARRAY_SIZE 8
#define GODOT_POOL_COLOR_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_COLOR_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_COLOR_ARRAY_TYPE_DEFINED
......
......@@ -36,7 +36,7 @@ extern "C" {
#include <stdint.h>
#define GODOT_RID_SIZE 8
#define GODOT_RID_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_RID_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_RID_TYPE_DEFINED
......
......@@ -37,7 +37,7 @@ extern "C" {
#include <stdint.h>
#include <wchar.h>
#define GODOT_STRING_SIZE 8
#define GODOT_STRING_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_STRING_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_STRING_TYPE_DEFINED
......
......@@ -36,7 +36,7 @@ extern "C" {
#include <stdint.h>
#define GODOT_VARIANT_SIZE 24
#define GODOT_VARIANT_SIZE (16 + sizeof(void *))
#ifndef GODOT_CORE_API_GODOT_VARIANT_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_VARIANT_TYPE_DEFINED
......
......@@ -58,7 +58,7 @@ extern "C" {
#define GDAPI GDCALLINGCONV
#endif
#else
#define GDCALLINGCONV __attribute__((sysv_abi))
#define GDCALLINGCONV __attribute__((sysv_abi, visibility("default")))
#define GDAPI GDCALLINGCONV
#endif
......
......@@ -994,6 +994,8 @@ void NativeScriptLanguage::init_library(const Ref<GDNativeLibrary> &lib) {
#endif
// See if this library was "registered" already.
const String &lib_path = lib->get_active_library_path();
ERR_EXPLAIN(lib->get_name() + " does not have a library for the current platform");
ERR_FAIL_COND(lib_path.length() == 0);
Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path);
if (!E) {
......
......@@ -240,7 +240,7 @@ def configure(env):
env.Append(CPPPATH=['#platform/android'])
env.Append(CPPFLAGS=['-DANDROID_ENABLED', '-DUNIX_ENABLED', '-DNO_FCNTL', '-DMPC_FIXED_POINT'])
env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'android', 'log', 'z'])
env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'android', 'log', 'z', 'dl'])
# TODO: Move that to opus module's config
if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"):
......
......@@ -468,11 +468,36 @@ class EditorExportAndroid : public EditorExportPlatform {
return zipfi;
}
static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
static Set<String> get_abis() {
Set<String> abis;
abis.insert("armeabi");
abis.insert("armeabi-v7a");
abis.insert("arm64-v8a");
abis.insert("x86");
abis.insert("x86_64");
abis.insert("mips");
abis.insert("mips64");
return abis;
}
static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
APKExportData *ed = (APKExportData *)p_userdata;
String dst_path = p_path;
dst_path = dst_path.replace_first("res://", "assets/");
static Set<String> android_abis = get_abis();
if (dst_path.ends_with(".so")) {
String abi = dst_path.get_base_dir().get_file().strip_edges(); // parent dir name
if (android_abis.has(abi)) {
dst_path = "lib/" + abi + "/" + dst_path.get_file();
} else {
String err = "Dynamic libraries must be located in the folder named after Android ABI they were compiled for. " +
p_path + " does not follow this convention.";
ERR_PRINT(err.utf8().get_data());
return ERR_FILE_BAD_PATH;
}
} else {
dst_path = dst_path.replace_first("res://", "assets/");
}
zip_fileinfo zipfi = get_zip_fileinfo();
......@@ -1393,15 +1418,15 @@ public:
}
}
if (file == "lib/x86/libgodot_android.so" && !export_x86) {
if (file == "lib/x86/*.so" && !export_x86) {
skip = true;
}
if (file.match("lib/armeabi*/libgodot_android.so") && !export_arm) {
if (file.match("lib/armeabi*/*.so") && !export_arm) {
skip = true;
}
if (file.match("lib/arm64*/libgodot_android.so") && !export_arm64) {
if (file.match("lib/arm64*/*.so") && !export_arm64) {
skip = true;
}
......
......@@ -37,7 +37,7 @@ public class GodotLib {
public static GodotIO io;
static {
System.loadLibrary("godot_android");
System.loadLibrary("godot_android");
}
/**
......@@ -45,7 +45,8 @@ public class GodotLib {
* @param height the current view height
*/
public static native void initialize(Godot p_instance,boolean need_reload_hook,String[] p_cmdline,Object p_asset_manager);
public static native void initialize(Godot p_instance,boolean need_reload_hook,Object p_asset_manager, boolean use_apk_expansion);
public static native void setup(String[] p_cmdline);
public static native void resize(int width, int height,boolean reload);
public static native void newcontext(boolean p_32_bits);
public static native void back();
......
......@@ -89,7 +89,7 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
}
public void callbackSuccess(String ticket, String signature, String sku) {
GodotLib.callobject(purchaseCallbackId, "purchase_success", new Object[]{ticket, signature, sku});
GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[]{ticket, signature, sku});
}
public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku) {
......
......@@ -208,8 +208,9 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
@Override public void onInputDeviceAdded(int deviceId) {
joystick joy = new joystick();
joy.device_id = deviceId;
int id = joy_devices.size();
final int id = joy_devices.size();
InputDevice device = mInputManager.getInputDevice(deviceId);
final String name = device.getName();
joy.name = device.getName();
joy.axes = new ArrayList<InputDevice.MotionRange>();
joy.hats = new ArrayList<InputDevice.MotionRange>();
......@@ -224,19 +225,29 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
}
}
joy_devices.add(joy);
GodotLib.joyconnectionchanged(id, true, joy.name);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joyconnectionchanged(id, true, name);
}
});
}
@Override public void onInputDeviceRemoved(int deviceId) {
int id = find_joy_device(deviceId);
final int id = find_joy_device(deviceId);
joy_devices.remove(id);
GodotLib.joyconnectionchanged(id, false, "");
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joyconnectionchanged(id, false, "");
}
});
}
@Override public void onInputDeviceChanged(int deviceId) {
}
@Override public boolean onKeyUp(int keyCode, KeyEvent event) {
@Override public boolean onKeyUp(final int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return true;
......@@ -249,22 +260,38 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
int source = event.getSource();
if ((source & InputDevice.SOURCE_JOYSTICK) != 0 || (source & InputDevice.SOURCE_DPAD) != 0 || (source & InputDevice.SOURCE_GAMEPAD) != 0) {
int button = get_godot_button(keyCode);
int device = find_joy_device(event.getDeviceId());
final int button = get_godot_button(keyCode);
final int device = find_joy_device(event.getDeviceId());
GodotLib.joybutton(device, button, false);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joybutton(device, button, false);
}
});
return true;
} else {
GodotLib.key(keyCode, event.getUnicodeChar(0), false);
final int chr = event.getUnicodeChar(0);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(keyCode, chr, false);
}
});
};
return super.onKeyUp(keyCode, event);
};
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
@Override public boolean onKeyDown(final int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
GodotLib.back();
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.back();
}
});
// press 'back' button should not terminate program
//normal handle 'back' event in game logic
return true;
......@@ -281,16 +308,26 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
if (event.getRepeatCount() > 0) // ignore key echo
return true;
int button = get_godot_button(keyCode);
int device = find_joy_device(event.getDeviceId());
final int button = get_godot_button(keyCode);
final int device = find_joy_device(event.getDeviceId());
//Log.e(TAG, String.format("joy button down! button %x, %d, device %d", keyCode, button, device));
GodotLib.joybutton(device, button, true);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joybutton(device, button, true);
}
});
return true;
} else {
GodotLib.key(keyCode, event.getUnicodeChar(0), true);
final int chr = event.getUnicodeChar(0);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(keyCode, chr, true);
}
});
};
return super.onKeyDown(keyCode, event);
}
......@@ -299,21 +336,32 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
int device_id = find_joy_device(event.getDeviceId());
final int device_id = find_joy_device(event.getDeviceId());
joystick joy = joy_devices.get(device_id);
for (int i = 0; i < joy.axes.size(); i++) {
InputDevice.MotionRange range = joy.axes.get(i);
float value = (event.getAxisValue(range.getAxis()) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
final float value = (event.getAxisValue(range.getAxis()) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
//Log.e(TAG, String.format("axis event: %d, value %f", i, value));
GodotLib.joyaxis(device_id, i, value);
final int idx = i;
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joyaxis(device_id, idx, value);
}
});
}
for (int i = 0; i < joy.hats.size(); i+=2) {
int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
int hatY = Math.round(event.getAxisValue(joy.hats.get(i+1).getAxis()));
final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
final int hatY = Math.round(event.getAxisValue(joy.hats.get(i+1).getAxis()));
//Log.e(TAG, String.format("HAT EVENT %d, %d", hatX, hatY));
GodotLib.joyhat(device_id, hatX, hatY);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joyhat(device_id, hatX, hatY);
}
});
}
return true;
};
......
......@@ -89,8 +89,13 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
//Log.d(TAG, "beforeTextChanged(" + pCharSequence + ")start: " + start + ",count: " + count + ",after: " + after);
for (int i=0;i<count;i++){
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
mView.queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
}
});
}
}
......@@ -99,9 +104,14 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
//Log.d(TAG, "onTextChanged(" + pCharSequence + ")start: " + start + ",count: " + count + ",before: " + before);
for (int i=start;i<start+count;i++){
int ch = pCharSequence.charAt(i);
GodotLib.key(0, ch, true);
GodotLib.key(0, ch, false);
final int ch = pCharSequence.charAt(i);
mView.queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(0, ch, true);
GodotLib.key(0, ch, false);
}
});
}
}
......@@ -111,8 +121,14 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
if (this.mEdit == pTextView && this.isFullScreenEdit()) {
// user press the action button, delete all old text and insert new text
for (int i = this.mOriginText.length(); i > 0; i--) {
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
mView.queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
}
});
/*
if (BuildConfig.DEBUG) {
Log.d(TAG, "deleteBackward");
......@@ -131,9 +147,14 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
}
for(int i = 0; i < text.length(); i++) {
int ch = text.codePointAt(i);
GodotLib.key(0, ch, true);
GodotLib.key(0, ch, false);
final int ch = text.codePointAt(i);
mView.queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(0, ch, true);
GodotLib.key(0, ch, false);
}
});
}
/*
if (BuildConfig.DEBUG) {
......@@ -141,7 +162,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
}
*/
}
if (pActionID == EditorInfo.IME_ACTION_DONE) {
this.mView.requestFocus();
}
......
......@@ -36,7 +36,8 @@
#include <jni.h>
extern "C" {
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jboolean p_need_reload_hook, jobjectArray p_cmdline, jobject p_asset_manager);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jboolean p_need_reload_hook, jobject p_asset_manager, jboolean p_use_apk_expansion);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jobject obj, jobjectArray p_cmdline);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height, jboolean reload);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment