使用示例
local Input = CS.UnityEngine.Input local TouchPhase = CS.UnityEngine.TouchPhase local EventSystem = CS.UnityEngine.EventSystems.EventSystem
创建原理
全局变量CS的设置,在LuaEnv.cs中的init_xlua里面
local metatable = {} local import_type = xlua.import_type function metatable:__index(key) local fqn = rawget(self,'.fqn') fqn = ((fqn and fqn .. '.') or '') .. key local obj = import_type(fqn) --除了获得是不是中间路径外, 实际上wrap代码的导入也是这个函数做的 if obj == nil then --中间路径 -- It might be an assembly, so we load it too. obj = { ['.fqn'] = fqn } setmetatable(obj, metatable) --并且把路径上的元素设置这个元表(其实就是递归的, CS的这个元表已经在入口设置了。) --但是中间的, 比如说CS.ZX.NAME中的ZX并没有设置元表, 就需要这里设置了 --设置完才能做到.NAME的时候访问到这个__index elseif obj == true then return rawget(self, key) --如果是obj, 说明不是中间路径了,已经找到了, end -- Cache this lookup rawset(self, key, obj) --如果是中间路径, 第二次访问就不走__index而是直接访问键了 import_type的一系列操作中设置了这个值 return obj end function metatable:__newindex() error('No such type: ' .. rawget(self,'.fqn'), 2) end --用于泛型的__call元表暂时省略 CS = CS or {} setmetatable(CS, metatable)
这里设置为元表,是因为这一切都是动态设置的,也就是说第一次访问的时候,会访问到这个__index,(后续访问就会直接访问到rawset设置到真实的表里面的字段了)。
举个例子吧,比如说CS.UnityEngine.Input的调用流程
第一次调用CS.UnityEngine的时候,CS表里没有UnityEngine这个键,所以会触发__index方法。此时fqn为空,会调用local obj = import_type(‘UnityEngine’)。由于此时只是路径,所以返回的obj=nil,此时把obj={[‘.fqn’] = fqn},因为这个obj其实不是obj只是调用链上的一部分,但是肯定要记录下这个链前面是什么方便后面访问的。顺便还要设置元表(毕竟需要入口嘛,不然.Input没法访问到了)。最后rawset(self, key, obj)就是CS[‘UnityEngine’] = obj, 也就是说下一次再调用CS.UnityEngine就不需要走元表了直接走table的键值对了。
后续第一次调用CS.UnityEngine.Input的时候,其实是UnityEngine.Input,而UnityEngine也就是我们上述存的那个obj, 其值也就是obj={[‘.fqn’] = fqn},所以这里fqn就是UnityEngine.Input了,然后调用C#层的import_type(‘UnityEngine.Input’)获得了obj=true,(其实并且这个import_type会设置UnityEngine[Input] = real_obj,并return这个cls_table)
第二次调用CS.UnityEngine.Input的时候,就不会走任何元表了,直接走table了。下面再看看关键的import_type是如何设置的这个cls_table
//ObjectTranslator.cs importTypeFunction = new LuaCSFunction(StaticLuaCallbacks.ImportType); loadAssemblyFunction = new LuaCSFunction(StaticLuaCallbacks.LoadAssembly); public void OpenLib(RealStatePtr L) { if (0 != LuaAPI.xlua_getglobal(L, "xlua")) { throw new Exception("call xlua_getglobal fail!" + LuaAPI.lua_tostring(L, -1)); } LuaAPI.xlua_pushasciistring(L, "import_type"); LuaAPI.lua_pushstdcallcfunction(L,importTypeFunction); LuaAPI.lua_rawset(L, -3); }
首先通过如上代码,将import_type放入xlua,下面是ImportType的实现
//StatistiLuaCallbacks.cs [MonoPInvokeCallback(typeof(LuaCSFunction))] public static int ImportType(RealStatePtr L) { try { 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) //关键函数 ---- 导入wrap or 反射 { LuaAPI.lua_pushboolean(L, true); } else { return LuaAPI.luaL_error(L, "can not load type " + type); } } else { LuaAPI.lua_pushnil(L); } return 1; } catch (System.Exception e) { return LuaAPI.luaL_error(L, "c# exception in xlua.import_type:" + e); } }
再来展开看看translator.GetTypeId(L, type)
//ObjectTranslator.cs public int GetTypeId(RealStatePtr L, Type type) { bool isFirst; return getTypeId(L, type, out isFirst); } 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 { LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName); if (LuaAPI.lua_isnil(L, -1)) //没有元表说明还没有注册过 { 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); } else { throw new Exception("Fatal: can not load metatable of type:" + type); } } } return type_id; } 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)) //这里先简单地只看生成过wrap文件的 { delayWrap.Remove(type); loader(L); } return true; }
如上是超级简化版,关键函数为loader(L); 这行代码后,会进入wrap的__Register函数中,wrap为根据具体的类生成的,大体如下:
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.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); }
由于我们要找的是CS.UnityEngine.GameObject的注册,所以我们这里只关注Utils.BeginClassRegister(type, L, __CreateInstance, 6, 0, 0);这一段代码。
public static void BeginClassRegister(Type type, RealStatePtr L, LuaCSFunction creator, int class_field_count, int static_getter_count, int static_setter_count) #endif { 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 SetCSTable(L, type, cls_table); //将这个cls_table设置到CS.path.class供lua调用 //略 }
关键点是SetCSTable,(当然这个函数只是注册到global CS中,具体这个表的配置根据反射、wrap有不同的调用地方,后续再说)
public static void SetCSTable(RealStatePtr L, Type type, int cls_table) { int oldTop = LuaAPI.lua_gettop(L); cls_table = abs_idx(oldTop, cls_table); LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE); LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); //就是取出来CS这个global变量 List<string> path = getPathOfType(type); for (int i = 0; i < path.Count - 1; ++i) { LuaAPI.xlua_pushasciistring(L, path[i]); if (0 != LuaAPI.xlua_pgettable(L, -2)) //包装pcall的gettable CS[path[i]] !=0代表出错了 { var err = LuaAPI.lua_tostring(L, -1); LuaAPI.lua_settop(L, oldTop); throw new Exception("SetCSTable for [" + type + "] error: " + err); } if (LuaAPI.lua_isnil(L, -1))//CS[path[i]]是空 { LuaAPI.lua_pop(L, 1); //把CS[path[i]]这个nil pop出来 LuaAPI.lua_createtable(L, 0, 0); LuaAPI.xlua_pushasciistring(L, path[i]); LuaAPI.lua_pushvalue(L, -2); /* -4 CS -3 {} -2 path[i] -1 {} */ LuaAPI.lua_rawset(L, -4); //CS[path[i]] = {} or 上一个循环的那个空表{}.path[i] = {} /* -2 CS -1 {} */ } else if (!LuaAPI.lua_istable(L, -1)) { LuaAPI.lua_settop(L, oldTop); throw new Exception("SetCSTable for [" + type + "] error: ancestors is not a table!"); } LuaAPI.lua_remove(L, -2); /* -1 {} */ } LuaAPI.xlua_pushasciistring(L, path[path.Count - 1]); LuaAPI.lua_pushvalue(L, cls_table); LuaAPI.lua_rawset(L, -3);// CS[className] = cls_table or 上面获得的链 //其实这里相当于只设置了指针,而具体cls_table的值是后面确定的 //反射的话是 makeReflectionWrap,有wrap文件的话是__Register中的某些东西 LuaAPI.lua_pop(L, 1); //弹出CS? 不一定,或者上面的路径 LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE); LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); //再次获得CS?????去掉这三行会报错==为什么 //我知道了,上面那个不一定是CS表,for (int i = 0; i < path.Count - 1; ++i)这一段是有用的啊 ObjectTranslatorPool.Instance.Find(L).PushAny(L, type); LuaAPI.lua_pushvalue(L, cls_table); LuaAPI.lua_rawset(L, -3); //可以通过type获得cls_table LuaAPI.lua_pop(L, 1); }
上面的注释已经解释的很明白了就不再赘述了,至此,CS.UnityEngine.GameObject就映射到了一个lua层的table中了,在上面的代码中通常叫做cls_table这个东西