Welcome to an expose of the wonders of Luajit and FFI, within Defold of course.
Lets make some things very clear about Luajit and FFI first:
- FFI - Foreign Function Interface is a specific call and bind mechanism in Luajit that can interact with C/C++ constructs and methods directly. It can crash your application if you do nasty things with it - like corrupting a pointer.
- FFI is very platform dependent. You have to have different interfaces for different platforms. Sometimes (like in the cases of OSX and Linux) the headers are identical, but you still need to load different dynamic libraries. If you want full cross platform capability, it is up to you to make it!
- FFI is much more suited to custom applications. However, it can be extremely useful in games, but be warned, this can be challenging
- Some platforms dont support Luajit. IOS didnt use to, but I think it does now. Check with Defold to see which platforms are Luajit driven and which are pure Lua. As of writing this, I think it is only HTML targets that dont use Luajit. Thus, remember, Luajit + FFI is not going to work on html.
Ok. That out the way. Lets talk fun. The topic is intended as a living document. And I will intermittently add more and more FFI examples and methods as well as reference links.
Example ffi project here:
What is FFI?
FFI lets the developer call and create C/C++ constructs and access external C/C++ developed code bases. The best reference for it is where it is built:
https://luajit.org/ext_ffi.html
When using FFI in Defold you need to include it like so:
local ffi = package.preload.ffi()
This is slightly different to using pure Luajit so beware if you are copying from a lib on github
First Simple Use
One of the many things people find frustrating with Lua is that it uses 1 indexed arrays. Well, with FFI you dont have to. Because you can create normal C allocated memory and objects you can also create normal C allocated arrays. Heres an example:
local ffi = package.preload.ffi()
--- Declare a structure of a C object we want to make an array of
ffi.cdef[[
typedef struct _MyObject {
int number;
float decimal;
char name[255];
char * ptr;
} MyObject;
]]
local myobj = ffi.new("MyObject[?]", 20)
myobj[0].number = 1
myobj[0].decimal = 2.0
myobj[0].name = "My Name"
myobj[0].ptr = ffi.new("char[?]", 20)
ffi.copy(myobj[0].ptr, ffi.string("Another Name"))
pprint(myobj[0])
pprint(myobj[0].number)
pprint(myobj[0].decimal)
pprint(ffi.string(myobj[0].name))
pprint(ffi.string(myobj[0].ptr))
This creates 20 objects we can use in the array. This manipulates just the first one at index 0.
Notice that you can change properties just like you would in a table (mostly - more details on this later).
You shall see this in your console output:
DEBUG:SCRIPT: cdata<struct _MyObject &>: 0x018d62ef1468
DEBUG:SCRIPT: 1
DEBUG:SCRIPT: 2
DEBUG:SCRIPT: My Name
DEBUG:SCRIPT: Another Name
Notice that the object when printed is a “cdata”. This is luajit speak for “user defined object”.
Most of the above is fairly obvious. Please comment if you are puzzled by anything.
And thats it for simple array and object manipulation.
The question is: Why use this instead of a table?
Firstly, because its pure memory. There is nothing else allocated for the object. Luajit does create handles to the memory when you use ff.new, which means it will be garbage collected for you. That includes the pointer that was assigned.
Secondly, if you are worried about memory and footprints (small games for instance) then packing objects into tight arrays can really help with this. And you can define exactly how large the data types you want to be. For example, if you know the int number is only going to go up to 200, then you could make it an unsigned char instead, reducing the mem use from 4 bytes (int 32bit) to 1 byte.
Other big benefits are for operating with external methods. You can modify and change the data here, and then pass it to the library you are using to execute some important algorithms on, or maybe store it in a database?
Thats enough for the intial post. I will add one tomorrow with calls to common windows functions - we will make a window in Lua.