|
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还需作者和社区花费时间和精力去完善一些细节,大幅减少其github上的issue。现使用ToLua1.0.8最新版插件已有一个商业mmo手游,Android、iOS双平台线上运行了2个月左右,iOS包也过了好几个!
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
-------------------------------------------------------
end
ToLuaInjectionTestInjector.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
-------------------------------------------------------
end
ToLuaInjectionTestInjector.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
-------------------------------------------------------
end
ToLuaInjectionTestInjector.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
-------------------------------------------------------
end
ToLuaInjectionTestInjector.TestOverload_int_bool = function()
return function (self, count, state)
print("Lua Inject TestOverload_int_bool " .. tostring(state))
end, LuaInterface.InjectType.After
end
ToLuaInjectionTestInjector["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
-------------------------------------------------------
end
ToLuaInjectionTestInjector.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,仅仅用做简单介绍举例。 |
|