MEGALOVANIA MEGALOVANIA

游戏开发中

目录
Cocos2dx中userdata关联Peer表的实现
/    

Cocos2dx中userdata关联Peer表的实现

起因

在 log 里面看到一条报错,大概情况如下,在 socket 回调里面报了一条 attempt to call a nil value(大意)。然后类结构大概是这样的:

 1local MessageLayer = class("MessageLayer", CC.Layer)
 2
 3function MessageLayer:showDisappearEffect()
 4-- do something
 5end
 6
 7local a = class("layer", MessageLayer)
 8
 9local scene = cc.Director:getInstance():getRunningScene()
10
11local instance = a:create()
12instance:addTo(scene)

然后这个 instance 被移除的情况下,它上面的 luafunction 调不到了。但是这个 instance 本身还是存在的。所以我们关注的问题是:在 lua 里,对于一个 userData 的实例,当 C++ 对象析构了之后,为什么它上面的 lua 方法会失效

尝试解决

打印了一下 userdata 的原表,大概这样写:

1function ThemeScene:onEnter( )
2  
3    for k,v in pairs(getmetatable(self)) do
4        print(k,v)
5    end
6end

打印出的结果和我想的不太一样。

 1[LUA-print] getDefaultCamera	function: 0x133752a0
 2[LUA-print] __index	function: 0x13371448
 3[LUA-print] __newindex	function: 0x13371480
 4[LUA-print] __gc	function: 0x1336a220
 5[LUA-print] __eq	function: 0x1336bad8
 6[LUA-print] __lt	function: 0x1336bbb0
 7[LUA-print] __le	function: 0x1336bbe8
 8[LUA-print] getNavMesh	function: 0x13498df0
 9[LUA-print] __call	function: 0x13374888
10[LUA-print] __add	function: 0x1336baa0
11[LUA-print] __sub	function: 0x13371410
12[LUA-print] __mul	function: 0x1336bb40
13[LUA-print] __div	function: 0x1336bb78
14[LUA-print] setNavMesh	function: 0x13498d88
15[LUA-print] tolua_ubox	table: 0x1336c028
16[LUA-print] setNavMeshDebugCamera	function: 0x13498d20
17[LUA-print] setPhysics3DDebugCamera	function: 0x13498cb0
18[LUA-print] getPhysics3DWorld	function: 0x13498c40
19[LUA-print] createWithPhysics	function: 0x133753b0
20[LUA-print] create	function: 0x13375340
21[LUA-print] .classname	cc.Scene
22[LUA-print] createWithSize	function: 0x13375308
23[LUA-print] onProjectionChanged	function: 0x13374a00
24[LUA-print] initWithPhysics	function: 0x1336e0c0
25[LUA-print] setCameraOrderDirty	function: 0x13374e90
26[LUA-print] render	function: 0x13374ef0
27[LUA-print] stepPhysicsAndNavigation	function: 0x13374990
28[LUA-print] new	function: 0x1336df70
29[LUA-print] getPhysicsWorld	function: 0x13374a68
30[LUA-print] initWithSize	function: 0x13375230

明显可以看出来,基本上所有的东西都是 c++ 里的方法,也就是 userdata 里的方法,那么我们自己定义的一些 lua 方法去哪里了呢?

前置知识

其实方向一开始就错了。本身 userdata 上面其实不应该直接绑定任何 lua 的东西的,正确的做法应该是套一层,然后在套娃的那个 luatable 里来做事,这个套娃一般称之为 peer

c++_obj 的 metatable 的__index 指向一个 c 函数,当访问 c++_obj 中的一个域的时候,会调用这个 c 函数,这个 c 函数会去查找各个关联表,来取得我们要访问的域,这其中就包括对 peer 表的查询。

具体解决

另外,在 functions.lua 里其实也可以找到证据
先看下 class 方法的完整代码

 1function class(classname, ...)
 2    local cls = {__cname = classname}
 3
 4    local supers = {...}
 5    for _, super in ipairs(supers) do
 6        local superType = type(super)
 7        assert(superType == "nil" or superType == "table" or superType == "function",
 8            string.format("class() - create class \"%s\" with invalid super class type \"%s\"",
 9                classname, superType))
10
11        if superType == "function" then
12            assert(cls.__create == nil,
13                string.format("class() - create class \"%s\" with more than one creating function",
14                    classname));
15            -- if super is function, set it to __create
16            cls.__create = super
17        elseif superType == "table" then
18            if super[".isclass"] then
19                -- super is native class
20                assert(cls.__create == nil,
21                    string.format("class() - create class \"%s\" with more than one creating function or native class",
22                        classname));
23                cls.__create = function() return super:create() end
24            else
25                -- super is pure lua class
26                cls.__supers = cls.__supers or {}
27                cls.__supers[#cls.__supers + 1] = super
28                if not cls.super then
29                    -- set first super pure lua class as class.super
30                    cls.super = super
31                end
32            end
33        else
34            error(string.format("class() - create class \"%s\" with invalid super type",
35                        classname), 0)
36        end
37    end
38
39    cls.__index = cls
40    if not cls.__supers or #cls.__supers == 1 then
41        setmetatable(cls, {__index = cls.super})
42    else
43        setmetatable(cls, {__index = function(_, key)
44            local supers = cls.__supers
45            for i = 1, #supers do
46                local super = supers[i]
47                if super[key] then return super[key] end
48            end
49        end})
50    end
51
52    if not cls.ctor then
53        -- add default constructor
54        cls.ctor = function() end
55    end
56    cls.new = function(...)
57        local instance
58        if cls.__create then
59            instance = cls.__create(...)
60        else
61            instance = {}
62        end
63        setmetatableindex(instance, cls)
64        instance.class = cls
65        instance:ctor(...)
66        return instance
67    end
68    cls.create = function(_, ...)
69        return cls.new(...)
70    end
71
72    return cls
73end

关键关注 setmetatableindex 这里。

他的定义是这样的:

 1local setmetatableindex_
 2setmetatableindex_ = function(t, index)
 3    if type(t) == "userdata" then
 4        local peer = tolua.getpeer(t)
 5        if not peer then
 6            peer = {}
 7            tolua.setpeer(t, peer)
 8        end
 9        setmetatableindex_(peer, index)
10    else
11        local mt = getmetatable(t)
12        if not mt then mt = {} end
13        if not mt.__index then
14            mt.__index = index
15            setmetatable(t, mt)
16        elseif mt.__index ~= index then
17            setmetatableindex_(mt, index)
18        end
19    end
20end
21setmetatableindex = setmetatableindex_

其实看到这里就很明显了,所有对 self 所做的操作其实都是放在这个 peer 表中的。

首先简单些一个 lua 类,继承自 cc.Layer,并且自己在 lua 里面定义一些方法

1local testLayer = class("layerClass",cc.Layer)
2
3function testLayer:testFunc()
4	print("testFunc")
5end

然后我们将他实例化:

1...
2local layer = testLayer:create()

这个过程发生的事情,可以对照 class 方法里的东西

 1if not cls.ctor then
 2        -- add default constructor
 3        cls.ctor = function() end
 4    end
 5    cls.new = function(...)
 6        local instance
 7        if cls.__create then
 8            instance = cls.__create(...)
 9        else
10            instance = {}
11        end
12        setmetatableindex(instance, cls)
13        instance.class = cls
14        instance:ctor(...)
15        return instance
16    end
17    cls.create = function(_, ...)
18        return cls.new(...)
19    end

重点在于这个 new 方法里,如果他有__create 方法,那么就用__create 来创建实例。在此例中,便是 cc.Layer 的 create 方法。它返回了一个 layer 的 userData 实例。

接下来的关键,是这个 setmetatableindex,这是一个针对 userdata 重新定义的一个方法。看下它的实现

 1setmetatableindex_ = function(t, index)
 2    if type(t) == "userdata" then
 3        local peer = tolua.getpeer(t)
 4        if not peer then
 5            peer = {}
 6            tolua.setpeer(t, peer)
 7        end
 8        setmetatableindex_(peer, index)
 9    else
10        local mt = getmetatable(t)
11        if not mt then mt = {} end
12        if not mt.__index then
13            mt.__index = index
14            setmetatable(t, mt)
15        elseif mt.__index ~= index then
16            setmetatableindex_(mt, index)
17        end
18    end
19end
20setmetatableindex = setmetatableindex_

也很好懂,首先如果是要对一个 userdata 设置 metatable,不可以直接设置,而是去创建一个这个 userdata 的 peer 表。这个 peer 表可以看作是一个纯粹的 luatable,之后所有的操作都是在这个 peer 表上进行的。对 userdata 做索引,不但可以索引到它自己导出的 C++ 方法,也可以索引到这个 peer 表。那么,对于我们上面例子所定义的 testLayer:testFunc 它其实就是关联到了 peer 表上。

那么可以有一个猜想,当 C++ 对象析构的时候,是不是这个 peer 表被清掉了导致找不到 lua 方法呢?
简单写个例子佐证一下。

 1local node = cc.Node:create()
 2    node:addTo(self)
 3
 4    node._val = 1000
 5
 6    print("<<<<<<<<<<<<<<")
 7    print("node._peer",tolua.getpeer(node))
 8    print("node._val:",node._val)
 9    print("<<<<<<<<<<<<<<")
10
11    self:delayCall(0.01,function()
12        node:removeFromParent()
13
14        print("===========")
15        print("node._peer",tolua.getpeer(node))
16        print("node._val:",node._val)
17        print("===========")
18    end)

之后的输出验证了这点:

1[LUA-print] <<<<<<<<<<<<<<
2[LUA-print] node._peer	table: 0x1fcb8e50
3[LUA-print] node._val:	1000
4[LUA-print] <<<<<<<<<<<<<<
1[LUA-print] ===========
2[LUA-print] node._peer	nil
3[LUA-print] node._val:	nil
4[LUA-print] ===========

C++ 内部实现

清理的过程

那么当一个 CCRef removeFromParent 之后,为什么 peer 表会被清除呢。

 1Ref::~Ref()
 2{
 3
 4    ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
 5    if (pEngine != nullptr && _luaID)
 6    {
 7        // if the object is referenced by Lua engine, remove it
 8        pEngine->removeScriptObjectByObject(this);
 9    }
10...

luaEngine 里的代码

1void LuaEngine::removeScriptObjectByObject(Ref* pObj)
2{
3    _stack->removeScriptObjectByObject(pObj);
4    ScriptHandlerMgr::getInstance()->removeObjectAllHandlers(pObj);
5}

关于 scripthandler 的东西我们不关心,我们关心的其实是这一句
_stack->removeScriptObjectByObject(pObj);

顺着继续看下去

1void LuaStack::removeScriptObjectByObject(Ref* pObj)
2{
3    toluafix_remove_ccobject_by_refid(_state, pObj->_luaID);
4}

再继续

  1TOLUA_API int toluafix_remove_ccobject_by_refid(lua_State* L, int refid)
  2{
  3	void* ptr = NULL;
  4    const char* type = NULL;
  5    void** ud = NULL;
  6    if (refid == 0) return -1;
  7
  8    // get ptr from tolua_refid_ptr_mapping
  9    lua_pushstring(L, TOLUA_REFID_PTR_MAPPING);
 10    lua_rawget(L, LUA_REGISTRYINDEX);                               /* stack: refid_ptr */
 11    lua_pushinteger(L, refid);                                      /* stack: refid_ptr refid */
 12    lua_rawget(L, -2);                                              /* stack: refid_ptr ptr */
 13    ptr = lua_touserdata(L, -1);
 14    lua_pop(L, 1);                                                  /* stack: refid_ptr */
 15    if (ptr == NULL)
 16    {
 17        lua_pop(L, 1);
 18        // Lua stack has closed, C++ object not in Lua.
 19        // printf("[LUA ERROR] remove CCObject with NULL ptr, refid: %d\n", refid);
 20        return -2;
 21    }
 22
 23    // remove ptr from tolua_refid_ptr_mapping
 24    lua_pushinteger(L, refid);                                      /* stack: refid_ptr refid */
 25    lua_pushnil(L);                                                 /* stack: refid_ptr refid nil */
 26    lua_rawset(L, -3);                     /* delete refid_ptr[refid], stack: refid_ptr */
 27    lua_pop(L, 1);                                                  /* stack: - */
 28
 29
 30    // get type from tolua_refid_type_mapping
 31    lua_pushstring(L, TOLUA_REFID_TYPE_MAPPING);
 32    lua_rawget(L, LUA_REGISTRYINDEX);                               /* stack: refid_type */
 33    lua_pushinteger(L, refid);                                      /* stack: refid_type refid */
 34    lua_rawget(L, -2);                                              /* stack: refid_type type */
 35    if (lua_isnil(L, -1))
 36    {
 37        lua_pop(L, 2);
 38        printf("[LUA ERROR] remove CCObject with NULL type, refid: %d, ptr: %p\n", refid, ptr);
 39        return -1;
 40    }
 41
 42    type = lua_tostring(L, -1);
 43    lua_pop(L, 1);                                                  /* stack: refid_type */
 44
 45    // remove type from tolua_refid_type_mapping
 46    lua_pushinteger(L, refid);                                      /* stack: refid_type refid */
 47    lua_pushnil(L);                                                 /* stack: refid_type refid nil */
 48    lua_rawset(L, -3);                    /* delete refid_type[refid], stack: refid_type */
 49    lua_pop(L, 1);                                                  /* stack: - */
 50
 51    // get ubox
 52    luaL_getmetatable(L, type);                                     /* stack: mt */
 53    lua_pushstring(L, "tolua_ubox");                                /* stack: mt key */
 54    lua_rawget(L, -2);                                              /* stack: mt ubox */
 55    if (lua_isnil(L, -1))
 56    {
 57        // use global ubox
 58        lua_pop(L, 1);                                              /* stack: mt */
 59        lua_pushstring(L, "tolua_ubox");                            /* stack: mt key */
 60        lua_rawget(L, LUA_REGISTRYINDEX);                           /* stack: mt ubox */
 61    };
 62  
 63  
 64    // cleanup root
 65    tolua_remove_value_from_root(L, ptr);
 66
 67    lua_pushlightuserdata(L, ptr);                                  /* stack: mt ubox ptr */
 68    lua_rawget(L,-2);                                               /* stack: mt ubox ud */
 69    if (lua_isnil(L, -1))
 70    {
 71        // Lua object has released (GC), C++ object not in ubox.
 72        //printf("[LUA ERROR] remove CCObject with NULL ubox, refid: %d, ptr: %x, type: %s\n", refid, (int)ptr, type);
 73        lua_pop(L, 3);
 74        return -3;
 75    }
 76
 77    // cleanup peertable
 78    lua_pushvalue(L, LUA_REGISTRYINDEX);
 79    lua_setfenv(L, -2);
 80
 81    ud = (void**)lua_touserdata(L, -1);
 82    lua_pop(L, 1);                                                  /* stack: mt ubox */
 83    if (ud == NULL)
 84    {
 85        printf("[LUA ERROR] remove CCObject with NULL userdata, refid: %d, ptr: %p, type: %s\n", refid, ptr, type);
 86        lua_pop(L, 2);
 87        return -1;
 88    }
 89
 90    // clean userdata
 91    *ud = NULL;
 92
 93    lua_pushlightuserdata(L, ptr);                                  /* stack: mt ubox ptr */
 94    lua_pushnil(L);                                                 /* stack: mt ubox ptr nil */
 95    lua_rawset(L, -3);                             /* ubox[ptr] = nil, stack: mt ubox */
 96
 97    lua_pop(L, 2);
 98    //printf("[LUA] remove CCObject, refid: %d, ptr: %x, type: %s\n", refid, (int)ptr, type);
 99    return 0;
100}

这里清除了好几个东西,我们一个个来看。对 lua 栈操作不熟悉的可以参考这个参考文档

如果需要看 5.1 的参考,参考这个文档

首先明确的是,这个 refid 是 CCRef 里的 luaid。
先看下第一部分

 1// get ptr from tolua_refid_ptr_mapping
 2    lua_pushstring(L, TOLUA_REFID_PTR_MAPPING);
 3    lua_rawget(L, LUA_REGISTRYINDEX);                               /* stack: refid_ptr */
 4    lua_pushinteger(L, refid);                                      /* stack: refid_ptr refid */
 5    lua_rawget(L, -2);                                              /* stack: refid_ptr ptr */
 6    ptr = lua_touserdata(L, -1);
 7    lua_pop(L, 1);                                                  /* stack: refid_ptr */
 8    if (ptr == NULL)
 9    {
10        lua_pop(L, 1);
11        // Lua stack has closed, C++ object not in Lua.
12        // printf("[LUA ERROR] remove CCObject with NULL ptr, refid: %d\n", refid);
13        return -2;
14    }

tolua 在 lua5.1 的 binding 里面有一个重要的概念就是这个 LUA_REGISTRYINDEX 为 index 的全局注册表。我们姑且把这个表称之为为 Reg,暂时把他理解为一个 luatable。

通过阅读可以知道,他其实是拿出 Reg[TOLUA_REFID_PTR_MAPPING][refid]存到 ptr 这个变量里。

接下来,它清除了 Reg[TOLUA_REFID_PTR_MAPPING][refid],也就是相当于调用了 Reg[TOLUA_REFID_PTR_MAPPING][refid] = nil

1// remove ptr from tolua_refid_ptr_mapping
2    lua_pushinteger(L, refid);                                      /* stack: refid_ptr refid */
3    lua_pushnil(L);                                                 /* stack: refid_ptr refid nil */
4    lua_rawset(L, -3);                     /* delete refid_ptr[refid], stack: refid_ptr */
5    lua_pop(L, 1);                                                  /* stack: - */

接下来的代码同理,只是换了一个表清.这次取得是 Reg[``TOLUA_REFID_TYPE_MAPPING ][ refid`],里面存着一个 string.

 1// get type from tolua_refid_type_mapping
 2    lua_pushstring(L, TOLUA_REFID_TYPE_MAPPING);
 3    lua_rawget(L, LUA_REGISTRYINDEX);                               /* stack: refid_type */
 4    lua_pushinteger(L, refid);                                      /* stack: refid_type refid */
 5    lua_rawget(L, -2);                                              /* stack: refid_type type */
 6    if (lua_isnil(L, -1))
 7    {
 8        lua_pop(L, 2);
 9        printf("[LUA ERROR] remove CCObject with NULL type, refid: %d, ptr: %p\n", refid, ptr);
10        return -1;
11    }
12
13    type = lua_tostring(L, -1);
14    lua_pop(L, 1);                                                  /* stack: refid_type */
15
16    // remove type from tolua_refid_type_mapping
17    lua_pushinteger(L, refid);                                      /* stack: refid_type refid */
18    lua_pushnil(L);                                                 /* stack: refid_type refid nil */
19    lua_rawset(L, -3);                    /* delete refid_type[refid], stack: refid_type */
20    lua_pop(L, 1);                                                  /* stack: - */

接下来是拿出 ubox 表。

这个 ubox 表有两种存的方法,有可能存在 Reg[type]["tolua_ubox"]里,也有可能直接存在 Reg[“tolua_ubox”]。如果前者不存在,那么就拿后者。

 1// get ubox
 2    luaL_getmetatable(L, type);                                     /* stack: mt */
 3    lua_pushstring(L, "tolua_ubox");                                /* stack: mt key */
 4    lua_rawget(L, -2);                                              /* stack: mt ubox */
 5    if (lua_isnil(L, -1))
 6    {
 7        // use global ubox
 8        lua_pop(L, 1);                                              /* stack: mt */
 9        lua_pushstring(L, "tolua_ubox");                            /* stack: mt key */
10        lua_rawget(L, LUA_REGISTRYINDEX);                           /* stack: mt ubox */
11    };

之后,拿着这个 ubox 表,清除 tolua_value_from_root 这个表,也就是 tolua_remove_value_from_root 方法里写的。具体如下

1tolua_remove_value_from_root(L, ptr);

具体定义
其实只干了一件事,那就是执行了 Reg[TOLUA_VALUE_ROOT][prt]=nil,也就是把 value_root 表里的 ptr 对应的项置空了。

 1TOLUA_API void tolua_remove_value_from_root (lua_State* L, void* ptr)
 2{
 3    lua_pushstring(L, TOLUA_VALUE_ROOT);
 4    lua_rawget(L, LUA_REGISTRYINDEX);                               /* stack: root */
 5    lua_pushlightuserdata(L, ptr);                                  /* stack: root ptr */
 6  
 7    lua_pushnil(L);                                                 /* stack: root ptr nil */
 8    lua_rawset(L, -3);                                              /* root[ptr] = nil, stack: root */
 9    lua_pop(L, 1);
10}

接下来,就要在 ubox 表里取出来我们 ptr 所对应的值

1lua_pushlightuserdata(L, ptr);                                  /* stack: mt ubox ptr */
2    lua_rawget(L,-2);                                               /* stack: mt ubox ubox[ptr] */
3    if (lua_isnil(L, -1))
4    {
5        // Lua object has released (GC), C++ object not in ubox.
6        //printf("[LUA ERROR] remove CCObject with NULL ubox, refid: %d, ptr: %x, type: %s\n", refid, (int)ptr, type);
7        lua_pop(L, 3);
8        return -3;
9    }

接下来就是我们的重点,peer 表。此时我们的栈顶是 reg ubox ubox[ptr]
这里先把 lua 的全局注册表 push 进站,然后把他作为 ubox[ptr]的新的 lua_env

1// cleanup peertable
2    lua_pushvalue(L, LUA_REGISTRYINDEX);
3    lua_setfenv(L, -2);

然后在注册表的 env 之下,把顶层指向 userdata 的指针,也就是 ubox[ptr]取出来,把它所指向的 userdata 置空。

 1ud = (void**)lua_touserdata(L, -1);
 2    lua_pop(L, 1);                                                  /* stack: mt ubox */
 3    if (ud == NULL)
 4    {
 5        printf("[LUA ERROR] remove CCObject with NULL userdata, refid: %d, ptr: %p, type: %s\n", refid, ptr, type);
 6        lua_pop(L, 2);
 7        return -1;
 8    }
 9
10    // clean userdata
11    *ud = NULL;

最后,再把 ubox 里以 ptr 为 key 的值置空。ubox[ptr]=nil

1lua_pushlightuserdata(L, ptr);                                  /* stack: mt ubox ptr */
2    lua_pushnil(L);                                                 /* stack: mt ubox ptr nil */
3    lua_rawset(L, -3);                             /* ubox[ptr] = nil, stack: mt ubox */

至此,整个清理过程完成。

创建绑定的过程

经过上面代码的阅读我们明白了 lua 部分是如何清理的,对需要清理哪些表有了一个大概的了解。但是我们还需要知道一件事情:那就是我们的 userdata 是怎么调到 peer 表的。

那么相应的,我们需要看一下在绑定过程中,userdata 到底需要绑定哪些东西。

从开始注册绑定开始

其实观察下 lua 的注册过程,会发现这个东西:

1TOLUA_API void tolua_usertype (lua_State* L, const char* type)
2{
3    char ctype[128] = "const ";
4    strncat(ctype,type,120);
5
6    /* create both metatables */
7    if (tolua_newmetatable(L,ctype) && tolua_newmetatable(L,type))
8        mapsuper(L,type,ctype);             /* 'type' is also a 'const type' */
9}

然后重点是 tolua_newmetatable

 1static int tolua_newmetatable (lua_State* L, const char* name)
 2{
 3    int r = luaL_newmetatable(L,name);
 4
 5#ifdef LUA_VERSION_NUM /* only lua 5.1 */
 6    if (r) {
 7        lua_pushvalue(L, -1);
 8        lua_pushstring(L, name);
 9        lua_settable(L, LUA_REGISTRYINDEX); /* reg[mt] = type_name */
10    };
11#endif
12
13    if (r)
14        tolua_classevents(L); /* set meta events */
15  
16    // metatable[".classname"] = name
17    lua_pushliteral(L, ".classname");   // stack: metatable ".classname"
18    lua_pushstring(L, name);            // stack: metatable ".classname" name
19    lua_rawset(L, -3);                  // stack: metatable
20  
21    lua_pop(L,1);
22    return r;
23}

其中的 tolua_classevents

 1TOLUA_API void tolua_classevents (lua_State* L)
 2{
 3    lua_pushstring(L,"__index");
 4    lua_pushcfunction(L,class_index_event);
 5    lua_rawset(L,-3);
 6    lua_pushstring(L,"__newindex");
 7    lua_pushcfunction(L,class_newindex_event);
 8    lua_rawset(L,-3);
 9
10    lua_pushstring(L,"__add");
11    lua_pushcfunction(L,class_add_event);
12    lua_rawset(L,-3);
13    lua_pushstring(L,"__sub");
14    lua_pushcfunction(L,class_sub_event);
15    lua_rawset(L,-3);
16    lua_pushstring(L,"__mul");
17    lua_pushcfunction(L,class_mul_event);
18    lua_rawset(L,-3);
19    lua_pushstring(L,"__div");
20    lua_pushcfunction(L,class_div_event);
21    lua_rawset(L,-3);
22
23    lua_pushstring(L,"__lt");
24    lua_pushcfunction(L,class_lt_event);
25    lua_rawset(L,-3);
26    lua_pushstring(L,"__le");
27    lua_pushcfunction(L,class_le_event);
28    lua_rawset(L,-3);
29    lua_pushstring(L,"__eq");
30    lua_pushcfunction(L,class_eq_event);
31    lua_rawset(L,-3);
32
33    lua_pushstring(L,"__call");
34    lua_pushcfunction(L,class_call_event);
35    lua_rawset(L,-3);
36
37    lua_pushstring(L,"__gc");
38    lua_pushstring(L, "tolua_gc_event");
39    lua_rawget(L, LUA_REGISTRYINDEX);
40    /*lua_pushcfunction(L,class_gc_event);*/
41    lua_rawset(L,-3);
42}

是不是这里的内容就很熟悉了,正如我们正常操作原表来做继承的过程。只不过这里很多东西都会存到注册表里,比纯粹 lua 里面的继承要复杂很多。
那当然我们要看下熟悉的__index 方法注册的静态方法。

  1static int class_index_event (lua_State* L)
  2{
  3    int t = lua_type(L,1);
  4    if (t == LUA_TUSERDATA)
  5    {
  6        /* Access alternative table */
  7#ifdef LUA_VERSION_NUM /* new macro on version 5.1 */
  8        lua_getfenv(L,1);
  9        if (!lua_rawequal(L, -1, TOLUA_NOPEER)) {
 10            lua_pushvalue(L, 2); /* key */
 11            lua_gettable(L, -2); /* on lua 5.1, we trade the "tolua_peers" lookup for a gettable call */
 12            if (!lua_isnil(L, -1))
 13                return 1;
 14        };
 15#else
 16        lua_pushstring(L,"tolua_peers");
 17        lua_rawget(L,LUA_REGISTRYINDEX);        /* stack: obj key ubox */
 18        lua_pushvalue(L,1);
 19        lua_rawget(L,-2);                       /* stack: obj key ubox ubox[u] */
 20        if (lua_istable(L,-1))
 21        {
 22            lua_pushvalue(L,2);  /* key */
 23            lua_rawget(L,-2);                      /* stack: obj key ubox ubox[u] value */
 24            if (!lua_isnil(L,-1))
 25                return 1;
 26        }
 27#endif
 28        lua_settop(L,2);                        /* stack: obj key */
 29        /* Try metatables */
 30        lua_pushvalue(L,1);                     /* stack: obj key obj */
 31        while (lua_getmetatable(L,-1))
 32        {   /* stack: obj key obj mt */
 33            lua_remove(L,-2);                      /* stack: obj key mt */
 34            if (lua_isnumber(L,2))                 /* check if key is a numeric value */
 35            {
 36                /* try operator[] */
 37                lua_pushstring(L,".geti");
 38                lua_rawget(L,-2);                      /* stack: obj key mt func */
 39                if (lua_isfunction(L,-1))
 40                {
 41                    lua_pushvalue(L,1);
 42                    lua_pushvalue(L,2);
 43                    lua_call(L,2,1);
 44                    return 1;
 45                }
 46            }
 47            else
 48            {
 49                lua_pushvalue(L,2);                    /* stack: obj key mt key */
 50                lua_rawget(L,-2);                      /* stack: obj key mt value */
 51                if (!lua_isnil(L,-1))
 52                    return 1;
 53                else
 54                    lua_pop(L,1);
 55                /* try C/C++ variable */
 56                lua_pushstring(L,".get");
 57                lua_rawget(L,-2);                      /* stack: obj key mt tget */
 58                if (lua_istable(L,-1))
 59                {
 60                    lua_pushvalue(L,2);
 61                    lua_rawget(L,-2);                      /* stack: obj key mt value */
 62                    if (lua_iscfunction(L,-1))
 63                    {
 64                        lua_pushvalue(L,1);
 65                        lua_pushvalue(L,2);
 66                        lua_call(L,2,1);
 67                        return 1;
 68                    }
 69                    else if (lua_istable(L,-1))
 70                    {
 71                        /* deal with array: create table to be returned and cache it in ubox */
 72                        void* u = *((void**)lua_touserdata(L,1));
 73                        lua_newtable(L);                /* stack: obj key mt value table */
 74                        lua_pushstring(L,".self");
 75                        lua_pushlightuserdata(L,u);
 76                        lua_rawset(L,-3);               /* store usertype in ".self" */
 77                        lua_insert(L,-2);               /* stack: obj key mt table value */
 78                        lua_setmetatable(L,-2);         /* set stored value as metatable */
 79                        lua_pushvalue(L,-1);            /* stack: obj key met table table */
 80                        lua_pushvalue(L,2);             /* stack: obj key mt table table key */
 81                        lua_insert(L,-2);               /*  stack: obj key mt table key table */
 82                        storeatubox(L,1);               /* stack: obj key mt table */
 83                        return 1;
 84                    }
 85                }
 86            }
 87            lua_settop(L,3);
 88        }
 89        lua_pushnil(L);
 90        return 1;
 91    }
 92    else if (t== LUA_TTABLE)
 93    {
 94        lua_pushvalue(L,1);
 95        class_table_get_index(L);
 96        return 1;
 97    }
 98    lua_pushnil(L);
 99    return 1;
100}

不要被长段代码吓到,其实我们关注的只有这些代码

 1if (t == LUA_TUSERDATA)
 2    {
 3        /* Access alternative table */
 4#ifdef LUA_VERSION_NUM /* new macro on version 5.1 */
 5        lua_getfenv(L,1);
 6        if (!lua_rawequal(L, -1, TOLUA_NOPEER)) {
 7            lua_pushvalue(L, 2); /* key */
 8            lua_gettable(L, -2); /* on lua 5.1, we trade the "tolua_peers" lookup for a gettable call */
 9            if (!lua_isnil(L, -1))
10                return 1;
11        };

其实这里就很明显了,就是拿下这个 userdata 对应的 env 表。然后如果没有设置过 peer 表,此时的 env 就是 TOLUA_NOPEER,TOLUA_NOPEER 其实就是 LUA_REGISTRYINDEX

1#define TOLUA_NOPEER    LUA_REGISTRYINDEX /* for lua 5.1 */

然后在这个 env 表中索引。注意,lua_gettable 是会触发元方法的,比如__index。这也就是为什么我们会在前面的 lua 代码中看到我们的 peer 上不会直接设置属性,而是给他设置原表通过__index 触发。

同样的,__newindex 也是同样的套路,这里就不展开说明了,读者可以自行考究。

tolua 中的 peer 函数

最后,为了证明我们的想法,我们看下 tolua 中的 getpeer 和 setpeer,来佐证我们的想法。

1#ifdef LUA_VERSION_NUM /* lua 5.1 */
2        tolua_function(L, "setpeer", tolua_bnd_setpeer);
3        tolua_function(L, "getpeer", tolua_bnd_getpeer);
4#endif

setpeer 的实现

 1static int tolua_bnd_setpeer(lua_State* L) {
 2
 3    /* stack: userdata, table */
 4    if (!lua_isuserdata(L, -2)) {
 5        lua_pushstring(L, "Invalid argument #1 to setpeer: userdata expected.");
 6        lua_error(L);
 7    };
 8
 9    if (lua_isnil(L, -1)) {
10
11        lua_pop(L, 1);
12        lua_pushvalue(L, TOLUA_NOPEER);
13    };
14    lua_setfenv(L, -2);
15
16    return 0;
17};

getpeer 的实现

 1static int tolua_bnd_getpeer(lua_State* L) {
 2
 3    /* stack: userdata */
 4    lua_getfenv(L, -1);
 5    if (lua_rawequal(L, -1, TOLUA_NOPEER)) {
 6        lua_pop(L, 1);
 7        lua_pushnil(L);
 8    };
 9    return 1;
10};

实现与我们前面的观察基本一致,也是利用了 fenv 来造一个 peer_table。当然这只是 lua5.1 中的实现,lua 后续的版本已经不用这套实现了,但是基本思路也大同小异,读者可以自行考究。

没想到一个简单的问题引起了这么多看代码的过程。纸上得来终觉浅,绝知此事要躬行。如果只是浅尝辄止地看了下大概的实现,可能这次看代码经历也就没什么意义了。希望以后遇到了一个看起来简单或者直觉型的问题能够有时间,也有心思去细细看一遍整个代码的流程。带着问题多看源码永远都是一个好处理方法。希望今后遇到问题能多看看源码,有时候也没有想象中那么复杂。


标题:Cocos2dx中userdata关联Peer表的实现
作者:matengli110
地址:https://www.sunsgo.world/articles/2022/01/19/1642565231906.html