tolua源码分析(五)lua使用C#的enum

tolua源码分析(五)lua使用C#的enum

上一节我们讨论了C#类是如何注册到lua的过程,以及lua调用C#函数时底层所做的事情。在此基础之上,本节我们来看看C#的enum是如何注册到lua的,它和一般类的注册有哪些区别。

老规矩,还是看官方提供的例子,这次是examples 10,代码如下:

string script =
    @"
        space = nil

        function TestEnum(e)        
            print('Enum is:'..tostring(e))        

            if space:ToInt() == 0 then
                print('enum ToInt() is ok')                
            end

            if not space:Equals(0) then
                print('enum compare int is ok')                
            end

            if space == e then
                print('enum compare enum is ok')
            end

            local s = UnityEngine.Space.IntToEnum(0)

            if space == s then
                print('IntToEnum change type is ok')
            end
        end
    ";

LuaState state = null;

new LuaResLoader();
state = new LuaState();
state.Start();
LuaBinder.Bind(state);

state.DoString(script);
state["space"] = Space.World;

LuaFunction func = state.GetFunction("TestEnum");
func.BeginPCall();
func.Push(Space.World);
func.PCall();
func.EndPCall();
func.Dispose();        
func = null;

例子很简单,就是把C#的UnityEngine.Space类型的enum传给了lua,并在lua层测试了tostring,ToInt,Equals等接口,验证了在lua层可以对enum判等,以及将一个int转换为enum,或者将enum转换为int等操作,输出结果如下:

tolua源码分析(五)lua使用C

首先我们来看下lua层是如何表示C#的enum。例子的第36行和第40行都是将enum push到lua层,相关代码如下:

public object GetEnumObj(Enum e)
{
    object o = null;

    if (!enumMap.TryGetValue(e, out o))
    {
        o = e;
        enumMap.Add(e, o);
    }

    return o;
}

public void Push(Enum e)
{
    if (e == null)
    {                
        LuaPushNil();
    }
    else
    {
        object o = GetEnumObj(e);
        PushUserData(o, EnumMetatable);
    }
}

由于enum是值类型,C#层使用了enumMap来缓存装箱后的object与enum的映射关系。其他的操作与普通类是相似的,EnumMetatable就是注册到lua层的Enum类reference。

接下来我们来看下C#的enum注册到lua的方法,可以在System_EnumWrap.Register方法中一览:

public static void Register(LuaState L)
{
    L.BeginClass(typeof(System.Enum), null);
    L.RegFunction("GetTypeCode", GetTypeCode);
    L.RegFunction("GetValues", GetValues);
    L.RegFunction("GetNames", GetNames);
    L.RegFunction("GetName", GetName);
    L.RegFunction("IsDefined", IsDefined);
    L.RegFunction("GetUnderlyingType", GetUnderlyingType);
    L.RegFunction("CompareTo", CompareTo);
    L.RegFunction("ToString", ToString);
    L.RegFunction("Equals", Equals);
    L.RegFunction("GetHashCode", GetHashCode);
    L.RegFunction("Format", Format);
    L.RegFunction("Parse", Parse);
    L.RegFunction("ToObject", ToObject);
    L.RegFunction("ToInt", ToInt);
    L.RegFunction("__tostring", ToLua.op_ToString);
    L.EndClass();
}

我们依次看例子中使用到的C#方法,首先是__tostring

public static int op_ToString(IntPtr L)
{
    object obj = ToLua.ToObject(L, 1);

    if (obj != null)
    {
        LuaDLL.lua_pushstring(L, obj.ToString());
    }
    else
    {
        LuaDLL.lua_pushnil(L);
    }

    return 1;
}

这里ToLua.ToObject会将lua栈上的userdata转换为object,也是通过userdata的index,在C#的object缓存中查找,然后返回,因此这里是不会产生gc的。

下面看一下ToInt方法:

static int ToInt(IntPtr L)
{
    try
    {
        object arg0 = ToLua.CheckObject<System.Enum>(L, 1);
        int ret = Convert.ToInt32(arg0);
        LuaDLL.lua_pushinteger(L, ret);
        return 1;
    }
    catch (Exception e)
    {
        return LuaDLL.toluaL_exception(L, e);
    }
}

同样地,这里的CheckObject也是在C#的object缓存中查找,只不过会进行类型检查,因此也不会有gc。在将取出的object转换为int时,由于使用的是Convert.ToInt32方法,也不会触发拆箱。该方法并不涉及类型转换,而是调用了一个接口返回转换结果:

 public static int ToInt32(object value) {
     return value == null? 0: ((IConvertible)value).ToInt32(null);
 }

接下来是和int类型判等的Equals方法:

static int Equals(IntPtr L)
{
    try
    {
        ToLua.CheckArgsCount(L, 2);
        System.Enum obj = (System.Enum)ToLua.CheckObject<System.Enum>(L, 1);
        object arg0 = ToLua.ToVarObject(L, 2);
        bool o = obj != null ? obj.Equals(arg0) : arg0 == null;
        LuaDLL.lua_pushboolean(L, o);
        return 1;
    }
    catch (Exception e)
    {
        return LuaDLL.toluaL_exception(L, e);
    }
}

我们看第7行,此时lua栈上的元素是一个number,返回到C#是double,它是一个值类型,然而ToLua.ToVarObject会将其转换为object,因此这里会触发装箱,也会产生gc。所以,在实际使用中,应当尽量避免在lua层对C#的enum与number进行比较。

再看例子中的第16行,直接使用了==操作符来比较spacee是否相等。这两者都是C# push到lua层的userdata,userdata只和它自身相等。由于enum的唯一性,它们来自于同一个C#缓存,对应的userdata index也相同,那么在lua层它们就是同一份userdata。

最后看一下例子中的第20行,它是在lua层将一个number转换为enum。IntToEnum定义在C#的UnityEngine_SpaceWrap类中,在了解它的具体实现之前,我们先来研究一下Space这个enum的注册过程:

public static void Register(LuaState L)
{
    L.BeginEnum(typeof(UnityEngine.Space));
    L.RegVar("World", get_World, null);
    L.RegVar("Self", get_Self, null);
    L.RegFunction("IntToEnum", IntToEnum);
    L.EndEnum();
    TypeTraits<UnityEngine.Space>.Check = CheckType;
    StackTraits<UnityEngine.Space>.Push = Push;
}

BeginEnum会调用到C层的tolua_beginenum函数:

LUALIB_API int tolua_beginenum(lua_State *L, const char *name)
{
	lua_pushstring(L, name);                               
    lua_newtable(L);                                       
    _addtoloaded(L);
    lua_newtable(L);
    lua_pushvalue(L, -1);
    int reference = luaL_ref(L, LUA_REGISTRYINDEX);            
    lua_pushlightuserdata(L, &tag);
    lua_pushnumber(L, 1);
    lua_rawset(L, -3);

    lua_pushstring(L, ".name");
    _pushfullname(L, -4);  
    lua_rawset(L, -3);

	lua_pushstring(L, "__index");
	lua_pushcfunction(L, enum_index_event);
	lua_rawset(L, -3);

	lua_pushstring(L, "__newindex");
	lua_pushcfunction(L, enum_newindex_event);
	lua_rawset(L, -3);	

	return reference;
}

函数的实现与tolua_beginclass类似,也是使用两个table,一个table表示enum本身,另一个table表示enum的metatable,保存了enum类的全称,以及访问enum类的属性或者方法的__index__newindex元方法。由于enum是不可被继承的,所以这里不需要考虑基类相关的内容。这里以__index元方法为例,来看看enum_index_event的实现:

static int enum_index_event (lua_State *L)
{
	lua_getmetatable(L, 1);									//stack: t, k, mt

	if (lua_istable(L, -1))
	{
		lua_pushvalue(L, 2);								//stack: t, k, mt, k
		lua_rawget(L, -2);									//stack: t, k, mt, v

		if (!lua_isnil(L, -1))
		{		
			return 1;
		}

		lua_pop(L, 1);										//stack: t, k, mt
		lua_pushlightuserdata(L, &gettag);		
		lua_rawget(L, -2); 									//stack: t, k, mt, tget

		if (lua_istable(L,-1)) 
		{
            lua_pushvalue(L,2);  							//stack: t k mt tget k
            lua_rawget(L,-2);								//stack: t k mt tget v

           	if (lua_isfunction(L,-1)) 
			{  
				lua_call(L, 0, 1);					
				lua_pushvalue(L,2); 				
				lua_pushvalue(L,-2); 				
				lua_rawset(L, 3);		
				return 1;			
			}

			lua_pop(L, 1);
        }			
	}

	lua_settop(L, 2);
	lua_pushnil(L);
	return 1;	
}

这里的实现与class_index_event相比,要简化许多。函数首先获取当前enum对象或者enum类的metatable,如果metatable存在,说明是已经注册过的enum,则先去metatable中直接查找要访问的key,如果查找成功直接返回即可;如果查找失败,则继续在metatable的get table中查找,如果查找到对应的get属性,则将其缓存后返回。由于enum不存在基类的说法,所以也不需要像class一样,进行循环查找。

现在我们回过头再看IntToEnum的具体实现:

static int IntToEnum(IntPtr L)
{
    int arg0 = (int)LuaDLL.lua_tonumber(L, 1);
    UnityEngine.Space o = (UnityEngine.Space)arg0;
    ToLua.Push(L, o);
    return 1;
}

这里直接将lua栈上返回的double转换为int,然后再转换为UnityEngine.Space类型,由于都只是值类型之间的转换,因此不存在拆装箱,也不会有gc。前面我们已经提到过C#层push enum到lua层时,是会从C#的缓存中取到enum对应的object,然后再将object push到lua层,所以这里也不会产生gc。

总结一下,在lua使用C#的enum时,C#传递enum到lua层不会产生gc,在lua层number与enum类型互相转换不会产生gc,两个enum直接比较也不会产生gc。但是,enum与number类型进行比较时,会有一次number类型转换到object类型的操作,触发装箱并产生gc。这一点,其实我们可以自己进行针对性优化。

下一节我们将研究平时开发中相当常用的,C#的委托/事件注册lua函数的实现。

如果你觉得我的文章有帮助,欢迎关注我的微信公众号 我是真的想做游戏啊

Reference

[1] Does Convert.ToInt32(object) skip unboxing?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值