xlua实现原理
类型
一切从LuaEnv.cs中的init_xlua开始。
local metatable = {}
local rawget = rawget
local setmetatable = setmetatable
local import_type = xlua.import_type
local import_generic_type = xlua.import_generic_type
local load_assembly = xlua.load_assembly
function metatable:__index(key)
local fqn = rawget(self,'.fqn')
fqn = ((fqn and fqn .. '.') or '') .. key
local obj = import_type(fqn)
if obj == nil then
-- It might be an assembly, so we load it too.
obj = { ['.fqn'] = fqn }
setmetatable(obj, metatable) // 也是这样的方式能处理嵌套的namespace
elseif obj == true then
return rawget(self, key)
end
-- Cache this lookup
rawset(self, key, obj)
return obj
end
function metatable:__newindex()
error('No such type: ' .. rawget(self,'.fqn'), 2)
end
-- A non-type has been called; e.g. foo = System.Foo()
function metatable:__call(...)
local n = select('#', ...)
local fqn = rawget(self,'.fqn')
if n > 0 then
local gt = import_generic_type(fqn, ...)
if gt then
return rawget(CS, gt)
end
end
error('No such type: ' .. fqn, 2)
end
CS = CS or {}
setmetatable(CS, metatable)
在这里rawget,setmetatable使用的lua原生的。import_type,import_generic_type,load_assembly用的是c#中扩展出来的。xlua这个全局table是在xlua的C代码中定义。CS.SOMETYPE时通过metatable就load对应的类型。
函数调用
先要说下lua基础的函数都在LuaDLL.cs中,这些函数都是从luaC源码中来的。lua和别的语言协作就是在lua调用栈中工作,这些函数不可或缺。要知道对象的函数调用就引出对象在lua层的表示。lua中表示外部数据结构用userdata和lightuserdata(区别就是lightuserdata自己管理分配和回收)。从UnityEngineGameObjectWrap的__CreateInstance能看出来:是ObjectTranslator.Push把对象推到lua中去。实际是调用了xlua_pushcsobj。源代码在xlua.c中。
LUA_API void xlua_pushcsobj(lua_State *L, int key, int meta_ref, int need_cache, int cache_ref) {
int* pointer = (int*)lua_newuserdata(L, sizeof(int));
*pointer = key;
if (need_cache) cacheud(L, key, cache_ref);
lua_rawgeti(L, LUA_REGISTRYINDEX, meta_ref);
lua_setmetatable(L, -2);
}
代码内部的lua_newuserdata表示对象是以userdata传给lua的。第三个参数meta_ref就索引了metatable,函数调用时就会从metatable中获取函数调用。metatable是从luaL_getmetatable获取的。更加类型名获取metatable。如果以前没加载呢,初次加载执行TryDelayWrapLoader。根据是否有生成Wrap有不同调用。没生成wrap的用反射注册metatable(函数是ReflectionWrap):
//create obj meta table
LuaAPI.luaL_getmetatable(L, type.FullName);
if (LuaAPI.lua_isnil(L, -1))
{
LuaAPI.lua_pop(L, 1);
LuaAPI.luaL_newmetatable(L, type.FullName);
}
LuaAPI.lua_pushlightuserdata(L, LuaAPI.xlua_tag());
... 下面用反射把类静态函数,成员函数,getter,setter填充好
如果有生成wrap的话,在BeginObjectRegister/EndObjectRegister,BeginClassRegister/EndClassRegister这配对调用中把类静态函数,成员函数,getter,setter设置好。
然后就能函数调用了。经过中间wrap代码的效率更高。
delegate/event
在lua代码中为C#委托增加监听要怎么做?先要把委托标记为[CSharpCallLua]。在lua代码中一段
csharpInstance.OnValueChanged= function() end
Utils.RegisterFunc(L, Utils.SETTER_IDX, "OnValueChanged", _s_set_OnValueChanged);
static int _s_set_OnValueChanged(RealStatePtr L)函数如下
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
ObservableList<BubbleWord> gen_to_be_invoked = (ObservableList<BubbleWord>)translator.FastGetCSObj(L, 1);
gen_to_be_invoked.OnValueChanged = translator.GetDelegate<ObservableList<BubbleWord>.ValueChangedHandler>(L, 2);
GetDelegate内部调用CreateDelegateBridge。然后从生成的DelegateBridge获取对应的delegate。会根据[CSharpCallLua]配置依据函数签名生成很多很多的delegate签名函数,找不到就只能反射获取了(在ObjectTranslator的getDelegate函数中)。如果使用了hotfix会生成更多hotfix依赖更多delegate
[XLua.GCOptimize]
如果是值类型而且比较小,可以直接传递给lua(就是内存字节推送)。
public void PushUnityEngineVector3(RealStatePtr L, UnityEngine.Vector3 val)
{
if (UnityEngineVector3_TypeID == -1)
{
bool is_first;
UnityEngineVector3_TypeID = getTypeId(L, typeof(UnityEngine.Vector3), out is_first);
}
IntPtr buff = LuaAPI.xlua_pushstruct(L, 12, UnityEngineVector3_TypeID);
if (!CopyByValue.Pack(buff, 0, val))
{
throw new Exception("pack fail fail for UnityEngine.Vector3 ,value="+val);
}
}
获取到lua中当然也可以用CopyByValue.UnPack见以前文章。没有GC效率高