(\"math.max\");Debug.Log(\"max:\" + max(32, 12));就那么简单,把lua的math.max绑定到C#的max变量后,调⽤就和⼀个C#函数调⽤差不多了,⽽且,最最重要的是,执⾏了“XLua/GenerateCode”后,max(32, 12)调⽤是不产⽣(C#)gc alloc的,既优雅,⼜⾼效!(更详细的可以看XLuaDoc下的⽂档。)xLua全局观
易⽤性:编辑器下⽆需⽣成代码⽀持所有特性
xLua的易⽤不仅仅体现在编程,还体现在⽅⽅⾯⾯的细节考虑,甚⾄考虑到团队配合⼯作流。xLua仅有两个菜单选择,分别是⽣成代码和清除⽣成代码。在菜单之外,甚⾄只需要在build⼿机版本前执⾏⼀下“Generate Code”即可(这也有API可集成到项⽬的⾃动化打包流程)。
这就是xLua的特⾊功能之⼀:编辑器下⽆需⽣成代码⽀持所有特性。之所以做这个功能,是因为有的项⽬反馈,“⽣成代码”对于策划美术太过遥远,教了很久还是⽼忘;还有个⼤项⽬反馈说由于代码很多,每次⽣成代码后,Unity3D都要转很久。扩展性:授之以鱼,不如授之以渔
开发中我们往往要⽤到很多东西,⽐如⽤PB和后台交互,解析json格式的配置⽂件等等。虽说我们都可以在C#那找到相应的库,然后通过xLua去使⽤这些库,但这效率不⾼,最好能有相应Lua的库。
不少⽅案是直接集成⼀些常⽤的Lua库,但这带来些新问题:这些库不⼀定⽤到,却增⼤安装包;集成的库也不⼀定符合项⽬习惯:json解析有⼈喜欢rapidjson,有⼈爱⽤cjson,所谓众⼝难调;对于某些项⽬,这些库还是不够,还是得⾃⼰去想办法加;腾讯团队的设计原则是授之以鱼,不如授之以渔,因此xLua:
提供了接⼝、教程,在不修改xLua代码的情况下,开发者可以根据个⼈喜好加⼊库;
通过cmake实现跨平台编译,可以选择伴随xLua⼀起编译,修改⼀个makefile⽂件,搞定各平台编译。
除了很⽅便加⼊第三⽅Lua插件,xLua的⽣成引擎⽀持⼆次开发,可以编写⽣成插件,⽣成⾃⼰所需的⼀些代码以及配置。性能的保证
游戏的性能备受关注,因此任何模块的变化都需要尽可能不降低甚⾄调优游戏整体的性能。xLua设计原则是在保证运⾏效率的前提下,尽量的保证开发效率。
对于性能这块,有⼏个⾄关重要的版本:
第⼀个版本1.0.0在05年3⽉份发布,当时delegate,interface作为最主要的C#访问Lua的设定,从接⼝层⾯避免了boxing、unboxing、gcalloc,这是⼀个良好的起点。做⼀个通⽤组件的都知道,接⼝⼀开始设计不合理导致的问题很难解决,别⼈已经⽤了,甚⾄已经养成习惯了,很难纠正。
ps:说起这习惯,有的从别的lua插件转为使⽤xLua的童鞋,⼀开始习惯⽤LuaFunction.Call去调⽤lua(xLua也保留了这接⼝,可⽤于性能要求不⾼的场合),他们后期就痛苦了,还得⼀个个地⽅的改回来。
第⼆个很重要的版本是2.0.0(06年3⽉发布),这版本主要⽬标就性能优化,因为当时有个对性能要求极其严苛的项⽬想⽤lua,严苛到什么程度呢?他们觉得C#性能都不放⼼,战⽃系统打算⽤C++写。那版本我们把虚拟机切换到luajit,加⼊了lazyload技术,逐⾏语句的优化,甚⾄关键地⽅不⽤C#提供的容器,⾃⼰写专⽤的(⽐Dictionary实测性能⾼4倍)。。。可以认为我们重做了⼀个xLua。最终他们的选型测试结论是选xLua。
后来和⼀些项⽬的交流发现,项⽬组很关注gc alloc这指标,甚⾄⽐lua和C#间的互调性能指标还要看重。于是有了2.1.0版本(06年7⽉发布),这版本主要⽬标是gc优化,我们重写了反射,反射调⽤的gc减少到原来的⼏分之⼀,性能提⾼了3倍左右。我们设计了⼀个全新的复杂值类型⽀持⽅案,该⽅案⽀持的类型更多(只要struct的字段都是值类型即可),包括⽤户⾃定义的struct(别的⽅案都不⽀持),也更省内存(Vector3为例,内存占⽤只有别的⽅案的30%)。
但也有劣势的地⽅,⽐如你调⽤Vector3上的⼀些⽅法,会⽐ulua、slua要差,因为后⾯两个把Vector3⽤lua重新实现了,这类耗时不⼤的运算相⽐lua和C#直接的适配成本⼩太多了,直接在lua做更划算,不过这差距仅限于那⼏个ulua、slua完全重新实现的类。
上⾯只是三个重⼤节点,我们觉得性能是⼀个需要持续关注的点:平时想到⼀个好点⼦,就会改改,测试下,有提升就加⼊;建⽴性能基线,防⽌某个新功能的加⼊,某个bug的修改把性能给改坏了。
xLua内置Lua代码profiler;⽀持真机调试。⽬前lua profiler只是⼀个⼩⼯具,所以没有做图形化界⾯,典型的⼀个报告如下:
⽹上也有类似的⼯具,我们这个的优势是对C#函数的⽀持以及luajit下更为准确。
真机调试⽀持各lua插件都⼀样,就是把ZeroBraneStudio调试需要⽤到的luasocket库预先编译进去⽽已,没什么值得介绍的地⽅。技术实现的细节泛型
泛型类型除了运⾏时动态实例化之外都⽀持,⽽运⾏时动态实例化需要jit的⽀持,iOS下⾏不通。举个例⼦,如果你配了对Dictionary ⽣成代码,那这个类型是可以⽤的,但如果你新更新的lua代码,想⽤⼀个Dictionary ,这个类型之前没⽣成代码,⽽且C#⾥头也没任何地⽅使⽤过,这就不⽀持。静态实例化的泛型,其实和⾮泛型类型处理上没区别。委托事件的封装
委托封装是根据委托的接⼝⽣成⼀段操作lua栈的代码作为委托的实现。举个例⼦就很好懂了。⽐如对于委托:delegate double Add(doublea, double b),我们⽣成如下代码:
public double SystemDouble(double a, double b){ RealStatePtr L = luaEnv.L; int err_func =LuaAPI.load_error_func(L, errorFuncRef);LuaAPI.lua_getref(L, luaReference); LuaAPI.lua_pushnumber(L, a); LuaAPI.lua_pushnumber(L, b); int __gen_error = LuaAPI.lua_pcall(L,2, 1, err_func);if (__gen_error != 0) luaEnv.ThrowExceptionFromError(err_func - 1); double __gen_ret = LuaAPI.lua_tonumber(L, err_func +1); LuaAPI.lua_settop(L, err_func - 1); return __gen_ret;}这代码把调⽤转给lua函数,调⽤委托就是调⽤这函数。
其它⽅案都有delegate的⽀持,⼀般仅⽤于在lua侧主动传递/设置⼀个lua函数到C#,⽽xLua⽀持更为完整,⽐如:
⽀持C#主动⽤delegate来引⽤⼀个lua函数。⽤delegate代替类似object[] Call(params object[] args)的接⼝调⽤lua最⼤的好处是可以避免值类型传递时的boxing/unboxing,还有参数数组,返回值数组的gc alloc;⽀持返回delegate的delegate,可对应到lua的⾼阶函数;
作为这技术的⼀个延伸,xLua⽀持⽤⼀个c# interface引⽤⼀个lua table,这个特性和⼀些IOC框架配合可以实现C#和Lua间⽆感知(模块间都通过interface耦合,然后由框架去组装)。⽆缝⽀持⽣成代码及反射
⽣成代码固然重要,已然是各⼤主流⽅案的标配。
反射有的⽅案明确不⽀持,但从项⽬的反馈来说,也是⾄关重要的:有的项⽬代码很多,已经接近苹果的80M Text段的,对他们来说,代码量⼤⼩关乎到能否发布,反射⽅式性能不如⽣成代码,但对安装包影响⼩。这的⽆缝有两个含义:
1. 两者在⽀持的特性以及特性的使⽤⽅式都是⼀致的,两者⽅式间切换,业务逻辑代码不⽤修改,改改配置就可以了;
2. 两者⽆缝配合,⽐如⼀个继承链上,任意⼀个类都可以选择⽣成代码或者反射,⽐如⼦类选择⽣成代码,⽗类由于不常⽤选择了反射,还是可以在⼦类对象上调⽤⽗类的⽅法;对于il2cpp的stripping,xLua也考虑到了,只要你对⼀个类配置了ReflectionUse,会⾃动⽣成Unity的link.xml配置⽂件,将该类型列为不剪
裁。
其他Lua插件⼀览
在xLua之外,还有其他的Lua插件,如 uLua、SLua、C#light等。
(1) ulua应⽤项⽬是最多的,由于开源得早,名⽓也最⼤,这是它很⼤的优势。腾讯也有项⽬⽤ulua,反馈⽐较多的问题是它版本的前后兼容问题:
ulua最早是⼀个叫LuaInterface开源库的Unity移植,在2015年初换成cs2lua,⼜在2016年初换成tolua c#,只所以说“换”,是因为这从API⾓度看可认为三个不同的产品,它们间很难升级,⽽且是每换⼀次,之前的版本就彻底不维护了,这给项⽬带来很⼤的困扰。ulua的第⼀个版本纯反射,并不实⽤,已经淡出市场,现存应⽤⽤后两个版本居多。cstolua版本接⼝⽐较混乱:它保留了第⼀版ulua接⼝之余,搞了⼀套新接⼝,这两套接⼝之间并不正交,也不是后者完全替代前者,让⼈有点⽆所适从。到了tolua c#版本,这问题解决了,但同时也把反射特性(⽼接⼝)给废了。不过总体来说,ulua在向好的⽅向⾛。
(2) slua代码质量⽐cstolua好很多(很多⼈当时选slua的理由),部分⽀持反射。性能按我们的测试⽤例整体⽐tolua c#略低,另外代码质量对⽐tolua c#已经形成不了明显优势。(3) C#light,个⼈觉得主要有两个不⾜:
按其实现原理来说,性能不会靠谱,到不了⼿机上实⽤的地步;
由于不完整⽀持C#,本质上只是另⼀种叫C#light的语⾔(C# like?名字倒很贴切),这两者代码配合起来也复杂,甚⾄它能做到⽐C#和lua配合更复杂些
事实也证明了,C# light基本淡出市场,可以忽略不计了。
(4) LSharp是C# light作者的后续作品,倒是可以期盼些,从il层⾯执⾏,这两个问题有望改善,可惜后⾯没了下⽂(不维护了)。相⽐之下,腾讯在设计xLua时,实现的功能更全,这“全”体现在C#的特性⽀持得更全些,lua虚拟机版本⽀持更全;更易⽤些,⽐如编辑器下不⽤⽣成代码;另外,性能也不⽐它们差。
说到功能更全,可能有⼈抱怨并没有pb,json,sqlite等等功能。其实稍熟悉lua的⼈都知道,那只是把⼀些现成lua扩展编译进去⽽已,算不上是它做了这些功能。预集成好处是⽅便,坏处是没选择的余地,⽤不上的东西会占空间,⽤得上的东西也不⼀定是你喜欢的库。xLua的lua库基于cmake编译,要加这些库门槛很低,有教程,改⼀个Makefile搞定各平台编译。在C#测也提供了api来初始化这些库。总⽽⾔之,xLua的原则是授之以渔。xLua的灵感来源
xLua⽴项当初,考察了当时能找到的所有⽅案,并分析各⽅案优劣,定出第⼀个版本的特性,⼤体是基于NLua基础上加上代码⽣成。介绍下NLua,NLua的作者就是LuaInterface的作者,NLua可以认为是LuaInterface的升级版,⽽前⾯也说了,第⼀版uLua是LuaInterface的Unity移植版本,也不能算原创。
因为是“站在”⽣成代码当时有看过cstolua的实现(那时还没挂ulua的牌),觉得它通过硬编码字符串拼接的⽅式维护性不太好,就⽤模版来做。感觉这步是⾛对了,后续⽣成代码调整起来⽐较简单,这对性能调优很有好处。
经过⼗多个版本的迭代,优化,现在NLua的影⼦⽐较淡了(NLua仅⽀持反射,⽽xLua的反射在2.1.0版本已经完全重写),就剩下C#引⽤类型对象在lua的表达的思路没变。
此外,遇到需要调整较⼤的bug,我们也会先看同类插件是不是已经解决了,对⽐他们的修改⽅案和我们的,选更适合的。xLua背后的研发与团队
xLua⽬前迭代了⼗多个版本,从第⼀个项⽬开始,平均⼀个⽉⼀个版本。研发团队⼈员⽬前有⼀个全职开发。测试使⽤的是腾讯互娱的公有资源,很规范:有⼀套不断补充的功能⾃动化⽤例,性能测试也建⽴了基线,确保不会因为功能迭代⽽影响性能。腾讯互娱有专门的客户端兼容性测试实验室,⾄少中版本号以上的变动我们会提交给他们针对top 100的机型进⾏兼容性测试。
⾄于lua,luajit的更新跟进,先说luajit吧,luajit变动不⼤,我第⼀次⽤luajit是11年,那时⽀持到lua5.1,现在也还是lua5.1,中间只是⼀些bug的修复,性能优化,或者新平台⽀持等,我们要做事情不多。⽽lua中版本间差别还是蛮⼤的,但中版本变动并不频繁,从5.1到5.2⽤了6年,从5.2到5.3⽤了3年,5.3是2015年初发布的,我个⼈觉得到下⼀次中版本变动会很久,不亚于甚⾄⼤于5.1到5.2的时间跨度(5.2个⼈认为只是⼀个过渡版本)。
⼩版本⼀般改改bug,等稳定后直接升级就可以了,不需要做很多事情,⽬前xLua的lua版本⽤的是lua的最新版本5.3.3。聊聊C#,谈谈Lua
C#在开发效率和运⾏效率平衡得很好,语⾔特性也⽐较全,个⼈觉得是很优秀的⼀门语⾔。在Unity3D上的缺憾主要是其mono版本太低,⼀些很古⽼的bug,⽐如著名的foreach性能问题很多个版本都没解决,新的特性,⽐如await⼜不⽀持。
另外在⼿机平台iOS不允许应⽤下载native code运⾏,jit,刚好把mono应⽤的热更新给堵死了,要是mono虚拟机能够做到像luajit那样,jit⾛不通就⽤interpret模式,其实就没lua或者其它热更新⽅案什么事了。
⽽lua被称为游戏脚本之王,在游戏领域应⽤⽐较⼴泛,它设计之初就考虑到嵌⼊式领域,⽐如相对它提供的特性来说,它体积⾮常⼩,启动⼀个vm占资源也不多,性能也是脚本⾥头的佼佼者。
lua相对C#⽽⾔,⾸先是它⽀持解析执⾏,进⽽⽀持热更新。⽽免编译对开发效率提升也是蛮⼤的,特别是较⼤的项⽬。
lua的动态类型有利有弊,好的是没有编译期的类型检查,快速开发⽐较有优势,特别在需求三天两头就变的游戏领域。缺点是要做出健壮的软件得有⼤量的测试来保证,还有由于要做运⾏期检查,性能会⽐静态类型语⾔低。
lua的⼀⼤特⾊是语⾔级的协程(coroutine)的⽀持,⽐Unity3D基于generator模拟的协程要好很多,对于复杂异步业务逻辑编写很有帮助,xLua的配套例⼦有范例(ps⼀下,Unity3D的mono版本升级到⽀持await的话,是更理想的异步⽅案)。
⾄于C#和lua间如何配合,可能每个⼈都有不同的看法,但⾄少有⼀点是确定的:需求变更⼤,预计很可能需要热更的地⽅,⽤lua。当然,也可以尝试最新的开发模式,全C#开发,lua fix bug。写在最后
xLua应该还有不⾜,我们会在发现的第⼀时间去修改。腾讯xLua团队极度欢迎⼤家在发现不⾜之后提出反馈。