使用方法
xlua对值类型进行了一些优化,标签是[GCOptimize],打有该类型的struct会生成专属的代码用于push和get,并且会在其他用到这个值类型的wrap也对代码进行了修改。
拿最常见的Vector3举例,如果没有对vector3进行值类型优化,则transformWrap代码为:
static int _g_get_position(RealStatePtr L)
{
try {
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
UnityEngine.Transform gen_to_be_invoked = (UnityEngine.Transform)translator.FastGetCSObj(L, 1);
translator.Push(L, gen_to_be_invoked.position);
} catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
}
return 1;
}
而加上[GCOptimize]后,transformWrap的代码为:
static int _g_get_position(RealStatePtr L)
{
try {
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
UnityEngine.Transform gen_to_be_invoked = (UnityEngine.Transform)translator.FastGetCSObj(L, 1);
translator.PushUnityEngineVector3(L, gen_to_be_invoked.position);
} catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
}
return 1;
}
我们可以发现,translator.Push(L, gen_to_be_invoked.position);替换为了translator.PushUnityEngineVector3(L, gen_to_be_invoked.position); 而后者便是打了标签之后生成的专属的push方法。
优化了什么
如果不使用值类型优化,那么push的时候会把这个类型当作object来push。
当作object来push的话,
1. 首先会多出来装箱的消耗,向objectPool加入obj的时候,int index = objects.Add(obj);会发生装箱。
2. 值类型cache不了,因为每一次装箱都是生成的新的对象。
3. 一个值同时在C#层和Lua层存储,Lua层的udata指向了C#的objectPool的对应包装后的元素,这会让C#层和lua层同时存在GC。
如果使用值类型优化的话,C#不会额外存储obj,只在udata里面编码后存值,需要的时候解码一下就好了。
小实验
考虑如下测试代码
local tsm = input.transform
local pos
start_time = os.clock()
for i = 1, 1000000 do
pos = tsm.position
end
end_time = os.clock()
elapsed_time = end_time - start_time
print("Elapsed time: " .. elapsed_time .. " seconds")
使用值类型优化前,运行时间为1.14s,使用值类型优化后,运行时间为0.55s。
具体代码
C# push值到lua虚拟机
加了[GCOptimize]标签后,会在Gen的WrapPusher(translator的partial类)里面生成专属的Push方法
int UnityEngineVector2_TypeID = -1;
public void PushUnityEngineVector2(RealStatePtr L, UnityEngine.Vector2 val)
{
if (UnityEngineVector2_TypeID == -1)
{
bool is_first;
UnityEngineVector2_TypeID = getTypeId(L, typeof(UnityEngine.Vector2), out is_first);
}
IntPtr buff = LuaAPI.xlua_pushstruct(L, 8, UnityEngineVector2_TypeID);
if (!CopyByValue.Pack(buff, 0, val))
{
throw new Exception("pack fail fail for UnityEngine.Vector2 ,value="+val);
}
}
首先和pushobject一样,首次push需要生成type_id用于指定元表。其实关键的函数只有连部分,xlua_pushstruct和CopyByValue.Pack。xlua_pushstruct是用来创建一个Lua层的udata来存储这些数据,Pack是用来把这个struct的数据编码编入刚刚创建的udata中。
LUA_API void *xlua_pushstruct(lua_State *L, unsigned int size, int meta_ref) {
CSharpStruct *css = (CSharpStruct *)lua_newuserdata(L, size + sizeof(int) + sizeof(unsigned int));
css->fake_id = -1;
css->len = size;
lua_rawgeti(L, LUA_REGISTRYINDEX, meta_ref);
lua_setmetatable(L, -2);
return css;
}
看起来很简单,创建udata,设置元表。
//PackUnPack.cs (partial of CopyByValue)
public static bool Pack(IntPtr buff, int offset, UnityEngine.Vector2 field)
{
if(!LuaAPI.xlua_pack_float2(buff, offset, field.x, field.y))
{
return false;
}
return true;
}
这个Pack方法也是由添加标签[GCOptimize]后生成的,调用了xlua_pack_float2方法如下:
LUALIB_API int xlua_pack_float2(void *p, int offset, float f1, float f2) {
CSharpStruct *css = (CSharpStruct *)p;
if (css->fake_id != -1 || css->len < offset + sizeof(float) * 2) {
return 0;
} else {
float *pos = (float *)(&(css->data[0]) + offset);
pos[0] = f1;
pos[1] = f2;
return 1;
}
}
其实就是将udata对应内存填值。
C#层 update之前push到lua的struct的udata
有的时候,需要对值类型进行更新,比如说lua层v.x = 0,这时候就需要将udata中的对应内存也修改。考虑vector3wrap中的setx操作:
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _s_set_x(RealStatePtr L)
{
try {
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
UnityEngine.Vector3 gen_to_be_invoked;translator.Get(L, 1, out gen_to_be_invoked);
gen_to_be_invoked.x = (float)LuaAPI.lua_tonumber(L, 2);
translator.UpdateUnityEngineVector3(L, 1, gen_to_be_invoked);
} catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
}
return 0;
}
这里就调用了updata,更新lua层对应的udata中的数据。
public void UpdateUnityEngineVector3(RealStatePtr L, int index, UnityEngine.Vector3 val)
{
if (LuaAPI.lua_type(L, index) == LuaTypes.LUA_TUSERDATA)
{
if (LuaAPI.xlua_gettypeid(L, index) != UnityEngineVector3_TypeID)
{
throw new Exception("invalid userdata for UnityEngine.Vector3");
}
IntPtr buff = LuaAPI.lua_touserdata(L, index);
if (!CopyByValue.Pack(buff, 0, val))
{
throw new Exception("pack fail for UnityEngine.Vector3 ,value="+val);
}
}
else
{
throw new Exception("try to update a data with lua type:" + LuaAPI.lua_type(L, index));
}
}
update的代码和push的代码很相似,只不过从 new_userdata获取指针 改成了找之前已经创建的userdata的指针,后面的赋值操作是一样的。不过值得注意的是,虽然只改变了x,但是这里面的修改还是将x, y都修改了一遍,因为这里面的translator.Get(L, 1, out gen_to_be_invoked);只能整体get而不是只get到x的值(其实理论上可以,但是应该没什么必要,而且这样自动生成的代码需要N种)
C# 获取lua虚拟机传来的值类型
加了[GCOptimize]标签后,会在Gen的WrapPusher(translator的partial类)里面生成专属的Get方法
public void Get(RealStatePtr L, int index, out UnityEngine.Vector3 val)
{
LuaTypes type = LuaAPI.lua_type(L, index);
if (type == LuaTypes.LUA_TUSERDATA )
{
if (LuaAPI.xlua_gettypeid(L, index) != UnityEngineVector3_TypeID)
{
throw new Exception("invalid userdata for UnityEngine.Vector3");
}
IntPtr buff = LuaAPI.lua_touserdata(L, index);if (!CopyByValue.UnPack(buff, 0, out val))
{
throw new Exception("unpack fail for UnityEngine.Vector3");
}
}
else if (type ==LuaTypes.LUA_TTABLE)
{
CopyByValue.UnPack(this, L, index, out val);
}
else
{
val = (UnityEngine.Vector3)objectCasters.GetCaster(typeof(UnityEngine.Vector3))(L, index, null);
}
}
代码结构也很简单,获取udata,解码!不过这里还有一个优化,就是可以接收lua层的table。我们把接收udata和接收table分开来看:
从table转换为C#的struct
public static void UnPack(ObjectTranslator translator, RealStatePtr L, int idx, out UnityEngine.Vector3 val)
{
val = new UnityEngine.Vector3();
int top = LuaAPI.lua_gettop(L);
if (Utils.LoadField(L, idx, "x"))
{
translator.Get(L, top + 1, out val.x);
}
LuaAPI.lua_pop(L, 1);
if (Utils.LoadField(L, idx, "y"))
{
translator.Get(L, top + 1, out val.y);
}
LuaAPI.lua_pop(L, 1);
if (Utils.LoadField(L, idx, "z"))
{
translator.Get(L, top + 1, out val.z);
}
LuaAPI.lua_pop(L, 1);
}
就是从table读各个同名field。
从udata转换为C#的struct
public static bool UnPack(IntPtr buff, int offset, out UnityEngine.Vector3 field)
{
field = default(UnityEngine.Vector3);
float x = default(float);
float y = default(float);
float z = default(float);
if(!LuaAPI.xlua_unpack_float3(buff, offset, out x, out y, out z))
{
return false;
}
field.x = x;
field.y = y;
field.z = z;
return true;
}
LUALIB_API int xlua_unpack_float3(void *p, int offset, float *f1, float *f2, float *f3) {
CSharpStruct *css = (CSharpStruct *)p;
if (css->fake_id != -1 || css->len < offset + sizeof(float) * 3) {
return 0;
} else {
float *pos = (float *)(&(css->data[0]) + offset);
*f1 = pos[0];
*f2 = pos[1];
*f3 = pos[2];
return 1;
}
}
就是把编码到udata里面的值解码
总结
C#Push struct到lua,就是生成个udata并且将对应内存赋值。C#获得lua的值类型有两种方式,可以lua层自己实现一个table,设置对应的值,传给C#,或者将之前C#传回来的udata传回去也可以。但是C#只能push udata到lua,没办法再把这个lua的table传回去==。