Tutorial: Quick and easy C++ native code in iOS with FFI

Show off your games, demos and other (playable) creations.
Post Reply
denismr
Prole
Posts: 7
Joined: Thu Sep 15, 2016 1:24 am

Tutorial: Quick and easy C++ native code in iOS with FFI

Post by denismr »

Obs.: I'm sorry if this is not the right place for this kind of topic. I really don't know where to post something like this.

The purpose of this tutorial is to show an easy and quick way of running C/C++ code from Lua (inside Love), in iOS, without really modifying Love source code or using Lua API. The problem is: FFI is supposed to be used with dynamic libraries (which aren't allowed in iOS).

I made this tutorial because figuring out whether it was possible to use FFI the way I do here (dirty and quick) in iOS was a bit time demanding, even though it is possible and very easy to set up. I suppose this topic can be useful for someone, in the future.

Before anything else, I'll apologize for the lack of references. If anything seems too odd and reference demanding, just ask.

Running C/C++ code in iOS has the following advantages:
  • As Luajit is not jitted on iOS (only interpreted), C/C++ code usually is much faster and requires less energy;
  • You can use and/or interface native components;
  • Using FFI avoids modifying Löve source code and is, in general, easier.
However, there are a few caveats that must be taken into account:
  • When Lua is jitted, calling C functions with FFI is usually as fast as a C -> C call, therefore faster than calling through LUA API (used by Löve). However, calling C functions with FFI has a considerable overhead when the JIT compiler is off and is slower than LUA API. I read a thread in the Luajit mailing list where Luajit's main developer (Mike Pall) warned that calling C functions repeatedly (as in a loop) will hurt the performance (badly) when JIT is off;
  • No callback functions to FFI calls! :( When JIT is on, you can pass Lua functions as arguments to C functions. In our case, this will not be possible.
  • In order to prevent our C/C++ code from being stripped from the binary, we'll have to disable linking optimisations. This will mainly impact the binary size. In my experience, the app became just slightly larger.
The first caveat is a bit disappointing, but there still are plenty of heavy things that are useful for games and that are called only once in a while: AI, FOV, Tree Searches, some simulations in general.. For example, I used C++ to run a Minimax for an AI, in a board game. I just had to call it once per turn, but every run was very computationally demanding.

And before any criticism regarding the uglyness of the present solution (mainly due to the third caveat), it can, at least, be used for quick prototyping and benchmarking of native code for a game already on the rails. And I agree with those criticisms. It is an ugly solution. :3

So, finally, let's get our hands dirty, shall we?

Programming C++ code is out of the scope of this tutorial. We will keep things simple. In this tutorial, we are going to interface the priority queue from C++ standard library to Lua.
First step: download and open the xcode project of Love for iOS. Information regarding this step can be obtained at the wiki.

After the project is open, we'll create our CPP and HPP files. We'll put them in a separate group from the rest of Love, so that we don't mix things. The following pictures illustrate how to do that.

Image

Image

Image

Image

Before we start coding, let's change the building options so that we can call our functions later, from lua. Click on the project's name, in the list on the left side of the screen. Select the Building Settings tab. Click on "all", on the top, to show all options.

Image

Image

You can use the search bar to help you out with the next part. We want to change the following options:
  • Strip Style -> Debugging Symbols
  • Dead Code Stripping -> No
  • Link-Time Optimisations -> No (note that this option is No for debug. Change it for release and distribution as well)
  • Symbols Hidden by Default -> No
Image

Image

Image

Image

Basically, we did these changes for two reasons:
  • The list of exported symbols is kinda an Yellow Pages. FFI searches it to find where our functions are implemented. Thus, we need to keep symbols for our functions there;
  • Functions that are never called are typically removed from the final binary, because they take space for nothing. As FFI decides which functions must be called at runtime, the compiler cannot foresee that our functions will be needed, and will remove them (as they are never called inside Love). This is why we have to disable dead code stripping and linking optimisations.
Finally, if you'd like to run the Release or the Distribution build on your iDevice, instead of the Debug build, look at the menu bar, click on Project -> Scheme -> Edit Scheme.

Then, on the list on left, select Run, and on the right pane, change Build Configuration to Release or Distribution (as you wish).


Image

Image

Now we are ready to code our example. The idea here is to interface std::priority_queue<std::pair<double, int> >. That is, we want a priority queue that, given a number of integer elements, each one associated with a real priority, pops the elements so that the element with greatest priority comes out first.

Open the header file (MyCode.hpp, in my case).
After the include line, add the following:

Code: Select all

extern "C" {
    typedef struct {
        void * pointer;
    } My_PQ_Wrapper;

    My_PQ_Wrapper my_pq_create_new();
    void my_pq_delete(My_PQ_Wrapper wrapper);
    void my_pq_push_element(My_PQ_Wrapper wrapper, double priority, int element);
    int my_pq_pop_element(My_PQ_Wrapper wrapper);
}
Note that we don't really need the My_PQ_Wrapper struct. We could directly use void*. However, if we did that, we should have to trick FFI later, because it won't set metatypes to void*. So, in order to keep things simple, we are wrapping void* here. What each function must do is easily guessable from the name. The extern "C" is a hint that we want our symbols to stay visible externally.

Open the cpp file. Its final content is the following:

Code: Select all

#include "MyCode.hpp"
#include <queue>
#include <utility>

My_PQ_Wrapper my_pq_create_new() {
	My_PQ_Wrapper wrapper;
	auto pq  = new std::priority_queue<std::pair<double, int>>();
	wrapper.pointer = reinterpret_cast<void*>(pq);
	return wrapper;
}

void my_pq_delete(My_PQ_Wrapper wrapper) {
	auto pq = reinterpret_cast<std::priority_queue<std::pair<double,int>>*>(wrapper.pointer);
	delete pq;
}

void my_pq_push_element(My_PQ_Wrapper wrapper, double priority, int element) {
	auto pq = reinterpret_cast<std::priority_queue<std::pair<double,int>>*>(wrapper.pointer);
	pq->push(std::make_pair(priority, element));
}

int my_pq_pop_element(My_PQ_Wrapper wrapper) {
	auto pq = reinterpret_cast<std::priority_queue<std::pair<double,int>>*>(wrapper.pointer);
	auto top = pq->top();
	pq->pop();
	return top.second;
}
There's no secret to it. Any questions, just ask.

We can jump to Lua, now. Minimize xcode and create a new main.lua as you would if you were going to create a new game.

First, we need to let FFI knows about our types and functions. We do that with ffi.cdef:

Code: Select all

local ffi = require 'ffi'

ffi.cdef [[
typedef struct {
  void * pointer;
} My_PQ_Wrapper;

My_PQ_Wrapper my_pq_create_new();
void my_pq_delete(My_PQ_Wrapper wrapper);
void my_pq_push_element(My_PQ_Wrapper wrapper, double priority, int element);
int my_pq_pop_element(My_PQ_Wrapper wrapper);
]]
As our code is statically linked to luajit (they are in the same binary file), we don't need to do anything else before being able to call our C functions. However, for the sake of awareness, note note that you would have to open the dynamic library before using it, in other operating systems.

Code: Select all

local PQLib
if love.system.getOS() == "iOS" then
  PQLib = ffi.C
else
  PQLib = ffi.load 'dll_location_if_windows_etc'
end
Now, PQLib is the namespace of our functions (it is a table that contains them). When JIT is on, Mike Pall doesn't recommend creating variables to hold C functions directly (instead, use Namespace.Function(...)). He says that the JIT compiler performs better this way. Even though JIT is off, we will keep the recommended practice here (because the code will probably be multiplatform).

To make things easier to use, we'll create a metatable for our C:

Code: Select all

local mt = {
  __index = {
    Push = function(self, priority, element)
      PQLib.my_pq_push_element(self, priority, element)
    end,

    Pop = function(self)
      return PQLib.my_pq_pop_element(self)
    end,
  },
}

ffi.metatype("My_PQ_Wrapper", mt)
We'll need a destructor so that the memory is empty after the priority queue is used:

Code: Select all

local function destructor(pq)
  PQLib.my_pq_delete(pq)
end
And a function that creates a priority queue and associate its destructor to the new instance:

Code: Select all

local function PriorityQueue()
  return ffi.gc(PQLib.my_pq_create_new(), destructor)
end
We can finally test it!

Code: Select all

local pq = PriorityQueue()

pq:Push(5, 1000)
pq:Push(2, 1001)
pq:Push(10, 1002)

for i = 1, 3 do
  print(pq:Pop())
end
Run Love through xcode (so that you can read the output in the console). To open a .love file on my iPad, I usually run a http server on my computer and download the file on the iPad. You can also use iTunes to transfer the game.

The results on the console:

Image
gianmichele
Citizen
Posts: 63
Joined: Tue Jan 14, 2014 11:03 pm

Re: Tutorial: Quick and easy C++ native code in iOS with FFI

Post by gianmichele »

This is nice! Thanks a lot for the tutorial.

Would it be possible or recommended to use this method to access let's say GameCenter or an ad provider like Vungle?
User avatar
Ulydev
Party member
Posts: 445
Joined: Mon Nov 10, 2014 10:46 pm
Location: Paris
Contact:

Re: Tutorial: Quick and easy C++ native code in iOS with FFI

Post by Ulydev »

gianmichele wrote:This is nice! Thanks a lot for the tutorial.

Would it be possible or recommended to use this method to access let's say GameCenter or an ad provider like Vungle?
Was about to ask this. I'd need to use native functions like music picker.
denismr
Prole
Posts: 7
Joined: Thu Sep 15, 2016 1:24 am

Re: Tutorial: Quick and easy C++ native code in iOS with FFI

Post by denismr »

gianmichele wrote:This is nice! Thanks a lot for the tutorial.

Would it be possible or recommended to use this method to access let's say GameCenter or an ad provider like Vungle?
Ulydev wrote:
gianmichele wrote:...
Was about to ask this. I'd need to use native functions like music picker.
I'll talk about offscreen things, first.

You can use anything that is written in C, Obj-C or Swift, since they are exposed through exported C interfaces.
Notice that I didn't write C functions. They are all C++, but the exported interface is C-style (and therefore I can use FFI).
I'm no expert in Swift, and I know nothing about Obj-C, but I'm sure that as for Obj-C++, at least, you can also export C functions, and you can expose Swift functions to Obj-C with the @objc mark up, so that the Swfit -> C route is possible.

Now, regarding things that need the screen. Well.. At the moment, I can't help you with this. I don't know how to draw something over love, mainly because I just don't know how it's drawn. I will dig into this later, but I can't do this right now because I'm a bit busy with my VIVA. I'm sorry =/
In any case, I would just just try to use these things, first, and see what happens.
Germanunkol
Party member
Posts: 712
Joined: Fri Jun 22, 2012 4:54 pm
Contact:

Re: Tutorial: Quick and easy C++ native code in iOS with FFI

Post by Germanunkol »

i think this could go in the wiki or in a blog post? http://blogs.love2d.org/
trAInsported - Write AI to control your trains
Bandana (Dev blog) - Platformer featuring an awesome little ninja by Micha and me
GridCars - Our jam entry for LD31
Germanunkol.de
Post Reply

Who is online

Users browsing this forum: No registered users and 174 guests