使用示例
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这个东西