思考并回答以下问题:
之前学习了热补丁使用的流程,也学习了简单的例子。这里面很重要的一环是如何给已有的C#代码打补丁,如何去替换方法、属性、事件等等,哪些可以替换,哪些不能替换呢?
打补丁
xlua可以用lua函数替换C#的构造函数,函数,属性,事件的替换。lua实现都是函数,比如属性对于一个getter函数和一个setter函数,事件对应一个add函数和一个remove函数。
函数
method_name传函数名,支持重载,不同重载都是转发到同一个lua函数。
比如:1
2
3
4
5
6
7
8
9
10
11
12
13
14// 要fix的C#类
[ ]
public class HotfixCalc
{
public int Add(int a, int b)
{
return a - b;
}
public Vector3 Add(Vector3 a, Vector3 b)
{
return a - b;
}
}
— lua代码1
2
3xlua.hotfix(CS.HotfixCalc, 'Add', function(self, a, b)
return a + b
end)
静态函数和成员函数的区别是,成员函数会加一个self参数,这个self是C#对象本身(对应C#的this)
普通参数对于lua的参数,ref参数对应lua的一个参数和一个返回值,out参数对于lua的一个返回值。
泛化函数的打补丁规则和普通函数一样。
构造函数
构造函数对应的method_name是”.ctor”。
和普通函数不一样的是,构造函数的热补丁并不是替换,而是执行原有逻辑后调用lua。
属性
对于名为“AProp”的属性,会对应一个getter,method_name等于get_AProp,setter的method_name等于set_AProp。
[]操作符
赋值对应method_name为set_Item,取值对应get_Item。
赋值第一个参数是self,赋值后面跟key,value;
取值只有key参数,返回值是取出的值。
其它操作符
C#的操作符都有一套内部表示,比如+号的操作符函数名是op_Addition(其它操作符的内部表示可以去请参照相关资料),覆盖这函数就覆盖了C#的+号操作符。
下图是C#一元操作符对应的函数名:
下图是C#二元操作符对应的函数名:
事件
比如对于事件“AEvent”,+=操作符是add_AEvent,-=对应的是remove_AEvent。这两个函数均是第一个参数是self,第二个参数是操作符后面跟的delegate。
通过xlua.private_accessible(xLua版本号大于2.1.11不需要调用xlua.private_accessible)来获取事件对应的私有delegate的直接访问后,可以通过对象的”&事件名”字段直接触发事件,例如self‘&MyEvent’,其中MyEvent是事件名。
析构函数
method_name是”Finalize”,传一个self参数。
和普通函数不一样的是,析构函数的热补丁并不是替换,而是开头调用lua函数后继续原有逻辑。
泛化类型
其它规则一致,需要说明的是,每个泛化类型实例化后都是一个独立的类型,只能针对实例化后的类型分别打补丁。比如:1
2
3public class GenericClass‹T›
{
}
你只能对GenericClass,GenericClass这些类,而不是对GenericClass打补丁。
对GenericClass打补丁的实例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14luaenv.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 1314
end
})
");
Unity协程
通过util.cs_generator可以用一个function模拟一个IEnumerator,在里头用coroutine.yield,就类似C#里头的yield return。
比如下面的C#代码和对应的hotfix代码是等同效果的:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24[ ]
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 do
coroutine.yield(CS.UnityEngine.WaitForSeconds(3))
print('Wait for 3 seconds')
end
end)
end;
})
");
整个类
如果要替换整个类,不需要一次次的调用xlua.hotfix去替换,可以整个一次完成。只要给一个table,按method_name = function组织即可:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48xlua.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
})
总结
在使用热补丁时,需要注意以下几点:
对所有较大可能变动的类型加上Hotfix标识,建议使用List方式;
建议用反射找出所有函数参数、字段、属性、事件涉及的delegate类型,放到List中,标注CSharpCallLua;
业务代码、引擎API、系统API,需要在Lua补丁里头高性能访问的类型,放到List中,加上LuaCallCSharp;
引擎API、系统API可能被代码剪裁调(C#无引用的地方都会被剪裁),如果觉得可能会新增C#代码之外的API调用,这些API所在的类型要么加LuaCallCSharp,要么加ReflectionUse。