local AddonName, Addon = ... Addon.DebugLib = { Version = 1, } if not Addon.DebugColorCode then Addon.DebugColorCode = GREEN_FONT_COLOR_CODE end -- Add the /reloadui command if it isn't already installed if not SLASH_RELOADUI1 then SlashCmdList.RELOADUI = function () if SlashCmdList.SWATTER then SlashCmdList.SWATTER("clear") end ReloadUI() end SLASH_RELOADUI1 = "/reloadui" end if not tern then function tern(a, b, c) if a then return b else return c end end end function Addon.DebugLib:Initialize() if self.Initialized then return end self.Initialized = true hooksecurefunc( "ChatFrame_ConfigEventHandler", function (event) if event == "UPDATE_CHAT_WINDOWS" and not self.DebugFrame then self:FindDebugFrame() end end) self:FindDebugFrame() end function Addon.DebugLib:FindDebugFrame() if self.DebugFrame then return end for vChatIndex = 1, NUM_CHAT_WINDOWS do local vChatFrame = _G["ChatFrame"..vChatIndex] if vChatFrame and (vChatFrame:IsVisible() or vChatFrame.isDocked) then local vTab = _G["ChatFrame"..vChatIndex.."Tab"] local vName = vTab:GetText() if vName == "Debug" then self.DebugFrame = vChatFrame if self.DebugFrame:GetMaxLines() < 1000 then self.DebugFrame:SetMaxLines(1000) end self.DebugFrame:SetFading(false) self.DebugFrame:SetMaxResize(1200, 1000) end end end if self.DebugFrame then Addon:NoteMessage("Found debugging chat frame") end end function Addon.DebugLib:ClearDebugLog() if not self.Initialized then self:Initialize() end if not self.DebugFrame then return end self.DebugFrame:Clear() end function Addon.DebugLib:AddDebugMessage(pPrefix, pMessage, ...) if not self.Initialized then self:Initialize() end local vMessage = (pPrefix or "")..Addon.DebugColorCode..string.format("[%s]", AddonName)..FONT_COLOR_CODE_CLOSE..HIGHLIGHT_FONT_COLOR_CODE..pMessage..FONT_COLOR_CODE_CLOSE if true then -- set to false to diagnose debug message problems if select("#", ...) > 0 then if type(select(1, ...)) == "table" then vMessage = string.gsub(vMessage, "%$(%w+)", ...) else vMessage = string.format(vMessage, ...) end end else local vSucceeded if select("#", ...) > 0 then if type(select(1, ...)) == "table" then vSucceeded, vMessage = pcall(string.gsub, vMessage, "%$(%w+)", ...) else vSucceeded, vMessage = pcall(string.format, vMessage, ...) end end end if self.DebugFrame then self.DebugFrame:AddMessage(vMessage) local vTabFlash = _G[self.DebugFrame:GetName().."TabFlash"] vTabFlash:Show() UIFrameFlash(vTabFlash, 0.25, 0.25, 60, nil, 0.5, 0.5) end return vMessage end function Addon:DebugMessage(pMessage, ...) if not self.DebugLib.Initialized then self.DebugLib:Initialize() end if not self.DebugLib.DebugFrame then return end self.DebugLib:AddDebugMessage(NORMAL_FONT_COLOR_CODE.."[DEBUG] "..FONT_COLOR_CODE_CLOSE, pMessage, ...) end function Addon:TestMessage(pMessage, ...) local vMessage = self.DebugLib:AddDebugMessage(GREEN_FONT_COLOR_CODE.."[ TEST] "..FONT_COLOR_CODE_CLOSE, pMessage, ...) DEFAULT_CHAT_FRAME:AddMessage(vMessage) end function Addon:ErrorMessage(pMessage, ...) local vMessage = self.DebugLib:AddDebugMessage(RED_FONT_COLOR_CODE.."[ERROR] "..FONT_COLOR_CODE_CLOSE, pMessage, ...) self:DebugStack() DEFAULT_CHAT_FRAME:AddMessage(vMessage) end function Addon:NoteMessage(pMessage, ...) local vMessage = self.DebugLib:AddDebugMessage("", pMessage, ...) DEFAULT_CHAT_FRAME:AddMessage(vMessage) end function Addon:DebugTable(pValue, pValuePath, pMaxDepth, pMode) if not pValuePath then pValuePath = "table" end if not pValue then self:DebugMessage(pValuePath.." = nil") return end local vType = type(pValue) if vType == "number" then self:DebugMessage(pValuePath.." = "..pValue) elseif vType == "string" then self:DebugMessage(pValuePath.." = \""..pValue.."\"") elseif vType == "boolean" then if pValue then self:DebugMessage(pValuePath.." = true") else self:DebugMessage(pValuePath.." = false") end elseif vType == "table" then local vMaxDepth if pMaxDepth then vMaxDepth = pMaxDepth else vMaxDepth = 5 end if vMaxDepth == 0 then self:DebugMessage(pValuePath.." = {...}") else local vFoundElement = false for vIndex, vElement in pairs(pValue) do local vValuePath if not vFoundElement then vValuePath = pValuePath elseif string.sub(pValuePath, 1, 10) == "|cff888888" then vValuePath = string.gsub(pValuePath, "|cffffffff", "") else vValuePath = "|cff888888"..string.gsub(pValuePath, "|cffffffff", "") end if type(vIndex) ~= "string" then vValuePath = vValuePath.."|cffffffff["..tostring(vIndex).."]" else vValuePath = vValuePath.."|cffffffff."..vIndex end self:DebugTable(vElement, vValuePath, vMaxDepth - 1, pMode) vFoundElement = true end if not vFoundElement then self:DebugMessage(pValuePath.." = {}") end end elseif vType == "function" then if pMode ~= "NO_FUNCTIONS" then self:DebugMessage(pValuePath.." "..vType) end else self:DebugMessage(pValuePath.." "..vType) end end function Addon:DebugStack(pPrefix, pDepth) local vCallStack = self.DebugLib:GetCallStack(pPrefix, pDepth, 1) for vIndex, vMessage in ipairs(vCallStack) do self:DebugMessage(vMessage) end end function Addon:ErrorStack(pPrefix, pDepth) local vCallStack = self.DebugLib:GetCallStack(pPrefix, pDepth, 1) for vIndex, vMessage in ipairs(vCallStack) do self:ErrorMessage(vMessage) end end function Addon:DebugMark() self:DebugMessage("————————————————————————————————————————") end function Addon.DebugLib:ReduceAddonPath(pPath) return string.gsub(pPath, "^Interface\\AddOns\\[^\\]*\\", "") end function Addon.DebugLib:GetCallStack(pPrefix, pDepth, pDepthOffset) local vCallStack = {} if not pPrefix then pPrefix = " " end if not pDepth then pDepth = 18 end local vStackString = debugstack((pDepthOffset or 0) + 2, pDepth, 0) for vMessageLine in string.gmatch(vStackString, "(.-)\n") do --table.insert(vCallStack, pPrefix.."LINE: "..vMessageLine) local _, _, vFile, vLine, vFunction = string.find(vMessageLine, "([%w%.]+):(%d+): in function .(.*)'") if not vFunction then _, _, vFunction, vLine, vFile = string.find(vMessageLine, "%[string \"(.*)\"%]:(%d+): in (.*)") end if not vFunction then _, _, vFile, vLine, vFunctionFile, vFunctionLine = string.find(vMessageLine, "([^:]+):(%d+): in function <([^:]+):(%d+)>") if vFunctionLine then vFunctionFile = self:ReduceAddonPath(vFunctionFile) vFunction = vFunctionFile..", "..vFunctionLine end end if not vFunction then _, _, vFunction = string.find(vMessageLine, "(tail call.*)") end if not vFunction then _, _, _, vFunction = string.find(vMessageLine, "(%[C%]): in function .(.*)'") if vFunction then vFunction = "[C] "..vFunction vFile = nil vLine = nil end end if vFunction then if vFile then if not vLine then vLine = 0 end vFile = self:ReduceAddonPath(vFile) table.insert(vCallStack, pPrefix..vFile..", "..vLine..": "..vFunction) else table.insert(vCallStack, pPrefix..vFunction) end else table.insert(vCallStack, pPrefix.."Unknown function: "..vMessageLine) end end return vCallStack end function Addon.DebugLib:NewBuckets(pInterval) local vBuckets = { Interval = pInterval, BucketStartTime = GetTime(), BucketIndex = 1, NumBuckets = math.floor(pInterval), Buckets = {[1] = {Value = 0, Time = 0}}, AddSample = self.BucketsAddSample, GetValue = self.BucketsGetValue, } return vBuckets end function Addon.DebugLib:BucketsAddSample(pValue, pTime) local vBucket = self.Buckets[self.BucketIndex] vBucket.Value = vBucket.Value + pValue local vElapsed = pTime - self.BucketStartTime if vElapsed > 1 then vBucket.Time = vElapsed self.BucketStartTime = pTime self.BucketIndex = self.BucketIndex + 1 if self.BucketIndex > self.NumBuckets then self.BucketIndex = 1 end vBucket = self.Buckets[self.BucketIndex] if not vBucket then vBucket = {} self.Buckets[self.BucketIndex] = vBucket end vBucket.Value = 0 vBucket.Time = 0 end end function Addon.DebugLib:BucketsGetValue() local vTotalElapsed = 0 local vTotalValue = 0 for vIndex, vBucket in ipairs(self.Buckets) do if vBucket.Time > 0 then vTotalElapsed = vTotalElapsed + vBucket.Time vTotalValue = vTotalValue + vBucket.Value end end if vTotalElapsed == 0 then return 0, 0 end return vTotalValue / vTotalElapsed, vTotalElapsed end function Addon.DebugLib:NewPerfMonitor(pLabel) local vPerfMonitor = { Label = pLabel, CPUBuckets = self:NewBuckets(10), MemBuckets = self:NewBuckets(10), FunctionEnter = self.PerfMonitorFunctionEnter, FunctionExit = self.PerfMonitorFunctionExit, DumpValue = self.PerfMonitorDumpValue, } return vPerfMonitor end function Addon.DebugLib:PerfMonitorFunctionEnter(pTime) self.StartTime = pTime self.StartMem = gcinfo() end function Addon.DebugLib:PerfMonitorFunctionExit(pTime) local vEndMem = gcinfo() self.CPUBuckets:AddSample(pTime - self.StartTime, pTime) self.MemBuckets:AddSample(vEndMem - self.StartMem, pTime) end function Addon.DebugLib:PerfMonitorDumpValue() local vCPUTime, vTotalTime = self.CPUBuckets:GetValue() local vMemRate = self.MemBuckets:GetValue() local vCPUPercent if vTotalTime > 0 then vCPUPercent = 100 * vCPUTime / vTotalTime else vCPUPercent = 0 end self:DebugMessage("%s: CPU: %.1f%% Mem: %dKB/sec", self.Label, vCPUPercent, vMemRate) end function Addon.DebugLib.BoolToString(pValue) if pValue then return "true" else return "false" end end function Addon.DebugLib:ShowFrameStatus(pFrame, pPrefix) self:DebugMessage("%sVisible: %s Protected: %s Width: %d Height: %d Level: %d", pPrefix or pFrame:GetName(), self.BoolToString(pFrame:IsVisible()), self.BoolToString(pFrame:IsProtected()), pFrame:GetWidth(), pFrame:GetHeight(), pFrame:GetFrameLevel()) end function Addon.DebugLib:ShowParentTree(pFrame, pPrefix) if not pPrefix then pPrefix = "" end local vParent = pFrame:GetParent() if vParent then self:DebugMessage(pPrefix.."Parent: "..vParent:GetName()) if vParent ~= UIParent then self:ShowFrameStatus(vParent, pPrefix.." ") self:ShowParentTree(vParent, pPrefix.." ") end end end function Addon.DebugLib:ShowAnchorTree(pFrame, pPrefix) if not pPrefix then pPrefix = "" end local vIndex = 1 local vDidAnchor = {} local vIndex = 1 while true do local vPoint, vRelativeTo, vRelativePoint, vOffsetX, vOffsetY = pFrame:GetPoint(vIndex) if not vPoint or not vRelativeTo then break end self:DebugMessage("%sAnchor %d: %s, %s, %s, %d, %d", pPrefix, vIndex, vPoint, vRelativeTo:GetName(), vRelativePoint, vOffsetX, vOffsetY) if vRelativeTo ~= UIParent and not vDidAnchor[vRelativeTo] then vDidAnchor[vRelativeTo] = true self:DebugMessage(pPrefix.."RelativeTo: "..vRelativeTo:GetName()) self:ShowFrameStatus(vRelativeTo, pPrefix.." ") self:ShowAnchorTree(vRelativeTo, pPrefix.." ") end vIndex = vIndex + 1 end if vIndex == 1 then self:DebugMessage(pPrefix.." No Anchors") end end function Addon.DebugLib:ShowFrameTree(pFrame) self:DebugMessage("ShowFrameTree: "..pFrame:GetName()) self:ShowFrameStatus(pFrame, " ") self:ShowParentTree(pFrame, " ") self:ShowAnchorTree(pFrame, " ") end