## 简介 热补丁允许你使用 xLua 的代码逻辑, 替换掉原有的 C# 程序逻辑, 以实现热补丁. ## 使用方式 1. 打开该特性 添加 `HOTFIX_ENABLE` 宏,(在 Unity3D 的 "File->Build Setting->Scripting Define Symbols" 下添加)。编辑器、各手机平台这个宏要分别设置!如果是自动化打包,要注意在代码里头用 API 设置的宏是不生效的,需要在编辑器设置。 (建议平时开发业务代码不打开 `HOTFIX_ENABLE`,只在构建手机版本或者要在编译器下开发补丁时打开 `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): ```lua 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标签(不建议,示例只是为了方便演示采取这种方式); !!注意,方式一在高版本 Unity 不支持 方式二:在一个静态类的静态字段或者属性里头配置一个列表。属性可以用于实现的比较复杂的配置,比如根据命名空间做白名单。 !!注意,高版本 Unity 需要把配置文件放 Editor 目录下 ```csharp //如果涉及到Assembly-CSharp.dll之外的其它dll,如下代码需要放到Editor目录 public static class HotfixCfg { [Hotfix] public static List by_field = new List() { typeof(HotFixSubClass), typeof(GenericClass<>), }; [Hotfix] public static List 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模式注入,该文件只返回个空表): ```lua return { ["HotfixTest"] = { [".ctor"] = { 5 }, ["Start"] = { 6 }, ["Update"] = { 7 }, ["FixedUpdate"] = { 8 }, ["Add"] = { 9,10 }, ["OnGUI"] = { 11 }, }, } ``` 想要替换 `HotfixTest` 的 `Update` 函数,你得 ```lua CS.XLua.HotfixDelegateBridge.Set(7, func) ``` 如果是重载函数,将会一个函数名对应多个 id,比如上面的 `Add` 函数。 能不能自动化一些呢?可以,`xlua.util` 提供了 `auto_id_map` 函数,执行一次后你就可以像以前那样直接用类,方法名去指明修补的函数。 ```lua (require 'xlua.util').auto_id_map() xlua.hotfix(CS.HotfixTest, 'Update', function(self) self.tick = self.tick + 1 if (self.tick % 50) == 0 then print('<<<<<<< { } ``` 你只能对 `GenericClass`,`GenericClass` 这些类,而不是对 `GenericClass` 打补丁。 对 `GenericClass` 打补丁的实例如下: ```csharp luaenv.DoString(@" xlua.hotfix(CS.GenericClass(CS.System.Double), { ['.ctor'] = function(obj, a) print('GenericClass', obj, a) end; Func1 = function(obj) print('GenericClass.Func1', obj) end; Func2 = function(obj) print('GenericClass.Func2', obj) return 1314 end }) "); ``` * Unity协程 通过 `util.cs_generator` 可以用一个 function 模拟一个 `IEnumerator`,在里头用 `coroutine.yield`,就类似 C# 里头的 yield return。比如下面的 C# 代码和对应的 hotfix 代码是等同效果的 ```csharp [XLua.Hotfix] public class HotFixSubClass : MonoBehaviour { IEnumerator Start() { while (true) { yield return new WaitForSeconds(3); Debug.Log("Wait for 3 seconds"); } } } ``` ```csharp luaenv.DoString(@" local util = require 'xlua.util' xlua.hotfix(CS.HotFixSubClass,{ Start = function(self) return util.cs_generator(function() while true do coroutine.yield(CS.UnityEngine.WaitForSeconds(3)) print('Wait for 3 seconds') end end) end; }) "); ``` * 整个类 如果要替换整个类,不需要一次次的调用 `xlua.hotfix` 去替换,可以整个一次完成。只要给一个 table,按 `method_name = function` 组织即可 ```lua 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 = v end; get_AProp = function(self) return self.prop end; get_Item = function(self, k) print('get_Item', k) return 1024 end; 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) do if v == cb then table.remove(self.evt, i) break end end end; Start = function(self) print('Start') for _, cb in ipairs(self.evt) do cb(self.start, 2) end self.start = self.start + 1 end; 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 }) ```