在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
目录:Xlua源码学习 本篇以CS.XLua.LuaDLL.Lua:xlua_is_eq_str(L,index,str)的调用为例子。 文章比较长,先说结论: 1.CS:LuaEnv的init_xlua代码块里生成的全局表,核心init_xlua里的metatable:__index方法。 2. XLua:以XLua为类名的类不存在,当做命名空间处理。CS.XLua = { ['.fqn'] = fqn }。 3. LuaDLL:同上,CS.XLua.LuaDLL = { ['.fqn'] = fqn }。 4. Lua:Lua类存在,导出类。 调用StaticLuaCallbacks.ImportType导出类。 ObjectTranslator.TryDelayWrapLoader:调用wrap的__Register方法加载类。 __Register:生成元表obj_meta,该元表封装了类的所有成员方法、get、set调用接口__index,__newindex。 生成cls_table,该表包含了所有类的静态方法。 生成cls_meta,cls_table的元表,封装了静态的属性设置、获取接口__index,__newindex。 设置CS.XLua.LuaDLL.Lua = cls_table。 类导出结束。 5.调用静态方法 1. 静态方法获取:cls_table.staticFunc,直接取。 2. 静态get方法:通过cls_meta的__index(xlua.c的cls_indexer)获取,非父类方法存在getters。 3. 静态父类方法:通过cls_meta的__index(xlua.c的cls_indexer)获取,实际通过父类的cls_index获取的,即register["LuaClassIndexs"][base_ud] = base_meta.__index。涉及父类操作下同。 4. 静态set:通过cls_meta的__newindex(xlua.c的cls_newindexer)获取,非父类方法存在setters。 6.对象实例获取: 两种方式: 1. new一个对象,例如在lua执行UIHierarchy(),会触发UIHierarchy的cls_meta的__call方法,而__call方法绑定了下面的UIHierarchyWrap .__CreateInstance(),最终是new了一个对象,然后把实例对象的ud压栈返回,你就可以通过这个ud的元表obj_meta调用到成员方法。 static int __CreateInstance(RealStatePtr L) { try { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); if(LuaAPI.lua_gettop(L) == 1) { UIHierarchy gen_ret = new UIHierarchy(); translator.Push(L, gen_ret); return 1; } } } 2. 从其他方法返回值获取,例如gameobject.transform会把transform的ud压栈并返回并lua,lua通过这个ud进行成员方法的访问。 static int _g_get_transform(RealStatePtr L) { try { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); UnityEngine.GameObject gen_to_be_invoked = (UnityEngine.GameObject)translator.FastGetCSObj(L, 1); translator.Push(L, gen_to_be_invoked.transform); } catch(System.Exception gen_e) { return LuaAPI.luaL_error(L, "c# exception:" + gen_e); } return 1; } 6.调用成员方法 1. 通过压栈的ud的元表obj_meta访问__index方法(xlua.c的obj_indexer),取到方法,非父类方法存在methods。 2. 成员get:同上。 3. 成员set: 通过压栈的ud的元表访问__newindex方法(xlua.c的obj_newindexer),取到方法。 4. lua方法调用时会依次把参数压栈,成员方法(:)调用会先把自身的ud压栈,再根据ud到objects缓存取到对应的obj实例进行调用。 static int _s_set_widgets(RealStatePtr L) { try { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); UIHierarchy gen_to_be_invoked = (UIHierarchy)translator.FastGetCSObj(L, 1);//第一个参数,自身ud地址。 gen_to_be_invoked.widgets = (System.Collections.Generic.List<UIHierarchy.ItemInfo>)translator.GetObject(L, 2, typeof(System.Collections.Generic.List<UIHierarchy.ItemInfo>)); } catch(System.Exception gen_e) { return LuaAPI.luaL_error(L, "c# exception:" + gen_e); } return 0; } 一、CS全局表。 LuaEnv的构造方法里会执行init_xlua文本,在这个可执行的文本里生成里CS的全局表,并对CS赋了值 CS = Register["CSHARP_NAMESPACE"] DoString(init_xlua, "Init"); LuaAPI.xlua_pushasciistring(rawL, CSHARP_NAMESPACE); if (0 != LuaAPI.xlua_getglobal(rawL, "CS")) { throw new Exception("get CS fail!"); } LuaAPI.lua_rawset(rawL, LuaIndexes.LUA_REGISTRYINDEX); init_xlua核心的代码段如下: function metatable:__index(key) local fqn = rawget(self,'.fqn') fqn = ((fqn and fqn .. '.') or '') .. key local obj = import_type(fqn) if obj == nil then -- It might be an assembly, so we load it too. obj = { ['.fqn'] = fqn } setmetatable(obj, metatable) elseif obj == true then return rawget(self, key) end -- Cache this lookup rawset(self, key, obj) return obj end function metatable:__newindex() error('No such type: ' .. rawget(self,'.fqn'), 2) end __index的官方说明如下: __index: 索引 table[key]。 当 table 不是表或是表 table 中不存在 key 这个键时,这个事件被触发。 此时,会读出 table 相应的元方法。 这个事件的元方法其实可以是一个函数也可以是一张表。 如果它是一个函数,则以 table 和 key 作为参数调用它。 如果它是一张表,最终的结果就是以 key 取索引这张表的结果。 (这个索引过程是走常规的流程,而不是直接索引, 所以这次索引有可能引发另一次元方法。) 这段lua的意思是,如果table[className] 不存在,则执行这个__index。如果className对应的类型存在,加载这个类型到内存(wrap的__Register),否则创建一张空表,当成命名空间处理。 这边的obj是cls_table,这个表里存着这个类对应wrap的所有静态方法,通过这个类的wrap文件的__CreateInstance方法可以获取这个类的ud,而这个ud的元表里封装了可以获取类的成员方法、属性的_index方法。这边所指的方法都是在wrap里面的。具体后面再说。 __newindex:实际是禁止了对这个表的元表设置操作。 二、命名空间:XLua、LuaDLL。 如上的index代码,local obj = import_type(fqn)。这个obj是nil,那么这边是创建了两个空表。即 CS.XLua = {['.fqn'] = fqn} CS.XLua.LuaDLL = {['.fqn'] = fqn} 三、import_type,加载wrap文件进内存。 1.这边的import_type实际是StaticLuaCallbacks.ImportType方法。 核心代码如下,type == null,当做命名空间处理。否则通过translator.GetTypeId加载类。 ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); string className = LuaAPI.lua_tostring(L, 1); Type type = translator.FindType(className); if (type != null) { if (translator.GetTypeId(L, type) >= 0) { LuaAPI.lua_pushboolean(L, true); } else { return LuaAPI.luaL_error(L, "can not load type " + type); } } else { LuaAPI.lua_pushnil(L); } 2.translator.getTypeId:获取type元表在注册表中引用Id,如果不存在,则创建元表registry[type.FullName]={},并在registry表中创建新的引用指向该元表并返回type_id。维护了Type和TypeId间的映射表typeMap、typeIdMap。 internal int getTypeId(RealStatePtr L, Type type, out bool is_first, LOGLEVEL log_level = LOGLEVEL.WARN) { int type_id; is_first = false; if (!typeIdMap.TryGetValue(type, out type_id)) // no reference { is_first = true; Type alias_type = null; aliasCfg.TryGetValue(type, out alias_type); LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName); if (LuaAPI.lua_isnil(L, -1)) //no meta yet, try to use reflection meta { LuaAPI.lua_pop(L, 1); if (TryDelayWrapLoader(L, alias_type == null ? type : alias_type)) { LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName); } } //循环依赖,自身依赖自己的class,比如有个自身类型的静态readonly对象。 if (typeIdMap.TryGetValue(type, out type_id)) { LuaAPI.lua_pop(L, 1); } else { LuaAPI.lua_pushvalue(L, -1); type_id = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);//register["type_id"] = meta LuaAPI.lua_pushnumber(L, type_id); LuaAPI.xlua_rawseti(L, -2, 1);//meta["1"] = typeId LuaAPI.lua_pop(L, 1); if (type.IsValueType()) { typeMap.Add(type_id, type); } typeIdMap.Add(type, type_id); } } return type_id; } 3. TryDelayWrapLoader。 delayWrap在XLua_Gen_Initer_Register__.Init赋值,Init方法在ObjectTranslator的s_gen_reg_dumb_obj参数生成里调用(即构造函数前就初始化好了)。他实际是Type到TypeWrap.__Register的映射。 下面的loader(L);实际调用的是对应类的wrap文件的__Register方法。 public bool TryDelayWrapLoader(RealStatePtr L, Type type) { if (loaded_types.ContainsKey(type)) return true; loaded_types.Add(type, true);//避免重复加载 LuaAPI.luaL_newmetatable(L, type.FullName); //先建一个metatable,因为加载过程可能会需要用到 LuaAPI.lua_pop(L, 1); Action<RealStatePtr> loader; int top = LuaAPI.lua_gettop(L); if (delayWrap.TryGetValue(type, out loader)) { delayWrap.Remove(type); loader(L); } return true; } 四、类的核心加载方法XXXWrap.__Register 这边不贴代码了,核心方法都在Utils类里。 public static void __Register(RealStatePtr L) { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); System.Type type = typeof(UIHierarchy); Utils.BeginObjectRegister(type, L, translator, 0, 2, 3, 3); Utils.RegisterFunc(L, Utils.METHOD_IDX, "SetWidgets", _m_SetWidgets); Utils.RegisterFunc(L, Utils.GETTER_IDX, "widgets", _g_get_widgets); Utils.RegisterFunc(L, Utils.SETTER_IDX, "widgets", _s_set_widgets); Utils.EndObjectRegister(type, L, translator, null, null, null, null, null); Utils.BeginClassRegister(type, L, __CreateInstance, 1, 0, 0); Utils.RegisterFunc(L, Utils.CLS_IDX, "IsNull", _m_IsNull_xlua_st_); Utils.EndClassRegister(type, L, translator); } 1.BeginObjectRegister:生成类的成员元表、成员方法、Get、Set的存放表。 obj_meta[ud]=1 obj_meta["__gc"]=StaticLuaCallbacks.LuaGC obj_meta["__tostring"]=StaticLuaCallbacks.ToString 方法执行结束栈:obj_meta, methon, get, set(-4, -3, -2, -1) 2.Utils.RegisterFunc:注册方法,这个比较简单,就是做一次赋值。 method_table[name] = func,这边包括method、set、get等方法设置。 这边其实把lua调用的方法都封装成LuaCSFunction委托,再转成指针赋值给c,c在把这个函数指针封装成闭包供lua调用。 public static void RegisterFunc(RealStatePtr L, int idx, string name, LuaCSFunction func) { idx = abs_idx(LuaAPI.lua_gettop(L), idx); LuaAPI.xlua_pushasciistring(L, name); LuaAPI.lua_pushstdcallcfunction(L, func); LuaAPI.lua_rawset(L, idx); } public static void lua_pushstdcallcfunction(IntPtr L, lua_CSFunction function, int n = 0)//[-0, +1, m] { IntPtr fn = Marshal.GetFunctionPointerForDelegate(function);//转指针 xlua_push_csharp_function(L, fn, n); } xlua.c接口 static int csharp_function_wrap(lua_State *L) { lua_CFunction fn = (lua_CFunction)lua_tocfunction(L, lua_upvalueindex(1)); int ret = fn(L); return ret; } LUA_API void xlua_push_csharp_function(lua_State* L, lua_CFunction fn, int n) { lua_pushcfunction(L, fn); lua_pushboolean(L, 0); lua_pushcclosure(L, csharp_function_wrap, 2 + (n > 0 ? n : 0)); } 3.EndObjectRegister:设置成员元表的__index、__newindex方法。 方法开始堆栈:obj_meta,methon,get,set 栈中内容:obj_meta,method,get,set,--index,method,get,csIndexer(array),baseType(父类),register[luaindex],arrayIndexer(nil) LuaAPI.gen_obj_indexer(L); //设置__index方法 栈中内容:obj_meta,method,get,set,--index, closure(xlua.c 的obj_indexer) 这个闭包如下,提供了根据key在methon,get、basetype中查找方法的实现,很重要。method、get并没有存在obj_meta,而是存在obj_indexer闭包的上值里。 它会依次查找methods、getters、arrayindexer(数组)、base(基类)。
register[luaindex][ud] = obj_indexer obj_meta[__index]=obj_indexer 栈中内容:obj_meta,method,get,set 栈中内容:obj_meta,method,get,set,__newindex,set,csNewIndexer(nil),baseType(nil),register[LuaNewIndexs],arrayNewIndexer(nil) LuaAPI.gen_obj_newindexer(L); //设置__newindex方法 栈中内容:obj_meta,method,get,set,__newindex,closure(xlua.x obj_newindexer) register[LuaNewIndexs][ud] = obj_newindexer meta[__newindex] = obj_newindexer 方法执行结束:空栈 //upvalue --- [1]: methods, [2]:getters, [3]:csindexer, [4]:base, [5]:indexfuncs, [6]:arrayindexer, [7]:baseindex //param --- [1]: obj, [2]: key LUA_API int obj_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 method return 1; } lua_pop(L, 1); } if (!lua_isnil(L, lua_upvalueindex(2))) { lua_pushvalue(L, 2); lua_gettable(L, lua_upvalueindex(2));//get直接调用了,所以lua里get不当做方法处理 if (!lua_isnil(L, -1)) {//has getter lua_pushvalue(L, 1); lua_call(L, 1, 1); return 1; } lua_pop(L, 1); } if (!lua_isnil(L, lua_upvalueindex(6)) && lua_type(L, 2) == LUA_TNUMBER) { lua_pushvalue(L, lua_upvalueindex(6)); lua_pushvalue(L, 1); lua_pushvalue(L, 2); lua_call(L, 2, 1); return 1; } if (!lua_isnil(L, lua_upvalueindex(3))) { lua_pushvalue(L, lua_upvalueindex(3)); lua_pushvalue(L, 1); lua_pushvalue(L, 2); lua_call(L, 2, 2); if (lua_toboolean(L, -2)) { return 1; } lua_pop(L, 2); } 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 } if (!lua_isnil(L, lua_upvalueindex(7))) { lua_settop(L, 2); lua_pushvalue(L, lua_upvalueindex(7)); lua_insert(L, 1); lua_call(L, 2, 1); return 1; } else { return 0; } } 到这边成员meta的内容填充就结束了。 meta = //LuaAPI.luaL_getmetatable(L, type.FullName) = registry[type.FullName] { ["__index"] = obj_indexer//(见xlua.c) ["__newindex"] = obj_newindexer//(见xlua.c) [UserData] = 1 ["__tostring"] = StaticLuaCallbacks.ToString [1] = typeId } 4.BeginClassRegister:生成类的静态(static)元表、静态方法、Get、Set的存放表。 方法结束 栈:cls_table ,meta_table, get, set(-4, -3, -2, -1) cls_table[UnderlyingSystemType] = userdata cls_meta["__call"] = creator(wrap 的 __CreateInstance,该方法会把ud压栈。 setmetatable(cls_table, cls_meta) static int __CreateInstance(RealStatePtr L) { try { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); if(LuaAPI.lua_gettop(L) == 1) { UIHierarchy gen_ret = new UIHierarchy(); translator.Push(L, gen_ret); return 1; } } } 5.SetCSTable:生成路径表。 最终实现的是:XLua.LuaDLL.Lua = cls_table CS[ud] = cls_table 6.EndClassRegister:设置类的元表的__index、__newindex方法 方法开始栈:cls_table, cls_meta,get,set 栈:cls_table, cls_meta,get,set,__index,get, cls_table, base(父类), reister["LuaClassIndexs"] LuaAPI.gen_cls_indexer(L);//设置__index方法 栈:cls_table, cls_meta,get,set,__index,closure(cls_indexer) register[LuaClassIndexs][ud] = closure(cls_indexer) meta[__index] = closure(cls_indexer) 栈:cls_table,cls_meta,get,set 栈:cls_table, cls_meta,get,set,__newindex,set, base(父类), reister["LuaClassNewIndexs"] LuaAPI.gen_cls_newindexer(L); //设置__newindex方法 栈:cls_table, cls_meta,get,set,__newindex,closure(cls_indexer) register[LuaClassNewIndexs][ud] = closure(cls_newindexer) meta[__newindex] = closure(cls_newindexer) 方法结束栈:空 7.用到的几个lua表: 1.CLS_IDX对应的cls_table:保存了所有静态方法的引用。 1.CLS_META_IDX对应的cls_getter:保存了所有get方法的引用,通过cls_meta的__index访问。 1.CLS_GETTER_IDX对应的cls_setter:保存了所有静态set的引用,通过cls_meta的__newindex访问。 1.CLS_SETTER_IDX对应的cls_meta:cls_table的元表。 1.CLS_IDX对应的cls_table:保存了所有静态方法的引用。 1.CLS_IDX对应的cls_table:保存了所有静态方法的引用。 五、translator.Push:获取type的userdata,如果没有,则创建,保留userdata在栈顶。 1. addObject:将object添加到对象池中,创建object-index的映射关系,通过index可以获取到c#对象,而通过c#对象可以获取唯一的index,用于获取对象的ud,从而访问对象的从成员方法。 public void Push(RealStatePtr L, object o) { if (o == null) { LuaAPI.lua_pushnil(L); return; } int index = -1; Type type = o.GetType(); bool is_enum = type.GetTypeInfo().IsEnum; bool is_valuetype = type.GetTypeInfo().IsValueType; bool needcache = !is_valuetype || is_enum; if (needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index))) { //已缓存,通过唯一的typeId取到ud并压栈 if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1) { return; } } bool is_first; int type_id = getTypeId(L, type, out is_first); if (is_first && needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index))) { if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1) { return; } } index = addObject(o, is_valuetype, is_enum); LuaAPI.xlua_pushcsobj(L, index, type_id, needcache, cacheRef); } 2. xlua_pushcsobj:创建一个新的userdata,内容是对象在objects的下标,通过这个下标可以在c#取到对应的对象。 cacheud:cache [index] = ud setmetatable(ud, meta) 设置元表 也就是说通过translator.Push(L, type)可以获取对象的userdata,这个userdata的元表obj_meta里封装了对该类的所有成员方法、get、set属性的访问方法,我们可以通过该元表的__index、__newindex调用所有wrap内的成员方法、get、set方法。 static void cacheud(lua_State *L, int key, int cache_ref) { lua_rawgeti(L, LUA_REGISTRYINDEX, cache_ref); lua_pushvalue(L, -2); lua_rawseti(L, -2, key); lua_pop(L, 1); } LUA_API void xlua_pushcsobj(lua_State *L, int key, int meta_ref, int need_cache, int cache_ref) { int* pointer = (int*)lua_newuserdata(L, sizeof(int)); *pointer = key; if (need_cache) cacheud(L, key, cache_ref); lua_rawgeti(L, LUA_REGISTRYINDEX, meta_ref); lua_setmetatable(L, -2); } 六、总结用到的容器,以及各个meta的内容。 c端 register[typeId] = meta //typeId是唯一的 register[type.FullName] = {} 元表是meta,通过LuaAPI.luaL_getmetatable(L, type.FullName);获取到元表 func_meta = {"__index" = StaticLuaCallbacks.MetaFuncIndex} 下面这个四个表是用于获取父类的相对应__index方法的。 translator.Push(L, type == null ? base_type : type.BaseType());实际传到xlua的obj_indexer的是父类的ud,在通过register["LuaIndexs"][ud]获取父类的obj_indexer闭包。
register["LuaIndexs"] = {__index = func_meta}//LuaEnv初始化 register["LuaNewIndexs"] = {__index = func_meta}//LuaEnv初始化 register["LuaClassIndexs"] = {__index = func_meta}//LuaEnv初始化 register["LuaClassNewIndexs"] = {__index = func_meta}//LuaEnv初始化 register["LuaIndexs"][ud] = obj_indexer(这是个闭包,它的上值包括了这个类的method、set、baseType,实际是__index方法,提供对象查找方法) register["LuaNewIndexs"][ud] = obj_newindexer register[LuaClassIndexs][ud] = cls_indexer register[LuaClassNewIndexs][ud] = cls_newindexer register["LuaIndexs"] = {}//元表的__index指向StaticLuaCallbacks.MetaFuncIndex 其他几个类似,luaenv初始化 cache = register[cacheRef] cache [index] = ud (ud = index,元表是typeId对应的meta,即LuaAPI.luaL_getmetatable(L, type.FullName)) xlua_csharp_namespace = register[xlua_csharp_namespace],在LuaEnv 赋值了 register[xlua_csharp_namespace] = CS,即xlua_csharp_namespace = CS CS [path][type.Name] = cls_table //CS.XLua.LuaDLL.Lua = cls_table CS [ud] = cls_table c# typeMap[type_id] = type typeIdMap[type] = type_id objects[index] = obj //可以试试Type reverseMap[obj] = index enumMap[obj] = index obj_meta = //LuaAPI.luaL_getmetatable(L, type.FullName) = registry[type.FullName] { ["__index"] = obj_indexer//(见xlua.c) ["__newindex"] = obj_newindexer//(见xlua.c) [UserData] = 1 ["__tostring"] = StaticLuaCallbacks.ToString [1] = typeId } userdata = index//每个实例都会有一个ud,但是meta是一样的。ud只是用与区别不同的实例,便于在objects中取得对应的实例对象。 { ["__index"] = obj_meta } cls_meta = //这个是新建的表,是新建的cls_table的元表,用于查找静态的Get方法(包括从基类base查找,静态设置接口) { ["__call"] = __CreateInstance//(每个类的wrap文件),该方法会把类的ud压栈,而通过这个ud,我们可以访问到类的成员方法。 ["__index"] = cls_indexer//(见xlua.c) ["__newindex"] = cls_newindexer//(见xlua.c) } cls_table = //每个类都会有一个cls_table,可能会有多个ud.这里保存了所有的静态方法映射。 { staticFunc = func ["__index"] = cls_meta } 七、静态方法、成员方法的wrap区别。 如下图所示,成员方法需要调用以下方法取得实例: UIHierarchy gen_to_be_invoked = (UIHierarchy)translator.FastGetCSObj(L, 1); 也就是它的upvalue第一个参数就是对象的实例,对应lua的调用就是冒号调用:UIHierarchy:SetWidgets,而静态方法因为不需要传本身,所以对应lua就是点号调用。 lua点号和冒号区别:https://www.jianshu.com/p/56961e4bd693
这边的index是对象的ud的内存地址。 internal object FastGetCSObj(RealStatePtr L,int index) { return getCsObj(L, index, LuaAPI.xlua_tocsobj_fast(L,index)); } 静态方法:直接查询cls_table表,一次查询。 成员方法:查询ud-查询ud的元表obj_meta-调用obj_meta的_index查询obj_method表。三次查表+一次__index调用。 总结:尽量封装静态方法给lua使用,性能计较好。 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论