上一節(jié)我們主要分析了xlua中C# Call lua的實(shí)現(xiàn)思路,本節(jié)我們將根據(jù)Examples 03_UIEvent,分析lua Call C#的底層實(shí)現(xiàn)。例子場(chǎng)景里有一個(gè)簡(jiǎn)單的UI面板,面板中包含一個(gè)input field,一個(gè)button:
輸入任意文本,點(diǎn)擊button,就會(huì)打印出輸入的內(nèi)容:
響應(yīng)點(diǎn)擊事件的代碼是在lua層,位于ButtonInteraction.lua.txt
這個(gè)文件中,lua代碼很簡(jiǎn)單,就是一個(gè)簡(jiǎn)單的函數(shù):
function start()
print("lua start...")
self:GetComponent("Button").onClick:AddListener(function()
print("clicked, you input is '" ..input:GetComponent("InputField").text .."'")
end)
end
那么C#層從哪里讀取到這個(gè)文件的呢?可以看到,Button這個(gè)GameObject上綁了上一節(jié)我們提到過的LuaBehaviour
組件,而組件里設(shè)置的Lua Script就是這個(gè)文件了:
上一節(jié)我們說(shuō)過,LuaBehaviour
組件會(huì)在Awake的時(shí)候會(huì)執(zhí)行l(wèi)ua代碼,獲取lua層寫的start函數(shù),然后在MonoBehaviour的Start中執(zhí)行它。在lua層的start函數(shù)中,首先可以發(fā)現(xiàn)一個(gè)self,這個(gè)self也是在C#層Awake的時(shí)候設(shè)置的,對(duì)應(yīng)的就是C#的LuaBehaviour對(duì)象。和tolua一樣,xlua也會(huì)把C#對(duì)象當(dāng)作userdata來(lái)處理,每個(gè)要push到lua層的C#類型都有唯一的type_id,對(duì)應(yīng)到不同的metatable,用來(lái)定義userdata的行為。并且,除了值類型和枚舉類型之外,所有push到lua層的C#對(duì)象,都會(huì)在C#層緩存,這一點(diǎn)也是和tolua一樣的,甚至緩存的數(shù)據(jù)結(jié)構(gòu)也大差不差。
public void Push(RealStatePtr L, object o)
{
if (needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index)))
{
if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1)
{
return;
}
}
bool is_first;
int type_id = getTypeId(L, type, out is_first);
index = addObject(o, is_valuetype, is_enum);
LuaAPI.xlua_pushcsobj(L, index, type_id, needcache, cacheRef);
}
xlua_tryget_cachedud函數(shù)就是通過C#緩存拿到的index,去lua層的緩存去拿userdata,lua層的緩存與C#不同,它只負(fù)責(zé)查詢,不負(fù)責(zé)存儲(chǔ),因此是一個(gè)value為弱引用的弱表,這一點(diǎn)和tolua也是一樣的,xlua在初始化時(shí)就會(huì)將這個(gè)弱表準(zhǔn)備好:
LuaAPI.lua_newtable(L);
LuaAPI.lua_newtable(L);
LuaAPI.xlua_pushasciistring(L, "__mode");
LuaAPI.xlua_pushasciistring(L, "v");
LuaAPI.lua_rawset(L, -3);
LuaAPI.lua_setmetatable(L, -2);
cacheRef = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);
由于這個(gè)緩存是弱表,意味著userdata在被真正gc之前,弱表里對(duì)應(yīng)的值有可能已經(jīng)不存在了。那么xlua_tryget_cachedud這個(gè)函數(shù)有可能是取不到userdata的:
LUA_API int xlua_tryget_cachedud(lua_State *L, int key, int cache_ref) {
lua_rawgeti(L, LUA_REGISTRYINDEX, cache_ref);
lua_rawgeti(L, -1, key);
if (!lua_isnil(L, -1))
{
lua_remove(L, -2);
return 1;
}
lua_pop(L, 2);
return 0;
}
取不到的話就通過xlua_pushcsobj這個(gè)函數(shù)新增一個(gè)userdata:
static void cacheud(lua_State *L, int key, int cache_ref) {
lua_rawgeti(L, LUA_REGISTRYINDEX, cache_ref);
lua_pushvalue(L, -2);
lua_rawseti(L, -2, key);
lua_pop(L, 1);
}
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);
}
但是,xlua設(shè)置userdata metatable的做法和tolua完全不同。xlua使用delay wrap的策略,即只有某個(gè)C#類型的對(duì)象push到了lua層,才會(huì)將這個(gè)C#類型的信息,真正地加載到lua層,在此之前,這個(gè)metatable并不存在;而tolua默認(rèn)是在一開始就wrap的,這樣的話類型一多,初始化的時(shí)間就大大增加,而且根據(jù)二八定律,可能絕大部分的類型在一開始?jí)焊貌坏健?/p>
那么,這個(gè)delay wrap具體是怎么實(shí)現(xiàn)的呢?既然它是在C#對(duì)象push到lua層觸發(fā)的,那么顯而易見,在獲取這個(gè)類的type_id時(shí),就要把C#類的信息加載進(jìn)來(lái)了:
internal int getTypeId(RealStatePtr L, Type type, out bool is_first, LOGLEVEL log_level = LOGLEVEL.WARN)
{
int type_id;
if (!typeIdMap.TryGetValue(type, out type_id)) // no reference
{
LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);
if (LuaAPI.lua_isnil(L, -1)) //no meta yet, try to use reflection meta
{
LuaAPI.lua_pop(L, 1);
if (TryDelayWrapLoader(L, alias_type == null ? type : alias_type))
{
LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);
}
else
{
throw new Exception("Fatal: can not load metatable of type:" + type);
}
}
typeIdMap.Add(type, type_id);
}
return type_id;
}
負(fù)責(zé)這件事情的函數(shù)就是TryDelayWrapLoader
。在例子中,由于我們沒有生成過類的wrap,默認(rèn)就會(huì)使用反射的方式來(lái)注冊(cè)各種C#方法與成員。具體實(shí)現(xiàn)的邏輯比較復(fù)雜,主要在ReflectionWrap
這個(gè)函數(shù)中:
public static void ReflectionWrap(RealStatePtr L, Type type, bool privateAccessible)
{
LuaAPI.lua_checkstack(L, 20);
int top_enter = LuaAPI.lua_gettop(L);
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
//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());
LuaAPI.lua_pushnumber(L, 1);
LuaAPI.lua_rawset(L, -3);
int obj_meta = LuaAPI.lua_gettop(L);
LuaAPI.lua_newtable(L);
int cls_meta = LuaAPI.lua_gettop(L);
LuaAPI.lua_newtable(L);
int obj_field = LuaAPI.lua_gettop(L);
LuaAPI.lua_newtable(L);
int obj_getter = LuaAPI.lua_gettop(L);
LuaAPI.lua_newtable(L);
int obj_setter = LuaAPI.lua_gettop(L);
LuaAPI.lua_newtable(L);
int cls_field = LuaAPI.lua_gettop(L);
//set cls_field to namespace
SetCSTable(L, type, cls_field);
//finish set cls_field to namespace
LuaAPI.lua_newtable(L);
int cls_getter = LuaAPI.lua_gettop(L);
LuaAPI.lua_newtable(L);
int cls_setter = LuaAPI.lua_gettop(L);
LuaCSFunction item_getter;
LuaCSFunction item_setter;
makeReflectionWrap(L, type, cls_field, cls_getter, cls_setter, obj_field, obj_getter, obj_setter, obj_meta,
out item_getter, out item_setter, privateAccessible ? (BindingFlags.Public | BindingFlags.NonPublic) : BindingFlags.Public);
// init obj metatable
LuaAPI.xlua_pushasciistring(L, "__gc");
LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
LuaAPI.lua_rawset(L, obj_meta);
LuaAPI.xlua_pushasciistring(L, "__tostring");
LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.ToStringMeta);
LuaAPI.lua_rawset(L, obj_meta);
LuaAPI.xlua_pushasciistring(L, "__index");
LuaAPI.lua_pushvalue(L, obj_field);
LuaAPI.lua_pushvalue(L, obj_getter);
translator.PushFixCSFunction(L, item_getter);
translator.PushAny(L, type.BaseType());
LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
LuaAPI.lua_pushnil(L);
LuaAPI.gen_obj_indexer(L);
//store in lua indexs function tables
LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
translator.Push(L, type);
LuaAPI.lua_pushvalue(L, -3);
LuaAPI.lua_rawset(L, -3);
LuaAPI.lua_pop(L, 1);
LuaAPI.lua_rawset(L, obj_meta); // set __index
LuaAPI.xlua_pushasciistring(L, "__newindex");
LuaAPI.lua_pushvalue(L, obj_setter);
translator.PushFixCSFunction(L, item_setter);
translator.Push(L, type.BaseType());
LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
LuaAPI.lua_pushnil(L);
LuaAPI.gen_obj_newindexer(L);
//store in lua newindexs function tables
LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
translator.Push(L, type);
LuaAPI.lua_pushvalue(L, -3);
LuaAPI.lua_rawset(L, -3);
LuaAPI.lua_pop(L, 1);
LuaAPI.lua_rawset(L, obj_meta); // set __newindex
//finish init obj metatable
LuaAPI.xlua_pushasciistring(L, "UnderlyingSystemType");
translator.PushAny(L, type);
LuaAPI.lua_rawset(L, cls_field);
if (type != null && type.IsEnum())
{
LuaAPI.xlua_pushasciistring(L, "__CastFrom");
translator.PushFixCSFunction(L, genEnumCastFrom(type));
LuaAPI.lua_rawset(L, cls_field);
}
//init class meta
LuaAPI.xlua_pushasciistring(L, "__index");
LuaAPI.lua_pushvalue(L, cls_getter);
LuaAPI.lua_pushvalue(L, cls_field);
translator.Push(L, type.BaseType());
LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
LuaAPI.gen_cls_indexer(L);
//store in lua indexs function tables
LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
translator.Push(L, type);
LuaAPI.lua_pushvalue(L, -3);
LuaAPI.lua_rawset(L, -3);
LuaAPI.lua_pop(L, 1);
LuaAPI.lua_rawset(L, cls_meta); // set __index
LuaAPI.xlua_pushasciistring(L, "__newindex");
LuaAPI.lua_pushvalue(L, cls_setter);
translator.Push(L, type.BaseType());
LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
LuaAPI.gen_cls_newindexer(L);
//store in lua newindexs function tables
LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
translator.Push(L, type);
LuaAPI.lua_pushvalue(L, -3);
LuaAPI.lua_rawset(L, -3);
LuaAPI.lua_pop(L, 1);
LuaAPI.lua_rawset(L, cls_meta); // set __newindex
LuaCSFunction constructor = typeof(Delegate).IsAssignableFrom(type) ? translator.metaFunctions.DelegateCtor : translator.methodWrapsCache.GetConstructorWrap(type);
if (constructor == null)
{
constructor = (RealStatePtr LL) =>
{
return LuaAPI.luaL_error(LL, "No constructor for " + type);
};
}
LuaAPI.xlua_pushasciistring(L, "__call");
translator.PushFixCSFunction(L, constructor);
LuaAPI.lua_rawset(L, cls_meta);
LuaAPI.lua_pushvalue(L, cls_meta);
LuaAPI.lua_setmetatable(L, cls_field);
LuaAPI.lua_pop(L, 8);
System.Diagnostics.Debug.Assert(top_enter == LuaAPI.lua_gettop(L));
}
相比于tolua只使用兩個(gè)table,xlua使用了若干的table來(lái)輔助索引查找C#的方法和成員。從代碼中可以看出,cls_meta,cls_field,cls_getter和cls_setter是用直接給類訪問用的,比如一些靜態(tài)的方法與成員,lua層可以通過namespace和類名直接訪問。而相應(yīng)地,obj_meta,obj_field,obj_getter和obj_setter是給userdata訪問用的,對(duì)應(yīng)C#層實(shí)例方法與成員。從命名中也可看出,field對(duì)應(yīng)的是C#的字段和方法,getter對(duì)應(yīng)的是C#的get屬性,setter對(duì)應(yīng)的是set屬性,meta就是對(duì)外設(shè)置的metatable了。cls_meta中包含__index
,__newindex
,__call
這三個(gè)元方法,這樣lua層就可以通過類名創(chuàng)建一個(gè)C#對(duì)象;obj_meta中包含__index
,__newindex
,__gc
,__tostring
這四個(gè)元方法,并且它就是userdata的type_id。__index
,__newindex
這兩個(gè)元方法,還會(huì)通過registry表,記錄對(duì)應(yīng)的type,來(lái)進(jìn)行額外的緩存,這么做的目的主要是為了基類查找,xlua不像tolua一樣,嵌套使用多個(gè)metatable來(lái)實(shí)現(xiàn)繼承機(jī)制。
那么field,getter,setter這三種table是如何跟meta進(jìn)行關(guān)聯(lián)的呢?xlua使用了一種非常巧妙的機(jī)制,以u(píng)serdata的__index
為例,它其實(shí)對(duì)應(yīng)著一個(gè)函數(shù),這個(gè)函數(shù)使用包含field,getter,setter這三種table在內(nèi),以及其他的一些參數(shù),作為upvalue來(lái)引用。
LUA_API int gen_obj_indexer(lua_State *L) {
lua_pushnil(L);
lua_pushcclosure(L, obj_indexer, 7);
return 0;
}
obj_indexer這個(gè)函數(shù)持有了7個(gè)upvalue,是有點(diǎn)多,注釋里也標(biāo)明了每個(gè)upvalue的用途:
//upvalue --- [1]: methods, [2]:getters, [3]:csindexer, [4]:base, [5]:indexfuncs, [6]:arrayindexer, [7]:baseindex
//param --- [1]: obj, [2]: key
LUA_API int obj_indexer(lua_State *L) {
if (!lua_isnil(L, lua_upvalueindex(1))) {
lua_pushvalue(L, 2);
lua_gettable(L, lua_upvalueindex(1));
if (!lua_isnil(L, -1)) {//has method
return 1;
}
lua_pop(L, 1);
}
if (!lua_isnil(L, lua_upvalueindex(2))) {
lua_pushvalue(L, 2);
lua_gettable(L, lua_upvalueindex(2));
if (!lua_isnil(L, -1)) {//has getter
lua_pushvalue(L, 1);
lua_call(L, 1, 1);
return 1;
}
lua_pop(L, 1);
}
if (!lua_isnil(L, lua_upvalueindex(6)) && lua_type(L, 2) == LUA_TNUMBER) {
lua_pushvalue(L, lua_upvalueindex(6));
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_call(L, 2, 1);
return 1;
}
if (!lua_isnil(L, lua_upvalueindex(3))) {
lua_pushvalue(L, lua_upvalueindex(3));
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_call(L, 2, 2);
if (lua_toboolean(L, -2)) {
return 1;
}
lua_pop(L, 2);
}
if (!lua_isnil(L, lua_upvalueindex(4))) {
lua_pushvalue(L, lua_upvalueindex(4));
while(!lua_isnil(L, -1)) {
lua_pushvalue(L, -1);
lua_gettable(L, lua_upvalueindex(5));
if (!lua_isnil(L, -1)) // found
{
lua_replace(L, lua_upvalueindex(7)); //baseindex = indexfuncs[base]
lua_pop(L, 1);
break;
}
lua_pop(L, 1);
lua_getfield(L, -1, "BaseType");
lua_remove(L, -2);
}
lua_pushnil(L);
lua_replace(L, lua_upvalueindex(4));//base = nil
}
if (!lua_isnil(L, lua_upvalueindex(7))) {
lua_settop(L, 2);
lua_pushvalue(L, lua_upvalueindex(7));
lua_insert(L, 1);
lua_call(L, 2, 1);
return 1;
} else {
return 0;
}
}
我們著重看一下第4個(gè)upvalue的情況,走到這里說(shuō)明在當(dāng)前類中沒有查找到,例子中的GetComponent方法是在Component類里,在LuaBehaviour類里自然是查找不到的,那么就需要不斷地往父類查找。第4個(gè)upvalue是當(dāng)前類的基類類型base type,第5個(gè)upvalue就是緩存了當(dāng)前所有type的__index
元方法函數(shù),那么自然而然就要去這個(gè)緩存中查找base type的__index
元方法,然后把事情直接交給它做就好了,這其實(shí)就是一個(gè)遞歸的做法。為了避免下次還要從緩存中查找基類,這里直接把第4個(gè)upvalue置為空,然后把基類的__index
元方法緩存到第7個(gè)upvalue上。
那問題來(lái)了,我們之前提到xlua是delay wrap的,在訪問C#對(duì)象的時(shí)候,它的基類信息很可能還沒wrap到lua層。所以這里也需要獲取一下基類的type_id。在從緩存中獲取__index
元方法時(shí),代碼中使用的是:
lua_gettable(L, lua_upvalueindex(5));
lua_gettable是會(huì)觸發(fā)metatable的,這個(gè)緩存table在xlua初始化時(shí)就設(shè)置了一個(gè)metatable:
LuaAPI.lua_newtable(rawL); //metatable of indexs and newindexs functions
LuaAPI.xlua_pushasciistring(rawL, "__index");
LuaAPI.lua_pushstdcallcfunction(rawL, StaticLuaCallbacks.MetaFuncIndex);
LuaAPI.lua_rawset(rawL, -3);
LuaAPI.xlua_pushasciistring(rawL, Utils.LuaIndexsFieldName);
LuaAPI.lua_newtable(rawL);
LuaAPI.lua_pushvalue(rawL, -3);
LuaAPI.lua_setmetatable(rawL, -2);
LuaAPI.lua_rawset(rawL, LuaIndexes.LUA_REGISTRYINDEX);
因此如果基類信息還沒wrap,就會(huì)觸發(fā)到C#層的MetaFuncIndex
方法:
public static int MetaFuncIndex(RealStatePtr L)
{
try
{
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
Type type = translator.FastGetCSObj(L, 2) as Type;
if (type == null)
{
return LuaAPI.luaL_error(L, "#2 param need a System.Type!");
}
translator.GetTypeId(L, type);
LuaAPI.lua_pushvalue(L, 2);
LuaAPI.lua_rawget(L, 1);
return 1;
}
catch (System.Exception e)
{
return LuaAPI.luaL_error(L, "c# exception in MetaFuncIndex:" + e);
}
}
這個(gè)函數(shù)首先會(huì)從lua層獲取當(dāng)前要wrap的type,生成唯一的type_id,并把類型信息wrap到lua層,然后再使用一次rawget把__index
方法放回lua層,這樣lua層就可以繼續(xù)遞歸查找了。在例子中,想要調(diào)用到GetComponent得沿著LuaBehaviour=>MonoBehaviour=>Behaviour=>Component這條鏈一直查找3次才能找到。
最后,push到lua層的這些C#函數(shù),都是使用PushFixCSFunction
這個(gè)方法完成的,這個(gè)方法把push到lua層的函數(shù)統(tǒng)一放到一個(gè)list中管理,實(shí)際調(diào)用時(shí)根據(jù)list中的索引,觸發(fā)具體的某個(gè)函數(shù):文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-742592.html
internal void PushFixCSFunction(RealStatePtr L, LuaCSFunction func)
{
if (func == null)
{
LuaAPI.lua_pushnil(L);
}
else
{
LuaAPI.xlua_pushinteger(L, fix_cs_functions.Count);
fix_cs_functions.Add(func);
LuaAPI.lua_pushstdcallcfunction(L, metaFunctions.FixCSFunctionWraper, 1);
}
}
static int FixCSFunction(RealStatePtr L)
{
try
{
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
int idx = LuaAPI.xlua_tointeger(L, LuaAPI.xlua_upvalueindex(1));
LuaCSFunction func = (LuaCSFunction)translator.GetFixCSFunction(idx);
return func(L);
}
catch (Exception e)
{
return LuaAPI.luaL_error(L, "c# exception in FixCSFunction:" + e);
}
}
推測(cè)這么做的原因可能是為了少一些MonoPInvokeCallback吧:)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-742592.html
到了這里,關(guān)于xlua源碼分析(二)lua Call C#的無(wú)wrap實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!