Following the suggestion of one of my readers, I decided to take a deeper look into LUAJIT.
At first glance, LUAJIT is a Just-In-Time compiler for Lua, that uses exactly the same interface as the normal Lua library, but compiles the code instead of interpreting, resulting in very significant speed improvements.
Just that is enough reason to use it, and I intended to do it later in the project…
But what caught my attention was the FFI library that enables me to bind functions and C structures in a much easier fashion…
Under normal circumstances, if I wanted to have a function available to Lua (for example purposes, a sum function that takes two arguments and returns the sum of both), I’d have to:
-
Write the function itself
-
Write a bind function (a C function)
-
Bind that function to the environment
On the example:
LuaGlue lua_sum(lua_State* L)
int a=lua_get_number_argument(L,1);
int b=lua_get_number_argument(L,2);
env->add_function(“sum”,lua_sum);
This is of course a simplification, and misses parameter validation, etc…
With Lua JIT, I can just:
-
Write the function itself
-
Bind the function directly in LUA
Step 1 is almost the same as before:
extern “C” int __declspec(dllexport) sum(int a,int b)
The difference is that now I have to say that the function is to be implemented through C call, instead of stdcall (the extern “C” part) and that the function has to be exported (the __declspec(dllexport) part).
Then, for step 2 on LUA, I just have to do:
extern “C” int __declspec(dllexport) sum(int a,int b)
local var=ffi.C.sum(10,20)
Much simpler and safer, I think!
The benefits aren’t just here, you can declare C structs in both sides (C and Lua) and use them without incurring performance penalties, which allied to the metatype tables allow you to actually add something like a Vec3 class or something like that… Check out the part on “Defining Metamethods for a C Type” on the FFI Tutorial. It really shows off what you can do with this…
The only issue I see is that class support is tricky, although you can use classes like this:
C code (assuming a classA):
extern "C" classA __declspec(dllexport) *classA_new()
{
return new classA;
}
extern "C" void __declspec(dllexport) classA_set_a(classA* __this,int a)
{
__this->set_a(a);
}
extern "C" int __declspec(dllexport) classA_get_a(classA* __this)
{
return __this->get_a();
}
Lua code:
local ffi = require("ffi")ffi.cdef[[
typedef struct { void *__this; } classA;
classA* classA_new();
void classA_set_a(classA* t,int a);
int classA_get_a(classA* t);
]]
local classAlocal classA_mt =
{ __index =
{
set_a = function(t,a) ffi.C.classA_set_a(t,a) end,
get_a = function(t) return ffi.C.classA_get_a(t) end,
},
}
classA = ffi.metatype("classA", classA_mt)
function test()
local object=ffi.C.classA_new()
print(object)
local value=object:get_a()
std.log(log_debug,string.format("Value=%i",value))
object:set_a(25)
std.log(log_debug,string.format("Value=%i",object:get_a()))
end
Very convenient indeed!
Only problem is that I designed the SurgePlayer Lua structure without this in mind, and suddenly I have what seems like a better way of doing things available…
Now I need to rethink the whole system to take advantage of this, but still keep some of the benefits I had on the previous system.
I believe it will be well worth the trouble!
By the way, there’s a new post down at http://www.spellcasterstudios.com, with video! Check it out!
Until next time!