cocos2d+lua绑定关系理解——一

通过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

 

 

 

 

你可能感兴趣的:(cocos+lua)