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

[iOS] [4.0] iOS Plugins #41230

Merged
merged 4 commits into from
Nov 10, 2020

Conversation

naithar
Copy link
Contributor

@naithar naithar commented Aug 13, 2020

Implements godotengine/godot-proposals#1185 for master branch.
Godot iOS Plugin template
Since this plugin implementation directly uses Godot's source headers it's important to use correct compilation flags.

This PR allows iOS project to load plugins at runtime.
This would allow developers to work on platform functionality (like Facebook Auth, AdMob and other platform specific modules) without requiring to rebuild an engine to embed it into application.
Godot's iOS binary would probably be needed to be supplied like Androids' AAR library is. Compiling plugins without Godot's iOS binary actually works fine, only headers are required, but correct compilation flags should be used.
Plugins is also more portable, even than GDNative which requires additional setup for same functionality.
Plugin also produces smaller binary size for same code - ~800KB for GameCenter plugin vs ~4MB for GameCenter in GDNative as framework or ~800 KB for module + ~3MB for GDNative itself as dependency.

Also plugins can work with current module system, it's just makes things a lot easier for iOS. So it should be possible to migrate some parts of this to Godot 3.

iOS Plugin is basically a .gdip (like .gdap for Android) configuration file with static library (which is linked to Godot's binary) and dependencies bundled with it.
.gdip file format allows to specify dependency libraries, frameworks and resource files as well as plist values and system capabilities. Dependencies as well as binary itself could use absolute and relative path.
Plugin files should be placed in ios/plugins folder or it's child folder.
Supports multiple target binaries.

Some parts for implementation was taken from Android's plugin system. Such as configuration file parsing, configuration observer.

Plugins configuration syntax
[config]
name=""
binary=""

initialization=""
deinitialization=""

[dependencies]
linked=[]
embedded=[]
system=[]

capabilities=[]

files=[]

[plist]
Example

Having project structure like this:

*project
|-...
|--ios
  |--plugins
    |--gamecenter
      |--gamecenter.gdip
      |--gamecenter_lib.release.a
      |--gamecenter_lib.debug.a
    |--arkit
      |--arkit.gdip
      |--arkit_lib.release.a
      |--arkit_lib.debug.a
    |--camera
      |--camera.gdip
      |--camera_lib.a
    |--inappstore
      |--inappstore.gdip
      |--inappstore_lib.a
    |--icloud
      |--icloud.gdip
      |--icloud_lib.a

Enabled this export options in editor:
Снимок экрана 2020-08-14 в 20 43 39

dymmy.cpp file in Xcode project looks like this when exporting multiple plugins.

// Godot Plugins
void godot_ios_plugins_initialize();
void godot_ios_plugins_deinitialize();
// Exported Plugins

// Plugin: GameCenter
extern void register_gamecenter_types();
extern void unregister_gamecenter_types();

// Plugin: Camera
extern void register_camera_types();
extern void unregister_camera_types();

// Plugin: InAppStore
extern void register_inappstore_types();
extern void unregister_inappstore_types();

// Plugin: ARKit
extern void register_arkit_types();
extern void unregister_arkit_types();

// Plugin: iCloud
extern void register_icloud_types();
extern void unregister_icloud_types();

// Use Plugins
void godot_ios_plugins_initialize() {
	register_gamecenter_types();
	register_camera_types();
	register_inappstore_types();
	register_arkit_types();
	register_icloud_types();
}

void godot_ios_plugins_deinitialize() {
	unregister_gamecenter_types();
	unregister_camera_types();
	unregister_inappstore_types();
	unregister_arkit_types();
	unregister_icloud_types();
}

This gives developer access to GameCenter, InAppStore, iCloud and other functionality for iOS that previously required rebuilding an engine to be enabled or disabled.

Obj-C interaction

Modifying template likes this:

#import <Foundation/Foundation.h>

#include "core/project_settings.h"
#include "core/class_db.h"
#import "platform/iphone/app_delegate.h"
#import "platform/iphone/view_controller.h"

#import "godot_plugin_implementation.h"

void PluginExample::_bind_methods() {
    ClassDB::bind_method(D_METHOD("example_method"), &PluginExample::example_method);
}

void PluginExample::example_method(String str) {
    NSLog(@"foo");
    print_error(str);
    NSLog(@"%@", AppDelegate.viewController);
    
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 300, 300)];
    view.backgroundColor = [UIColor redColor];
    
    [AppDelegate.viewController.view addSubview:view];
}

Results in this:

So direct communication with ObjC is not a problem.

Signals integration

static int i = 0;

void PluginExample::_bind_methods() {
    ClassDB::bind_method(D_METHOD("foo"), &PluginExample::foo);
    ADD_SIGNAL(MethodInfo("event", PropertyInfo(Variant::INT, "id")));
}

Error PluginExample::foo() {
    NSLog(@"foo");
    
    i += 1;
    emit_signal("event", i);
    
    return OK;
}

@Calinou Calinou added this to the 4.0 milestone Aug 13, 2020
@naithar naithar force-pushed the feature/pluggable-ios-modules branch from a450ee6 to a8dc521 Compare August 14, 2020 18:59
@naithar naithar changed the title [WIP] [iOS] [4.0] Pluggable iOS Modules [WIP] [iOS] [4.0] iOS Plugins Aug 14, 2020
@naithar
Copy link
Contributor Author

naithar commented Aug 14, 2020

Added a iOS template for plugins, which also could work as simple example.
Currently it seems that plugin doesn't really need to actually link to Godot's iOS binary. Only headers are required including the ones generated by compiling. But I've left linking to iOS binary in Scons config just in case.

@naithar naithar force-pushed the feature/pluggable-ios-modules branch from a8dc521 to 7f41129 Compare August 15, 2020 12:10
@naithar naithar changed the title [WIP] [iOS] [4.0] iOS Plugins [iOS] [4.0] iOS Plugins Aug 15, 2020
@naithar naithar marked this pull request as ready for review August 15, 2020 14:45
@naithar
Copy link
Contributor Author

naithar commented Aug 15, 2020

I've retested and everything seems to work fine.

But @cagdasc is also implementing his own plugin system in #41269, so the best approach could be chosen :)

@naithar

This comment has been minimized.

@naithar

This comment has been minimized.

@naithar
Copy link
Contributor Author

naithar commented Aug 17, 2020

Retested with every module being rebuilt with new SConstruct in templates. No crashes on cleanup and everything seems to be working correctly.

@naithar naithar force-pushed the feature/pluggable-ios-modules branch 3 times, most recently from 73271ff to f96dddb Compare August 17, 2020 19:57
@m4gr3d
Copy link
Contributor

m4gr3d commented Aug 17, 2020

cc @BastiaanOlij since this may affect how ARKit function is provided.

@naithar
Copy link
Contributor Author

naithar commented Aug 17, 2020

Is there any ARKit test project for 4.0 that I can use?
I've just tested ARKit for 3.2 and made some changes for it to work, which I'll push in #41340 - plugin initialization was called to late for ARServer to catch it.

@naithar naithar force-pushed the feature/pluggable-ios-modules branch 2 times, most recently from 265aa8d to 5eef519 Compare August 19, 2020 13:40
@tolgakaranlik
Copy link

tolgakaranlik commented Sep 12, 2020

naithar I believe what you have done so far is amazing. My humble request from you is that please consider making an example function which takes a parameter from gdscript side and emit an example signal in your plugin template if you can find some spare time. It would be very useful for guys like me =) Anyway, you have my gratitude, keep up your good work!

@naithar
Copy link
Contributor Author

naithar commented Sep 12, 2020

@tolgakaranlik for now you can probably look at websocket module for reference

@naithar
Copy link
Contributor Author

naithar commented Oct 21, 2020

Using bundled linker, maybe. LLD do support all required platforms and architectures, and AFAIK can be used as standalone executable, but it's almost 50 MB in size (at least on macOS).

And, there're plans to compile GDScript to native code, so it's probably going to happen anyway.

Well it could come as editor (?) plugin in case user doesn't need this feature, but I'm not really sure how it would work for now.

@akien-mga
Copy link
Member

Needs a rebase, otherwise I guess we could merge the current state already and build upon it if further changes are needed?

Moved previously builtin modules 'GameCenter', 'AppStore', 'iCloud' to separate modules to be represented as plugin.
Modified 'ARKit' and 'Camera' to not be builtin into engine and work as plugin.
Changed platform code so it's not affected by the move.
Modified Xcode project file to remove parameters that doesn't make any effect.
Added basic '.gdip' plugin config file.
@naithar naithar force-pushed the feature/pluggable-ios-modules branch from e69ee49 to eb2bc22 Compare November 10, 2020 13:31
@naithar
Copy link
Contributor Author

naithar commented Nov 10, 2020

Rebased.
I'll try to run some more tests, but it should be save to merge.

I guess we could merge the current state already and build upon it if further changes are needed

Yeah, I think so. Moving modules to separate repo could be done in next PRs.
Also implementing a pluggable AppDelegate to simplify support for Push Notifications, Facebook or any other SDK that requires swizzling right now seems like a completely separate functionality.

Added plugin configuration.
Export options now use plugins that could be enabled/disabled.
Plugin changes are observed at runtime.
Plugins can use 'binary_name.a' or 'binary_name.release.a' and 'binary_name.debug.a' for plugin library.
@naithar naithar force-pushed the feature/pluggable-ios-modules branch from eb2bc22 to 1f2f477 Compare November 10, 2020 13:41
@akien-mga akien-mga merged commit 1626cfd into godotengine:master Nov 10, 2020
@akien-mga
Copy link
Member

Thanks a ton for your work!

@alexzheng
Copy link

alexzheng commented Feb 1, 2021

A little suggestion:
Make the godot_ios_plugins_initialize called before any GDScripts.
It's strange that a plugin is available in the _ready method and not available in _init.

Take the Game Center plugin for example
add an autoload node, the scripts for that node is:

func _init() -> void:
if Engine.has_singleton("GameCenter"):
printt("has_singleton GameCenter in _init")
else:
printt("not has_singleton GameCenter _init")

func _ready() -> void:
if Engine.has_singleton("GameCenter"):
printt("has_singleton GameCenter in _ready")
else:
printt("not has_singleton GameCenter in _ready")

The results is
not has_singleton GameCenter _init
has_singleton GameCenter in _ready

That's caused by the _init is happened before the godot_ios_plugins_initialize called.

@naithar
Copy link
Contributor Author

naithar commented Feb 1, 2021

@alexzheng this two branches should probably allow this. But they require testing.
https://github.com/naithar/godot/tree/fix/ios-plugin-initialization
https://github.com/naithar/godot/tree/fix/ios-plugin-initialization-4.0
If you can test 3.2 version it would help greatly.

@alexzheng
Copy link

alexzheng commented Feb 1, 2021

I have tested in 3.2.1 rc 1
the result is it does not work.

output in Xcode:
SCRIPT ERROR: Compile Error: Can't load global class GameCenter, cyclic reference?

My steps:
build libs from the Godot source
replace the libgodot.iphone.release.fat.a and libgodot.iphone.debug.fat.a in iphone.zip in the templates
export an Xcode project from godot editor.

it show the above error.

The process is not very straightforward and I'm not sure if my steps is correct.

So I did the above steps again from the source code of 3.2.4 rc1, it can run without error.

@naithar
Copy link
Contributor Author

naithar commented Feb 1, 2021

RC1 doesn't have changes from https://github.com/naithar/godot/tree/fix/ios-plugin-initialization in it and it wouldn't until it's tested.
You have to manually build iOS template library from source. After that you can simply change <project_name>.a in exported Xcode project with the library you've just built.

output in Xcode:
SCRIPT ERROR: Compile Error: Can't load global class GameCenter, cyclic reference?

Without example I cannot reproduce this.

With the code that you've provided earlier there are no errors:

func _init() -> void:
if Engine.has_singleton("GameCenter"):
printt("has_singleton GameCenter in _init")
else:
printt("not has_singleton GameCenter _init")

func _ready() -> void:
if Engine.has_singleton("GameCenter"):
printt("has_singleton GameCenter in _ready")
else:
printt("not has_singleton GameCenter in _ready")

@alexzheng
Copy link

I made modification in RC1 with your committed code, just move godot_ios_plugins_initialize from set_main_loop to start

@naithar
Copy link
Contributor Author

naithar commented Feb 1, 2021

I can't reproduce your SCRIPT ERROR: Compile Error: Can't load global class GameCenter, cyclic reference issue with my project. You have to make MRP.

@alexzheng
Copy link

That's my custom gdscript class, it can run in RC1

class_name GameCenter

var _GameCenter = null

func _init() -> void:
if Engine.has_singleton("GameCenter"):
_GameCenter = Engine.get_singleton("GameCenter")
_GameCenter.authenticate()
printt("2 has_singleton GameCenter")
else:
printt("2 not has_singleton GameCenter")

When it run in your branch:
SCRIPT ERROR: Parse Error: The class "GameCenter" shadows a native class.
2021-02-01 21:29:24.666473+0800 Idiom[44623:10254439] At: res://Idiom/autoload/gamekit.gdc:4:GDScript::load_byte_code() - Parse Error: The class "GameCenter" shadows a native class.
2021-02-01 21:29:24.666487+0800 Idiom[44623:10254439] ERROR: Method failed. Returning: ERR_PARSE_ERROR
2021-02-01 21:29:24.666498+0800 Idiom[44623:10254439] At: modules/gdscript/gdscript.cpp:801:load_byte_code() - Method failed. Returning: ERR_PARSE_ERROR
2021-02-01 21:29:24.666508+0800 Idiom[44623:10254439] ERROR: Condition "err != OK" is true. Returned: RES()
2021-02-01 21:29:24.666521+0800 Idiom[44623:10254439] At: modules/gdscript/gdscript.cpp:2311:load() - Condition "err != OK" is true. Returned: RES()
2021-02-01 21:29:24.666533+0800 Idiom[44623:10254439] ERROR: Condition "found" is true. Returned: RES()
2021-02-01 21:29:24.666543+0800 Idiom[44623:10254439] At: core/io/resource_loader.cpp:279:_load() - Condition "found" is true. Returned: RES()
2021-02-01 21:29:24.666667+0800 Idiom[44623:10254439] SCRIPT ERROR: Compile Error: Can't load global class GameCenter, cyclic reference?
2021-02-01 21:29:24.666756+0800 Idiom[44623:10254439] At: res://Idiom/autoload/global.gdc:50:GDScript::load_byte_code() - Compile Error: Can't load global class GameCenter, cyclic reference?
2021-02-01 21:29:24.666855+0800 Idiom[44623:10254439] ERROR: Method failed. Returning: ERR_COMPILATION_FAILED
2021-02-01 21:29:24.666927+0800 Idiom[44623:10254439] At: modules/gdscript/gdscript.cpp:809:load_byte_code() - Method failed. Returning: ERR_COMPILATION_FAILED
2021-02-01 21:29:24.666951+0800 Idiom[44623:10254439] ERROR: Condition "err != OK" is true. Returned: RES()
2021-02-01 21:29:24.667074+0800 Idiom[44623:10254439] At: modules/gdscript/gdscript.cpp:2311:load() - Condition "err != OK" is true. Returned: RES()
2021-02-01 21:29:24.667130+0800 Idiom[44623:10254439] ERROR: Condition "found" is true. Returned: RES()
2021-02-01 21:29:24.667232+0800 Idiom[44623:10254439] At: core/io/resource_loader.cpp:279:_load() - Condition "found" is true. Returned: RES()
2021-02-01 21:29:24.667302+0800 Idiom[44623:10254439] ERROR: Condition "res.is_null()" is true. Continuing.
2021-02-01 21:29:24.667367+0800 Idiom[44623:10254439] At: main/main.cpp:1762:start() - Condition "res.is_null()" is true. Continuing.

@alexzheng
Copy link

After change class_name GameCenter to class_name MyGameCenter, your branch works, and it output:
has_singleton GameCenter in _init, it works as expected.

@naithar
Copy link
Contributor Author

naithar commented Feb 1, 2021

Didn't you say that _init isn't working with RC1? So your script wasn't working in RC1 too?

Also SCRIPT ERROR: Parse Error: The class "GameCenter" shadows a native class. is pretty clear error in my opinion, just change the class name.

@naithar naithar deleted the feature/pluggable-ios-modules branch February 1, 2021 13:39
@alexzheng
Copy link

Yes, I did not notice my class name is the same as the native class in RC1. However it can works in RC1, only has the error that the Plugin GameCenter is not available in _init.

@naithar
Copy link
Contributor Author

naithar commented Feb 1, 2021

That's because in RC1 GameCenter plugin isn't registered yet when _init is called. And that's probably enough to allow to shadow native class name.

@alexzheng
Copy link

alexzheng commented Feb 1, 2021

Yes, I can understand, in RC1, the GDscript file that defined the class GameCenter is loaded before the plugin is registered.
Whatever, your modification has fix that issue.

naithar referenced this pull request in godot-sdk-integrations/godot-ios-plugins Feb 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants