目录

使用方式

约束

API

标识要热更新的类型

Hotfix Flag

使用建议

打补丁

函数

构造函数

属性

[]操作符

其它操作符

事件

析构函数

泛化类型

Unity协程

整个类


使用方式

1、打开该特性

添加HOTFIX_ENABLE宏,(在Unity3D的File->Build Setting->Scripting Define Symbols下添加)。编辑器、各手机平台这个宏要分别设置!如果是自动化打包,要注意在代码里头用API设置的宏是不生效的,需要在编辑器设置。

(建议平时开发业务代码不打开HOTFIX_ENABLE,只在build手机版本或者要在编译器下开发补丁时打开HOTFIX_ENABLE)

2、执行XLua/Generate Code菜单。

3、注入,构建手机包这个步骤会在构建时自动进行,编辑器下开发补丁需要手动执行"XLua/Hotfix Inject In Editor"菜单。打印“hotfix inject finish!”或者“had injected!”才算成功,否则会打印错误信息。

如果已经打印了“hotfix inject finish!”或者“had injected!”,执行xlua.hotfix仍然报类似“xlua.access, no field __Hitfix0_Update”的错误,要么是该类没配置到Hotfix列表,要么是注入成功后,又触发了编译,覆盖了注入结果。

约束

不支持静态构造函数。

目前只支持Assets下代码的热补丁,不支持引擎,c#系统库的热补丁。

API

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

  • 描述 : 注入lua补丁
  • class : C#类,两种表示方法,CS.Namespace.TypeName或者字符串方式"Namespace.TypeName",字符串格式和C#的Type.GetType要求一致,如果是内嵌类型(Nested Type)是非Public类型的话,只能用字符串方式表示"Namespace.TypeName+NestedTypeName";
  • method_name : 方法名,可选;
  • fix : 如果传了method_name,fix将会是一个function,否则通过table提供一组函数。table的组织按key是method_name,value是function的方式。

base(csobj)

  • 描述 : 子类override函数通过base调用父类实现。
  • csobj : 对象
  • 返回值 : 新对象,可以通过该对象base上的方法

例子(位于HotfixTest2.cs):

xlua.hotfix(CS.BaseTest, 'Foo', function(self, p)print('BaseTest', p)base(self):Foo(p)
end)

util.hotfix_ex(class, method_name, fix)

  • 描述 : xlua.hotfix的增强版本,可以在fix函数里头执行原来的函数,缺点是fix的执行会略慢。
  • method_name : 方法名;
  • fix : 用来替换C#方法的lua function。

标识要热更新的类型

和其它配置一样,有两种方式

方式一:直接在类里头打Hotfix标签(不建议,示例只是为了方便演示采取这种方式);

方式二:在一个static类的static字段或者属性里头配置一个列表。属性可以用于实现的比较复杂的配置,比如根据Namespace做白名单。

//如果涉及到Assembly-CSharp.dll之外的其它dll,如下代码需要放到Editor目录
public static class HotfixCfg
{[Hotfix]public static List<Type> by_field = new List<Type>(){typeof(HotFixSubClass),typeof(GenericClass<>),};[Hotfix]public static List<Type> by_property{get{return (from type in Assembly.Load("Assembly-CSharp").GetTypes()where type.Namespace == "XXXX"select type).ToList();}}
}

Hotfix Flag

Hotfix标签可以设置一些标志位对生成代码及插桩定制化

  • Stateless、Stateful

遗留设置,Stateful方式在新版本已经删除,因为这种方式可以用xlua.util.state接口达到类似的效果,该接口的使用可以看下HotfixTest2.cs里的示例代码。

由于没Stateful,默认就是Stateless,所以也没必要设置该标志位。

  • ValueTypeBoxing

值类型的适配delegate会收敛到object,好处是代码量更少,不好的是值类型会产生boxing及gc,适用于对text段敏感的业务。

  • IgnoreProperty

不对属性注入及生成适配代码,一般而言,大多数属性的实现都很简单,出错几率比较小,建议不注入。

  • IgnoreNotPublic

不对非public的方法注入及生成适配代码。除了像MonoBehaviour那种会被反射调用的私有方法必须得注入,其它仅被本类调用的非public方法可以不注入,只不过修复时会工作量稍大,所有引用到这个函数的public方法都要重写。

  • Inline

不生成适配delegate,直接在函数体注入处理代码。

  • IntKey

不生成静态字段,而是把所有注入点放到一个数组集中管理。

好处:对text段影响小。

坏处:使用不像默认方式那么方便,需要通过id来指明hotfix哪个函数,而这个id是代码注入工具时分配的,函数到id的映射会保存在Gen/Resources/hotfix_id_map.lua.txt,并且自动加时间戳备份到hotfix_id_map.lua.txt同级目录,发布手机版本后请妥善保存该文件。

该文件的格式大概如下(注意:该文件仅IntKey模式使用,当你没类型指定IntKey模式注入,该文件只返回个空表):

return {["HotfixTest"] = {[".ctor"] = {5},["Start"] = {6},["Update"] = {7},["FixedUpdate"] = {8},["Add"] = {9,10},["OnGUI"] = {11},},
}

想要替换HotfixTest的Update函数,你得

CS.XLua.HotfixDelegateBridge.Set(7, func)

如果是重载函数,将会一个函数名对应多个id,比如上面的Add函数。

能不能自动化一些呢?可以,xlua.util提供了auto_id_map函数,执行一次后你就可以像以前那样直接用类,方法名去指明修补的函数。

(require 'xlua.util').auto_id_map()
xlua.hotfix(CS.HotfixTest, 'Update', function(self)self.tick = self.tick + 1if (self.tick % 50) == 0 thenprint('<<<<<<<<Update in lua, tick = ' .. self.tick)endend)

前提是hotfix_id_map.lua.txt放到可以通过require 'hotfix_id_map'引用到的地方。

使用建议

  • 对所有较大可能变动的类型加上Hotfix标识;
  • 建议用反射找出所有函数参数、字段、属性、事件涉及的delegate类型,标注CSharpCallLua;
  • 业务代码、引擎API、系统API,需要在Lua补丁里头高性能访问的类型,加上LuaCallCSharp;
  • 引擎API、系统API可能被代码剪裁调(C#无引用的地方都会被剪裁),如果觉得可能会新增C#代码之外的API调用,这些API所在的类型要么加LuaCallCSharp,要么加ReflectionUse;

打补丁

xlua可以用lua函数替换C#的构造函数,函数,属性,事件的替换。lua实现都是函数,比如属性对于一个getter函数和一个setter函数,事件对应一个add函数和一个remove函数。

  • 函数

method_name传函数名,支持重载,不同重载都是转发到同一个lua函数。

比如:

// 要fix的C#类
[Hotfix]
public class HotfixCalc
{public int Add(int a, int b){return a - b;}public Vector3 Add(Vector3 a, Vector3 b){return a - b;}
}
xlua.hotfix(CS.HotfixCalc, 'Add', function(self, a, b)return a + b
end)

静态函数和成员函数的区别是,成员函数会加一个self参数,这个self在Stateless方式下是C#对象本身(对应C#的this)

普通参数对于lua的参数,ref参数对应lua的一个参数和一个返回值,out参数对于lua的一个返回值。

泛化函数的打补丁规则和普通函数一样。

  • 构造函数

构造函数对应的method_name是".ctor"。

和普通函数不一样的是,构造函数的热补丁并不是替换,而是执行原有逻辑后调用lua。

  • 属性

对于名为“AProp”的属性,会对应一个getter,method_name等于get_AProp,setter的method_name等于set_AProp。

  • []操作符

赋值对应set_Item,取值对应get_Item。第一个参数是self,赋值后面跟key,value,取值只有key参数,返回值是取出的值。

  • 其它操作符

C#的操作符都有一套内部表示,比如+号的操作符函数名是op_Addition(其它操作符的内部表示可以去请参照相关资料),覆盖这函数就覆盖了C#的+号操作符。

  • 事件

比如对于事件“AEvent”,+=操作符是add_AEvent,-=对应的是remove_AEvent。这两个函数均是第一个参数是self,第二个参数是操作符后面跟的delegate。

通过xlua.private_accessible(版本号大于2.1.11不需要调用xlua.private_accessible)来直接访问事件对应的私有delegate的直接访问后,可以通过对象的"&事件名"字段直接触发事件,例如self['&MyEvent'](),其中MyEvent是事件名。

  • 析构函数

method_name是"Finalize",传一个self参数。

和普通函数不一样的是,析构函数的热补丁并不是替换,而是开头调用lua函数后继续原有逻辑。

  • 泛化类型

其它规则一致,需要说明的是,每个泛化类型实例化后都是一个独立的类型,只能针对实例化后的类型分别打补丁。比如:

public class GenericClass<T>
{
}

你只能对GenericClass<double>,GenericClass<int>这些类,而不是对GenericClass打补丁。

对GenericClass打补丁的实例如下:

luaenv.DoString(@"xlua.hotfix(CS.GenericClass(CS.System.Double), {['.ctor'] = function(obj, a)print('GenericClass<double>', obj, a)end;Func1 = function(obj)print('GenericClass<double>.Func1', obj)end;Func2 = function(obj)print('GenericClass<double>.Func2', obj)return 1314end})
");
  • Unity协程

通过util.cs_generator可以用一个function模拟一个IEnumerator,在里头用coroutine.yield,就类似C#里头的yield return。比如下面的C#代码和对应的hotfix代码是等同效果的

[XLua.Hotfix]
public class HotFixSubClass : MonoBehaviour {IEnumerator Start(){while (true){yield return new WaitForSeconds(3);Debug.Log("Wait for 3 seconds");}}
}
luaenv.DoString(@"local util = require 'xlua.util'xlua.hotfix(CS.HotFixSubClass,{Start = function(self)return util.cs_generator(function()while true docoroutine.yield(CS.UnityEngine.WaitForSeconds(3))print('Wait for 3 seconds')endend)end;})
");
  • 整个类

如果要替换整个类,不需要一次次的调用xlua.hotfix去替换,可以整个一次完成。只要给一个table,按method_name = function组织即可

xlua.hotfix(CS.StatefullTest, {['.ctor'] = function(csobj)return util.state(csobj, {evt = {}, start = 0, prop = 0})end;set_AProp = function(self, v)print('set_AProp', v)self.prop = vend;get_AProp = function(self)return self.propend;get_Item = function(self, k)print('get_Item', k)return 1024end;set_Item = function(self, k, v)print('set_Item', k, v)end;add_AEvent = function(self, cb)print('add_AEvent', cb)table.insert(self.evt, cb)end;remove_AEvent = function(self, cb)print('remove_AEvent', cb)for i, v in ipairs(self.evt) doif v == cb thentable.remove(self.evt, i)breakendendend;Start = function(self)print('Start')for _, cb in ipairs(self.evt) docb(self.start, 2)endself.start = self.start + 1end;StaticFunc = function(a, b, c)print(a, b, c)end;GenericTest = function(self, a)print(self, a)end;Finalize = function(self)print('Finalize', self)end
})

XLua官方教程 06 热更新实例 hotfix样例相关推荐

  1. [置顶] 【稀饭】react native 实战系列教程之热更新原理分析与实现

    很多人在技术选型的时候,会选择RN是因为它具有热更新,而且这是它的一个特性,所以实现起来会相对比较简单,不像原生那样,原生的热更新是一个大工程.那就目前来看,RN的热更新方案已有的,有微软的CodeP ...

  2. react native 实战系列教程之热更新原理分析与实现

    很多人在技术选型的时候,会选择RN是因为它具有热更新,而且这是它的一个特性,所以实现起来会相对比较简单,不像原生那样,原生的热更新是一个大工程.那就目前来看,RN的热更新方案已有的,有微软的CodeP ...

  3. Stanford UFLDL教程 逻辑回归的向量化实现样例

    逻辑回归的向量化实现样例 我们想用批量梯度上升法对logistic回归分析模型进行训练,其模型如下: 让我们遵从公开课程视频与CS229教学讲义的符号规范,设 ,于是 ,, 为截距.假设我们有m个训练 ...

  4. Unity XLua 官方教程学习

    一.Lua 文件加载 1. 执行字符串 using UnityEngine; using XLua;public class ByString : MonoBehaviour {XLua.LuaEnv ...

  5. 10 legospike 官方教程 06——词语模块、更多词语模块、帮助功能

    1.词语模块 学过scr*ch的同学,都会非常熟悉.所以,有争论,先学scr*ch程序语言,再来学lego spike.或者反过来,其实无所谓,都能学好编程.只要课程设计的好. 2.通常不会显示更多* ...

  6. ArcGIS教程:地理处理服务演示样例(河流网络)(三)

    设置输出符号系统 步骤: 展开 StoweStreamNet.tbx 并双击创建河流网络模型. 接受默认的 45 公顷并单击确定以运行模型. StreamNet 图层将加入至 ArcMap. 右键单击 ...

  7. xLua热更新(二)实现热更新

    一.环境配置 要实现热更新功能,我们首先需要开启热更新的宏.操作方法是在「File->Build Settings->Player Settings->Player->Othe ...

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

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

  9. Elasticsearch7.15.2 修改IK分词器源码实现基于MySql8的词库热更新

    文章目录 一.源码分析 1. 默认热更新 2. 热更新分析 3. 方法分析 二.词库热更新 2.1. 导入依赖 2.2. 数据库 2.3. JDBC 配置 2.4. 打包配置 2.5. 权限策略 2. ...

  10. RN 实现热更新及手动热更新,记录实现的方式

    需求:App需要一个热更新的功能,可以默认更新用来修正线上问题 实现路线: 使用微软的依赖包 react-native-code-push 实现步骤 全局安装code-push-cli npm i - ...

最新文章

  1. 为什么不让安装卫xing×××啊
  2. Spring事务属性详解
  3. Delphi 能不能从Ring 3进入Ring 0
  4. 个人帐目管理系统java_Java 项目 个人帐目管理系统
  5. GC原理---对象可达判断
  6. 什么叫做形态学图像处理_形态学腐蚀和膨胀原理和python实现
  7. postman中文设置_严选 | Elastic中文社区201902错题本
  8. 二十四、JAVA集合框架(三)
  9. 【转】Python基础-字符串
  10. 若泽数据 巨人_面部识别巨人拒绝分享有关其算法数据集的详细信息
  11. 深度步态识别综述(二)
  12. Flask项目基本流程
  13. 简单描述数字签名:私钥签名;公钥验签。
  14. Android平台挖矿木马研究报告
  15. 文献记录(part104)--Distance-Based Outlier Detection: Consolidation and Renewed Bearing
  16. 怎样用自己电脑做服务器供他人访问自己的网站
  17. 小荷才露尖尖角,和Flutter应用说你好
  18. 49.现有移动端开源框架及其特点—MACE( Mobile AI Compute Engine)
  19. 二级菜单选中,一级菜单背景变换
  20. 为什么vue3 需要 Composition API?

热门文章

  1. 66个求职应聘技巧性问答(六)
  2. 曾号称永久免费的知名国产浏览器推出 VIP 会员模式,网友吵翻天
  3. 空间中直线到平面的距离的公式是什么?
  4. FFmpeg码率控制及内置编码参数介绍
  5. 突发!ITELLYOU要改版了!
  6. 让百度、GOOGLE、搜狗、有道等搜到你的博客
  7. R语言knn算法的两种方法:class包与kknn包
  8. 【MATLAB】创建网格图和曲面图
  9. 微信推出赞赏码,有人欢喜有人愁
  10. c语言出现错误c1083,DES 算法,出现异常:fatal error C1083: Cannot open include file: 'des_encode.h'...