使用方法
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传回去==。