java lua热更新_【程序猿】腾讯开源手游热更新方案:Unity3D下的XLua技术内幕(一)...

本文介绍了腾讯开源的手游热更新方案,重点探讨了Unity3D下的XLua技术,如何解决双语编程的问题,以及如何实现C#到Lua的热补丁替换,以实现高性能和低接入成本的热更新。该技术允许在出现问题时使用Lua修复C#代码,且对原有C#项目的改动较小。
摘要由CSDN通过智能技术生成

原标题:【程序猿】腾讯开源手游热更新方案:Unity3D下的XLua技术内幕(一)

目前Unity下的热更新方案有不少,这些方案都要求要热更新的地方一开始就得用脚本来实现,这带来一些问题:

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

2、即使一开始就接入了,也存在同时用两种语言开发难度较大的问题,有些项目只是想fix下bug而已,却被迫使用两种语言开发,明显代价有点大;

3、脚本性能不如C#;

对于双语编程的问题,有人尝试通过把C#翻译成脚本来解决(js,lua都见过)。这种翻译往往在C#语法支持上都不完备,而且,性能问题仍然未解决,甚至更严重(除了脚本本身比C#差之外,自动翻译性能会高效?我持保留态度)。

反观Unity之外有不少方案,比如ios下的jspatch,android下的tinker等,并没这约束。以jspatch为例,它支持把一个oc函数替换为js实现,平时开发可以只写oc,只有出现bug,才需要写个js去fix。

把这两个方案应用到Unity技术上可能可以行得通,但问题是jspatch是用来修复oc,android是修复java的,难道要把逻辑在ios下用oc写一份,android下用java写一份,然后编辑器又另外一份?这在需求急剧变化的游戏领域显然是不现实的。

Unity下的热补丁实现也有,但目前看到的方案,都不支持iOS,由于审核制度的存在,iOS热更才是硬需求,一切不支持iOS的热更方案都是耍流氓!

好吧,重头戏来了,xLua最新的热补丁技术比较完美的解决了上面的问题!等等,不是说好了忘掉lua,怎么说起xLua了?好吧,至少项目组大多数人不需要懂Lua,有一个就可以了,这人大多数时间也不用写Lua,出了线上问题才需要。

xLua热补丁技术支持在线把一个C#(方法,属性,事件等等)实现替换成Lua实现。这意味着你可以:

1、开发只用C#;

2、运行也是C#,性能可以秒杀lua;

3、出问题了才用Lua来改掉C#出问题的部位,替换甚至做到不用重启游戏;

用完即走,是小龙哥对一个好工具的定义,xLua这个特性也如此:没问题的时候你感觉不到它的存在,出了问题才需要它来救场。

极其简单的统一接入方式

在启动的地方加入几行代码即可:

3e973df42917e9dfbc241eb8b953f1ee.png

好,你的应用就有了热补丁的能力了,没有hotfix.lua(名字你可以安装你的需要改)时,什么事情都不干,自然也没开销。

你需要在可能需要热更新的类型打上[Hotfix]标签即可(打了标签的类有极其轻微的性能损失,后面会分析)。

一个API打补丁

通过某种方式下载了hotfix.lua,hotfix.lua就是修复代码。

先看简单示例:

12b5e73f2010e47b82e3607e6e2fd1b7.png

上述代码把C#的HotfixTest类(继承于MonoBehaviour)的Update函数替换成lua的实现。相关API就一个:

xlua.hotfix(class,[method_name], fix)

class是C#的类名,method_name可选,如果写了就替换某函数,没有就是替换类,fix如果给了method_name就提供个函数,没给就通过table提供一组函数。

lua函数的实参,以及属性,事件这些的修复,可以看详细指南。

Stateless和Stateful

打Hotfix标签时,默认是Stateless方式,你也可以选Stateful方式,我们先说区别,再说使用场景。

Stateless方式是指用Lua对成员函数修复时,C#对象直接透传给作为Lua函数的第一个参数。

Stateful方式下你可以在Lua的构造函数返回一个table,然后后续成员函数调用会把这个table给传递过去。

Stateless比较适合无状态的类,有状态的话,你得通过反射去操作私有成员,也没法新增状态(field)。Stateless有个好处,可以运行的任意时刻执行替换。

Stateful的代价是会在类增加一个LuaTable类型的字段(中间层面增加,不会改源代码)。但这种方式是适用性更广,比如你不想要lua状态,可以在构造函数拦截那返回空。而且操作状态性能比反射操作C#私有变量要好,也可以随意新增任意的状态信息。缺点是,执行成员函数之前就new好的对象,接收到的状态会是空,所以需要重启,在一开始就执行替换。

两条指令的性能开销

热补丁基本原理非常简单,所以其性能、内存、安装包影响一目了然。

xLua会在C#编译成il后插入一个处理,该处理会从il层面为每个打了Hotfix标签的类型的函数开头加入几个il指令,如果用C#描述是这样的:

加il指令前:

1fd60b4a68a1c15e45e52cbc8ab24734.png

判断一个delegate是否为空(没用过C#的童鞋可以理解为函数指针),如果不为空就调用这个delegate,而这个delegate指向的是一个把调用转发给lua的函数。

由于是在il层面做这事,所以不会影响到源码。il2cpp是在我们处理完成后执行,所以这方案il2cpp也能行得通,mono就更不在话下了。换句话说,理论上支持所有Unity支持的平台。

上面的delegate是静态的,所以内存开销不大,一个类(和对象实例个数没关)的每个函数对应一个delegate引用。

对函数有轻微影响,没hotfix时就一个指针为空判断而已(两条指令)。Windows测试开销大约是空函数调用的十分之一到五分之一,window下C#空函数能够到每秒十个亿,加了那两指令也将近十个亿,这数字对老王来说也不算小目标了。

对安装包大小有少许影响,影响程度和你本身函数的复杂度有关,很难有个普适的数据。用每个函数都有几百行的类型测出结果是影响极少,如果全是空函数结论就是影响很大了。

热补丁支持的C#特性

各种函数,包括私有/公有,静态/成员,操作符重载,泛化函数。

构造函数,析构函数,和普通函数不一样的是,构造函数和析构函数并不是用lua函数替换了,而是执行了lua函数后还会执行原来的逻辑。

属性,包括私有/公有,静态/成员,泛化的。

事件的add/remove。

泛化类型。

总而言之,目前已知唯一尚不支持的是类静态构造函数。

总结

热补丁实现并不困难,思维定势的跳出才是关键。记得有次总监发了jspatch和tinker的介绍给我,因为之前已经了解过他们的思路,我不假思索的回了“热补丁在Unity下做不到”。其实换一种思路就海阔天空。

下回预告及拉票

热补丁是xLua的突破之一,也不是实现最难的突破,而且这篇也仅是其最基本原理的介绍,深入点还有属性,事件,操作符支持的原理,重载是怎么实现的,而构造函数、析构函数的处理也略有不同,而泛化的处理思路更是大不一样。除了功能关注点,还有性能上怎么避免GC,适配代码如何收敛的考虑。

下面会有一系列的剖析文章,介绍xLua的一些突破的实现原理,敬请关注。

点击一下

立即阅读近期热文

……

添加小编微信,发送“程序”

即可直接加入GAD程序猿交流基地

获取行业干货资讯,观看大牛分享直播

↓长按添加小编GAD-沫沫↓返回搜狐,查看更多

责任编辑:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值