dobrograd-13-06-2022/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/frame.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

437 lines
13 KiB
Lua

--[[-------------------------------------------------------------------------
The panel that contains the realm switcher
---------------------------------------------------------------------------]]
local REALMPANEL = {}
function REALMPANEL:Init()
self:DockPadding(0, 0, 0, 0)
self:DockMargin(0, 0, 5, 0)
self.realmLabel = vgui.Create("DLabel", self)
self.realmLabel:SetDark(true)
self.realmLabel:SetText("Realm:")
self.realmLabel:SizeToContents()
self.realmLabel:Dock(TOP)
self.realmbox = vgui.Create("DComboBox", self)
self.realmbox:AddChoice("Client")
self.realmbox:AddChoice("Server")
self.realmbox:Dock(TOP)
FProfiler.UI.onModelUpdate("realm", function(new)
self.realmbox.selected = new == "client" and 1 or 2
self.realmbox:SetText(new == "client" and "Client" or "Server")
end)
FProfiler.UI.onModelUpdate("serverAccess", function(hasAccess)
self.realmbox:SetDisabled(not hasAccess)
if not hasAccess and self.realmbox.selected == 2 then
FProfiler.UI.updateModel("realm", "client")
end
end)
self.realmbox.OnSelect = function(_, _, value) FProfiler.UI.updateModel("realm", string.lower(value)) end
end
function REALMPANEL:PerformLayout()
self.realmLabel:SizeToContents()
local top = ( self:GetTall() - self.realmLabel:GetTall() - self.realmbox:GetTall()) * 0.5
self:DockPadding(0, top, 0, 0)
end
derma.DefineControl("FProfileRealmPanel", "", REALMPANEL, "Panel")
--[[-------------------------------------------------------------------------
The little red or green indicator that indicates whether the focussing
function is correct
---------------------------------------------------------------------------]]
local FUNCINDICATOR = {}
function FUNCINDICATOR:Init()
self:SetTall(5)
self.color = Color(0, 0, 0, 0)
end
function FUNCINDICATOR:Paint()
draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.color)
end
derma.DefineControl("FProfileFuncIndicator", "", FUNCINDICATOR, "DPanel")
--[[-------------------------------------------------------------------------
The panel that contains the focus text entry and the focus indicator
---------------------------------------------------------------------------]]
local FOCUSPANEL = {}
function FOCUSPANEL:Init()
self:DockPadding(0, 0, 0, 0)
self:DockMargin(0, 0, 5, 0)
self.focusLabel = vgui.Create("DLabel", self)
self.focusLabel:SetDark(true)
self.focusLabel:SetText("Profiling Focus:")
self.focusLabel:SizeToContents()
self.focusLabel:Dock(TOP)
self.funcIndicator = vgui.Create("FProfileFuncIndicator", self)
self.funcIndicator:Dock(BOTTOM)
self.focusBox = vgui.Create("DTextEntry", self)
self.focusBox:SetText("")
self.focusBox:SetWidth(150)
self.focusBox:Dock(BOTTOM)
self.focusBox:SetTooltip("Focus the profiling on a single function.\nEnter a global function name here (like player.GetAll)\nYou're not allowed to call functions in here (e.g. hook.GetTable() is not allowed)")
function self.focusBox:OnChange()
FProfiler.UI.updateCurrentRealm("focusStr", self:GetText())
end
FProfiler.UI.onCurrentRealmUpdate("focusObj", function(new)
self.funcIndicator.color = FProfiler.UI.getCurrentRealmValue("focusStr") == "" and Color(0, 0, 0, 0) or new and Color(80, 255, 80, 255) or Color(255, 80, 80, 255)
end)
FProfiler.UI.onCurrentRealmUpdate("focusStr", function(new, old)
if self.focusBox:GetText() == new then return end
self.focusBox:SetText(tostring(new))
end)
end
function FOCUSPANEL:PerformLayout()
self.focusBox:SetWide(200)
self.focusLabel:SizeToContents()
end
derma.DefineControl("FProfileFocusPanel", "", FOCUSPANEL, "Panel")
--[[-------------------------------------------------------------------------
The timer that keeps track of for how long the profiling has been going on
---------------------------------------------------------------------------]]
local TIMERPANEL = {}
function TIMERPANEL:Init()
self:DockPadding(0, 5, 0, 5)
self:DockMargin(5, 0, 5, 0)
self.timeLabel = vgui.Create("DLabel", self)
self.timeLabel:SetDark(true)
self.timeLabel:SetText("Total profiling time:")
self.timeLabel:SizeToContents()
self.timeLabel:Dock(TOP)
self.counter = vgui.Create("DLabel", self)
self.counter:SetDark(true)
self.counter:SetText("00:00:00")
self.counter:SizeToContents()
self.counter:Dock(RIGHT)
function self.counter:Think()
local recordTime, sessionStart = FProfiler.UI.getCurrentRealmValue("recordTime"), FProfiler.UI.getCurrentRealmValue("sessionStart")
local totalTime = recordTime + (sessionStart and (CurTime() - sessionStart) or 0)
self:SetText(string.FormattedTime(totalTime, "%02i:%02i:%02i"))
end
end
function TIMERPANEL:PerformLayout()
self.timeLabel:SizeToContents()
self.counter:SizeToContents()
end
derma.DefineControl("FProfileTimerPanel", "", TIMERPANEL, "Panel")
--[[-------------------------------------------------------------------------
The top bar
---------------------------------------------------------------------------]]
local MAGICBAR = {}
function MAGICBAR:Init()
self:DockPadding(5, 5, 5, 5)
self.realmpanel = vgui.Create("FProfileRealmPanel", self)
-- (Re)Start profiling
self.restartProfiling = vgui.Create("DButton", self)
self.restartProfiling:SetText(" (Re)Start\n Profiling")
self.restartProfiling:DockMargin(0, 0, 5, 0)
self.restartProfiling:Dock(LEFT)
self.restartProfiling.DoClick = function()
FProfiler.UI.updateCurrentRealm("shouldReset", true)
FProfiler.UI.updateCurrentRealm("status", "Started")
end
FProfiler.UI.onCurrentRealmUpdate("status", function(new)
self.restartProfiling:SetDisabled(new == "Started")
end)
-- Stop profiling
self.stopProfiling = vgui.Create("DButton", self)
self.stopProfiling:SetText(" Stop\n Profiling")
self.stopProfiling:DockMargin(0, 0, 5, 0)
self.stopProfiling:Dock(LEFT)
self.stopProfiling.DoClick = function()
FProfiler.UI.updateCurrentRealm("status", "Stopped")
end
FProfiler.UI.onCurrentRealmUpdate("status", function(new)
self.stopProfiling:SetDisabled(new == "Stopped")
end)
-- Continue profiling
self.continueProfiling = vgui.Create("DButton", self)
self.continueProfiling:SetText(" Continue\n Profiling")
self.continueProfiling:DockMargin(0, 0, 5, 0)
self.continueProfiling:Dock(LEFT)
self.continueProfiling.DoClick = function()
FProfiler.UI.updateCurrentRealm("shouldReset", false)
FProfiler.UI.updateCurrentRealm("status", "Started")
end
FProfiler.UI.onCurrentRealmUpdate("status", function(new)
self.continueProfiling:SetDisabled(new == "Started")
end)
self.realmpanel:Dock(LEFT)
self.focuspanel = vgui.Create("FProfileFocusPanel", self)
self.focuspanel:Dock(LEFT)
-- Timer
self.timerpanel = vgui.Create("FProfileTimerPanel", self)
self.timerpanel:Dock(RIGHT)
end
function MAGICBAR:PerformLayout()
self.realmpanel:SizeToChildren(true, false)
self.focuspanel:SizeToChildren(true, false)
self.timerpanel:SizeToChildren(true, false)
end
derma.DefineControl("FProfileMagicBar", "", MAGICBAR, "DPanel")
--[[-------------------------------------------------------------------------
The Bottlenecks tab's contents
---------------------------------------------------------------------------]]
local BOTTLENECKTAB = {}
function BOTTLENECKTAB:Init()
self:SetMultiSelect(false)
self:AddColumn("Name")
self:AddColumn("Path")
self:AddColumn("Lines")
self:AddColumn("Amount of times called")
self:AddColumn("Total time in ms (inclusive)")
self:AddColumn("Average time in ms (inclusive)")
FProfiler.UI.onCurrentRealmUpdate("bottlenecks", function(new)
self:Clear()
for _, row in ipairs(new) do
local names = {}
local path = row.info.short_src
local lines = path ~= "[C]" and row.info.linedefined .. " - " .. row.info.lastlinedefined or "N/A"
local amountCalled = row.total_called
local totalTime = row.total_time * 100
local avgTime = row.average_time * 100
for _, fname in ipairs(row.names or {}) do
if fname.namewhat == "" and fname.name == "" then continue end
table.insert(names, fname.namewhat .. " " .. fname.name)
end
if #names == 0 then names[1] = "Unknown" end
local line = self:AddLine(table.concat(names, "/"), path, lines, amountCalled, totalTime, avgTime)
line.data = row
end
end)
FProfiler.UI.onCurrentRealmUpdate("currentSelected", function(new, old)
if new == old then return end
for _, line in pairs(self.Lines) do
line:SetSelected(line.data.func == new.func)
end
end)
end
function BOTTLENECKTAB:OnRowSelected(id, line)
FProfiler.UI.updateCurrentRealm("currentSelected", line.data)
end
derma.DefineControl("FProfileBottleNecks", "", BOTTLENECKTAB, "DListView")
--[[-------------------------------------------------------------------------
The Top n lag spikes tab's contents
---------------------------------------------------------------------------]]
local TOPTENTAB = {}
function TOPTENTAB:Init()
self:SetMultiSelect(false)
self:AddColumn("Name")
self:AddColumn("Path")
self:AddColumn("Lines")
self:AddColumn("Runtime in ms")
FProfiler.UI.onCurrentRealmUpdate("topLagSpikes", function(new)
self:Clear()
for _, row in ipairs(new) do
if not row.func then break end
local name = row.info.name and row.info.name ~= "" and (row.info.namewhat .. " " .. row.info.name) or "Unknown"
local path = row.info.short_src
local lines = path ~= "[C]" and row.info.linedefined .. " - " .. row.info.lastlinedefined or "N/A"
local runtime = row.runtime * 100
local line = self:AddLine(name, path, lines, runtime)
line.data = row
end
end)
FProfiler.UI.onCurrentRealmUpdate("currentSelected", function(new, old)
if new == old then return end
for _, line in pairs(self.Lines) do
line:SetSelected(line.data.func == new.func)
end
end)
end
function TOPTENTAB:OnRowSelected(id, line)
FProfiler.UI.updateCurrentRealm("currentSelected", line.data)
end
derma.DefineControl("FProfileTopTen", "", TOPTENTAB, "DListView")
--[[-------------------------------------------------------------------------
The Tab panel of the bottlenecks and top n lag spikes
---------------------------------------------------------------------------]]
local RESULTSHEET = {}
function RESULTSHEET:Init()
self:DockMargin(0, 10, 0, 0)
self:SetPadding(0)
self.bottlenecksTab = vgui.Create("FProfileBottleNecks")
self:AddSheet("Bottlenecks", self.bottlenecksTab)
self.toptenTab = vgui.Create("FProfileTopTen")
self:AddSheet("Top 50 most expensive function calls", self.toptenTab)
end
derma.DefineControl("FProfileResultSheet", "", RESULTSHEET, "DPropertySheet")
--[[-------------------------------------------------------------------------
The function details panel
---------------------------------------------------------------------------]]
local FUNCDETAILS = {}
function FUNCDETAILS:Init()
self.titleLabel = vgui.Create("DLabel", self)
self.titleLabel:SetDark(true)
self.titleLabel:SetFont("DermaLarge")
self.titleLabel:SetText("Function Details")
self.titleLabel:SizeToContents()
-- self.titleLabel:Dock(TOP)
self.focus = vgui.Create("DButton", self)
self.focus:SetText("Focus")
self.focus:SetTall(50)
self.focus:SetFont("DermaDefaultBold")
self.focus:Dock(BOTTOM)
function self.focus:DoClick()
local sel = FProfiler.UI.getCurrentRealmValue("currentSelected")
if not sel then return end
FProfiler.UI.updateCurrentRealm("focusStr", sel.func)
end
self.source = vgui.Create("DTextEntry", self)
self.source:SetKeyboardInputEnabled(false)
self.source:DockMargin(0, 40, 0, 0)
self.source:SetMultiline(true)
self.source:Dock(FILL)
FProfiler.UI.onCurrentRealmUpdate("sourceText", function(new)
self.source:SetText(string.Replace(new, "\t", " "))
end)
self.toConsole = vgui.Create("DButton", self)
self.toConsole:SetText("Print Details to Console")
self.toConsole:SetTall(50)
self.toConsole:SetFont("DermaDefaultBold")
self.toConsole:Dock(BOTTOM)
function self.toConsole:DoClick()
FProfiler.UI.updateCurrentRealm("toConsole", FProfiler.UI.getCurrentRealmValue("currentSelected"))
end
end
function FUNCDETAILS:PerformLayout()
self.titleLabel:CenterHorizontal()
end
derma.DefineControl("FProfileFuncDetails", "", FUNCDETAILS, "DPanel")
--[[-------------------------------------------------------------------------
The actual frame
---------------------------------------------------------------------------]]
local FRAME = {}
local frameInstance
function FRAME:Init()
self:SetTitle("FProfiler profiling tool")
self:SetSize(ScrW() * 0.8, ScrH() * 0.8)
self:Center()
self:SetVisible(true)
self:MakePopup()
self:SetDeleteOnClose(false)
self.magicbar = vgui.Create("FProfileMagicBar", self)
self.magicbar:SetTall(math.max(self:GetTall() * 0.07, 48))
self.magicbar:Dock(TOP)
self.resultsheet = vgui.Create("FProfileResultSheet", self)
self.resultsheet:SetWide(self:GetWide() * 0.8)
self.resultsheet:Dock(LEFT)
self.details = vgui.Create("FProfileFuncDetails", self)
self.details:SetWide(self:GetWide() * 0.2 - 12)
self.details:DockMargin(5, 31, 0, 0)
self.details:Dock(RIGHT)
end
function FRAME:OnClose()
FProfiler.UI.updateModel("frameVisible", false)
end
derma.DefineControl("FProfileFrame", "", FRAME, "DFrame")
--[[-------------------------------------------------------------------------
The command to start the profiler
---------------------------------------------------------------------------]]
concommand.Add("FProfiler",
function()
CAMI.PlayerHasAccess(LocalPlayer(), "FProfiler", function(b, _)
if not b then return end
frameInstance = frameInstance or vgui.Create("FProfileFrame")
frameInstance:SetVisible(true)
FProfiler.UI.updateModel("frameVisible", true)
end)
end,
nil, "Starts FProfiler")