腾讯开源手游热更新方案,Unity3D下的Lua编程

标签: 腾讯 开源 手游 | 发表时间:2017-01-05 02:58 | 作者:
出处:http://news.cnblogs.com/

xLua 是 Unity3D 下 Lua 编程解决方案,自 2016 年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能、易用性、扩展性而广受好评。现在腾讯已经将 xLua 开源到 GitHub。

2016 年 12 月末,xLua 刚刚实现新的突破:全平台支持用 Lua 修复 C# 代码 bug。

目前 Unity 下的 Lua 热更新方案大多都是要求要热更新的部分一开始就要用 Lua 语言实现,不足之处在于:

  1. 接入成本高,有的项目已经用 C# 写完了,这时要接入需要把需要热更的地方用 Lua 重新实现;

  2. 即使一开始就接入了,也存在同时用两种语言开发难度较大的问题;

  3. Lua 性能不如C#;

xLua 热补丁技术支持在运行时把一个 C# 实现(函数,操作符,属性,事件,或者整个类)替换成 Lua 实现,意味着你可以:

  1. 平时用 C# 开发;

  2. 运行也是C#,性能秒杀 Lua;

  3. 有 bug 的地方下发个 Lua 脚本 fix 了,下次整体更新时可以把 Lua 的实现换回正确的 C# 实现,更新时甚至可以做到不重启游戏;

这个新特性 iOS,Android,Window,Mac 都测试通过了,目前在做一些易用性优化。

那么,腾讯开源的 xLua 究竟是怎样的技术?它是为何如此设计的?更令人关心的是,xLua 的性能如何?带着这些问题,InfoQ 对其作者进行了采访并将内容整理成文。

技术背景

腾讯自研手游,就我了解的项目来说,大多数游戏引擎都是 Unity3D,少数用 coco2d。

xLua 这个插件具体用到了哪些游戏中?虽说 xLua 是 2015 年 3 月就完成了第一个版本,但由于当时项目组热更的意识并没有很普遍,需求不是很强烈,xLua 的开发资源都调到更紧急的项目了。直到 15 年年底正式集成到我们的 apollo 手游开发框架,才迎来 xLua 的第一个项目。到目前为止,我们已知的应用了 xLua 的项目有十多个,其中不乏一些重量级 IP,或者按星级标准打造的产品。

在 xLua 之前,面对 iOS 无法热更新的问题,有用 ulua 的,有用 slua 的,也有项目用自研的脚本语言,不过当时用人更新的项目也不多。

热更新流程

手游的热更新流程很简单,只是启动时检测下是否有新版本文件,有的话就下载覆盖老文件,然后启动。

下载的文件如果是图片,模型这些是没问题的,但如果是 Unity 原生的代码逻辑,无论是以前的 Mono AOT 或者后来的 il2cpp,都是编译成 native code,iOS 下是跑不了的。

解决办法就一个,别用 native code,别用 jit,解析执行就可以了。包括 xLua 在内的所有热更新支持方案都是通过“解析执行”来实现代码逻辑热更新。

来自 xLua 的 Hello world

(1)三行代码跑 lua 脚本

一个完整的例子仅需 3 行代码:

下载 xLua 后解压到 Unity 工程 Assets 目录下,建一个 MonoBehaviour 拖到场景,在 Start 里头加上这么三行:

XLua.LuaEnv luaenv = new XLua.LuaEnv ();
luaenv.DoString ("CS.UnityEngine.Debug.Log ('hello world')");
luaenv.Dispose ();

运行就可以看到 Console 打印的 hello world。

  1. 第一和第三行分别 LuaEnv 的创建以及销毁,所谓 LuaEnv 可以理解为 lua 虚拟机,往往整个工程一个虚拟机即可:

  2. DoString 里头可以是任意合法的 lua 代码,例子中调用了 UnityEngine.Debug.Log 接口打印了一个 log(C#的静态函数在 CS 下直接可用);

(2)C#调用 lua 系统函数 math.max

xLua 支持把一个 Lua 函数绑定到C# delegate。

我们先声明一个 delegate,并为它加上 CSharpCallLua 标签:

[XLua.CSharpCallLua]public delegate double LuaMax (double a, double b);

然后在上面那例子加上这么两行(luaenv 销毁前):

var max = luaenv.Global.GetInPath

  ("math.max");Debug.Log ("max:" + max (32, 12));

就那么简单,把 lua 的 math.max 绑定到 C# 的 max 变量后,调用就和一个 C# 函数调用差不多了,而且,最最重要的是,执行了“XLua/Generate Code”后,max (32, 12) 调用是不产生(C#)gc alloc 的,既优雅,又高效!(更详细的可以看 XLua\Doc 下的文档。)

xLua 全局观

(1)易用性:编辑器下无需生成代码支持所有特性

xLua 的易用不仅仅体现在编程,还体现在方方面面的细节考虑,甚至考虑到团队配合工作流。

xLua 仅有两个菜单选择,分别是生成代码和清除生成代码。在菜单之外,甚至只需要在 build 手机版本前执行一下“Generate Code”即可(这也有 API 可集成到项目的自动化打包流程)。

这就是 xLua 的特色功能之一:编辑器下无需生成代码支持所有特性。

之所以做这个功能,是因为有的项目反馈,“生成代码”对于策划美术太过遥远,教了很久还是老忘;还有个大项目反馈说由于代码很多,每次生成代码后,Unity3D 都要转很久。

(2)扩展性:授之以鱼,不如授之以渔

开发中我们往往要用到很多东西,比如用 PB 和后台交互,解析 json 格式的配置文件等等。虽说我们都可以在 C# 那找到相应的库,然后通过 xLua 去使用这些库,但这效率不高,最好能有相应 Lua 的库。

不少方案是直接集成一些常用的 Lua 库,但这带来些新问题:这些库不一定用到,却增大安装包;集成的库也不一定符合项目习惯:json 解析有人喜欢 rapidjson,有人爱用 cjson,所谓众口难调;对于某些项目,这些库还是不够,还是得自己去想办法加;

腾讯团队的设计原则是授之以鱼,不如授之以渔,因此 xLua:

  • 提供了接口、教程,在不修改 xLua 代码的情况下,开发者可以根据个人喜好加入库;

  • 通过 cmake 实现跨平台编译,可以选择伴随 xLua 一起编译,修改一个 makefile 文件,搞定各平台编译。

  • 除了很方便加入第三方 Lua 插件,xLua 的生成引擎支持二次开发,可以编写生成插件,生成自己所需的一些代码以及配置。

(3)性能的保证

游戏的性能备受关注,因此任何模块的变化都需要尽可能不降低甚至调优游戏整体的性能。 xLua 设计原则是在保证运行效率的前提下,尽量的保证开发效率。

对于性能这块,有几个至关重要的版本:

第一个版本 1.0.0 在 05 年 3 月份发布,当时 delegate,interface 作为最主要的 C# 访问 Lua 的设定,从接口层面避免了 boxing、unboxing、gc alloc,这是一个良好的起点。做一个通用组件的都知道,接口一开始设计不合理导致的问题很难解决,别人已经用了,甚至已经养成习惯了,很难纠正。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 库预先编译进去而已,没什么值得介绍的地方。

技术实现的细节

(1) 泛型

泛型类型除了运行时动态实例化之外都支持,而运行时动态实例化需要 jit 的支持,iOS 下行不通。举个例子,如果你配了对 Dictionary<int, string>生成代码,那这个类型是可以用的,但如果你新更新的 lua 代码,想用一个 Dictionary<int, double>,这个类型之前没生成代码,而且 C# 里头也没任何地方使用过,这就不支持。静态实例化的泛型,其实和非泛型类型处理上没区别。

(2) 委托事件的封装

委托封装是根据委托的接口生成一段操作 lua 栈的代码作为委托的实现。举个例子就很好懂了。比如对于委托:delegate double Add (double a, 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 耦合,然后由框架去组装)。

(3) 无缝支持生成代码及反射

生成代码固然重要,已然是各大主流方案的标配。

反射有的方案明确不支持,但从项目的反馈来说,也是至关重要的:有的项目代码很多,已经接近苹果的 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 团队极度欢迎大家在发现不足之后提出反馈。

本文链接

相关 [腾讯 开源 手游] 推荐:

腾讯开源手游热更新方案,Unity3D下的Lua编程

- - 博客园_新闻
xLua 是 Unity3D 下 Lua 编程解决方案,自 2016 年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能、易用性、扩展性而广受好评. 现在腾讯已经将 xLua 开源到 GitHub. 2016 年 12 月末,xLua 刚刚实现新的突破:全平台支持用 Lua 修复 C# 代码 bug.

腾讯手游大起底之向创业团队转型

- - 博客园_新闻
向创业团队转型,晚上 12 点前不回家. 美国著名企业史学家艾尔弗雷德·钱德勒(Alfred D.Chandler)指出:战略重点决定着组织结构,战略重点的转移决定着组织结构的调整,组织结构制约着战略重点的实施. 由于此前团队存在严重的管理和结构问题,在新的战略指引下,姚晓光紧接着必须对组织架构作出调整,否则将无法实现已经设定的战略意图.

腾讯推出HTML5的开源专业级图像处理引擎

- - 互联网的那点事
腾讯Web前端  AlloyTeam 近日推出了最新开源项目:一个基于HTML5技术的专业级图像处理引擎—— AlloyImage(简称AI),以及一个在线Web图像处理平台—— AlloyPhoto(简称AP). 这预示着腾讯的Web前端团队将在底层基础技术层面做深入研究,并将更多的为业界提供基础技术服务,同时也将与 HTML5梦工场一起推动HTML5技术在业界的广泛深入的应用.

腾讯AlloyTeam再次发力:开源HTML5图像处理引擎AlloyImage

- - InfoQ cn
近日,AlloyTeam的 腾讯Web前端Alloy团队Blog再次引起了业界的广泛关注,原因就是该团队近日推出了最新的开源项目:一个基于HTML5技术的专业级图像处理引擎——AlloyImage(简称AI),以及一个在线Web图像处理平台——AlloyPhoto(简称AP). 这预示着腾讯的Web前端团队将在底层基础技术层面做深入研究,并将更多的为业界提供基础技术服务.

腾讯的内涵图

- keeno - 阿禅日记
2011年5月31日10:33,腾讯QQ 浏览器首页的截图如下:. 内涵关键词:释放、aiww、64. 1小时后,腾讯将图片替换掉了. 对这位如此有内涵的经理或编辑或设计师致敬. © Jason Ng for 阿禅日记, |. 不要用中国手机号来找回Gmail密码.

腾讯电商帝国

- 以外 - 互联网的那点事...
腾讯宏伟的电子商务战略开始逐渐浮出水面. 5月30日,腾讯正式对电子商务业务的内部组织架构进行重组,机构更加复杂全面的电子商务业务线取代了原来的电子商务部,同时进行近十位中、高层管理人员的职位变动及内部人员的调整. 同时,腾讯一直在电商领域全面出击,通过投资进行战略布局. 从今年年初至今,腾讯以超乎想象的速度战略入股了数家电子商务企业,包括已经对外公 布的好乐买、易讯、F团以及数宗尚未对外公布的收购案.

腾讯CMEM的PHP扩展

- duyue - 平凡的世界
最近公司在做相关的业务,由于Memcached协议缺少返回码,为了保证业务数据的安全性,不得已只好自己写个扩展来实现需求. 基于memcache扩展的2.2.6的稳定版开发而来. 代码已经开源,有需要的朋友请拿走,License是PHP License,请自觉遵守. 项目主页:http://code.google.com/p/cmem/.

腾讯:变局前夜

- - 互联网的那点事
8条业务线,20座城市,20000名员工. 做为中国最大的互联网公司之一,腾讯正面临着前所未有的管理挑战. 2012年4月12日,网易发布的一则公告引起了业界的强烈反响. 网易称,旗下重要产品——新闻客户端遭到腾讯抄袭. 腾讯当天上架地新闻iPhone客户端2.0版本在产品整体布局、跟帖页面、图片浏览页面的设计几乎与网易新闻客户端的相关功能和设计完全一致.

抄袭,腾讯 和 产品

- - 黄小肆依旧在理性与感性里挣扎
作者:陈皓 很早就想写这篇文章了,只是想法比较零碎,所以一直没有成文,这两天觉得思考得比较成熟了一些,所以把我的这些想法整理下来,欢迎大家一起和我讨论. 首先,先表达我的立场,我对抄袭的立场持BS和痛恨的态度,尤其是 那些C2C的网站,痛恨这些国外有什么就山寨什么的做法,尤其是那些连界面都不改,像素级的抄袭,连CSS和img都是一样的,更甚者,连图片都链接到抄袭源的网站去了,连源代码都抄的行为,比如: 腾讯抄新浪的代码, 新浪抄twitter的源码.