通过lua代码往上层追溯,从整体上理解cocos2d+lua的接口设计思路和整套代码的运行流程。
首先来看一句简单的lua代码
local spr = cc.Sprite:create()
cc是一张表(table),表(table)是lua里面唯一的数据结构,cc存于_G中,_G是lua里面的全局表,cc表中存了所有cocos绑定的常量和“类”。
Sprite也是个表(table),Sprite表是怎样跟c++代码关联起来的呢,这就要分析c++的绑定代码了。首先来看看Sprite表里面的内容
["Sprite"] = {--table: 0x0032cce0
[".isclass"] = "true",
[".metatable"] = table: 0x0032c690
},
通过打印发现,Sprite表里面就一条数据key=[".isclass"] value=true,这个表明这个此table是cocos绑定的一个类型。看来秘密都在元表(metatable)里面。元表(metatable)在注册表(LUA_REGISTRYINDEX)里面,关于lua的全局表(_G)、注册表(Register)、元表(metatable),这里不再介绍了,不理解的需要先去学习一下。下面来打印一下Sprite的元表(metatable)里面的内容
["cc.Sprite"] = {--table: 0x0032c690
["getTextureRect"] = "function: 0x00b9b430",
["getBatchNode"] = "function: 0x00b9afe8",
["setTextureAtlas"] = "function: 0x00b9b770",
["new"] = "function: 0x00b9b168",
["setDisplayFrameWithAnimationName"] = "function: 0x00b9b750",
["create"] = "function: 0x003284a8",
["__lt"] = "function: 0x0032cbf0",
["getTexture"] = "function: 0x00b9b2b8",
["__sub"] = "function: 0x0032cc58",
["initWithFile"] = "function: 0x00b9b450",
["isTextureRectRotated"] = "function: 0x00b9b3e8",
["removeAllChildrenWithCleanup"] = "function: 0x00b9b050",
["setTextureRect"] = "function: 0x00b9b098",
["setDirty"] = "function: 0x00b9b398",
["getSpriteFrame"] = "function: 0x00b9b790",
["__newindex"] = "function: 0x0032c710",
["__eq"] = "function: 0x0032cc30",
["setPolygonInfo"] = "function: 0x00328488",
["initWithPolygon"] = "function: 0x00328440",
["__le"] = "function: 0x0032cc10",
["setFlippedX"] = "function: 0x00b9af60",
["createWithSpriteFrame"] = "function: 0x00b9be50",
["createWithSpriteFrameName"] = "function: 0x00b9be00",
["tolua_ubox"] = "table: 0x002c86f0" , -- loop table,
["createWithTexture"] = "function: 0x00b9b5c0",
["setTexture"] = "function: 0x00b9b298",
["setVertexRect"] = "function: 0x00b9b5a0",
["isFlippedY"] = "function: 0x00b9b558",
["isFlippedX"] = "function: 0x00b9b518",
["getResourceType"] = "function: 0x00b9afa8",
["setFlippedY"] = "function: 0x00b9b2f8",
["setBlendFunc"] = "function: 0x003283f8",
["__div"] = "function: 0x0032c6b8",
["getTextureAtlas"] = "function: 0x00b9b490",
["__index"] = "function: 0x0032c6f0",
["setAtlasIndex"] = "function: 0x00b9b358",
["getAtlasIndex"] = "function: 0x00b9b6b8",
["isDirty"] = "function: 0x00b9b338",
["getResourceName"] = "function: 0x00b9b7d8",
["getOffsetPosition"] = "function: 0x00b9b030",
["setBatchNode"] = "function: 0x00b9b6d8",
["__gc"] = "function: 0x002c1a90",
["__call"] = "function: 0x0032cc98",
["__mul"] = "function: 0x0032cc78",
["initWithSpriteFrame"] = "function: 0x00b9b4d8",
["__add"] = "function: 0x0032c730",
["initWithTexture"] = "function: 0x00b9afc8",
["setSpriteFrame"] = "function: 0x00b9b1b0",
["initWithSpriteFrameName"] = "function: 0x00b9b650",
["isFrameDisplayed"] = "function: 0x00b9b698",
["getBlendFunc"] = "function: 0x00b9b6f8",
[".metatable"] = table: 0x002ca9c8
}
这里的方法都很熟悉了,都是经常使用到的接口,在这里找到了create方法,cc.Sprite通过这个元表的__index方法调用到了create方法,先来看__index方法,它是一个c函数。
注:Lua想要通过元表(metatable)实现扩展功能必须要定义元表的__index字段。
static int class_table_get_index (lua_State* L)
{
//嵌套查找元表
while (lua_getmetatable(L,-1)) { /* stack: obj key obj mt */
lua_remove(L,-2); /* stack: ... mt */
//在元表中直接进行查找
lua_pushvalue(L,2); /* stack: ... mt key */
lua_rawget(L,-2); /* stack: ... mt value */
if (!lua_isnil(L,-1)) {
return 1;
} else {
lua_pop(L,1);
}
/* try C/C++ variable */
//在.get表中进行查找,绑定变量使用此功能
lua_pushstring(L,".get");
lua_rawget(L,-2); /* stack: obj key ... mt tget */
if (lua_istable(L,-1)) {
lua_pushvalue(L,2); /* stack: obj key ... mt tget key */
lua_rawget(L,-2); /* stack: obj key ... mt tget value */
if (lua_iscfunction(L,-1)) {
lua_call(L,0,1);
return 1;
} else if (lua_istable(L,-1)) {
return 1;
}
lua_pop(L, 2);
}
}
lua_pushnil(L);
return 1;
}
class_table_get_index 支持函数的调用和变量的获取,用while表明如果这个元表(metatable)里面没有找到就到元表(metatable)的元表(metatable)里面去找,嵌套查找。while里面的前半部分是到自身里面去找,如果找到了就return,后半部分是到.get表里面去找绑定的变量,也就是tolua_variable绑定的变量。
回到cc.Sprite:create,在元表(metatable)的前半部分找到到了create方法,下面来看一看create方法里面的内容:
int lua_cocos2dx_Sprite_create(lua_State* tolua_S)
{
int argc = 0;
bool ok = true;
argc = lua_gettop(tolua_S)-1;
do{
if (argc == 1){
std::string arg0;
ok &= luaval_to_std_string(tolua_S, 2,&arg0, "cc.Sprite:create");
if (!ok) { break; }
cocos2d::Sprite* ret = cocos2d::Sprite::create(arg0);
object_to_luaval(tolua_S, "cc.Sprite",(cocos2d::Sprite*)ret);
return 1;
}
} while (0);
luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d", "cc.Sprite:create",argc, 2);
return 0;
}
create方法当然也是一个c函数。lua_gettop()用来获取create函数参数数量,第一个参数是Sprite(table)自己,减1后是真正参数数量,所以create方法必须要用:调用,元表中其它的方法也一样。函数先用luaval_to_std_string判断传入参数是否是std::string类型,其核心就是调用tolua_isstring函数来进行判定。然后就是调用cocos2d::Sprite::create()创建c++对象,然后调用object_to_luaval<>()函数把c++指针压入lua的栈中,理解这个函数是理解cocos+lua的运作流程的关键。
template
void object_to_luaval(lua_State* L, const char* type, T* ret)
{
if (nullptr != ret)
{
//是否是cocos2d::Ref的派生对象
if (std::is_base_of::value)
{
// use c style cast, T may not polymorphic
cocos2d::Ref* dynObject = (cocos2d::Ref*)(ret);
int ID = (int)(dynObject->_ID);
int* luaID = &(dynObject->_luaID);
toluafix_pushusertype_ccobject(L, ID, luaID, (void*)ret, type);
}
else
{
tolua_pushusertype(L, (void*)ret, getLuaTypeName(ret, type));
}
}
else
{
lua_pushnil(L);
}
}
object_to_luaval函数首先判断如果是cocos2d::Ref的基类,调用toluafix_pushusertype_ccobject方法
TOLUA_API int toluafix_pushusertype_ccobject(lua_State* L,int refid,int* p_refid,void* ptr,const char* type)
{
if (ptr == NULL || p_refid == NULL)
{
lua_pushnil(L);
return -1;
}
Ref* vPtr = static_cast(ptr);
const char* vType = getLuaTypeName(vPtr, type);
//是否已经注册过
if (*p_refid == 0)
{
*p_refid = refid;
//从注册表中拿出来"toluafix_refid_ptr_mapping"表
//插入记录,以id为key,c++地址为value
lua_pushstring(L, TOLUA_REFID_PTR_MAPPING);
lua_rawget(L, LUA_REGISTRYINDEX); /* stack: refid_ptr */
lua_pushinteger(L, refid); /* stack: refid_ptr refid */
lua_pushlightuserdata(L, vPtr); /* stack: refid_ptr refid ptr */
lua_rawset(L, -3); /* refid_ptr[refid] = ptr, stack: refid_ptr */
lua_pop(L, 1); /* stack: - */
//从注册表中拿"toluafix_refid_type_mapping"表
//插入记录,以id为key,type为value
lua_pushstring(L, TOLUA_REFID_TYPE_MAPPING);
lua_rawget(L, LUA_REGISTRYINDEX); /* stack: refid_type */
lua_pushinteger(L, refid); /* stack: refid_type refid */
lua_pushstring(L, vType); /* stack: refid_type refid type */
lua_rawset(L, -3); /* refid_type[refid] = type, stack: refid_type */
lua_pop(L, 1); /* stack: - */
//printf("[LUA] push CCObject OK - refid: %d, ptr: %x, type: %s\n", *p_refid, (int)ptr, type);
}
tolua_pushusertype_and_addtoroot(L, vPtr, vType);
return 0;
}
接下来就是tolua_pushusertype_and_addtoroot、tolua_pushusertype_internal函数
void tolua_pushusertype_internal (lua_State* L, void* value, const char* type, int addToRoot)
{
if (value == NULL)
lua_pushnil(L);
else
{
luaL_getmetatable(L, type); /* stack: mt */
if (lua_isnil(L, -1)) { /* NOT FOUND metatable */
lua_pop(L, 1);
return;
}
//每个绑定的元表中都会共同拥有一个"tolua_ubox"字段,他们指向同一个表
lua_pushstring(L,"tolua_ubox");
lua_rawget(L,-2); /* stack: mt ubox */
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
lua_pushstring(L, "tolua_ubox");
lua_rawget(L, LUA_REGISTRYINDEX);
};
//以c++地址为key在"tolua_ubox"表中查找是否已经注册过
lua_pushlightuserdata(L,value); /* stack: mt ubox key */
lua_rawget(L,-2); /* stack: mt ubox ubox[value] */
if (lua_isnil(L,-1))
{
//如果没有添加过,以c++地址为key,新创建一个userdata为值,插入tolua_ubox并设置元表
lua_pop(L,1); /* stack: mt ubox */
lua_pushlightuserdata(L,value);
*(void**)lua_newuserdata(L,sizeof(void *)) = value; /* stack: mt ubox value newud */
lua_pushvalue(L,-1); /* stack: mt ubox value newud newud */
lua_insert(L,-4); /* stack: mt newud ubox value newud */
lua_rawset(L,-3); /* ubox[value] = newud, stack: mt newud ubox */
lua_pop(L,1); /* stack: mt newud */
/*luaL_getmetatable(L,type);*/
lua_pushvalue(L, -2); /* stack: mt newud mt */
lua_setmetatable(L,-2); /* update mt, stack: mt newud */
#ifdef LUA_VERSION_NUM
lua_pushvalue(L, TOLUA_NOPEER); /* stack: mt newud peer */
lua_setfenv(L, -2); /* stack: mt newud */
#endif
}
else
{
/* check the need of updating the metatable to a more specialized class */
lua_insert(L,-2); /* stack: mt ubox[u] ubox */
lua_pop(L,1); /* stack: mt ubox[u] */
lua_pushstring(L,"tolua_super");
lua_rawget(L,LUA_REGISTRYINDEX); /* stack: mt ubox[u] super */
lua_getmetatable(L,-2); /* stack: mt ubox[u] super mt */
lua_rawget(L,-2); /* stack: mt ubox[u] super super[mt] */
if (lua_istable(L,-1))
{
lua_pushstring(L,type); /* stack: mt ubox[u] super super[mt] type */
lua_rawget(L,-2); /* stack: mt ubox[u] super super[mt] flag */
if (lua_toboolean(L,-1) == 1) /* if true */
{
lua_pop(L,3); /* mt ubox[u]*/
lua_remove(L, -2);
return;
}
}
/* type represents a more specilized type */
/*luaL_getmetatable(L,type); // stack: mt ubox[u] super super[mt] flag mt */
lua_pushvalue(L, -5); /* stack: mt ubox[u] super super[mt] flag mt */
lua_setmetatable(L,-5); /* stack: mt ubox[u] super super[mt] flag */
lua_pop(L,3); /* stack: mt ubox[u] */
}
lua_remove(L, -2); /* stack: ubox[u]*/
if (0 != addToRoot)
{
lua_pushvalue(L, -1);
tolua_add_value_to_root(L, value);
}
}
}
这个函数的作用就是把新产生的c++对象(userdata)压入lua的栈中,同时维护相关的状态。这个函数做了这几件事情,luaL_getmetatable取出"cc.Sprite"对应的元表,在元表中查找"tolua_ubox"表,将c++对象压栈,在tolua_ubox表中查找这个对象是否已经存在,如果不存在以c++地址为key,lua的类型(userdata)为值,存入tolua_ubox并将"cc.Sprite"元表设置为c++对象(userdata)的元表,如果已经存在则不进行处理。
总结:
cc.sprite:create()返值spr是一个userdata数据类型,他的元表是"cc.Sprite"的元表,所以spr可以调用cc.Sprite元表中的方法从而实现类似于c++指针的效果。
参考资料:
http://www.lua.org/manual/5.2/manual.html