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

news2024/11/15 9:57:52

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?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/547601.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

互联网医院资质代办|互联网医院牌照的申请流程

随着互联网技术的不断发展&#xff0c;互联网医疗已经逐渐成为人们关注的热点话题。而互联网医院作为互联网医疗的一种重要形式&#xff0c;也越来越受到社会各界的关注。若想开展互联网医院业务&#xff0c;则需要具备互联网医院牌照。那么互联网医院牌照的申请流程和需要的资…

算法——归并排序和计数排序

Ⅰ. 归并排序 1. 基本思想 归并排序&#xff08; MERGE-SORT &#xff09;是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法&#xff08; Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;…

Python爬虫| 一文掌握XPath

本文概要 本篇文章主要介绍利用Python爬虫爬取付费文章&#xff0c;适合练习爬虫基础同学&#xff0c;文中描述和代码示例很详细&#xff0c;干货满满&#xff0c;感兴趣的小伙伴快来一起学习吧&#xff01; &#x1f31f;&#x1f31f;&#x1f31f;个人简介&#x1f31f;&…

公司来了个00后,我愿称之为卷王之王,卷的让人崩溃...

前几天我们公司一下子来了几个新人&#xff0c;看样子好像都是一些00后&#xff0c;这些年轻人是真能熬啊&#xff0c;本来我们几个老油子都是每天稍微加会班就打算下班走了&#xff0c;这几个新人一直不走&#xff0c;搞得我们也不好提前走。 2023年春招已经过去了&#xff0…

网络安全前景怎么样?怎么自学?看这一篇就够了

一、网络安全前景 网络安全行业细分岗位比较多&#xff0c;目前需求量比较大的几类网络安全岗位有渗透测试、安全运维、等保测评等&#xff0c;在岗位需求量和薪资待遇方面都比较可观。 这时很多人就会问&#xff0c;网络安全人才需求量这么大&#xff0c;进入行业的人就会越来…

【redis】案例--迷你版微信抢红包

系列文章目录 文章目录 系列文章目录前言在这里插入图片描述 一、业务描述二、需求分析三、架构设计关键点拆红包算法 二倍均值算法 图解 四、编码实现 RedPackageController整体思路&#xff1a;发红包代码进入拆分红包算法抢红包代码 五、多学一手 前言 一、业务描述 二、需求…

鲁棒优化入门(四)——超详细讲解:两阶段鲁棒优化以及列与约束生成算法(CCG)的matlab+yalmip代码实现

本文的主要参考文献&#xff1a; Zeng B , Zhao L . Solving Two-stage Robust Optimization Problems by A Constraint-and-Column Generation Method[J]. Operations Research Letters, 2013, 41(5):457-461. 1.两阶段鲁棒优化问题的引入 鲁棒优化是应对数据不确定性的一种优…

从零玩转设计模式之单例模式-danlimos

title: 从零玩转设计模式之单例模式 date: 2022-12-12 12:41:03.604 updated: 2022-12-23 15:35:29.0 url: https://www.yby6.com/archives/danlimos categories: - 单例模式 - 设计模式 tags: - Java模式 - 单例模式 - 设计模式 前言 单例设计模式是23种设计模式中最常用的设…

面试题背麻了,花3个月面过华为测开岗,拿个26K不过分吧?

计算机专业&#xff0c;代码能力一般&#xff0c;之前有过两段实习以及一个学校项目经历。第一份实习是大二暑期在深圳的一家互联网公司做前端开发&#xff0c;第二份实习由于大三暑假回国的时间比较短&#xff08;小于两个月&#xff09;&#xff0c;于是找的实习是在一家初创…

基于 ZYNQ 的电能质量系统高速数据采集系统设计

随着电网中非线性负荷用户的不断增加 &#xff0c; 电能质量问题日益严重 。 高精度数据采集系统能够为电能质 量分析提供准确的数据支持 &#xff0c; 是解决电能质量问题的关键依据 。 通过对比现有高速采集系统的设计方案 &#xff0c; 主 控电路多以 ARM 微控制器搭配…

抖音seo排名系统/账号矩阵源码关键词开发部署

抖音seo排名系统/账号矩阵源码关键词开发技术 如何提高 抖音 搜索排名&#xff1f;如何优化抖音搜索排名&#xff1f; 部分代码分析&#xff1a;场景&#xff1a;创建一个Tree()函数来实现以下特性&#xff0c;当我们需要时&#xff0c;所有中间对象 branch1、branch2 和 bra…

Mysql常见的索引模型

目录 有序数组哈希表二叉搜索树B-TreeBTree 有序数组 我们指定一个列为索引&#xff0c;然后按照这个列的值排序&#xff0c;以有序数据存放入数据表中&#xff0c;如下所示 这样&#xff0c;我们在查找数据的时候&#xff0c;就可以通过id这个列&#xff0c;在数据表中进行二…

阿里 P8 整理的《百亿级并发系统设计》实战手册,实在是太香了

面试官问&#xff1a;如何设计一个高并发系统&#xff1f; 说实话&#xff0c;如果面试官问你这个题目&#xff0c;那么你必须要使出全身吃奶劲了。为啥&#xff1f;因为你没看到现在很多公司招聘的 JD 里都是说啥有高并发经验者优先。 如果你确实有真才实学&#xff0c;在互…

9-《数据结构》

[TOC](9-《数据结构》 一、数组1.稀疏数组 二、链表三、队列四、栈五、树5.1 完全二叉树5.2 满二叉树&#xff1a;深度为k且有2^k-1个结点的二叉树称为满二叉树**5.3 二叉排序树&#xff08;二叉搜索树、二叉查找树&#xff09;5.4 平衡二叉树&#xff1a;5.5 红黑树 六、堆七、…

文件夹加密码的方法有哪些?文件夹加密方法盘点

在我们使用电脑的过程中&#xff0c;我们会将一些重要的数据放入文件夹内进行统一管理&#xff0c;为了保护数据安全&#xff0c;文件夹加密码通常是一个不错的选择。那么文件夹该怎么加密码呢&#xff1f;电脑文件夹加密码的方法有哪些呢&#xff1f; 文件夹加密码方法 首先…

opencv读取图片

opencv是一款非常强大的图像处理库&#xff0c;可以用来进行图像的处理。opencv库提供了丰富的工具&#xff0c;比如图像缩放&#xff0c;旋转&#xff0c;倾斜校正&#xff0c;自动对齐等等&#xff0c;使用这些工具可以很方便的进行图像的处理。那么你知道 opencv怎么读取图片…

Java学习笔记-04

目录 静态成员 mian方法 多态 抽象类 接口 内部类 成员内部类 静态内部类 方法内部类 匿名内部类 静态成员 static关键字可以修饰成员方法&#xff0c;成员变量被static修饰的成员&#xff0c;成员变量就变成了静态变量&#xff0c;成员方法就变成了静态方法static修…

Java流程控制(一)

⭐ 控制语句⭐ 条件判断结构(选择结构)⭐ switch 语句 做任何事情事情都要遵循一定的原则&#xff0c;毕竟不以规矩&#xff0c;不成方圆&#xff0c;例如&#xff0c;到图书馆去借书&#xff0c;就必须要有借书证&#xff0c;并且借书证不能过期&#xff0c;这两个条件缺一不可…

【新星计划回顾】第二篇学习计划-通过定义变量简单批量模拟数据

&#x1f3c6;&#x1f3c6;又到周末&#xff0c;最近这段时间非常忙&#xff0c;虽然导师首次参与新星计划活动已经在4月16日圆满结束&#xff0c;早想腾出时间来好好整理活动期间分享的知识点。 &#x1f3c6;&#x1f3c6;非常感谢大家的支持和活动期间的文章输出&#xff0…

软件测试基础(V模型W模型)

软件测试基础 1. 软件测试的生命周期 需求分析&#xff1a;站在用户的角度查看需求逻辑是否正确&#xff0c;是否符合用户的需求和行为习惯。站在开发的角度思考需求是否可以实现&#xff0c;或者说实现起来难度高不高测试计划&#xff1a;指定测试计划&#xff08;包括不限于…