ToLua#暖更新
ToLua#暖更新特性簡單說明
ToLua#暖更新使用說明
- ToLua#1.0.8版本更新帶來了暖更新特性,主要用於比較靈活的解決線上版的部分C#代碼BUG。也跟其他的所有的熱更新Hook方案一樣,由於無法在iOS上開啟jit,故也無法新增或者刪除已有的C#函數介面、數據類型等。目前只能在Lua端替換或者監聽非泛型C#函數的具體實現。
- 支持mono、il2cpp,可執行文件大小會變大,可以查看最開頭兩個文檔鏈接,過濾掉自己項目不需要的Inject方式。本方案主張「半全量」自動hook過濾列表之外的所有可以hook的函數,不必精細化的導出每一個可能需要在線Hook的函數,偏向於「模塊化」的過濾。故提供了一個較方便的黑名單生成工具,方便以文件夾為單位(比如過濾掉NGUI、Spine等第三方插件庫)去過濾。結合著黑名單生成工具,包體的可執行文件不會增長太多,都可以實踐下,視各自的項目而定(自己的項目iOS8.0過了)。
- 跟其他所有的Hook方案一樣,會存在線上版修BUG用Hook版的代碼,主幹開發版用直接修改C#原方法的代碼(ILRuntime也存在此種情況,只是都是「C#」代碼而已)。雖說分兩個版本的代碼維護起來有點成本,但是如果一直只有Hook版的代碼,那麼主幹代碼在迭代的時候,容易修改了C#函數的實現,Inject後測試的時候發現又被lua給「覆蓋」了!!!建議是換新包的話,就把所有的Hook版代碼丟棄掉,全用主幹直接修改C#函數原方法的代碼。不換新包,就維護好兩個版本的代碼。
- InjectionBridgeInfo.lua是我們能替換的C#函數的LUT(look up table),我們Lua代碼能替換的函數,都必須使用這個表裡面的對應項。其根據InjectionBridgeEditorInfo.xml這個文件來實現自動"增量",即允許一定程度上的2份不同C#代碼存運行於線上,並能被Lua替換監聽(具體查看使用說明文檔)。如果打包的之前,你修改了某一個函數的參數類型會導致不兼容歷史記錄,如果要兼容線上版的Inject代碼,則最好新建一個不同名字的函數,內容拷過去,將老函數的引用重定向到新函數,而不是直接刪除InjectionBridgeEditorInfo.xml這個文件(當然不考慮兼容線上版的Inject代碼,則可以直接刪掉這個文件,不管歷史記錄)。之所以提供這個增量的功能,是因為如果渠道多,打包環境會很複雜,容易遇到出多個包且包裡面的C#函數有迭代的情況。
- 「重寫」C#函數的Lua函數要訪問C#類對象的沒有wrap進Lua環境的私有數據成員、私有方法的時候,目前只能使用靜態反射。
- 關於全Lua開發、全C#開發的問題。全Lua開發可能或多或少,會遇到lua的gc導致的性能問題。而且相對於C#這種強類型語言,Lua對於團隊後期維護還是有一定的成本,不做好代碼複審,相對容易變得不好維護。全C#開發配合上本方案做暖更新,修修BUG,容易遇到jit的限制,無法像全Lua或者全ILRuntime開發那麼靈活的新增功能模塊,不方便不換包迭代功能。故建議業務邏輯相關的都可以放可熱更新的腳本層,比如角色養成,UI界面邏輯等。模塊化高,重用率高,性能要求高,穩定性強的模塊都可以封裝到C#,比如戰鬥系統相關的模塊,此時腳本層做好粘合劑的本職工作,搭搭積木就好。
- 為何ILRuntime出來了,還在完善ToLua?因為ToLua相對穩定,經過了大量的線上項目驗證躺坑,ILRuntime還需作者和社區花費時間和精力去完善一些細節。
C#代碼:
//簡單示例(ToLua demo中的例子Assets/ToLua/Examples/TestInjection/)。using System.Collections;using UnityEngine;public class BaseTest{ private int propertyTest; public virtual int TestRef(ref int count) { Debug.Log("CS:Base TestRef"); ++count; return 1; } public virtual int PropertyTest { get { Debug.Log("CS: Base PropertyTestGet"); return propertyTest; } set { Debug.Log("CS: Base PropertyTestSet"); propertyTest = value; } }}public class ToLuaInjectionTest : BaseTest{ private int propertyTest; public ToLuaInjectionTest() { Debug.Log("CS:Constructor Test"); } public override int PropertyTest { get { Debug.Log("CS:PropertyTestGet"); return propertyTest; } set { Debug.Log("CS:PropertyTestSet"); propertyTest = value; } } public override int TestRef(ref int count) { Debug.Log("CS:Override TestRef"); ++count; return 2; } public void TestOverload(int param1, bool param2) { Debug.Log("CS:TestOverload"); } public void TestOverload(int param1, ref bool param2) { Debug.Log("CS:TestOverload"); param2 = !param2; } public IEnumerator TestCoroutine(float delay) { Debug.Log("CS:TestCoroutine Run"); yield return new WaitForSeconds(delay); Debug.Log("CS:TestCoroutine End"); }}
暖更新lua代碼:
local ToLuaInjectionTestInjector = {}ToLuaInjectionTestInjector[".ctor"] = function() -- Only After Does Matter return function(self) print("Lua Inject Constructor") end, LuaInterface.InjectType.After ------------------------------------------------------- --return function(self) -- print("Lua Inject Constructor") --end, LuaInterface.InjectType.Before -------------------------------------------------------endToLuaInjectionTestInjector.set_PropertyTest = function() return function (self, value) print("Lua Inject Property set :" .. value) end, LuaInterface.InjectType.After ------------------------------------------------------- --return function (self, value) -- print("Lua Inject Property set :") -- return {3} --end, LuaInterface.InjectType.Replace -------------------------------------------------------endToLuaInjectionTestInjector.get_PropertyTest = function() return function (self) print("Lua Inject Property get :") end, LuaInterface.InjectType.After ------------------------------------------------------- --return function (self) -- print("Lua Inject Property get :") --end, LuaInterface.InjectType.Before ------------------------------------------------------- --return function (self) -- print("Lua Inject Property get :") -- return 2 --end, LuaInterface.InjectType.Replace -------------------------------------------------------endToLuaInjectionTestInjector.TestRef = function() --return function (self, count) -- print("Lua Inject TestRef ") -- count = 10 -- return { count , 3} --end, LuaInterface.InjectType.After ------------------------------------------------------- --return function (self, count) -- print("Lua Inject TestRef ") -- count = 10 -- return { count , 3} --end, LuaInterface.InjectType.ReplaceWithPreInvokeBase ------------------------------------------------------- return function (self, count) print("Lua Inject TestRef ") count = 10 return { count , 3} end, LuaInterface.InjectType.ReplaceWithPostInvokeBase ------------------------------------------------------- --return function (self, count) -- print("Lua Inject TestRef ") -- count = 10 -- return { count , 3} --end, LuaInterface.InjectType.Replace ------------------------------------------------------- --return function (self, count) -- print("Lua Inject TestRef ") -- count = 10 -- return { count , 3} --end, LuaInterface.InjectType.Before -------------------------------------------------------endToLuaInjectionTestInjector.TestOverload_int_bool = function() return function (self, count, state) print("Lua Inject TestOverload_int_bool " .. tostring(state)) end, LuaInterface.InjectType.AfterendToLuaInjectionTestInjector["TestOverload_int_bool&"] = function() --return function (self, param1, param2) -- print("Lua Inject TestOverload_int_bool& ") -- return {false} --end, LuaInterface.InjectType.After ------------------------------------------------------- --return function (self, param1, param2) -- print("Lua Inject TestOverload_int_bool& ") -- return {false} --end, LuaInterface.InjectType.Before ------------------------------------------------------- return function (self, param1, param2) print("Lua Inject TestOverload_int_bool& ") return {false} end, LuaInterface.InjectType.Replace -------------------------------------------------------endToLuaInjectionTestInjector.TestCoroutine = function() return function (self, delay, coroutineState) print("Lua Inject TestCoroutine " .. coroutineState) end, LuaInterface.InjectType.After ------------------------------------------------------- --return function (self, delay) -- return WrapLuaCoroutine(function() -- print("Lua Inject TestCoroutine Pulse" .. delay) -- return false -- end) --end, LuaInterface.InjectType.Replace ------------------------------------------------------- --return function (self, delay) -- local state = true -- local cor -- local function StartLuaCoroutine() -- if cor == nil then -- cor = coroutine.start(function() -- print("Lua Coroutine Before") -- coroutine.wait(delay) -- state = false -- print("Lua Coroutine After") -- end) -- end -- end -- -- return WrapLuaCoroutine(function() -- StartLuaCoroutine() -- return state -- end) --end, LuaInterface.InjectType.Replace -------------------------------------------------------end--InjectByName("ToLuaInjectionTest", ToLuaInjectionTestInjector)InjectByModule(ToLuaInjectionTest, ToLuaInjectionTestInjector)
上述例子代碼無法執行,需要查看詳細效果的請移步ToLua demo,僅僅用做簡單介紹舉例。
推薦閱讀:
※Spring AOP中定義切點(PointCut)和通知(Advice)
※小議webpack下的AOP式無侵入注入
※C++實現簡單的攔截器