--[[ 通过对函数进行包装,在执行方法体前后加入UnityProfiler的Sample函数的调用,从而将Lua函数的调用信息添加到UnityProfiler中。 由于会执行两次Lua->C#的函数调用,所以对于Lua中小而频繁的函数由于Overhead过高会造成Profiler结果中耗时数据不精确,但是调用次数和函数的层级信息是正确的。 可以在先开启对所有模块的自动包装用于观察代码性能趋势,在定位热点后针对性的为某些模块或者某些函数进行包装,来获得精确数据。 接口如下: WrapFunc 基于传入的函数生成一个新的包装函数,在原函数执行前后增加对UnityProfiler中Sample函数的调用 newFunc = function(...) BeginSample ... = func() EndSample return ... end WrapMod 包装一个指定的模块中字段中符合匹配条件的所有函数 匹配模式由funcPattern参数指定,符合条件string.find(funcName, funcPattern)的函数被认为识别功能 WrapAll 包装所有模块中符合条件的所有函数字段,可以选择仅对package.loaded中已加载的模块执行,或者劫持require函数并自动对后续加载的所有模块执行包装 匹配模式由modPattern和funcPattern参数指定 NOTICE: 1. setfenv/module这类特殊函数不可包装 2. 模块中的内部函数无法自动包装 --]] local _BeginSample = UnityEngineExt.BeginSample local _EndSample = UnityEngineExt.EndSample local _GetSampleID = UnityEngineExt.GetSampleID UnityEngineExt.ClearSampleID() local type = type local assert = assert local rawset = rawset local pairs = pairs local require = require local error = error local string_find = string.find local string_match = string.match local isCFunction = LuaExt.IsCFunction local UnityProfiler = {} local sampleNameID = {} --不可包装自身函数,setfenv和module函数的效果与函数层级有关,所以不可再次包装 --TODO 如何包装那些模块和函数upvalue中的原始函数? local wraped = { [UnityProfiler] = true, [_G.setfenv] = true, [_G.module] = true, } local function BeginSample(name) local id = sampleNameID[name] if not id then id = _GetSampleID(name) sampleNameID[name] = id end _BeginSample(id) end UnityProfiler.BeginSample = BeginSample UnityProfiler.EndSample = _EndSample local function _WrapMod(mod, nameMod, patternMod, patternFunc) if not wraped[mod] and type(mod) == 'table' and string_find(nameMod, patternMod) then for key, val in pairs(mod) do if not wraped[val] and type(val) == 'function' and not isCFunction(val) and type(key) == 'string' and string_find(key, patternFunc) then local match = string_match(nameMod, '%.?([%w_]+)$') or nameMod local sampleName = key..'@'..match rawset(mod, key, UnityProfiler.WrapFunc(val, sampleName)) end end wraped[mod] = true end end function UnityProfiler.WrapMod(mod, nameMod, patternFunc) patternFunc = patternFunc or '' _WrapMod(mod, nameMod, '', patternFunc) end function UnityProfiler.WrapAll(patternMod, patternFunc, wrapLoadedOnly) patternMod = patternMod or '' patternFunc = patternFunc or '' for nameMod, mod in pairs(package.loaded) do _WrapMod(mod, nameMod, patternMod, patternFunc) end if not wrapLoadedOnly then rawset(_G, 'require', function(nameMod) local mod = require(nameMod) _WrapMod(mod, nameMod, patternMod, patternFunc) return mod end) end end function UnityProfiler.WrapFunc(func, sampleName) return function(...) local id = sampleNameID[sampleName] if not id then id = _GetSampleID(sampleName) sampleNameID[sampleName] = id end _BeginSample(id) --TODO how to pack all return values without new table? local r1, r2, r3, r4, r5, r6, r7, r8, r9 = func(...) _EndSample() --TODO can't not handle nil as normal return value if r1 == nil then return nil elseif r2 == nil then return r1 elseif r3 == nil then return r1, r2 elseif r4 == nil then return r1, r2, r3 elseif r5 == nil then return r1, r2, r3, r4 elseif r6 == nil then return r1, r2, r3, r4, r5 elseif r7 == nil then return r1, r2, r3, r4, r5, r6 elseif r8 == nil then return r1, r2, r3, r4, r5, r6, r7 elseif r9 == nil then return r1, r2, r3, r4, r5, r6, r7, r8 else error('more than 8 return values is not supported by WrapFunc: ' .. sampleName, 2) end end end return UnityProfiler