CS全局表的创建

使用示例

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这个东西

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

大纲

Share the Post:
滚动至顶部