全面的理解cocos+Lua的绑定关系需要更加深入理解cocos和lua的交互中所操作的表(table),这些表大多存在于注册表中。
首先来看cocos中的对象对应lua中的userdata,绑定的元表
以cc.Sprite为例
["cc.Sprite"] = {--table: 0x0032c690
["new"] = "function: 0x00b9b168",
["__lt"] = "function: 0x0032cbf0",
["__sub"] = "function: 0x0032cc58",
["__newindex"] = "function: 0x0032c710",
["__eq"] = "function: 0x0032cc30",
["__le"] = "function: 0x0032cc10",
["createWithSpriteFrame"] = "function: 0x00b9be50",
["createWithSpriteFrameName"] = "function: 0x00b9be00",
["tolua_ubox"] = "table: 0x002c86f0" , -- loop table,
["createWithTexture"] = "function: 0x00b9b5c0"
["__div"] = "function: 0x0032c6b8",
["__index"] = "function: 0x0032c6f0",
["__gc"] = "function: 0x002c1a90",
["__call"] = "function: 0x0032cc98",
["__mul"] = "function: 0x0032cc78",
["__add"] = "function: 0x0032c730",
[".metatable"] = table: 0x002ca9c8,
......
},
["cc.Node"] = {--table: 0x002ca9c8
["visit"] = "function: 0x002cc550",
["setVisible"] = "function: 0x002cddb8",
["__index"] = "function: 0x002c7ec8",
["getPosition"] = "function: 0x010b3bf8",
["setPosition"] = "function: 0x002cccb8",
["setRotation"] = "function: 0x002cc030",
["create"] = "function: 0x002cd220",
["init"] = "function: 0x002cbfa8",
["__newindex"] = "function: 0x002c7ee8",
["__call"] = "function: 0x002cb588",
["__eq"] = "function: 0x002cb650",
["__add"] = "function: 0x002c7f08",
["__sub"] = "function: 0x002ca9f0",
["__gc"] = "function: 0x002c1a90",
["__lt"] = "function: 0x002c7f48",
["__mul"] = "function: 0x002caa10",
["__le"] = "function: 0x002c7f68",
["tolua_ubox"] = "table: 0x002c86f0" , -- loop table,
["__div"] = "function: 0x002c7f28",
["new"] = "function: 0x002c9e50",
["addChild"] = "function: 0x002c9e90",
[".metatable"] = table: 0x002c7070,
......
},
["cc.Ref"] = {--table: 0x002c7070
["__le"] = "function: 0x002c8458",
["getReferenceCount"] = "function: 0x002c8938",
["retain"] = "function: 0x002c88f0",
["tolua_ubox"] = "table: 0x002c86f0" , -- loop table,
["release"] = "function: 0x002c88b0",
["__gc"] = "function: 0x002c1a90",
["__lt"] = "function: 0x002c8438",
["__newindex"] = "function: 0x002c8220",
["__call"] = "function: 0x002c84a0",
["__add"] = "function: 0x002c8240",
["__eq"] = "function: 0x002c8478",
["__sub"] = "function: 0x002c8260",
["__div"] = "function: 0x002c81e8",
["__mul"] = "function: 0x002c8280",
["__index"] = "function: 0x002c7098",
[".metatable"] = table: 0x002c1ae8
},
["tolua_commonclass"] = {--table: 0x002c1ae8
["__le"] = "function: 0x002c1d58",
["__gc"] = "function: 0x002c1a90",
["__lt"] = "function: 0x002c1d38",
["__newindex"] = "function: 0x002c1b68",
["__call"] = "function: 0x002c1d98",
["__add"] = "function: 0x002c1b88",
["__eq"] = "function: 0x002c1d78",
["__sub"] = "function: 0x002c1c10",
["__div"] = "function: 0x002c1d18",
["__mul"] = "function: 0x002c1c30",
["__index"] = "function: 0x002c1b48",
},
可以看出来cocos在lua中用元表(metatable)实现了继承体系,cc.Sprite表元表是cc.Node,cc.Node的元表是cc.Ref,cc.Ref的元表是tolua_commonclass,cocos会为每个绑定的元表加入了"__index"、"__newindex"、"__add"、"__sub"、"__mul"、"__div"、"__lt"、"__le"、"__eq"、"__call"、"__gc"方法。
"__index"方法提供了查找功能,是使用最多的方法,它为userdata提供了查找所有绑定方法的功能,可以让我们像使用c++指针一样使用lua的userdata。
static int class_index_event (lua_State* L)
{
int t = lua_type(L,1);
//userdata类型
if (t == LUA_TUSERDATA)
{
//如果存在独立的环境表,先在环境表中进行查找
lua_getfenv(L,1);
if (!lua_rawequal(L, -1, TOLUA_NOPEER)) {
lua_pushvalue(L, 2); /* key */
lua_gettable(L, -2); /* on lua 5.1, we trade the "tolua_peers" lookup for a gettable call */
if (!lua_isnil(L, -1))
return 1;
}
lua_settop(L,2); /* stack: obj key */
/* Try metatables */
lua_pushvalue(L,1); /* stack: obj key obj */
//查找userdata的元表放于栈顶
while (lua_getmetatable(L,-1))
{ /* stack: obj key obj mt */
lua_remove(L,-2); /* stack: obj key mt */
//如果想要获取的key是个number类型,例如self[1]
if (lua_isnumber(L,2)) /* check if key is a numeric value */
{
//如果存在.geti方法,直接调用这个方法
lua_pushstring(L,".geti");
lua_rawget(L,-2); /* stack: obj key mt func */
if (lua_isfunction(L,-1))
{
lua_pushvalue(L,1);
lua_pushvalue(L,2);
lua_call(L,2,1);
return 1;
}
}
else
{
//在元表中直接获取,找到了直接返回
lua_pushvalue(L,2); /* stack: obj key mt key */
lua_rawget(L,-2); /* stack: obj key mt value */
if (!lua_isnil(L,-1))
return 1;
else
lua_pop(L,1);
/*在元表的.get表中尝试查找*/
lua_pushstring(L,".get");
lua_rawget(L,-2); /* stack: obj key mt tget */
if (lua_istable(L,-1))
{
lua_pushvalue(L,2);
lua_rawget(L,-2); /* stack: obj key mt value */
if (lua_iscfunction(L,-1))
{
lua_pushvalue(L,1);
lua_pushvalue(L,2);
lua_call(L,2,1);
return 1;
}
else if (lua_istable(L,-1))
{
/* deal with array: create table to be returned and cache it in ubox */
void* u = *((void**)lua_touserdata(L,1));
lua_newtable(L); /* stack: obj key mt value table */
lua_pushstring(L,".self");
lua_pushlightuserdata(L,u);
lua_rawset(L,-3); /* store usertype in ".self" */
lua_insert(L,-2); /* stack: obj key mt table value */
lua_setmetatable(L,-2); /* set stored value as metatable */
lua_pushvalue(L,-1); /* stack: obj key met table table */
lua_pushvalue(L,2); /* stack: obj key mt table table key */
lua_insert(L,-2); /* stack: obj key mt table key table */
storeatubox(L,1); /* stack: obj key mt table */
return 1;
}
}
}
lua_settop(L,3);
}
lua_pushnil(L);
return 1;
}
else if (t== LUA_TTABLE)
{
lua_pushvalue(L,1);
class_table_get_index(L);
return 1;
}
lua_pushnil(L);
return 1;
}
__index方法对应c函数class_index_event,这个函数把分成userdata和table两种情况来进行处理,根据调用者的类型来进行判断,所以必须要用:来进行调用。调用者为表(table)的情况上一篇博客里面有介绍,这里主要看一下调用都为userdata的情况。流程是:把userdata压入栈顶,获取userdata的元表(metatable),如果key是number而且存在.geti函数则调用这个函数,其它类型在元表中查找,如果没有找到在.get表中尝试查找。
____newindex对应c函数class_newindex_event
static int class_newindex_event (lua_State* L)
{
int t = lua_type(L,1);
if (t == LUA_TUSERDATA)
{
/* Try accessing a C/C++ variable to be set */
lua_getmetatable(L,1);
while (lua_istable(L,-1)) /* stack: t k v mt */
{
//如果是self[i]方式调用,且有.seti方法,直接调用
if (lua_isnumber(L,2)) /* check if key is a numeric value */
{
/* try operator[] */
lua_pushstring(L,".seti");
lua_rawget(L,-2); /* stack: obj key mt func */
if (lua_isfunction(L,-1))
{
lua_pushvalue(L,1);
lua_pushvalue(L,2);
lua_pushvalue(L,3);
lua_call(L,3,0);
return 0;
}
}
else
{
//如果在.set表中有这个函数,直接调用
lua_pushstring(L,".set");
lua_rawget(L,-2); /* stack: t k v mt tset */
if (lua_istable(L,-1))
{
lua_pushvalue(L,2);
lua_rawget(L,-2); /* stack: t k v mt tset func */
if (lua_iscfunction(L,-1))
{
lua_pushvalue(L,1);
lua_pushvalue(L,3);
lua_call(L,2,0);
return 0;
}
lua_pop(L,1); /* stack: t k v mt tset */
}
lua_pop(L,1); /* stack: t k v mt */
if (!lua_getmetatable(L,-1)) /* stack: t k v mt mt */
lua_pushnil(L);
lua_remove(L,-2); /* stack: t k v mt */
}
}
lua_settop(L,3); /* stack: t k v */
/*把新的字段存于新的环境表中*/
storeatubox(L,1);
}
else if (t== LUA_TTABLE)
{
lua_getmetatable(L,1); /* stack: t k v mt */
lua_pushstring(L,".set");
lua_rawget(L,-2); /* stack: t k v mt tset */
if (lua_istable(L,-1)) {
lua_pushvalue(L,2); /* stack: t k v mt tset k */
lua_rawget(L,-2);
if (lua_iscfunction(L,-1)) { /* ... func */
lua_pushvalue(L,1); /* ... func t */
lua_pushvalue(L,3); /* ... func t v */
lua_call(L,2,0);
return 0;
}
}
lua_settop(L,3);
class_backup_before_newindex(L);
lua_settop(L,3);
lua_getmetatable(L,1); /* stack: t k v mt */
lua_replace(L, 1); /* stack: mt k v */
lua_rawset(L,1);
}
return 0;
}
当调用者是userdata时,把其元表(metatable)压栈,如果key是number类型而且存在.seti函数则调用这个函数,其它类型的话如果存在.set表且.set表的key字段是个函数则调用这个函数,其它情况下会调用storeatubox函数,这个函数的作用就是把key和value存于一张新表中,供查找的时候使用。
"__add","__sub","__mul","__div","__lt","__le","__eq",可以实现userdata的加减乘除、比较操作,对应在表中查找".add",".sub",".mul",".div",".lt",".le",".eq"方法,这些方法并没有提供默认的实现,实现这些方法即可实现对应功能。
"__call"字段查找".call"方法并调用,调用者必须是table不能是userdata。
"__gc"绑定c函数class_gc_event,通过这个方法实现对c++对象内存的释放。那些需要我们手动进行对象生命周期管理的对象在创建的时候需要调用tolua_register_gc在"tolua_gc"表中注册对象地址,标明这个对象是需要手动释放的,在lua层进行垃圾回收的时候lua会自动调用class_gc_event,class_gc_event会检查并释放这个对象的内存空间。需要注意的是默认的释放调用的是free函数,new创建的对象需要自定义".collector"函数来进行释放。
总结:
cocos使用lua的元表(metatable)实现其继承体系。