调用堆栈

我们可以看到,入口就是上一篇文章所说的importType,也就是说当我们第一次调用CS.UnityEngine.GameObject的时候会首次调用importType,执行__register函数。如下是__Register函数。
实现原理
public static void __Register(RealStatePtr L) { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); System.Type type = typeof(UnityEngine.GameObject); Utils.BeginObjectRegister(type, L, translator, 0, 58, 9, 3); Utils.RegisterFunc(L, Utils.METHOD_IDX, "GetComponent", _m_GetComponent); Utils.RegisterFunc(L, Utils.GETTER_IDX, "transform", _g_get_transform); Utils.RegisterFunc(L, Utils.SETTER_IDX, "layer", _s_set_layer); Utils.EndObjectRegister(type, L, translator, null, null, null, null, null); Utils.BeginClassRegister(type, L, __CreateInstance, 6, 0, 0); Utils.RegisterFunc(L, Utils.CLS_IDX, "CreatePrimitive", _m_CreatePrimitive_xlua_st_); Utils.EndClassRegister(type, L, translator); }
下面分别来看这六个关键函数
BeginObjectRegister
//meta: -4, method:-3, getter: -2, setter: -1 public static void BeginObjectRegister(Type type, RealStatePtr L, ObjectTranslator translator, int meta_count, int method_count, int getter_count, int setter_count, int type_id = -1) { if (type == null) { if (type_id == -1) throw new Exception("Fatal: must provide a type of type_id"); LuaAPI.xlua_rawgeti(L, LuaIndexes.LUA_REGISTRYINDEX, type_id); //获取元表压入栈顶 } else { LuaAPI.luaL_getmetatable(L, type.FullName); if (LuaAPI.lua_isnil(L, -1)) { LuaAPI.lua_pop(L, 1); LuaAPI.luaL_newmetatable(L, type.FullName); //新建元表压入栈顶,并存入registry(如果已有,不覆盖) } } LuaAPI.lua_pushlightuserdata(L, LuaAPI.xlua_tag()); //标记下这个表是xlua生成的 LuaAPI.lua_pushnumber(L, 1); LuaAPI.lua_rawset(L, -3); //meta[&xlua_tag] = 1 /* -1 meta_table */ if ((type == null || !translator.HasCustomOp(type)) && type != typeof(decimal)) //如果有自定义push, 就不走通用gc { LuaAPI.xlua_pushasciistring(L, "__gc"); LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta); LuaAPI.lua_rawset(L, -3); //将__gc设置入上面那个meta_table } LuaAPI.xlua_pushasciistring(L, "__tostring"); LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.ToStringMeta); LuaAPI.lua_rawset(L, -3); //设置__tostring方法 if (method_count == 0) { LuaAPI.lua_pushnil(L); //因为要占位,所以push个nil } else { LuaAPI.lua_createtable(L, 0, method_count); } if (getter_count == 0) { LuaAPI.lua_pushnil(L); } else { LuaAPI.lua_createtable(L, 0, getter_count); } if (setter_count == 0) { LuaAPI.lua_pushnil(L); } else { LuaAPI.lua_createtable(L, 0, setter_count); } /* -4 meta_table -3 method_table -2 getter_table -1 setter */ }
创建metatable, 给metabale设置必要的__gc和__tostring, 将元表加入注册表,键为typeName, 在虚拟机栈中创建method, getter, setter表。
RegisterFunc
public static void RegisterFunc(RealStatePtr L, int idx, string name, LuaCSFunction func) { idx = abs_idx(LuaAPI.lua_gettop(L), idx);//method表的位置, 取正索引因为push了key和value会改变相对索引 LuaAPI.xlua_pushasciistring(L, name); LuaAPI.lua_pushstdcallcfunction(L, func); LuaAPI.lua_rawset(L, idx); //键为函数名字,值为函数,存入method表 }
index设计的很巧妙,在BeginObject之后,将栈空间设置为了meta: -4, method:-3, getter: -2, setter: -1,并且保持恒定,这里面传入不同的method or getter or setter即可设置函数到不同的表中。注意,到此为止,我们只是设置了四个lua层的table并且为他们赋值,但是他们并没有与任何东西关联起来,只是静静地躺在栈顶。
EndObjectRegister
public static void EndObjectRegister(Type type, RealStatePtr L, ObjectTranslator translator, LuaCSFunction csIndexer, LuaCSFunction csNewIndexer, Type base_type, LuaCSFunction arrayIndexer, LuaCSFunction arrayNewIndexer) { int top = LuaAPI.lua_gettop(L); int meta_idx = abs_idx(top, OBJ_META_IDX); int method_idx = abs_idx(top, METHOD_IDX); int getter_idx = abs_idx(top, GETTER_IDX); int setter_idx = abs_idx(top, SETTER_IDX); //begin index gen LuaAPI.xlua_pushasciistring(L, "__index"); LuaAPI.lua_pushvalue(L, method_idx); LuaAPI.lua_pushvalue(L, getter_idx); /* -3 '_index' -2 method_table -1 getter_table */ if (csIndexer == null) { LuaAPI.lua_pushnil(L); } else { #if GEN_CODE_MINIMIZE translator.PushCSharpWrapper(L, csIndexer); #else LuaAPI.lua_pushstdcallcfunction(L, csIndexer); //对于有索引器的,也就是vector[1]这种 #endif } translator.Push(L, type == null ? base_type : type.BaseType()); LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName); LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); if (arrayIndexer == null) { LuaAPI.lua_pushnil(L); } else { #if GEN_CODE_MINIMIZE translator.PushCSharpWrapper(L, arrayIndexer); #else LuaAPI.lua_pushstdcallcfunction(L, arrayIndexer); #endif } /* -7 '_index' -6 method_table -5 getter_table -4 csIndexer or nil -3 Basetype (udata) -2 registry['LuaIndexs'] -1 arrayIndexer or nil */ LuaAPI.gen_obj_indexer(L); //六个顶部参数都传给了它。。。。。。估计是lua调用太多了,直接写在xlua.c C文件里面了 /* LUA_API int gen_obj_indexer(lua_State *L) { lua_pushnil(L); lua_pushcclosure(L, obj_indexer, 7); return 0; } obj_indexer也在xlua.c */ /* -2 '_index' -1 obj_indexer (c_closure) */ if (type != null) { LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName); LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables translator.Push(L, type); LuaAPI.lua_pushvalue(L, -3); /* -5 '__index' -4 obj_indexer (c_closure) -3 registry[LuaIndexsFieldName] -2 type (udata) -1 obj_indexer (c_closure) */ LuaAPI.lua_rawset(L, -3); //registry[LuaIndexsFieldName][type] = obj_indexer LuaAPI.lua_pop(L, 1); } /* -2 '_index' -1 obj_indexer (c_closure) */ LuaAPI.lua_rawset(L, meta_idx); //metatable['__index'] = obj_indexer //栈空了 //end index gen //begin newindex gen LuaAPI.xlua_pushasciistring(L, "__newindex"); LuaAPI.lua_pushvalue(L, setter_idx); /* -2 '_newindex' -1 setter_table */ if (csNewIndexer == null) { LuaAPI.lua_pushnil(L); } else { #if GEN_CODE_MINIMIZE translator.PushCSharpWrapper(L, csNewIndexer); #else LuaAPI.lua_pushstdcallcfunction(L, csNewIndexer); #endif } translator.Push(L, type == null ? base_type : type.BaseType()); LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName); LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); if (arrayNewIndexer == null) { LuaAPI.lua_pushnil(L); } else { #if GEN_CODE_MINIMIZE translator.PushCSharpWrapper(L, arrayNewIndexer); #else LuaAPI.lua_pushstdcallcfunction(L, arrayNewIndexer); #endif } /* -6 '_index' -5 setter_table -4 csNewIndexer or nil -3 Basetype (udata) -2 registry['LuaIndexs'] -1 arrayNewIndexer or nil */ LuaAPI.gen_obj_newindexer(L);//和上面一样,不赘述了 if (type != null) { LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName); LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua newindexs function tables translator.Push(L, type); LuaAPI.lua_pushvalue(L, -3); /* -5 '__newindex' -4 obj_newindexer (c_closure) -3 registry[LuaNewIndexsFieldName] -2 type (udata) -1 obj_newindexer (c_closure) */ LuaAPI.lua_rawset(L, -3);//registry[LuaNewIndexsFieldName][type] = obj_newindexer LuaAPI.lua_pop(L, 1); } /* -2 '_newindex' -1 obj_newindexer (c_closure) */ LuaAPI.lua_rawset(L, meta_idx); //metatable['__newindex'] = obj_indexer //栈空了 //end new index gen LuaAPI.lua_pop(L, 4);//清空这四个meta: -4, method:-3, getter: -2, setter: -1 }
注释应该把整个lua栈情况写的比较详细了,此函数的主要作用就是把method, getter, setter写入元表,其中method和getter写入__index, setter写入__newindex。具体的设置方法写入了xlua.c,这里我猜测是因为对栈的操作比较多所以直接写C层了。
具体C层的obj_indexer暂略,就是把这些getter,indexer之类的加入到__index。值得注意的一点是,registry[LuaNewIndexsFieldName][type] = obj_newindexer这里将每个type的indexer都写到了注册表。
有个小插曲,刚开始由于没有细看C层的obj_indexer所以产生了疑问。既然metatable[‘__index’] = cls_indexer这里面已经设置好了metatable,那不就访问的时候直接访问这个就好了为什么还要在注册表中存副本?我还全局搜了LuaIndexsFieldName发现除了注册的时候确实也没有用到这个字段。不过后来仔细的看了看才发现,obj_indexer的上值是baseType,所以这个东西存着是为了找父亲的indexer。在obj_indexer中,如果其他几个表都找不到索引,就会去查找baseType在registry[LuaNewIndexsFieldName][type]中存的副本。(并且如果找到了就缓存在这个upvalue中,并将upvalue base置空了)
//xlua.c 节选 //upvalue --- [1]: methods, [2]:getters, [3]:csindexer, [4]:base, [5]:indexfuncs, [6]:arrayindexer, [7]:baseindex //param --- [1]: obj, [2]: key //upvalue是定义的时候传的, param是调用的时候传的 //这个函数是__index,也就是说obj就是调用__index的东西 LUA_API int obj_indexer(lua_State *L) { if (!lua_isnil(L, lua_upvalueindex(4))) { lua_pushvalue(L, lua_upvalueindex(4)); while(!lua_isnil(L, -1)) { lua_pushvalue(L, -1); lua_gettable(L, lua_upvalueindex(5)); if (!lua_isnil(L, -1)) // found { lua_replace(L, lua_upvalueindex(7)); //baseindex = indexfuncs[base] 缓存啦 lua_pop(L, 1); break; } lua_pop(L, 1); lua_getfield(L, -1, "BaseType"); lua_remove(L, -2); } lua_pushnil(L); lua_replace(L, lua_upvalueindex(4));//base = nil 已经查过一次baseType了该缓存的都缓存了 } }
然而疑问就来了,那如果是子类先register的怎么办?父类不就是空了吗?其实早在LuaEnv初始化,初始registry[Utils.LuaIndexsFieldName] = {},…,这四个表的时候,就已经为他们所有设置了通用的metatable的__index。(也就是找不到registry[Utils.LuaIndexsFieldName][baseType]的时候会调用),代码如下:
[MonoPInvokeCallback(typeof(LuaCSFunction))] public static int MetaFuncIndex(RealStatePtr L) { //registry[LuaIndex].type 由于是空表,所以触发了meta, 所以执行了这个函数 try { //此时registry[LuaIndex][type] 还是空的 ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); Type type = translator.FastGetCSObj(L, 2) as Type; if (type == null) { return LuaAPI.luaL_error(L, "#2 param need a System.Type!"); } // L 1 registry[LuaIndex] // L 2 type //UnityEngine.Debug.Log("============================load type by __index:" + type); //translator.TryDelayWrapLoader(L, type); translator.GetTypeId(L, type); //上述执行完注册之后,registry[LuaIndex][type]填入了真实的objindexer..4个 LuaAPI.lua_pushvalue(L, 2); LuaAPI.lua_rawget(L, 1); //已经有值啦可以获取这个值啦 return 1; } catch (System.Exception e) { return LuaAPI.luaL_error(L, "c# exception in MetaFuncIndex:" + e); } }
BeginClassRegister
public static void BeginClassRegister(Type type, RealStatePtr L, LuaCSFunction creator, int class_field_count, int static_getter_count, int static_setter_count) { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); LuaAPI.lua_createtable(L, 0, class_field_count); //cls_table的长度是class_field的数量,合理! LuaAPI.xlua_pushasciistring(L, "UnderlyingSystemType"); translator.PushAny(L, type); LuaAPI.lua_rawset(L, -3); int cls_table = LuaAPI.lua_gettop(L); //就是上面那个lua_createtable /* -1 cls_table */ SetCSTable(L, type, cls_table); //将这个cls_table设置到CS.path.class供lua调用 只设置哦不设值 /* -1 cls_table */ LuaAPI.lua_createtable(L, 0, 3); int meta_table = LuaAPI.lua_gettop(L); if (creator != null) { LuaAPI.xlua_pushasciistring(L, "__call"); #if GEN_CODE_MINIMIZE translator.PushCSharpWrapper(L, creator); #else LuaAPI.lua_pushstdcallcfunction(L, creator); //构造函数吧,__call,所以GameObject("obj")就相当于new了 #endif LuaAPI.lua_rawset(L, -3); //meta_table[__call] = creator } if (static_getter_count == 0) { LuaAPI.lua_pushnil(L); } else { LuaAPI.lua_createtable(L, 0, static_getter_count); } if (static_setter_count == 0) { LuaAPI.lua_pushnil(L); } else { LuaAPI.lua_createtable(L, 0, static_setter_count); } LuaAPI.lua_pushvalue(L, meta_table); LuaAPI.lua_setmetatable(L, cls_table); //cls.path.class.meta = meta_table /* -4 cls_table -3 metatable -2 static_getter -1 static_setter */ }
BeginClassRegister和BeginObjectRegister有异曲同工之妙,创建cls_table, meta_table, static_getter, static_setter,设置meta的构造方法,并把cls_table插入到CS.XX.XX路径中
EndClassRegister
public static void EndClassRegister(Type type, RealStatePtr L, ObjectTranslator translator) { int top = LuaAPI.lua_gettop(L); int cls_idx = abs_idx(top, CLS_IDX); int cls_getter_idx = abs_idx(top, CLS_GETTER_IDX); int cls_setter_idx = abs_idx(top, CLS_SETTER_IDX); int cls_meta_idx = abs_idx(top, CLS_META_IDX); //begin cls index LuaAPI.xlua_pushasciistring(L, "__index"); LuaAPI.lua_pushvalue(L, cls_getter_idx); LuaAPI.lua_pushvalue(L, cls_idx); translator.Push(L, type.BaseType()); LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName); LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); /* -5 '_index' -4 getter_table -3 cls_table -2 Basetype (udata) -1 registry['LuaClassIndexs'] */ LuaAPI.gen_cls_indexer(L); /* -2 '__index' -1 cls_indexer (c_closure) */ LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName); LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables translator.Push(L, type); LuaAPI.lua_pushvalue(L, -3); /* -5 '__index' -4 cls_indexer (c_closure) -3 registry['LuaClassIndexs'] -2 type (udata) -1 cls_indexer (c_closure) */ LuaAPI.lua_rawset(L, -3); //registry['LuaClassIndexs'][type] = cls_indexer LuaAPI.lua_pop(L, 1); /* -2 '_index' -1 cls_indexer (c_closure) */ LuaAPI.lua_rawset(L, cls_meta_idx);//metatable['__index'] = cls_indexer //end cls index //begin cls newindex LuaAPI.xlua_pushasciistring(L, "__newindex"); LuaAPI.lua_pushvalue(L, cls_setter_idx); translator.Push(L, type.BaseType()); LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName); LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); LuaAPI.gen_cls_newindexer(L); LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName); LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua newindexs function tables translator.Push(L, type); LuaAPI.lua_pushvalue(L, -3); LuaAPI.lua_rawset(L, -3); LuaAPI.lua_pop(L, 1); LuaAPI.lua_rawset(L, cls_meta_idx);//metatable['__newindex'] = cls_newindexer //end cls newindex LuaAPI.lua_pop(L, 4); //pop掉四个表 }
这里和EndClass很像,也是将四个表分别放入元表(CS.PATH.GameObject的元表)中,也是调用C层的函数。
RegisterObject(漏掉了)
Utils.RegisterObject(L, translator, Utils.CLS_IDX, "Skybox", UnityEngine.Rendering.AmbientMode.Skybox); public static void RegisterObject(RealStatePtr L, ObjectTranslator translator, int idx, string name, object obj) { idx = abs_idx(LuaAPI.lua_gettop(L), idx); LuaAPI.xlua_pushasciistring(L, name); translator.PushAny(L, obj); LuaAPI.lua_rawset(L, idx); }
整体感觉和RegisterFunc差不多,实际上也是差不多的。
为什么不用Getter? cls_getter_idx和cls_idx究竟有什么本质区别?
要解决这个问题,首先还是要看看C层的gen代码(节选)
//upvalue --- [1]:getters, [2]:feilds, [3]:base, [4]:indexfuncs, [5]:baseindex //param --- [1]: obj, [2]: key LUA_API int cls_indexer(lua_State *L) { if (!lua_isnil(L, lua_upvalueindex(1))) { lua_pushvalue(L, 2); lua_gettable(L, lua_upvalueindex(1)); if (!lua_isnil(L, -1)) {//has getter lua_call(L, 0, 1); //静态方法,不需要传obj哦,参数数量0 return 1; } lua_pop(L, 1); } if (!lua_isnil(L, lua_upvalueindex(2))) { lua_pushvalue(L, 2); lua_rawget(L, lua_upvalueindex(2)); //cls_table[key] if (!lua_isnil(L, -1)) {//has feild return 1; } lua_pop(L, 1); } //为什么getter有lua_call而field不用? //因为getter的调用一般是 gameObject.transform。本身调用不是当函数调用的,所以这里需要调用函数 //而field调用的时候是Gameobject.Find("xxx"),所以这里面我们找到的Find只需要是把这个函数放栈顶就行,调用由lua层来 }
注释写的很清楚了,所以getter应该放函数,cls_idx放函数放值都行。
还有个问题,为什么静态变量都用的getter,这里用的是cls_idx?因为这里是const,是编译的时候就确定的且不能改变的,所以并不需要函数动态获取,注册的时候写死就好了。
ObjectRegister VS ClassRegister
ObjectRegister和ClassRegister的区别是,顾名思义,object是对象,如果是gameobject.position,就调用的是object的逻辑。Class是类如果是CS.UnityEngine.GameObject.Find,则调用的是class的逻辑。
object将做好的含有indexer的metatable放入了注册表中,只有真正push_object的时候,才会将metatable以及其中的__index给对象(LuaAPI.xlua_pushcsobj这个API做的,Push函数的最后一行)
class则直接将信息直接存在了CS.XX.XX中(因为静态方法,直接使用CS.XX.XX就能调用而不需要产生object实例之后才能用)
还有不同就是class将方法直接写入了CS.XX.XX的表中(当然如果要访问基类的方法还是要走classindexer)。原因也简单,因为obj是很多属于相同class的obj可以共享这个__index,肯定不要复制很多份表的好。但是class就只有一个,所以直接设置会快一点。