目錄:Xlua源碼學習
本篇以CS.XLua.LuaDLL.Lua:xlua_is_eq_str(L,index,str)的調用為例子。
文章比較長,先說結論:
1.CS:LuaEnv的init_xlua代碼塊里生成的全局表,核心init_xlua里的metatable:__index方法。
2. XLua:以XLua為類名的類不存在,當做命名空間處理。CS.XLua = { ['.fqn'] = fqn }。
3. LuaDLL:同上,CS.XLua.LuaDLL = { ['.fqn'] = fqn }。
4. Lua:Lua類存在,導出類。
調用StaticLuaCallbacks.ImportType導出類。
ObjectTranslator.TryDelayWrapLoader:調用wrap的__Register方法加載類。
__Register:生成元表obj_meta,該元表封裝了類的所有成員方法、get、set調用接口__index,__newindex。
生成cls_table,該表包含了所有類的靜態方法。
生成cls_meta,cls_table的元表,封裝了靜態的屬性設置、獲取接口__index,__newindex。
設置CS.XLua.LuaDLL.Lua = cls_table。
類導出結束。
5.調用靜態方法
1. 靜態方法獲取:cls_table.staticFunc,直接取。
2. 靜態get方法:通過cls_meta的__index(xlua.c的cls_indexer)獲取,非父類方法存在getters。
3. 靜態父類方法:通過cls_meta的__index(xlua.c的cls_indexer)獲取,實際通過父類的cls_index獲取的,即register["LuaClassIndexs"][base_ud] = base_meta.__index。涉及父類操作下同。
4. 靜態set:通過cls_meta的__newindex(xlua.c的cls_newindexer)獲取,非父類方法存在setters。
6.對象實例獲取:
兩種方式:
1. new一個對象,例如在lua執行UIHierarchy(),會觸發UIHierarchy的cls_meta的__call方法,而__call方法綁定了下面的UIHierarchyWrap .__CreateInstance(),最終是new了一個對象,然后把實例對象的ud壓棧返回,你就可以通過這個ud的元表obj_meta調用到成員方法。
static int __CreateInstance(RealStatePtr L) { try { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); if(LuaAPI.lua_gettop(L) == 1) { UIHierarchy gen_ret = new UIHierarchy(); translator.Push(L, gen_ret); return 1; } } }
2. 從其他方法返回值獲取,例如gameobject.transform會把transform的ud壓棧並返回並lua,lua通過這個ud進行成員方法的訪問。
static int _g_get_transform(RealStatePtr L) { try { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); UnityEngine.GameObject gen_to_be_invoked = (UnityEngine.GameObject)translator.FastGetCSObj(L, 1); translator.Push(L, gen_to_be_invoked.transform); } catch(System.Exception gen_e) { return LuaAPI.luaL_error(L, "c# exception:" + gen_e); } return 1; }
6.調用成員方法
1. 通過壓棧的ud的元表obj_meta訪問__index方法(xlua.c的obj_indexer),取到方法,非父類方法存在methods。
2. 成員get:同上。
3. 成員set: 通過壓棧的ud的元表訪問__newindex方法(xlua.c的obj_newindexer),取到方法。
4. lua方法調用時會依次把參數壓棧,成員方法(:)調用會先把自身的ud壓棧,再根據ud到objects緩存取到對應的obj實例進行調用。
static int _s_set_widgets(RealStatePtr L) { try { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); UIHierarchy gen_to_be_invoked = (UIHierarchy)translator.FastGetCSObj(L, 1);//第一個參數,自身ud地址。 gen_to_be_invoked.widgets = (System.Collections.Generic.List<UIHierarchy.ItemInfo>)translator.GetObject(L, 2, typeof(System.Collections.Generic.List<UIHierarchy.ItemInfo>)); } catch(System.Exception gen_e) { return LuaAPI.luaL_error(L, "c# exception:" + gen_e); } return 0; }
一、CS全局表。
LuaEnv的構造方法里會執行init_xlua文本,在這個可執行的文本里生成里CS的全局表,並對CS賦了值
CS = Register["CSHARP_NAMESPACE"]
DoString(init_xlua, "Init"); LuaAPI.xlua_pushasciistring(rawL, CSHARP_NAMESPACE); if (0 != LuaAPI.xlua_getglobal(rawL, "CS")) { throw new Exception("get CS fail!"); } LuaAPI.lua_rawset(rawL, LuaIndexes.LUA_REGISTRYINDEX);
init_xlua核心的代碼段如下:
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) 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
__index的官方說明如下:
__index: 索引 table[key]。 當 table 不是表或是表 table 中不存在 key 這個鍵時,這個事件被觸發。 此時,會讀出 table 相應的元方法。
這個事件的元方法其實可以是一個函數也可以是一張表。 如果它是一個函數,則以 table 和 key 作為參數調用它。 如果它是一張表,最終的結果就是以 key 取索引這張表的結果。 (這個索引過程是走常規的流程,而不是直接索引, 所以這次索引有可能引發另一次元方法。)
這段lua的意思是,如果table[className] 不存在,則執行這個__index。如果className對應的類型存在,加載這個類型到內存(wrap的__Register),否則創建一張空表,當成命名空間處理。
這邊的obj是cls_table,這個表里存着這個類對應wrap的所有靜態方法,通過這個類的wrap文件的__CreateInstance方法可以獲取這個類的ud,而這個ud的元表里封裝了可以獲取類的成員方法、屬性的_index方法。這邊所指的方法都是在wrap里面的。具體后面再說。
__newindex:實際是禁止了對這個表的元表設置操作。
二、命名空間:XLua、LuaDLL。
如上的index代碼,local obj = import_type(fqn)。這個obj是nil,那么這邊是創建了兩個空表。即
CS.XLua = {['.fqn'] = fqn}
CS.XLua.LuaDLL = {['.fqn'] = fqn}
三、import_type,加載wrap文件進內存。
1.這邊的import_type實際是StaticLuaCallbacks.ImportType方法。
核心代碼如下,type == null,當做命名空間處理。否則通過translator.GetTypeId加載類。
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); string className = LuaAPI.lua_tostring(L, 1); Type type = translator.FindType(className); if (type != null) { if (translator.GetTypeId(L, type) >= 0) { LuaAPI.lua_pushboolean(L, true); } else { return LuaAPI.luaL_error(L, "can not load type " + type); } } else { LuaAPI.lua_pushnil(L); }
2.translator.getTypeId:獲取type元表在注冊表中引用Id,如果不存在,則創建元表registry[type.FullName]={},並在registry表中創建新的引用指向該元表並返回type_id。維護了Type和TypeId間的映射表typeMap、typeIdMap。
internal int getTypeId(RealStatePtr L, Type type, out bool is_first, LOGLEVEL log_level = LOGLEVEL.WARN) { int type_id; is_first = false; if (!typeIdMap.TryGetValue(type, out type_id)) // no reference { is_first = true; Type alias_type = null; aliasCfg.TryGetValue(type, out alias_type); 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); } } //循環依賴,自身依賴自己的class,比如有個自身類型的靜態readonly對象。 if (typeIdMap.TryGetValue(type, out type_id)) { LuaAPI.lua_pop(L, 1); } else { LuaAPI.lua_pushvalue(L, -1); type_id = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);//register["type_id"] = meta LuaAPI.lua_pushnumber(L, type_id); LuaAPI.xlua_rawseti(L, -2, 1);//meta["1"] = typeId LuaAPI.lua_pop(L, 1); if (type.IsValueType()) { typeMap.Add(type_id, type); } typeIdMap.Add(type, type_id); } } return type_id; }
3. TryDelayWrapLoader。
delayWrap在XLua_Gen_Initer_Register__.Init賦值,Init方法在ObjectTranslator的s_gen_reg_dumb_obj參數生成里調用(即構造函數前就初始化好了)。他實際是Type到TypeWrap.__Register的映射。
下面的loader(L);實際調用的是對應類的wrap文件的__Register方法。
public bool TryDelayWrapLoader(RealStatePtr L, Type type) { if (loaded_types.ContainsKey(type)) return true; loaded_types.Add(type, true);//避免重復加載 LuaAPI.luaL_newmetatable(L, type.FullName); //先建一個metatable,因為加載過程可能會需要用到 LuaAPI.lua_pop(L, 1); Action<RealStatePtr> loader; int top = LuaAPI.lua_gettop(L); if (delayWrap.TryGetValue(type, out loader)) { delayWrap.Remove(type); loader(L); } return true; }
四、類的核心加載方法XXXWrap.__Register
這邊不貼代碼了,核心方法都在Utils類里。
public static void __Register(RealStatePtr L) { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); System.Type type = typeof(UIHierarchy); Utils.BeginObjectRegister(type, L, translator, 0, 2, 3, 3); Utils.RegisterFunc(L, Utils.METHOD_IDX, "SetWidgets", _m_SetWidgets); Utils.RegisterFunc(L, Utils.GETTER_IDX, "widgets", _g_get_widgets); Utils.RegisterFunc(L, Utils.SETTER_IDX, "widgets", _s_set_widgets); Utils.EndObjectRegister(type, L, translator, null, null, null, null, null); Utils.BeginClassRegister(type, L, __CreateInstance, 1, 0, 0); Utils.RegisterFunc(L, Utils.CLS_IDX, "IsNull", _m_IsNull_xlua_st_); Utils.EndClassRegister(type, L, translator); }
1.BeginObjectRegister:生成類的成員元表、成員方法、Get、Set的存放表。
obj_meta[ud]=1
obj_meta["__gc"]=StaticLuaCallbacks.LuaGC
obj_meta["__tostring"]=StaticLuaCallbacks.ToString
方法執行結束棧:obj_meta, methon, get, set(-4, -3, -2, -1)
2.Utils.RegisterFunc:注冊方法,這個比較簡單,就是做一次賦值。
method_table[name] = func,這邊包括method、set、get等方法設置。
這邊其實把lua調用的方法都封裝成LuaCSFunction委托,再轉成指針賦值給c,c在把這個函數指針封裝成閉包供lua調用。
public static void RegisterFunc(RealStatePtr L, int idx, string name, LuaCSFunction func) { idx = abs_idx(LuaAPI.lua_gettop(L), idx); LuaAPI.xlua_pushasciistring(L, name); LuaAPI.lua_pushstdcallcfunction(L, func); LuaAPI.lua_rawset(L, idx); } public static void lua_pushstdcallcfunction(IntPtr L, lua_CSFunction function, int n = 0)//[-0, +1, m] { IntPtr fn = Marshal.GetFunctionPointerForDelegate(function);//轉指針 xlua_push_csharp_function(L, fn, n); }
xlua.c接口
static int csharp_function_wrap(lua_State *L) { lua_CFunction fn = (lua_CFunction)lua_tocfunction(L, lua_upvalueindex(1)); int ret = fn(L); return ret; } LUA_API void xlua_push_csharp_function(lua_State* L, lua_CFunction fn, int n) { lua_pushcfunction(L, fn); lua_pushboolean(L, 0); lua_pushcclosure(L, csharp_function_wrap, 2 + (n > 0 ? n : 0)); }
3.EndObjectRegister:設置成員元表的__index、__newindex方法。
方法開始堆棧:obj_meta,methon,get,set
棧中內容:obj_meta,method,get,set,--index,method,get,csIndexer(array),baseType(父類),register[luaindex],arrayIndexer(nil)
LuaAPI.gen_obj_indexer(L); //設置__index方法
棧中內容:obj_meta,method,get,set,--index, closure(xlua.c 的obj_indexer)
這個閉包如下,提供了根據key在methon,get、basetype中查找方法的實現,很重要。method、get並沒有存在obj_meta,而是存在obj_indexer閉包的上值里。
它會依次查找methods、getters、arrayindexer(數組)、base(基類)。
register[luaindex][ud] = obj_indexer
obj_meta[__index]=obj_indexer
棧中內容:obj_meta,method,get,set
棧中內容:obj_meta,method,get,set,__newindex,set,csNewIndexer(nil),baseType(nil),register[LuaNewIndexs],arrayNewIndexer(nil)
LuaAPI.gen_obj_newindexer(L); //設置__newindex方法
棧中內容:obj_meta,method,get,set,__newindex,closure(xlua.x obj_newindexer)
register[LuaNewIndexs][ud] = obj_newindexer
meta[__newindex] = obj_newindexer
方法執行結束:空棧
//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));//get直接調用了,所以lua里get不當做方法處理 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; } }
到這邊成員meta的內容填充就結束了。
meta = //LuaAPI.luaL_getmetatable(L, type.FullName) = registry[type.FullName] { ["__index"] = obj_indexer//(見xlua.c) ["__newindex"] = obj_newindexer//(見xlua.c) [UserData] = 1 ["__tostring"] = StaticLuaCallbacks.ToString [1] = typeId }
4.BeginClassRegister:生成類的靜態(static)元表、靜態方法、Get、Set的存放表。
方法結束 棧:cls_table ,meta_table, get, set(-4, -3, -2, -1)
cls_table[UnderlyingSystemType] = userdata
cls_meta["__call"] = creator(wrap 的 __CreateInstance,該方法會把ud壓棧。
setmetatable(cls_table, cls_meta)
static int __CreateInstance(RealStatePtr L) { try { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); if(LuaAPI.lua_gettop(L) == 1) { UIHierarchy gen_ret = new UIHierarchy(); translator.Push(L, gen_ret); return 1; } } }
5.SetCSTable:生成路徑表。
最終實現的是:XLua.LuaDLL.Lua = cls_table
CS[ud] = cls_table
6.EndClassRegister:設置類的元表的__index、__newindex方法
方法開始棧:cls_table, cls_meta,get,set
棧:cls_table, cls_meta,get,set,__index,get, cls_table, base(父類), reister["LuaClassIndexs"]
LuaAPI.gen_cls_indexer(L);//設置__index方法
棧:cls_table, cls_meta,get,set,__index,closure(cls_indexer)
register[LuaClassIndexs][ud] = closure(cls_indexer)
meta[__index] = closure(cls_indexer)
棧:cls_table,cls_meta,get,set
棧:cls_table, cls_meta,get,set,__newindex,set, base(父類), reister["LuaClassNewIndexs"]
LuaAPI.gen_cls_newindexer(L); //設置__newindex方法
棧:cls_table, cls_meta,get,set,__newindex,closure(cls_indexer)
register[LuaClassNewIndexs][ud] = closure(cls_newindexer)
meta[__newindex] = closure(cls_newindexer)
方法結束棧:空
7.用到的幾個lua表:
1.CLS_IDX對應的cls_table:保存了所有靜態方法的引用。
1.CLS_META_IDX對應的cls_getter:保存了所有get方法的引用,通過cls_meta的__index訪問。
1.CLS_GETTER_IDX對應的cls_setter:保存了所有靜態set的引用,通過cls_meta的__newindex訪問。
1.CLS_SETTER_IDX對應的cls_meta:cls_table的元表。
1.CLS_IDX對應的cls_table:保存了所有靜態方法的引用。
1.CLS_IDX對應的cls_table:保存了所有靜態方法的引用。
五、translator.Push:獲取type的userdata,如果沒有,則創建,保留userdata在棧頂。
1. addObject:將object添加到對象池中,創建object-index的映射關系,通過index可以獲取到c#對象,而通過c#對象可以獲取唯一的index,用於獲取對象的ud,從而訪問對象的從成員方法。
public void Push(RealStatePtr L, object o) { if (o == null) { LuaAPI.lua_pushnil(L); return; } int index = -1; Type type = o.GetType(); bool is_enum = type.GetTypeInfo().IsEnum; bool is_valuetype = type.GetTypeInfo().IsValueType; bool needcache = !is_valuetype || is_enum; if (needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index))) { //已緩存,通過唯一的typeId取到ud並壓棧 if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1) { return; } } bool is_first; int type_id = getTypeId(L, type, out is_first); if (is_first && needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index))) { if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1) { return; } } index = addObject(o, is_valuetype, is_enum); LuaAPI.xlua_pushcsobj(L, index, type_id, needcache, cacheRef); }
2. xlua_pushcsobj:創建一個新的userdata,內容是對象在objects的下標,通過這個下標可以在c#取到對應的對象。
cacheud:cache [index] = ud
setmetatable(ud, meta) 設置元表
也就是說通過translator.Push(L, type)可以獲取對象的userdata,這個userdata的元表obj_meta里封裝了對該類的所有成員方法、get、set屬性的訪問方法,我們可以通過該元表的__index、__newindex調用所有wrap內的成員方法、get、set方法。
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); }
六、總結用到的容器,以及各個meta的內容。
c端
register[typeId] = meta //typeId是唯一的
register[type.FullName] = {} 元表是meta,通過LuaAPI.luaL_getmetatable(L, type.FullName);獲取到元表
func_meta = {"__index" = StaticLuaCallbacks.MetaFuncIndex}
下面這個四個表是用於獲取父類的相對應__index方法的。
translator.Push(L, type == null ? base_type : type.BaseType());實際傳到xlua的obj_indexer的是父類的ud,在通過register["LuaIndexs"][ud]獲取父類的obj_indexer閉包。
register["LuaIndexs"] = {__index = func_meta}//LuaEnv初始化
register["LuaNewIndexs"] = {__index = func_meta}//LuaEnv初始化
register["LuaClassIndexs"] = {__index = func_meta}//LuaEnv初始化
register["LuaClassNewIndexs"] = {__index = func_meta}//LuaEnv初始化
register["LuaIndexs"][ud] = obj_indexer(這是個閉包,它的上值包括了這個類的method、set、baseType,實際是__index方法,提供對象查找方法)
register["LuaNewIndexs"][ud] = obj_newindexer
register[LuaClassIndexs][ud] = cls_indexer
register[LuaClassNewIndexs][ud] = cls_newindexer
register["LuaIndexs"] = {}//元表的__index指向StaticLuaCallbacks.MetaFuncIndex 其他幾個類似,luaenv初始化
cache = register[cacheRef]
cache [index] = ud (ud = index,元表是typeId對應的meta,即LuaAPI.luaL_getmetatable(L, type.FullName))
xlua_csharp_namespace = register[xlua_csharp_namespace],在LuaEnv 賦值了 register[xlua_csharp_namespace] = CS,即xlua_csharp_namespace = CS
CS [path][type.Name] = cls_table //CS.XLua.LuaDLL.Lua = cls_table
CS [ud] = cls_table
c#
typeMap[type_id] = type
typeIdMap[type] = type_id
objects[index] = obj //可以試試Type
reverseMap[obj] = index
enumMap[obj] = index
obj_meta = //LuaAPI.luaL_getmetatable(L, type.FullName) = registry[type.FullName] { ["__index"] = obj_indexer//(見xlua.c) ["__newindex"] = obj_newindexer//(見xlua.c) [UserData] = 1 ["__tostring"] = StaticLuaCallbacks.ToString [1] = typeId } userdata = index//每個實例都會有一個ud,但是meta是一樣的。ud只是用與區別不同的實例,便於在objects中取得對應的實例對象。 { ["__index"] = obj_meta } cls_meta = //這個是新建的表,是新建的cls_table的元表,用於查找靜態的Get方法(包括從基類base查找,靜態設置接口) { ["__call"] = __CreateInstance//(每個類的wrap文件),該方法會把類的ud壓棧,而通過這個ud,我們可以訪問到類的成員方法。 ["__index"] = cls_indexer//(見xlua.c) ["__newindex"] = cls_newindexer//(見xlua.c) } cls_table = //每個類都會有一個cls_table,可能會有多個ud.這里保存了所有的靜態方法映射。 { staticFunc = func ["__index"] = cls_meta }
七、靜態方法、成員方法的wrap區別。
如下圖所示,成員方法需要調用以下方法取得實例:
UIHierarchy gen_to_be_invoked = (UIHierarchy)translator.FastGetCSObj(L, 1);
也就是它的upvalue第一個參數就是對象的實例,對應lua的調用就是冒號調用:UIHierarchy:SetWidgets,而靜態方法因為不需要傳本身,所以對應lua就是點號調用。
lua點號和冒號區別:https://www.jianshu.com/p/56961e4bd693
這邊的index是對象的ud的內存地址。
internal object FastGetCSObj(RealStatePtr L,int index) { return getCsObj(L, index, LuaAPI.xlua_tocsobj_fast(L,index)); }
靜態方法:直接查詢cls_table表,一次查詢。
成員方法:查詢ud-查詢ud的元表obj_meta-調用obj_meta的_index查詢obj_method表。三次查表+一次__index調用。
總結:盡量封裝靜態方法給lua使用,性能計較好。