remove some memory leaks from rendered fragments
[?]
Jun 10, 2022, 3:24 PM
OGUV4HSA7XGSQLUVWBAE3AE263Z7Z6G3BZOB4CN2AOYD2DEJMOZACDependencies
- [2]
LS55YKGWswitch copy/paste to ctrl- hotkeys - [3]
NP7PIUBTbugfix: restore state after C-f (find) - [*]
BULPIBEGbeginnings of a module for the text editor - [*]
HMODUNJEscroll on backspace - [*]
XNFTJHC4split keyboard handling between Text and Drawing - [*]
KOYAJWE4extract a couple more methods - [*]
3OKKTUT4up and down arrow now moving by screen line where possible - [*]
242L3OQXbugfix: ensure Cursor_line is always on a text line - [*]
HOSPP2ANcrisp font rendering - [*]
R5QXEHUIsomebody stop me - [*]
OTIBCAUJlove2d scaffold - [*]
Z4XRNDTRfind text - [*]
NQWWTGXRswitch undo/redo to ctrl- hotkeys
Change contents
- edit in text.lua at line 226
Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaks - edit in text.lua at line 427
Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaks - edit in text.lua at line 667
Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaks - edit in text.lua at line 901
print('clearing fragments') - file addition: main.lua.[12.2]
local utf8 = require 'utf8'require 'app'require 'test'require 'keychord'require 'file'require 'button'local Text = require 'text'local Drawing = require 'drawing'local geom = require 'geom'require 'help'require 'icons'local mri = require 'MemoryReferenceInfo'-- run in both tests and a real runfunction App.initialize_globals()-- a line is either text or a drawing-- a text is a table with:-- mode = 'text',-- string data,-- a (y) coord in pixels (updated while painting screen),-- some cached data that's blown away and recomputed when data changes:-- fragments: snippets of rendered love.graphics.Text, guaranteed to not wrap-- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line-- a drawing is a table with:-- mode = 'drawing'-- a (y) coord in pixels (updated while painting screen),-- a (h)eight,-- an array of points, and-- an array of shapes-- a shape is a table containing:-- a mode-- an array points for mode 'freehand' (raw x,y coords; freehand drawings don't pollute the points array of a drawing)-- an array vertices for mode 'polygon', 'rectangle', 'square'-- p1, p2 for mode 'line'-- p1, p2, arrow-mode for mode 'arrow-line'-- center, radius for mode 'circle'-- center, radius, start_angle, end_angle for mode 'arc'-- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide-- The field names are carefully chosen so that switching modes in midstream-- remembers previously entered points where that makes sense.Lines = {{mode='text', data=''}}-- Lines can be too long to fit on screen, in which case they _wrap_ into-- multiple _screen lines_.---- Therefore, any potential location for the cursor can be described in two ways:-- * schema 1: As a combination of line index and position within a line (in utf8 codepoint units)-- * schema 2: As a combination of line index, screen line index within the line, and a position within the screen line.---- Most of the time we'll only persist positions in schema 1, translating to-- schema 2 when that's convenient.Screen_top1 = {line=1, pos=1} -- position of start of screen line at top of screenCursor1 = {line=1, pos=1} -- position of cursorScreen_bottom1 = {line=1, pos=1} -- position of start of screen line at bottom of screenSelection1 = {}Old_cursor1, Old_selection1, Mousepress_shift = nil -- some extra state to compute selection between mousepress and mousereleaseRecent_mouse = {} -- when selecting text, avoid recomputing some state on every single frameCursor_x, Cursor_y = 0, 0 -- in pixelsCurrent_drawing_mode = 'line'Previous_drawing_mode = nil-- values for testsFont_height = 14Line_height = 15Margin_top = 15Filename = love.filesystem.getUserDirectory()..'/lines.txt'-- undoHistory = {}Next_history = 1-- searchSearch_term = nilSearch_text = nilSearch_backup = nil -- stuff to restore when cancelling search-- resizeLast_resize_time = nil-- blinking cursorCursor_time = 0Initialize_done = falseBefore_done = falsemri.m_cConfig.m_bAllMemoryRefFileAddTime = falseend -- App.initialize_globalsfunction App.initialize(arg)love.keyboard.setTextInput(true) -- bring up keyboard on touch screenlove.keyboard.setKeyRepeat(true)if arg[1] == '-geometry' theninitialize_window_geometry(arg[2])table.remove(arg, 2)table.remove(arg, 1)elseinitialize_window_geometry()endinitialize_font_settings(20)if #arg > 0 thenFilename = arg[1]endprint('init', collectgarbage('count'))Lines = load_from_disk(Filename)print('load_from_disk', collectgarbage('count'))for i,line in ipairs(Lines) doif line.mode == 'text' thenCursor1.line = ibreakendendlove.window.setTitle('lines.love - '..Filename)if #arg > 1 thenprint('ignoring commandline args after '..arg[1])endInitialize_done = true--? if rawget(_G, 'jit') then--? jit.off()--? jit.flush()--? endend -- App.initializefunction initialize_window_geometry(geometry_spec)local geometry_initializedif geometry_spec thengeometry_initialized = parse_geometry_spec(geometry_spec)endif not geometry_initialized then-- maximize windowlove.window.setMode(0, 0) -- maximizeApp.screen.width, App.screen.height, App.screen.flags = love.window.getMode()-- shrink slightly to account for window decorationApp.screen.width = App.screen.width-100App.screen.height = App.screen.height-100endApp.screen.flags.resizable = trueApp.screen.flags.minwidth = math.min(App.screen.width, 200)App.screen.flags.minheight = math.min(App.screen.width, 200)love.window.updateMode(App.screen.width, App.screen.height, App.screen.flags)endfunction parse_geometry_spec(geometry_spec)local width, height, x, y = geometry_spec:match('(%d+)x(%d+)%+(%d+)%+(%d+)')if width == nil thenprint('invalid geometry spec: '..geometry_spec)print('expected format: {width}x{height}+{x}+{y}')return falseendApp.screen.width = math.floor(tonumber(width))App.screen.height = math.floor(tonumber(height))App.screen.flags = {x=math.floor(tonumber(x)), y=math.floor(tonumber(y))}return trueendfunction love.resize(w, h)--? print(("Window resized to width: %d and height: %d."):format(w, h))App.screen.width, App.screen.height = w, hLine_width = math.min(40*App.width(Em), App.screen.width-50)Text.redraw_all()Last_resize_time = love.timer.getTime()endfunction initialize_font_settings(font_height)Font_height = font_heightlove.graphics.setFont(love.graphics.newFont(Font_height))Line_height = math.floor(font_height*1.3)-- maximum width available to either text or drawings, in pixelsEm = App.newText(love.graphics.getFont(), 'm')-- readable text width is 50-75 charsLine_width = math.min(40*App.width(Em), App.screen.width-50)endfunction App.filedropped(file)App.initialize_globals() -- in particular, forget all undo historyFilename = file:getFilename()file:open('r')Lines = load_from_file(file)file:close()for i,line in ipairs(Lines) doif line.mode == 'text' thenCursor1.line = ibreakendendlove.window.setTitle('Text with Lines - '..Filename)endframe_index = 0function App.draw()frame_index = frame_index+1if frame_index % 10 == 0 thenprint(frame_index)endButton_handlers = {}love.graphics.setColor(1, 1, 1)love.graphics.rectangle('fill', 0, 0, App.screen.width-1, App.screen.height-1)love.graphics.setColor(0, 0, 0)-- some hysteresis while resizingif Last_resize_time thenif love.timer.getTime() - Last_resize_time < 0.1 thenreturnelseLast_resize_time = nilendendassert(Text.le1(Screen_top1, Cursor1))local y = Margin_top--? print('== draw')for line_index,line in ipairs(Lines) do--? print('draw:', y, line_index, line)if y + Line_height > App.screen.height then break end--? print('a')if line_index >= Screen_top1.line thenScreen_bottom1.line = line_indexif line.mode == 'text' and line.data == '' thenline.y = ybutton('draw', {x=4,y=y+4, w=12,h=12, color={1,1,0},icon = icon.insert_drawing,onpress1 = function()Drawing.before = snapshot()table.insert(Lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}})if Cursor1.line >= line_index thenCursor1.line = Cursor1.line+1endend})if Search_term == nil thenif line_index == Cursor1.line thenText.draw_cursor(25, y)endendScreen_bottom1.pos = Screen_top1.posy = y + Line_heightelseif line.mode == 'drawing' theny = y+10 -- paddingline.y = yDrawing.draw(line)y = y + Drawing.pixels(line.h) + 10 -- paddingelse--? print('text')line.y = yy, Screen_bottom1.pos = Text.draw(line, Line_width, line_index)y = y + Line_height--? print('=> y', y)endendend--? print('screen bottom: '..tostring(Screen_bottom1.pos)..' in '..tostring(Lines[Screen_bottom1.line].data))if Search_term thenText.draw_search_bar()endif Initialize_done thenif not Before_done thenBefore_done = trueprint('before', collectgarbage('count'))collectgarbage('collect')mri.m_cMethods.DumpMemorySnapshot('./', '0', -1)frame_index = 0elseif frame_index == 1000 thenprint('after', collectgarbage('count'))collectgarbage('collect')mri.m_cMethods.DumpMemorySnapshot('./', '1', -1)mri.m_cMethods.DumpMemorySnapshotComparedFile("./", "Compared", -1,"./LuaMemRefInfo-All-[0].txt","./LuaMemRefInfo-All-[1].txt")os.exit(1)endendendfunction App.update(dt)Cursor_time = Cursor_time + dt-- some hysteresis while resizingif Last_resize_time thenif love.timer.getTime() - Last_resize_time < 0.1 thenreturnelseLast_resize_time = nilendendDrawing.update(dt)endfunction App.mousepressed(x,y, mouse_button)if Search_term then return endpropagate_to_button_handlers(x,y, mouse_button)for line_index,line in ipairs(Lines) doif line.mode == 'text' thenif Text.in_line(line, x,y) then-- delicate dance between cursor, selection and old cursor-- manual tests:-- regular press+release: sets cursor, clears selection-- shift press+release:-- sets selection to old cursor if not set otherwise leaves it untouched-- sets cursor-- press and hold to start a selection: sets selection on press, cursor on release-- press and hold, then press shift: ignore shift-- i.e. mousereleased should never look at shift stateOld_cursor1 = Cursor1Old_selection1 = Selection1Mousepress_shift = App.shift_down()Selection1 = {line=line_index, pos=Text.to_pos_on_line(line, x, y)}endelseif line.mode == 'drawing' thenif Drawing.in_drawing(line, x, y) thenDrawing.mouse_pressed(line, x,y, button)endendendendfunction App.mousereleased(x,y, button)if Search_term then return endif Lines.current_drawing thenDrawing.mouse_released(x,y, button)elsefor line_index,line in ipairs(Lines) doif line.mode == 'text' thenif Text.in_line(line, x,y) thenCursor1 = {line=line_index, pos=Text.to_pos_on_line(line, x, y)}if Mousepress_shift thenif Old_selection1.line == nil thenSelection1 = Old_cursor1elseSelection1 = Old_selection1endendOld_cursor1, Old_selection1, Mousepress_shift = nilendendend--? print('select:', Selection1.line, Selection1.pos)endendfunction App.textinput(t)for _,line in ipairs(Lines) do line.y = nil end -- just in case we scrollif Search_term thenSearch_term = Search_term..tSearch_text = nilText.search_next()elseif Current_drawing_mode == 'name' thenlocal drawing = Lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]p.name = p.name..telseText.textinput(t)endsave_to_disk(Lines, Filename)endfunction App.keychord_pressed(chord)if Search_term thenif chord == 'escape' thenSearch_term = nilSearch_text = nilCursor1 = Search_backup.cursorScreen_top1 = Search_backup.screen_topSearch_backup = nilText.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leakselseif chord == 'return' thenSearch_term = nilSearch_text = nilSearch_backup = nilelseif chord == 'backspace' thenlocal len = utf8.len(Search_term)local byte_offset = utf8.offset(Search_term, len)Search_term = string.sub(Search_term, 1, byte_offset-1)Search_text = nilelseif chord == 'down' thenCursor1.pos = Cursor1.pos+1Text.search_next()elseif chord == 'up' thenText.search_previous()endreturnelseif chord == 'C-f' thenSearch_term = ''Search_backup = {cursor={line=Cursor1.line, pos=Cursor1.pos}, screen_top={line=Screen_top1.line, pos=Screen_top1.pos}}assert(Search_text == nil)elseif chord == 'C-=' theninitialize_font_settings(Font_height+2)Text.redraw_all()elseif chord == 'C--' theninitialize_font_settings(Font_height-2)Text.redraw_all()elseif chord == 'C-0' theninitialize_font_settings(20)Text.redraw_all()elseif chord == 'C-z' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal event = undo_event()if event thenlocal src = event.beforeScreen_top1 = deepcopy(src.screen_top)Cursor1 = deepcopy(src.cursor)Selection1 = deepcopy(src.selection)patch(Lines, event.after, event.before)Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaksendelseif chord == 'C-y' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal event = redo_event()if event thenlocal src = event.afterScreen_top1 = deepcopy(src.screen_top)Cursor1 = deepcopy(src.cursor)Selection1 = deepcopy(src.selection)patch(Lines, event.before, event.after)Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaksend-- clipboardelseif chord == 'C-c' thenprint('C-c', collectgarbage('count'))for _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal s = Text.selection()if s thenApp.setClipboardText(s)endelseif chord == 'C-x' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal s = Text.cut_selection()if s thenApp.setClipboardText(s)endsave_to_disk(Lines, Filename)elseif chord == 'C-v' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scroll-- We don't have a good sense of when to scroll, so we'll be conservative-- and sometimes scroll when we didn't quite need to.local before_line = Cursor1.linelocal before = snapshot(before_line)local clipboard_data = App.getClipboardText()local num_newlines = 0 -- hack 1for _,code in utf8.codes(clipboard_data) dolocal c = utf8.char(code)if c == '\n' thenText.insert_return()num_newlines = num_newlines+1elseText.insert_at_cursor(c)endend-- hack 1: if we have too many newlines we definitely need to scrollfor i=before_line,Cursor1.line doLines[i].screen_line_starting_pos = nilText.populate_screen_line_starting_pos(i)endif Cursor1.line-Screen_top1.line+1 + num_newlines > App.screen.height/Line_height thenText.snap_cursor_to_bottom_of_screen()end-- hack 2: if we have too much text wrapping we definitely need to scrolllocal clipboard_text = App.newText(love.graphics.getFont(), clipboard_data)local clipboard_width = App.width(clipboard_text)--? print(Cursor_y, Cursor_y*Line_width, Cursor_y*Line_width+Cursor_x, Cursor_y*Line_width+Cursor_x+clipboard_width, Line_width*App.screen.height/Line_height)if Cursor_y*Line_width+Cursor_x + clipboard_width > Line_width*App.screen.height/Line_height thenText.snap_cursor_to_bottom_of_screen()endsave_to_disk(Lines, Filename)record_undo_event({before=before, after=snapshot(before_line, Cursor1.line)})-- dispatch to drawing or textelseif love.mouse.isDown('1') or chord:sub(1,2) == 'C-' then-- DON'T reset line.y hereDrawing.keychord_pressed(chord)elseif chord == 'escape' and love.mouse.isDown('1') thenlocal drawing = Drawing.current_drawing()if drawing thendrawing.pending = {}endelseif chord == 'escape' and not love.mouse.isDown('1') thenfor _,line in ipairs(Lines) doif line.mode == 'drawing' thenline.show_help = falseendendelseif Current_drawing_mode == 'name' thenif chord == 'return' thenCurrent_drawing_mode = Previous_drawing_modePrevious_drawing_mode = nilelselocal drawing = Lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]if chord == 'escape' thenp.name = nilelseif chord == 'backspace' thenlocal len = utf8.len(p.name)local byte_offset = utf8.offset(p.name, len-1)p.name = string.sub(p.name, 1, byte_offset)endendsave_to_disk(Lines, Filename)elsefor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrollText.keychord_pressed(chord)endendfunction App.keyreleased(key, scancode)end - edit in main.lua at line 345
Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaks - edit in main.lua at line 384[15.351][15.351]
Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaks - edit in main.lua at line 395[15.638][2.38]
Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaks - file addition: MemoryReferenceInfo.lua.0[12.2]
---- Collect memory reference info.-- https://github.com/yaukeywang/LuaMemorySnapshotDump---- @filename MemoryReferenceInfo.lua-- @author WangYaoqi-- @date 2016-02-03-- The global config of the mri.local cConfig ={m_bAllMemoryRefFileAddTime = true,m_bSingleMemoryRefFileAddTime = true,m_bComparedMemoryRefFileAddTime = true}-- Get the format string of date time.local function FormatDateTimeNow()local cDateTime = os.date("*t")local strDateTime = string.format("%04d%02d%02d-%02d%02d%02d", tostring(cDateTime.year), tostring(cDateTime.month), tostring(cDateTime.day),tostring(cDateTime.hour), tostring(cDateTime.min), tostring(cDateTime.sec))return strDateTimeend-- Get the string result without overrided __tostring.local function GetOriginalToStringResult(cObject)if not cObject thenreturn ""endlocal cMt = getmetatable(cObject)if not cMt thenreturn tostring(cObject)end-- Check tostring override.local strName = ""local cToString = rawget(cMt, "__tostring")if cToString thenrawset(cMt, "__tostring", nil)strName = tostring(cObject)rawset(cMt, "__tostring", cToString)elsestrName = tostring(cObject)endreturn strNameend-- Create a container to collect the mem ref info results.local function CreateObjectReferenceInfoContainer()-- Create new container.local cContainer = {}-- Contain [table/function] - [reference count] info.local cObjectReferenceCount = {}setmetatable(cObjectReferenceCount, {__mode = "k"})-- Contain [table/function] - [name] info.local cObjectAddressToName = {}setmetatable(cObjectAddressToName, {__mode = "k"})-- Set members.cContainer.m_cObjectReferenceCount = cObjectReferenceCountcContainer.m_cObjectAddressToName = cObjectAddressToName-- For stack info.cContainer.m_nStackLevel = -1cContainer.m_strShortSrc = "None"cContainer.m_nCurrentLine = -1return cContainerend-- Create a container to collect the mem ref info results from a dumped file.-- strFilePath - The file path.local function CreateObjectReferenceInfoContainerFromFile(strFilePath)-- Create a empty container.local cContainer = CreateObjectReferenceInfoContainer()cContainer.m_strShortSrc = strFilePath-- Cache ref info.local cRefInfo = cContainer.m_cObjectReferenceCountlocal cNameInfo = cContainer.m_cObjectAddressToName-- Read each line from file.local cFile = assert(io.open(strFilePath, "rb"))for strLine in cFile:lines() dolocal strHeader = string.sub(strLine, 1, 2)if "--" ~= strHeader thenlocal _, _, strAddr, strName, strRefCount= string.find(strLine, "(.+)\t(.*)\t(%d+)")if strAddr thencRefInfo[strAddr] = strRefCountcNameInfo[strAddr] = strNameendendend-- Close and clear file handler.io.close(cFile)cFile = nilreturn cContainerend-- Create a container to collect the mem ref info results from a dumped file.-- strObjectName - The object name you need to collect info.-- cObject - The object you need to collect info.local function CreateSingleObjectReferenceInfoContainer(strObjectName, cObject)-- Create new container.local cContainer = {}-- Contain [address] - [true] info.local cObjectExistTag = {}setmetatable(cObjectExistTag, {__mode = "k"})-- Contain [name] - [true] info.local cObjectAliasName = {}-- Contain [access] - [true] info.local cObjectAccessTag = {}setmetatable(cObjectAccessTag, {__mode = "k"})-- Set members.cContainer.m_cObjectExistTag = cObjectExistTagcContainer.m_cObjectAliasName = cObjectAliasNamecContainer.m_cObjectAccessTag = cObjectAccessTag-- For stack info.cContainer.m_nStackLevel = -1cContainer.m_strShortSrc = "None"cContainer.m_nCurrentLine = -1-- Init with object values.cContainer.m_strObjectName = strObjectNamecContainer.m_strAddressName = (("string" == type(cObject)) and ("\"" .. tostring(cObject) .. "\"")) or GetOriginalToStringResult(cObject)cContainer.m_cObjectExistTag[cObject] = truereturn cContainerend-- Collect memory reference info from a root table or function.-- strName - The root object name that start to search, default is "_G" if leave this to nil.-- cObject - The root object that start to search, default is _G if leave this to nil.-- cDumpInfoContainer - The container of the dump result info.local function CollectObjectReferenceInMemory(strName, cObject, cDumpInfoContainer)if not cObject thenreturnendif not strName thenstrName = ""end-- Check container.if (not cDumpInfoContainer) thencDumpInfoContainer = CreateObjectReferenceInfoContainer()end-- Check stack.if cDumpInfoContainer.m_nStackLevel > 0 thenlocal cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")if cStackInfo thencDumpInfoContainer.m_strShortSrc = cStackInfo.short_srccDumpInfoContainer.m_nCurrentLine = cStackInfo.currentlineendcDumpInfoContainer.m_nStackLevel = -1end-- Get ref and name info.local cRefInfoContainer = cDumpInfoContainer.m_cObjectReferenceCountlocal cNameInfoContainer = cDumpInfoContainer.m_cObjectAddressToNamelocal strType = type(cObject)if "table" == strType then-- Check table with class name.if rawget(cObject, "__cname") thenif "string" == type(cObject.__cname) thenstrName = strName .. "[class:" .. cObject.__cname .. "]"endelseif rawget(cObject, "class") thenif "string" == type(cObject.class) thenstrName = strName .. "[class:" .. cObject.class .. "]"endelseif rawget(cObject, "_className") thenif "string" == type(cObject._className) thenstrName = strName .. "[class:" .. cObject._className .. "]"endend-- Check if table is _G.if cObject == _G thenstrName = strName .. "[_G]"end-- Get metatable.local bWeakK = falselocal bWeakV = falselocal cMt = getmetatable(cObject)if cMt then-- Check mode.local strMode = rawget(cMt, "__mode")if strMode thenif "k" == strMode thenbWeakK = trueelseif "v" == strMode thenbWeakV = trueelseif "kv" == strMode thenbWeakK = truebWeakV = trueendendend-- Add reference and name.cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1if cNameInfoContainer[cObject] thenreturnend-- Set name.cNameInfoContainer[cObject] = strName-- Dump table key and value.for k, v in pairs(cObject) do-- Check key type.local strKeyType = type(k)if "table" == strKeyType thenif not bWeakK thenCollectObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)endif not bWeakV thenCollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "function" == strKeyType thenif not bWeakK thenCollectObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)endif not bWeakV thenCollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "thread" == strKeyType thenif not bWeakK thenCollectObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)endif not bWeakV thenCollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "userdata" == strKeyType thenif not bWeakK thenCollectObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)endif not bWeakV thenCollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseCollectObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)endend-- Dump metatable.if cMt thenCollectObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)endelseif "function" == strType then-- Get function info.local cDInfo = debug.getinfo(cObject, "Su")-- Write this info.cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1if cNameInfoContainer[cObject] thenreturnend-- Set name.cNameInfoContainer[cObject] = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"-- Get upvalues.local nUpsNum = cDInfo.nupsfor i = 1, nUpsNum dolocal strUpName, cUpValue = debug.getupvalue(cObject, i)local strUpValueType = type(cUpValue)--print(strUpName, cUpValue)if "table" == strUpValueType thenCollectObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "function" == strUpValueType thenCollectObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "thread" == strUpValueType thenCollectObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "userdata" == strUpValueType thenCollectObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)endend-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)endendelseif "thread" == strType then-- Add reference and name.cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1if cNameInfoContainer[cObject] thenreturnend-- Set name.cNameInfoContainer[cObject] = strName-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)endend-- Dump metatable.local cMt = getmetatable(cObject)if cMt thenCollectObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)endelseif "userdata" == strType then-- Add reference and name.cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1if cNameInfoContainer[cObject] thenreturnend-- Set name.cNameInfoContainer[cObject] = strName-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)endend-- Dump metatable.local cMt = getmetatable(cObject)if cMt thenCollectObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)endelseif "string" == strType then-- Add reference and name.cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1if cNameInfoContainer[cObject] thenreturnend-- Set name.cNameInfoContainer[cObject] = strName .. "[" .. strType .. "]"else-- For "number" and "boolean". (If you want to dump them, uncomment the followed lines.)-- -- Add reference and name.-- cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1-- if cNameInfoContainer[cObject] then-- return-- end-- -- Set name.-- cNameInfoContainer[cObject] = strName .. "[" .. strType .. ":" .. tostring(cObject) .. "]"endend-- Collect memory reference info of a single object from a root table or function.-- strName - The root object name that start to search, can not be nil.-- cObject - The root object that start to search, can not be nil.-- cDumpInfoContainer - The container of the dump result info.local function CollectSingleObjectReferenceInMemory(strName, cObject, cDumpInfoContainer)if not cObject thenreturnendif not strName thenstrName = ""end-- Check container.if (not cDumpInfoContainer) thencDumpInfoContainer = CreateObjectReferenceInfoContainer()end-- Check stack.if cDumpInfoContainer.m_nStackLevel > 0 thenlocal cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")if cStackInfo thencDumpInfoContainer.m_strShortSrc = cStackInfo.short_srccDumpInfoContainer.m_nCurrentLine = cStackInfo.currentlineendcDumpInfoContainer.m_nStackLevel = -1endlocal cExistTag = cDumpInfoContainer.m_cObjectExistTaglocal cNameAllAlias = cDumpInfoContainer.m_cObjectAliasNamelocal cAccessTag = cDumpInfoContainer.m_cObjectAccessTaglocal strType = type(cObject)if "table" == strType then-- Check table with class name.if rawget(cObject, "__cname") thenif "string" == type(cObject.__cname) thenstrName = strName .. "[class:" .. cObject.__cname .. "]"endelseif rawget(cObject, "class") thenif "string" == type(cObject.class) thenstrName = strName .. "[class:" .. cObject.class .. "]"endelseif rawget(cObject, "_className") thenif "string" == type(cObject._className) thenstrName = strName .. "[class:" .. cObject._className .. "]"endend-- Check if table is _G.if cObject == _G thenstrName = strName .. "[_G]"end-- Get metatable.local bWeakK = falselocal bWeakV = falselocal cMt = getmetatable(cObject)if cMt then-- Check mode.local strMode = rawget(cMt, "__mode")if strMode thenif "k" == strMode thenbWeakK = trueelseif "v" == strMode thenbWeakV = trueelseif "kv" == strMode thenbWeakK = truebWeakV = trueendendend-- Check if the specified object.if cExistTag[cObject] and (not cNameAllAlias[strName]) thencNameAllAlias[strName] = trueend-- Add reference and name.if cAccessTag[cObject] thenreturnend-- Get this name.cAccessTag[cObject] = true-- Dump table key and value.for k, v in pairs(cObject) do-- Check key type.local strKeyType = type(k)if "table" == strKeyType thenif not bWeakK thenCollectSingleObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)endif not bWeakV thenCollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "function" == strKeyType thenif not bWeakK thenCollectSingleObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)endif not bWeakV thenCollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "thread" == strKeyType thenif not bWeakK thenCollectSingleObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)endif not bWeakV thenCollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "userdata" == strKeyType thenif not bWeakK thenCollectSingleObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)endif not bWeakV thenCollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseCollectSingleObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)endend-- Dump metatable.if cMt thenCollectSingleObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)endelseif "function" == strType then-- Get function info.local cDInfo = debug.getinfo(cObject, "Su")local cCombinedName = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"-- Check if the specified object.if cExistTag[cObject] and (not cNameAllAlias[cCombinedName]) thencNameAllAlias[cCombinedName] = trueend-- Write this info.if cAccessTag[cObject] thenreturnend-- Set name.cAccessTag[cObject] = true-- Get upvalues.local nUpsNum = cDInfo.nupsfor i = 1, nUpsNum dolocal strUpName, cUpValue = debug.getupvalue(cObject, i)local strUpValueType = type(cUpValue)--print(strUpName, cUpValue)if "table" == strUpValueType thenCollectSingleObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "function" == strUpValueType thenCollectSingleObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "thread" == strUpValueType thenCollectSingleObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "userdata" == strUpValueType thenCollectSingleObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)endend-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectSingleObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)endendelseif "thread" == strType then-- Check if the specified object.if cExistTag[cObject] and (not cNameAllAlias[strName]) thencNameAllAlias[strName] = trueend-- Add reference and name.if cAccessTag[cObject] thenreturnend-- Get this name.cAccessTag[cObject] = true-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectSingleObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)endend-- Dump metatable.local cMt = getmetatable(cObject)if cMt thenCollectSingleObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)endelseif "userdata" == strType then-- Check if the specified object.if cExistTag[cObject] and (not cNameAllAlias[strName]) thencNameAllAlias[strName] = trueend-- Add reference and name.if cAccessTag[cObject] thenreturnend-- Get this name.cAccessTag[cObject] = true-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectSingleObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)endend-- Dump metatable.local cMt = getmetatable(cObject)if cMt thenCollectSingleObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)endelseif "string" == strType then-- Check if the specified object.if cExistTag[cObject] and (not cNameAllAlias[strName]) thencNameAllAlias[strName] = trueend-- Add reference and name.if cAccessTag[cObject] thenreturnend-- Get this name.cAccessTag[cObject] = trueelse-- For "number" and "boolean" type, they are not object type, skip.endend-- The base method to dump a mem ref info result into a file.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- strRootObjectName - The header info to show the root object name, can be nil.-- cRootObject - The header info to show the root object address, can be nil.-- cDumpInfoResultsBase - The base dumped mem info result, nil means no compare and only output cDumpInfoResults, otherwise to compare with cDumpInfoResults.-- cDumpInfoResults - The compared dumped mem info result, dump itself only if cDumpInfoResultsBase is nil, otherwise dump compared results with cDumpInfoResultsBase.local function OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, cDumpInfoResultsBase, cDumpInfoResults)-- Check results.if not cDumpInfoResults thenreturnend-- Get time format string.local strDateTime = FormatDateTimeNow()-- Collect memory info.local cRefInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectReferenceCount) or nillocal cNameInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectAddressToName) or nillocal cRefInfo = cDumpInfoResults.m_cObjectReferenceCountlocal cNameInfo = cDumpInfoResults.m_cObjectAddressToName-- Create a cache result to sort by ref count.local cRes = {}local nIdx = 0for k in pairs(cRefInfo) donIdx = nIdx + 1cRes[nIdx] = kend-- Sort result.table.sort(cRes, function (l, r)return cRefInfo[l] > cRefInfo[r]end)-- Save result to file.local bOutputFile = strSavePath and (string.len(strSavePath) > 0)local cOutputHandle = nillocal cOutputEntry = printif bOutputFile then-- Check save path affix.local strAffix = string.sub(strSavePath, -1)if ("/" ~= strAffix) and ("\\" ~= strAffix) thenstrSavePath = strSavePath .. "/"end-- Combine file name.local strFileName = strSavePath .. "LuaMemRefInfo-All"if (not strExtraFileName) or (0 == string.len(strExtraFileName)) thenif cDumpInfoResultsBase thenif cConfig.m_bComparedMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "].txt"elsestrFileName = strFileName .. ".txt"endelseif cConfig.m_bAllMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "].txt"elsestrFileName = strFileName .. ".txt"endendelseif cDumpInfoResultsBase thenif cConfig.m_bComparedMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"elsestrFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"endelseif cConfig.m_bAllMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"elsestrFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"endendendlocal cFile = assert(io.open(strFileName, "w"))cOutputHandle = cFilecOutputEntry = cFile.writeendlocal cOutputer = function (strContent)if cOutputHandle thencOutputEntry(cOutputHandle, strContent)elsecOutputEntry(strContent)endend-- Write table header.if cDumpInfoResultsBase thencOutputer("--------------------------------------------------------\n")cOutputer("-- This is compared memory information.\n")cOutputer("--------------------------------------------------------\n")cOutputer("-- Collect base memory reference at line:" .. tostring(cDumpInfoResultsBase.m_nCurrentLine) .. "@file:" .. cDumpInfoResultsBase.m_strShortSrc .. "\n")cOutputer("-- Collect compared memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")elsecOutputer("--------------------------------------------------------\n")cOutputer("-- Collect memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")endcOutputer("--------------------------------------------------------\n")cOutputer("-- [Table/Function/String Address/Name]\t[Reference Path]\t[Reference Count]\n")cOutputer("--------------------------------------------------------\n")if strRootObjectName and cRootObject thenif "string" == type(cRootObject) thencOutputer("-- From Root Object: \"" .. tostring(cRootObject) .. "\" (" .. strRootObjectName .. ")\n")elsecOutputer("-- From Root Object: " .. GetOriginalToStringResult(cRootObject) .. " (" .. strRootObjectName .. ")\n")endend-- Save each info.for i, v in ipairs(cRes) doif (not cDumpInfoResultsBase) or (not cRefInfoBase[v]) thenif (nMaxRescords > 0) thenif (i <= nMaxRescords) thenif "string" == type(v) thenlocal strOrgString = tostring(v)local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"")if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) thenlocal strRepString = string.gsub(strOrgString, "([\n\r])", "\\n")cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")elsecOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")endelsecOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")endendelseif "string" == type(v) thenlocal strOrgString = tostring(v)local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"")if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) thenlocal strRepString = string.gsub(strOrgString, "([\n\r])", "\\n")cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")elsecOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")endelsecOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")endendendendif bOutputFile thenio.close(cOutputHandle)cOutputHandle = nilendend-- The base method to dump a mem ref info result of a single object into a file.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- cDumpInfoResults - The dumped results.local function OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoResults)-- Check results.if not cDumpInfoResults thenreturnend-- Get time format string.local strDateTime = FormatDateTimeNow()-- Collect memory info.local cObjectAliasName = cDumpInfoResults.m_cObjectAliasName-- Save result to file.local bOutputFile = strSavePath and (string.len(strSavePath) > 0)local cOutputHandle = nillocal cOutputEntry = printif bOutputFile then-- Check save path affix.local strAffix = string.sub(strSavePath, -1)if ("/" ~= strAffix) and ("\\" ~= strAffix) thenstrSavePath = strSavePath .. "/"end-- Combine file name.local strFileName = strSavePath .. "LuaMemRefInfo-Single"if (not strExtraFileName) or (0 == string.len(strExtraFileName)) thenif cConfig.m_bSingleMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "].txt"elsestrFileName = strFileName .. ".txt"endelseif cConfig.m_bSingleMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"elsestrFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"endendlocal cFile = assert(io.open(strFileName, "w"))cOutputHandle = cFilecOutputEntry = cFile.writeendlocal cOutputer = function (strContent)if cOutputHandle thencOutputEntry(cOutputHandle, strContent)elsecOutputEntry(strContent)endend-- Write table header.cOutputer("--------------------------------------------------------\n")cOutputer("-- Collect single object memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")cOutputer("--------------------------------------------------------\n")-- Calculate reference count.local nCount = 0for k in pairs(cObjectAliasName) donCount = nCount + 1end-- Output reference count.cOutputer("-- For Object: " .. cDumpInfoResults.m_strAddressName .. " (" .. cDumpInfoResults.m_strObjectName .. "), have " .. tostring(nCount) .. " reference in total.\n")cOutputer("--------------------------------------------------------\n")-- Save each info.for k in pairs(cObjectAliasName) doif (nMaxRescords > 0) thenif (i <= nMaxRescords) thencOutputer(k .. "\n")endelsecOutputer(k .. "\n")endendif bOutputFile thenio.close(cOutputHandle)cOutputHandle = nilendend-- Fileter an existing result file and output it.-- strFilePath - The existing result file.-- strFilter - The filter string.-- bIncludeFilter - Include(true) or exclude(false) the filter.-- bOutputFile - Output to file(true) or console(false).local function OutputFilteredResult(strFilePath, strFilter, bIncludeFilter, bOutputFile)if (not strFilePath) or (0 == string.len(strFilePath)) thenprint("You need to specify a file path.")returnendif (not strFilter) or (0 == string.len(strFilter)) thenprint("You need to specify a filter string.")returnend-- Read file.local cFilteredResult = {}local cReadFile = assert(io.open(strFilePath, "rb"))for strLine in cReadFile:lines() dolocal nBegin, nEnd = string.find(strLine, strFilter)if nBegin and nEnd thenif bIncludeFilter thennBegin, nEnd = string.find(strLine, "[\r\n]")if nBegin and nEnd and (string.len(strLine) == nEnd) thentable.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1))elsetable.insert(cFilteredResult, strLine)endendelseif not bIncludeFilter thennBegin, nEnd = string.find(strLine, "[\r\n]")if nBegin and nEnd and (string.len(strLine) == nEnd) thentable.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1))elsetable.insert(cFilteredResult, strLine)endendendend-- Close and clear read file handle.io.close(cReadFile)cReadFile = nil-- Write filtered result.local cOutputHandle = nillocal cOutputEntry = printif bOutputFile then-- Combine file name.local _, _, strResFileName = string.find(strFilePath, "(.*)%.txt")strResFileName = strResFileName .. "-Filter-" .. ((bIncludeFilter and "I") or "E") .. "-[" .. strFilter .. "].txt"local cFile = assert(io.open(strResFileName, "w"))cOutputHandle = cFilecOutputEntry = cFile.writeendlocal cOutputer = function (strContent)if cOutputHandle thencOutputEntry(cOutputHandle, strContent)elsecOutputEntry(strContent)endend-- Output result.for i, v in ipairs(cFilteredResult) docOutputer(v .. "\n")endif bOutputFile thenio.close(cOutputHandle)cOutputHandle = nilendend-- Dump memory reference at current time.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- strRootObjectName - The root object name that start to search, default is "_G" if leave this to nil.-- cRootObject - The root object that start to search, default is _G if leave this to nil.local function DumpMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject)-- Get time format string.local strDateTime = FormatDateTimeNow()-- Check root object.if cRootObject thenif (not strRootObjectName) or (0 == string.len(strRootObjectName)) thenstrRootObjectName = tostring(cRootObject)endelsecRootObject = debug.getregistry()strRootObjectName = "registry"end-- Create container.local cDumpInfoContainer = CreateObjectReferenceInfoContainer()local cStackInfo = debug.getinfo(2, "Sl")if cStackInfo thencDumpInfoContainer.m_strShortSrc = cStackInfo.short_srccDumpInfoContainer.m_nCurrentLine = cStackInfo.currentlineend-- Collect memory info.CollectObjectReferenceInMemory(strRootObjectName, cRootObject, cDumpInfoContainer)-- Dump the result.OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, nil, cDumpInfoContainer)end-- Dump compared memory reference results generated by DumpMemorySnapshot.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- cResultBefore - The base dumped results.-- cResultAfter - The compared dumped results.local function DumpMemorySnapshotCompared(strSavePath, strExtraFileName, nMaxRescords, cResultBefore, cResultAfter)-- Dump the result.OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter)end-- Dump compared memory reference file results generated by DumpMemorySnapshot.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- strResultFilePathBefore - The base dumped results file.-- strResultFilePathAfter - The compared dumped results file.local function DumpMemorySnapshotComparedFile(strSavePath, strExtraFileName, nMaxRescords, strResultFilePathBefore, strResultFilePathAfter)-- Read results from file.local cResultBefore = CreateObjectReferenceInfoContainerFromFile(strResultFilePathBefore)local cResultAfter = CreateObjectReferenceInfoContainerFromFile(strResultFilePathAfter)-- Dump the result.OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter)end-- Dump memory reference of a single object at current time.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- strObjectName - The object name reference you want to dump.-- cObject - The object reference you want to dump.local function DumpMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, strObjectName, cObject)-- Check object.if not cObject thenreturnendif (not strObjectName) or (0 == string.len(strObjectName)) thenstrObjectName = GetOriginalToStringResult(cObject)end-- Get time format string.local strDateTime = FormatDateTimeNow()-- Create container.local cDumpInfoContainer = CreateSingleObjectReferenceInfoContainer(strObjectName, cObject)local cStackInfo = debug.getinfo(2, "Sl")if cStackInfo thencDumpInfoContainer.m_strShortSrc = cStackInfo.short_srccDumpInfoContainer.m_nCurrentLine = cStackInfo.currentlineend-- Collect memory info.CollectSingleObjectReferenceInMemory("registry", debug.getregistry(), cDumpInfoContainer)-- Dump the result.OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoContainer)end-- Return methods.local cPublications = {m_cConfig = nil, m_cMethods = {}, m_cHelpers = {}, m_cBases = {}}cPublications.m_cConfig = cConfigcPublications.m_cMethods.DumpMemorySnapshot = DumpMemorySnapshotcPublications.m_cMethods.DumpMemorySnapshotCompared = DumpMemorySnapshotComparedcPublications.m_cMethods.DumpMemorySnapshotComparedFile = DumpMemorySnapshotComparedFilecPublications.m_cMethods.DumpMemorySnapshotSingleObject = DumpMemorySnapshotSingleObjectcPublications.m_cHelpers.FormatDateTimeNow = FormatDateTimeNowcPublications.m_cHelpers.GetOriginalToStringResult = GetOriginalToStringResultcPublications.m_cBases.CreateObjectReferenceInfoContainer = CreateObjectReferenceInfoContainercPublications.m_cBases.CreateObjectReferenceInfoContainerFromFile = CreateObjectReferenceInfoContainerFromFilecPublications.m_cBases.CreateSingleObjectReferenceInfoContainer = CreateSingleObjectReferenceInfoContainercPublications.m_cBases.CollectObjectReferenceInMemory = CollectObjectReferenceInMemorycPublications.m_cBases.CollectSingleObjectReferenceInMemory = CollectSingleObjectReferenceInMemorycPublications.m_cBases.OutputMemorySnapshot = OutputMemorySnapshotcPublications.m_cBases.OutputMemorySnapshotSingleObject = OutputMemorySnapshotSingleObjectcPublications.m_cBases.OutputFilteredResult = OutputFilteredResultreturn cPublications - file addition: MemoryReferenceInfo.lua.[12.2]
---- Collect memory reference info.-- https://github.com/yaukeywang/LuaMemorySnapshotDump---- @filename MemoryReferenceInfo.lua-- @author WangYaoqi-- @date 2016-02-03-- The global config of the mri.local cConfig ={m_bAllMemoryRefFileAddTime = true,m_bSingleMemoryRefFileAddTime = true,m_bComparedMemoryRefFileAddTime = true}-- Get the format string of date time.local function FormatDateTimeNow()local cDateTime = os.date("*t")local strDateTime = string.format("%04d%02d%02d-%02d%02d%02d", tostring(cDateTime.year), tostring(cDateTime.month), tostring(cDateTime.day),tostring(cDateTime.hour), tostring(cDateTime.min), tostring(cDateTime.sec))return strDateTimeend-- Get the string result without overrided __tostring.local function GetOriginalToStringResult(cObject)if not cObject thenreturn ""endlocal cMt = getmetatable(cObject)if not cMt thenreturn tostring(cObject)end-- Check tostring override.local strName = ""local cToString = rawget(cMt, "__tostring")if cToString thenprint('tostring overridden:', tostring(cObject))--? rawset(cMt, "__tostring", nil)--? strName = tostring(cObject)--? rawset(cMt, "__tostring", cToString)--? else--? strName = tostring(cObject)endstrName = tostring(cObject)return strNameend-- Create a container to collect the mem ref info results.local function CreateObjectReferenceInfoContainer()-- Create new container.local cContainer = {}-- Contain [table/function] - [reference count] info.local cObjectReferenceCount = {}setmetatable(cObjectReferenceCount, {__mode = "k"})-- Contain [table/function] - [name] info.local cObjectAddressToName = {}setmetatable(cObjectAddressToName, {__mode = "k"})-- Set members.cContainer.m_cObjectReferenceCount = cObjectReferenceCountcContainer.m_cObjectAddressToName = cObjectAddressToName-- For stack info.cContainer.m_nStackLevel = -1cContainer.m_strShortSrc = "None"cContainer.m_nCurrentLine = -1return cContainerend-- Create a container to collect the mem ref info results from a dumped file.-- strFilePath - The file path.local function CreateObjectReferenceInfoContainerFromFile(strFilePath)-- Create a empty container.local cContainer = CreateObjectReferenceInfoContainer()cContainer.m_strShortSrc = strFilePath-- Cache ref info.local cRefInfo = cContainer.m_cObjectReferenceCountlocal cNameInfo = cContainer.m_cObjectAddressToName-- Read each line from file.local cFile = assert(io.open(strFilePath, "rb"))for strLine in cFile:lines() dolocal strHeader = string.sub(strLine, 1, 2)if "--" ~= strHeader thenlocal _, _, strAddr, strName, strRefCount= string.find(strLine, "(.+)\t(.*)\t(%d+)")if strAddr thencRefInfo[strAddr] = strRefCountcNameInfo[strAddr] = strNameendendend-- Close and clear file handler.io.close(cFile)cFile = nilreturn cContainerend-- Create a container to collect the mem ref info results from a dumped file.-- strObjectName - The object name you need to collect info.-- cObject - The object you need to collect info.local function CreateSingleObjectReferenceInfoContainer(strObjectName, cObject)-- Create new container.local cContainer = {}-- Contain [address] - [true] info.local cObjectExistTag = {}setmetatable(cObjectExistTag, {__mode = "k"})-- Contain [name] - [true] info.local cObjectAliasName = {}-- Contain [access] - [true] info.local cObjectAccessTag = {}setmetatable(cObjectAccessTag, {__mode = "k"})-- Set members.cContainer.m_cObjectExistTag = cObjectExistTagcContainer.m_cObjectAliasName = cObjectAliasNamecContainer.m_cObjectAccessTag = cObjectAccessTag-- For stack info.cContainer.m_nStackLevel = -1cContainer.m_strShortSrc = "None"cContainer.m_nCurrentLine = -1-- Init with object values.cContainer.m_strObjectName = strObjectNamecContainer.m_strAddressName = (("string" == type(cObject)) and ("\"" .. tostring(cObject) .. "\"")) or GetOriginalToStringResult(cObject)cContainer.m_cObjectExistTag[cObject] = truereturn cContainerend-- Collect memory reference info from a root table or function.-- strName - The root object name that start to search, default is "_G" if leave this to nil.-- cObject - The root object that start to search, default is _G if leave this to nil.-- cDumpInfoContainer - The container of the dump result info.local function CollectObjectReferenceInMemory(strName, cObject, cDumpInfoContainer)if not cObject thenreturnendif not strName thenstrName = ""end-- Check container.if (not cDumpInfoContainer) thencDumpInfoContainer = CreateObjectReferenceInfoContainer()end-- Check stack.if cDumpInfoContainer.m_nStackLevel > 0 thenlocal cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")if cStackInfo thencDumpInfoContainer.m_strShortSrc = cStackInfo.short_srccDumpInfoContainer.m_nCurrentLine = cStackInfo.currentlineendcDumpInfoContainer.m_nStackLevel = -1end-- Get ref and name info.local cRefInfoContainer = cDumpInfoContainer.m_cObjectReferenceCountlocal cNameInfoContainer = cDumpInfoContainer.m_cObjectAddressToNamelocal strType = type(cObject)if "table" == strType then-- Check table with class name.if rawget(cObject, "__cname") thenif "string" == type(cObject.__cname) thenstrName = strName .. "[class:" .. cObject.__cname .. "]"endelseif rawget(cObject, "class") thenif "string" == type(cObject.class) thenstrName = strName .. "[class:" .. cObject.class .. "]"endelseif rawget(cObject, "_className") thenif "string" == type(cObject._className) thenstrName = strName .. "[class:" .. cObject._className .. "]"endend-- Check if table is _G.if cObject == _G thenstrName = strName .. "[_G]"end-- Get metatable.local bWeakK = falselocal bWeakV = falselocal cMt = getmetatable(cObject)if cMt then-- Check mode.local strMode = rawget(cMt, "__mode")if strMode thenif "k" == strMode thenbWeakK = trueelseif "v" == strMode thenbWeakV = trueelseif "kv" == strMode thenbWeakK = truebWeakV = trueendendend-- Add reference and name.cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1if cNameInfoContainer[cObject] thenreturnend-- Set name.cNameInfoContainer[cObject] = strName-- Dump table key and value.for k, v in pairs(cObject) do-- Check key type.local strKeyType = type(k)if "table" == strKeyType thenif not bWeakK thenCollectObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)endif not bWeakV thenCollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "function" == strKeyType thenif not bWeakK thenCollectObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)endif not bWeakV thenCollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "thread" == strKeyType thenif not bWeakK thenCollectObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)endif not bWeakV thenCollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "userdata" == strKeyType thenif not bWeakK thenCollectObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)endif not bWeakV thenCollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseCollectObjectReferenceInMemory(strName .. "." .. tostring(k), v, cDumpInfoContainer)endend-- Dump metatable.if cMt thenCollectObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)endelseif "function" == strType then-- Get function info.local cDInfo = debug.getinfo(cObject, "Su")-- Write this info.cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1if cNameInfoContainer[cObject] thenreturnend-- Set name.cNameInfoContainer[cObject] = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"-- Get upvalues.local nUpsNum = cDInfo.nupsfor i = 1, nUpsNum dolocal strUpName, cUpValue = debug.getupvalue(cObject, i)local strUpValueType = type(cUpValue)--print(strUpName, cUpValue)if "table" == strUpValueType thenCollectObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "function" == strUpValueType thenCollectObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "thread" == strUpValueType thenCollectObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "userdata" == strUpValueType thenCollectObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)endend-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)endendelseif "thread" == strType then-- Add reference and name.cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1if cNameInfoContainer[cObject] thenreturnend-- Set name.cNameInfoContainer[cObject] = strName-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)endend-- Dump metatable.local cMt = getmetatable(cObject)if cMt thenCollectObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)endelseif "userdata" == strType then-- Add reference and name.cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1if cNameInfoContainer[cObject] thenreturnend-- Set name.cNameInfoContainer[cObject] = strName-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)endend-- Dump metatable.local cMt = getmetatable(cObject)if cMt thenCollectObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)endelseif "string" == strType then-- Add reference and name.cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1if cNameInfoContainer[cObject] thenreturnend-- Set name.cNameInfoContainer[cObject] = strName .. "[" .. strType .. "]"else-- For "number" and "boolean". (If you want to dump them, uncomment the followed lines.)-- -- Add reference and name.-- cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1-- if cNameInfoContainer[cObject] then-- return-- end-- -- Set name.-- cNameInfoContainer[cObject] = strName .. "[" .. strType .. ":" .. tostring(cObject) .. "]"endend-- Collect memory reference info of a single object from a root table or function.-- strName - The root object name that start to search, can not be nil.-- cObject - The root object that start to search, can not be nil.-- cDumpInfoContainer - The container of the dump result info.local function CollectSingleObjectReferenceInMemory(strName, cObject, cDumpInfoContainer)if not cObject thenreturnendif not strName thenstrName = ""end-- Check container.if (not cDumpInfoContainer) thencDumpInfoContainer = CreateObjectReferenceInfoContainer()end-- Check stack.if cDumpInfoContainer.m_nStackLevel > 0 thenlocal cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")if cStackInfo thencDumpInfoContainer.m_strShortSrc = cStackInfo.short_srccDumpInfoContainer.m_nCurrentLine = cStackInfo.currentlineendcDumpInfoContainer.m_nStackLevel = -1endlocal cExistTag = cDumpInfoContainer.m_cObjectExistTaglocal cNameAllAlias = cDumpInfoContainer.m_cObjectAliasNamelocal cAccessTag = cDumpInfoContainer.m_cObjectAccessTaglocal strType = type(cObject)if "table" == strType then-- Check table with class name.if rawget(cObject, "__cname") thenif "string" == type(cObject.__cname) thenstrName = strName .. "[class:" .. cObject.__cname .. "]"endelseif rawget(cObject, "class") thenif "string" == type(cObject.class) thenstrName = strName .. "[class:" .. cObject.class .. "]"endelseif rawget(cObject, "_className") thenif "string" == type(cObject._className) thenstrName = strName .. "[class:" .. cObject._className .. "]"endend-- Check if table is _G.if cObject == _G thenstrName = strName .. "[_G]"end-- Get metatable.local bWeakK = falselocal bWeakV = falselocal cMt = getmetatable(cObject)if cMt then-- Check mode.local strMode = rawget(cMt, "__mode")if strMode thenif "k" == strMode thenbWeakK = trueelseif "v" == strMode thenbWeakV = trueelseif "kv" == strMode thenbWeakK = truebWeakV = trueendendend-- Check if the specified object.if cExistTag[cObject] and (not cNameAllAlias[strName]) thencNameAllAlias[strName] = trueend-- Add reference and name.if cAccessTag[cObject] thenreturnend-- Get this name.cAccessTag[cObject] = true-- Dump table key and value.for k, v in pairs(cObject) do-- Check key type.local strKeyType = type(k)if "table" == strKeyType thenif not bWeakK thenCollectSingleObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)endif not bWeakV thenCollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "function" == strKeyType thenif not bWeakK thenCollectSingleObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)endif not bWeakV thenCollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "thread" == strKeyType thenif not bWeakK thenCollectSingleObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)endif not bWeakV thenCollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseif "userdata" == strKeyType thenif not bWeakK thenCollectSingleObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)endif not bWeakV thenCollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)endelseCollectSingleObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)endend-- Dump metatable.if cMt thenCollectSingleObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)endelseif "function" == strType then-- Get function info.local cDInfo = debug.getinfo(cObject, "Su")local cCombinedName = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"-- Check if the specified object.if cExistTag[cObject] and (not cNameAllAlias[cCombinedName]) thencNameAllAlias[cCombinedName] = trueend-- Write this info.if cAccessTag[cObject] thenreturnend-- Set name.cAccessTag[cObject] = true-- Get upvalues.local nUpsNum = cDInfo.nupsfor i = 1, nUpsNum dolocal strUpName, cUpValue = debug.getupvalue(cObject, i)local strUpValueType = type(cUpValue)--print(strUpName, cUpValue)if "table" == strUpValueType thenCollectSingleObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "function" == strUpValueType thenCollectSingleObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "thread" == strUpValueType thenCollectSingleObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)elseif "userdata" == strUpValueType thenCollectSingleObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)endend-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectSingleObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)endendelseif "thread" == strType then-- Check if the specified object.if cExistTag[cObject] and (not cNameAllAlias[strName]) thencNameAllAlias[strName] = trueend-- Add reference and name.if cAccessTag[cObject] thenreturnend-- Get this name.cAccessTag[cObject] = true-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectSingleObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)endend-- Dump metatable.local cMt = getmetatable(cObject)if cMt thenCollectSingleObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)endelseif "userdata" == strType then-- Check if the specified object.if cExistTag[cObject] and (not cNameAllAlias[strName]) thencNameAllAlias[strName] = trueend-- Add reference and name.if cAccessTag[cObject] thenreturnend-- Get this name.cAccessTag[cObject] = true-- Dump environment table.local getfenv = debug.getfenvif getfenv thenlocal cEnv = getfenv(cObject)if cEnv thenCollectSingleObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)endend-- Dump metatable.local cMt = getmetatable(cObject)if cMt thenCollectSingleObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)endelseif "string" == strType then-- Check if the specified object.if cExistTag[cObject] and (not cNameAllAlias[strName]) thencNameAllAlias[strName] = trueend-- Add reference and name.if cAccessTag[cObject] thenreturnend-- Get this name.cAccessTag[cObject] = trueelse-- For "number" and "boolean" type, they are not object type, skip.endend-- The base method to dump a mem ref info result into a file.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- strRootObjectName - The header info to show the root object name, can be nil.-- cRootObject - The header info to show the root object address, can be nil.-- cDumpInfoResultsBase - The base dumped mem info result, nil means no compare and only output cDumpInfoResults, otherwise to compare with cDumpInfoResults.-- cDumpInfoResults - The compared dumped mem info result, dump itself only if cDumpInfoResultsBase is nil, otherwise dump compared results with cDumpInfoResultsBase.local function OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, cDumpInfoResultsBase, cDumpInfoResults)-- Check results.if not cDumpInfoResults thenreturnend-- Get time format string.local strDateTime = FormatDateTimeNow()-- Collect memory info.local cRefInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectReferenceCount) or nillocal cNameInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectAddressToName) or nillocal cRefInfo = cDumpInfoResults.m_cObjectReferenceCountlocal cNameInfo = cDumpInfoResults.m_cObjectAddressToName-- Create a cache result to sort by ref count.local cRes = {}local nIdx = 0for k in pairs(cRefInfo) donIdx = nIdx + 1cRes[nIdx] = kend-- Sort result.table.sort(cRes, function (l, r)return cRefInfo[l] > cRefInfo[r]end)-- Save result to file.local bOutputFile = strSavePath and (string.len(strSavePath) > 0)local cOutputHandle = nillocal cOutputEntry = printif bOutputFile then-- Check save path affix.local strAffix = string.sub(strSavePath, -1)if ("/" ~= strAffix) and ("\\" ~= strAffix) thenstrSavePath = strSavePath .. "/"end-- Combine file name.local strFileName = strSavePath .. "LuaMemRefInfo-All"if (not strExtraFileName) or (0 == string.len(strExtraFileName)) thenif cDumpInfoResultsBase thenif cConfig.m_bComparedMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "].txt"elsestrFileName = strFileName .. ".txt"endelseif cConfig.m_bAllMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "].txt"elsestrFileName = strFileName .. ".txt"endendelseif cDumpInfoResultsBase thenif cConfig.m_bComparedMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"elsestrFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"endelseif cConfig.m_bAllMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"elsestrFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"endendendlocal cFile = assert(io.open(strFileName, "w"))cOutputHandle = cFilecOutputEntry = cFile.writeendlocal cOutputer = function (strContent)if cOutputHandle thencOutputEntry(cOutputHandle, strContent)elsecOutputEntry(strContent)endend-- Write table header.if cDumpInfoResultsBase thencOutputer("--------------------------------------------------------\n")cOutputer("-- This is compared memory information.\n")cOutputer("--------------------------------------------------------\n")cOutputer("-- Collect base memory reference at line:" .. tostring(cDumpInfoResultsBase.m_nCurrentLine) .. "@file:" .. cDumpInfoResultsBase.m_strShortSrc .. "\n")cOutputer("-- Collect compared memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")elsecOutputer("--------------------------------------------------------\n")cOutputer("-- Collect memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")endcOutputer("--------------------------------------------------------\n")cOutputer("-- [Table/Function/String Address/Name]\t[Reference Path]\t[Reference Count]\n")cOutputer("--------------------------------------------------------\n")if strRootObjectName and cRootObject thenif "string" == type(cRootObject) thencOutputer("-- From Root Object: \"" .. tostring(cRootObject) .. "\" (" .. strRootObjectName .. ")\n")elsecOutputer("-- From Root Object: " .. GetOriginalToStringResult(cRootObject) .. " (" .. strRootObjectName .. ")\n")endend-- Save each info.for i, v in ipairs(cRes) doif (not cDumpInfoResultsBase) or (not cRefInfoBase[v]) thenif (nMaxRescords > 0) thenif (i <= nMaxRescords) thenif "string" == type(v) thenlocal strOrgString = tostring(v)local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"")if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) thenlocal strRepString = string.gsub(strOrgString, "([\n\r])", "\\n")cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")elsecOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")endelsecOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")endendelseif "string" == type(v) thenlocal strOrgString = tostring(v)local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"")if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) thenlocal strRepString = string.gsub(strOrgString, "([\n\r])", "\\n")cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")elsecOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")endelsecOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")endendendendif bOutputFile thenio.close(cOutputHandle)cOutputHandle = nilendend-- The base method to dump a mem ref info result of a single object into a file.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- cDumpInfoResults - The dumped results.local function OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoResults)-- Check results.if not cDumpInfoResults thenreturnend-- Get time format string.local strDateTime = FormatDateTimeNow()-- Collect memory info.local cObjectAliasName = cDumpInfoResults.m_cObjectAliasName-- Save result to file.local bOutputFile = strSavePath and (string.len(strSavePath) > 0)local cOutputHandle = nillocal cOutputEntry = printif bOutputFile then-- Check save path affix.local strAffix = string.sub(strSavePath, -1)if ("/" ~= strAffix) and ("\\" ~= strAffix) thenstrSavePath = strSavePath .. "/"end-- Combine file name.local strFileName = strSavePath .. "LuaMemRefInfo-Single"if (not strExtraFileName) or (0 == string.len(strExtraFileName)) thenif cConfig.m_bSingleMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "].txt"elsestrFileName = strFileName .. ".txt"endelseif cConfig.m_bSingleMemoryRefFileAddTime thenstrFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"elsestrFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"endendlocal cFile = assert(io.open(strFileName, "w"))cOutputHandle = cFilecOutputEntry = cFile.writeendlocal cOutputer = function (strContent)if cOutputHandle thencOutputEntry(cOutputHandle, strContent)elsecOutputEntry(strContent)endend-- Write table header.cOutputer("--------------------------------------------------------\n")cOutputer("-- Collect single object memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")cOutputer("--------------------------------------------------------\n")-- Calculate reference count.local nCount = 0for k in pairs(cObjectAliasName) donCount = nCount + 1end-- Output reference count.cOutputer("-- For Object: " .. cDumpInfoResults.m_strAddressName .. " (" .. cDumpInfoResults.m_strObjectName .. "), have " .. tostring(nCount) .. " reference in total.\n")cOutputer("--------------------------------------------------------\n")-- Save each info.for k in pairs(cObjectAliasName) doif (nMaxRescords > 0) thenif (i <= nMaxRescords) thencOutputer(k .. "\n")endelsecOutputer(k .. "\n")endendif bOutputFile thenio.close(cOutputHandle)cOutputHandle = nilendend-- Fileter an existing result file and output it.-- strFilePath - The existing result file.-- strFilter - The filter string.-- bIncludeFilter - Include(true) or exclude(false) the filter.-- bOutputFile - Output to file(true) or console(false).local function OutputFilteredResult(strFilePath, strFilter, bIncludeFilter, bOutputFile)if (not strFilePath) or (0 == string.len(strFilePath)) thenprint("You need to specify a file path.")returnendif (not strFilter) or (0 == string.len(strFilter)) thenprint("You need to specify a filter string.")returnend-- Read file.local cFilteredResult = {}local cReadFile = assert(io.open(strFilePath, "rb"))for strLine in cReadFile:lines() dolocal nBegin, nEnd = string.find(strLine, strFilter)if nBegin and nEnd thenif bIncludeFilter thennBegin, nEnd = string.find(strLine, "[\r\n]")if nBegin and nEnd and (string.len(strLine) == nEnd) thentable.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1))elsetable.insert(cFilteredResult, strLine)endendelseif not bIncludeFilter thennBegin, nEnd = string.find(strLine, "[\r\n]")if nBegin and nEnd and (string.len(strLine) == nEnd) thentable.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1))elsetable.insert(cFilteredResult, strLine)endendendend-- Close and clear read file handle.io.close(cReadFile)cReadFile = nil-- Write filtered result.local cOutputHandle = nillocal cOutputEntry = printif bOutputFile then-- Combine file name.local _, _, strResFileName = string.find(strFilePath, "(.*)%.txt")strResFileName = strResFileName .. "-Filter-" .. ((bIncludeFilter and "I") or "E") .. "-[" .. strFilter .. "].txt"local cFile = assert(io.open(strResFileName, "w"))cOutputHandle = cFilecOutputEntry = cFile.writeendlocal cOutputer = function (strContent)if cOutputHandle thencOutputEntry(cOutputHandle, strContent)elsecOutputEntry(strContent)endend-- Output result.for i, v in ipairs(cFilteredResult) docOutputer(v .. "\n")endif bOutputFile thenio.close(cOutputHandle)cOutputHandle = nilendend-- Dump memory reference at current time.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- strRootObjectName - The root object name that start to search, default is "_G" if leave this to nil.-- cRootObject - The root object that start to search, default is _G if leave this to nil.local function DumpMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject)-- Get time format string.local strDateTime = FormatDateTimeNow()-- Check root object.if cRootObject thenif (not strRootObjectName) or (0 == string.len(strRootObjectName)) thenstrRootObjectName = tostring(cRootObject)endelsecRootObject = debug.getregistry()strRootObjectName = "registry"end-- Create container.local cDumpInfoContainer = CreateObjectReferenceInfoContainer()local cStackInfo = debug.getinfo(2, "Sl")if cStackInfo thencDumpInfoContainer.m_strShortSrc = cStackInfo.short_srccDumpInfoContainer.m_nCurrentLine = cStackInfo.currentlineend-- Collect memory info.CollectObjectReferenceInMemory(strRootObjectName, cRootObject, cDumpInfoContainer)-- Dump the result.OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, nil, cDumpInfoContainer)end-- Dump compared memory reference results generated by DumpMemorySnapshot.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- cResultBefore - The base dumped results.-- cResultAfter - The compared dumped results.local function DumpMemorySnapshotCompared(strSavePath, strExtraFileName, nMaxRescords, cResultBefore, cResultAfter)-- Dump the result.OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter)end-- Dump compared memory reference file results generated by DumpMemorySnapshot.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- strResultFilePathBefore - The base dumped results file.-- strResultFilePathAfter - The compared dumped results file.local function DumpMemorySnapshotComparedFile(strSavePath, strExtraFileName, nMaxRescords, strResultFilePathBefore, strResultFilePathAfter)-- Read results from file.local cResultBefore = CreateObjectReferenceInfoContainerFromFile(strResultFilePathBefore)local cResultAfter = CreateObjectReferenceInfoContainerFromFile(strResultFilePathAfter)-- Dump the result.OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter)end-- Dump memory reference of a single object at current time.-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.-- strObjectName - The object name reference you want to dump.-- cObject - The object reference you want to dump.local function DumpMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, strObjectName, cObject)-- Check object.if not cObject thenreturnendif (not strObjectName) or (0 == string.len(strObjectName)) thenstrObjectName = GetOriginalToStringResult(cObject)end-- Get time format string.local strDateTime = FormatDateTimeNow()-- Create container.local cDumpInfoContainer = CreateSingleObjectReferenceInfoContainer(strObjectName, cObject)local cStackInfo = debug.getinfo(2, "Sl")if cStackInfo thencDumpInfoContainer.m_strShortSrc = cStackInfo.short_srccDumpInfoContainer.m_nCurrentLine = cStackInfo.currentlineend-- Collect memory info.CollectSingleObjectReferenceInMemory("registry", debug.getregistry(), cDumpInfoContainer)-- Dump the result.OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoContainer)end-- Return methods.local cPublications = {m_cConfig = nil, m_cMethods = {}, m_cHelpers = {}, m_cBases = {}}cPublications.m_cConfig = cConfigcPublications.m_cMethods.DumpMemorySnapshot = DumpMemorySnapshotcPublications.m_cMethods.DumpMemorySnapshotCompared = DumpMemorySnapshotComparedcPublications.m_cMethods.DumpMemorySnapshotComparedFile = DumpMemorySnapshotComparedFilecPublications.m_cMethods.DumpMemorySnapshotSingleObject = DumpMemorySnapshotSingleObjectcPublications.m_cHelpers.FormatDateTimeNow = FormatDateTimeNowcPublications.m_cHelpers.GetOriginalToStringResult = GetOriginalToStringResultcPublications.m_cBases.CreateObjectReferenceInfoContainer = CreateObjectReferenceInfoContainercPublications.m_cBases.CreateObjectReferenceInfoContainerFromFile = CreateObjectReferenceInfoContainerFromFilecPublications.m_cBases.CreateSingleObjectReferenceInfoContainer = CreateSingleObjectReferenceInfoContainercPublications.m_cBases.CollectObjectReferenceInMemory = CollectObjectReferenceInMemorycPublications.m_cBases.CollectSingleObjectReferenceInMemory = CollectSingleObjectReferenceInMemorycPublications.m_cBases.OutputMemorySnapshot = OutputMemorySnapshotcPublications.m_cBases.OutputMemorySnapshotSingleObject = OutputMemorySnapshotSingleObjectcPublications.m_cBases.OutputFilteredResult = OutputFilteredResultreturn cPublications