一个naive的方法
static int newSprite(lua_State *L)
{
int n = lua_gettop(L); // Number of arguments
if (n != 4)
return luaL_error(L, "Got %d arguments expected 4", n);
double x = luaL_checknumber (L, 1);
double y = luaL_checknumber (L, 2);
double dir = luaL_checknumber (L, 3);
double speed = luaL_checknumber (L, 4);
Sprite *s = new Sprite(Point2(x, y), dir, speed);
lua_pushlightuserdata(L, s);
return 1;
}
非常简单的一个实现,可惜它并不能工作,因为lightuserdata只是一个指针,但是这东西在Lua脚本里并不被识别,也就不能直接使用。
一个基本符合要求的方法
既然不能直接使用lightuserdata,那么我们就使用user data数据嘛。它是带metatable的。
static int newSprite(lua_State* l) {
int n = lua_gettop(l);
if(n != 2)
return luaL_error(l, "Got %d arguments expected 2", n);
Sprite **s = (Sprite **)lua_newuserdata(l, sizeof(Sprite *));
int x = luaL_checknumber(l, 1);
int y = luaL_checknumber(l, 2);
*s = new Sprite(x, y);
lua_getglobal(l ,"Sprite");
lua_setmetatable(l, -2);
return 1;
}
这里我们可以看到,它将全局变量的Sprite作为metatable绑定到新建的userdata元素(即是Sprite指针)。这个只是创建Sprite的绑定函数
static const luaL_Reg gSpriteFuncs[] = {
{"new", newSprite},
{"getx", spriteX},
{NULL, NULL}
};
void registerSprite(lua_State *l){
luaL_register(l, "Sprite", gSpriteFuncs); //注册到Sprite Table
lua_pushvalue(l,-1); //
lua_setfield(l, -2, "__index");
}
接着我们定义绑定表。下面的registerSprite函数就是在指定的lua环境里,实现注册Sprite类。
首先注册了一个函数集到Sprite Table,这就是上面使用的Sprite全局变量。然后再复制一个Table,作为自身的__index属性。
local sprite = Sprite.new(1, 2)
local posx = sprite:getx()
print(posx)
所以Sprite.new()可以就是调用newSprite创建类实例,然后类实例会绑定Sprite作为自己的metatable,因为userdata不是table,所以__index是永远触发的,因为metatable是table,不是function,所以触发就会在metatable里找对应的元素,比如getx。然后它就会找到newSprite这个function元素。因为是通过”:”来触发的,所以会存在一个userdata的参数。
一个绝对完善的方法
上面的方法基本可以用的,但是如果你在Lua改变了一个sprite实例的方法,那么所有的sprite实例方法都会被改变。
在registry里创建一个表,叫做“Lusion.Sprite”,里面注册一堆函数。在复制一份这个metatable,将其作为其本身的__index属性。再重复注册一个Sprite函数集。
Lusion.Sprite(Funcs)->__index->Lusion.Sprite(Funcs)
在newSprite的时候,第一个参数是一张表,其实就是Sprite函数集的表。我们新建一张表,将Sprite作为它的metatable,并将__index属性设成Sprite自己。然后就新建一个userdata,获得Lusion.Sprite,把它作为userdata的metatable。把早前建的新表的__self域指向userdata,将新表返回。结果如下所示:
table-->metatable->Sprite->__index->table
|->__self->userdata->metatable->Lusion.Sprite
读取的时候,就先通过table获得__self的值,然后通过self的值读取userdata,获得指针
这种方法为什么可以解决上面的问题呢。因为在new实例的时候,table是新建的,metatable里面保存了自己的函数。我还不是很理解为什么Lusion.Sprite需要链接自己,说不定是为什么destroy的时候用的。