From 548ccfac9fc10f01d082184f26187d93ca8b492a Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 10 Mar 2026 18:30:03 +0000 Subject: [PATCH 01/16] A very early WIP --- lua/autorun/gprofiler_load.lua | 13 +- lua/gprofiler/cl_language.lua | 3 +- lua/gprofiler/cl_menu.lua | 390 ++++---- .../modules/auto_profile/cl_autoprofile.lua | 5 + .../modules/auto_profile/sv_autoprofile.lua | 85 ++ lua/gprofiler/modules/external/cl_rndx.lua | 897 +++++++++++++++++ lua/gprofiler/modules/overview/cl_init.lua | 9 + lua/gprofiler/modules/utils/cl_syntax.lua | 287 ++++++ lua/gprofiler/modules/utils/cl_vgui.lua | 19 + lua/gprofiler/modules/utils/ui/cl_graphs.lua | 125 +++ .../modules/utils/ui/cl_splitpanels.lua | 164 ++++ .../profilers/auto_profile/cl_autoprofile.lua | 102 -- .../profilers/auto_profile/sv_autoprofile.lua | 85 -- .../profilers/concommands/cl_concommands.lua | 258 +---- .../profilers/database/cl_database.lua | 742 +------------- .../profilers/entvars/cl_entvars.lua | 137 +-- .../profilers/functions/cl_functions.lua | 457 +-------- lua/gprofiler/profilers/hooks/cl_hooks.lua | 334 +------ lua/gprofiler/profilers/net/cl_net.lua | 909 +++++++++++++----- lua/gprofiler/profilers/net/sh_net.lua | 302 +++++- .../profilers/netvars/cl_netvars.lua | 142 +-- .../profilers/netvars/sv_netvars.lua | 172 ++-- lua/gprofiler/profilers/timers/cl_timers.lua | 208 +--- lua/gprofiler/profilers/timers/sh_timers.lua | 400 ++++---- lua/gprofiler/sh_access.lua | 1 + lua/gprofiler/sh_config.lua | 2 +- lua/gprofiler/sh_utils.lua | 724 ++++++++------ lua/gprofiler/sv_init.lua | 42 +- 28 files changed, 3470 insertions(+), 3544 deletions(-) create mode 100644 lua/gprofiler/modules/auto_profile/cl_autoprofile.lua create mode 100644 lua/gprofiler/modules/auto_profile/sv_autoprofile.lua create mode 100644 lua/gprofiler/modules/external/cl_rndx.lua create mode 100644 lua/gprofiler/modules/overview/cl_init.lua create mode 100644 lua/gprofiler/modules/utils/cl_syntax.lua create mode 100644 lua/gprofiler/modules/utils/cl_vgui.lua create mode 100644 lua/gprofiler/modules/utils/ui/cl_graphs.lua create mode 100644 lua/gprofiler/modules/utils/ui/cl_splitpanels.lua delete mode 100644 lua/gprofiler/profilers/auto_profile/cl_autoprofile.lua delete mode 100644 lua/gprofiler/profilers/auto_profile/sv_autoprofile.lua diff --git a/lua/autorun/gprofiler_load.lua b/lua/autorun/gprofiler_load.lua index 763ff28..6d56e1d 100644 --- a/lua/autorun/gprofiler_load.lua +++ b/lua/autorun/gprofiler_load.lua @@ -1,4 +1,4 @@ -GProfiler = GProfiler or { Config = {}, Access = {} } +GProfiler = GProfiler or { Config = {}, Access = {}, Utils = {} } local logLevels = { [1] = {"DEBUG", Color(0, 255, 0)}, @@ -25,16 +25,17 @@ local incFuncs = { local function incFile(f) (incFuncs[string.GetFileFromFilename(f):sub(1,2)] or incFuncs.sh)(f) - GProfiler.Log(string.format("Loading file %s", f), 5) + GProfiler.Log(string.format("Loaded file %s", f), 5) end -local function incFolder(folder, fileOnly) +local function incFolder(folder, subFileOnly, fileOnly) GProfiler.Log(string.format("Loading folder %s", folder), 5) local files, folders = file.Find(folder.."/*", "LUA") for _, f in ipairs(files) do incFile(string.format("%s/%s", folder, f)) end + if fileOnly then return end - for _, f in ipairs(folders) do incFolder(folder.."/"..f, true) end + for _, f in ipairs(folders) do incFolder(folder.."/"..f, nil, subFileOnly) end end incFile("gprofiler/sv_init.lua") @@ -43,6 +44,8 @@ incFile("gprofiler/sh_utils.lua") incFile("gprofiler/cl_language.lua") incFile("gprofiler/cl_menu.lua") incFile("gprofiler/sh_access.lua") -incFolder("gprofiler/profilers") +incFolder("gprofiler/modules") +incFolder("gprofiler/profilers", true) hook.Run("GProfiler.Loaded") +GProfiler.Ready = true diff --git a/lua/gprofiler/cl_language.lua b/lua/gprofiler/cl_language.lua index 18ef130..7c2dd91 100644 --- a/lua/gprofiler/cl_language.lua +++ b/lua/gprofiler/cl_language.lua @@ -79,8 +79,9 @@ GProfiler.Language:AddLanguage("english", function(Lang) Lang:AddPhrase("profiler_no_profile", "No profile data available! (More than 100 queries?)") -- Tab Names + Lang:AddPhrase("tab_overview", "Overview") Lang:AddPhrase("tab_hooks", "Hooks") - Lang:AddPhrase("tab_networking", "Networking") + Lang:AddPhrase("tab_networking", "Network") Lang:AddPhrase("tab_functions", "Functions") Lang:AddPhrase("tab_commands", "Commands") Lang:AddPhrase("tab_timers", "Timers") diff --git a/lua/gprofiler/cl_menu.lua b/lua/gprofiler/cl_menu.lua index ac9acbf..70de6c4 100644 --- a/lua/gprofiler/cl_menu.lua +++ b/lua/gprofiler/cl_menu.lua @@ -1,34 +1,26 @@ -GProfiler.Menu.Tabs = GProfiler.Menu.Tabs or {} -GProfiler.Menu.Background = GProfiler.Menu.Background or nil -GProfiler.Menu.Content = GProfiler.Menu.Content or nil -GProfiler.Menu.LastTab = GProfiler.Menu.LastTab or 1 - -local MenuColors = GProfiler.MenuColors - -local function GetTabName(tabName) - return GProfiler.Language.GetPhrase(string.format("tab_%s", string.gsub(string.lower(tabName), " ", "_"))) +GProfiler.Menu = GProfiler.Menu or {} +local Menu = GProfiler.Menu +Menu.Tabs = Menu.Tabs or {} +Menu.Background = Menu.Background or nil +Menu.Content = Menu.Content or nil +Menu.LastTab = Menu.LastTab or 1 + +local CachedSizes = {} +function GProfiler.GetScaledSize(s) + if CachedSizes[s] then return CachedSizes[s] end + local scalingFactor = math.min(ScrW() / 3840, ScrH() / 2160) + CachedSizes[s] = s * scalingFactor + return s * scalingFactor end -local function formatTime(seconds) - local days = math.floor(seconds / 86400) - if days > 0 then - local hours = math.floor((seconds - days * 86400) / 3600) - local minutes = math.floor((seconds - days * 86400 - hours * 3600) / 60) - local seconds = math.floor(seconds - days * 86400 - hours * 3600 - minutes * 60) - return string.format("%dd %02d:%02d:%02d", days, hours, minutes, seconds) - else - local hours = math.floor(seconds / 3600) - local minutes = math.floor((seconds - hours * 3600) / 60) - local seconds = math.floor(seconds - hours * 3600 - minutes * 60) - return string.format("%02d:%02d:%02d", hours, minutes, seconds) - end -end +local function GetTabName(tabName) return GProfiler.Language.GetPhrase(string.format("tab_%s", string.gsub(string.lower(tabName), " ", "_"))) end function GProfiler.Menu:Open() if not GProfiler.Access.HasAccess(LocalPlayer()) then return end if IsValid(GProfiler.Menu.Background) then GProfiler.Menu.Background:Remove() end - local SColor = MenuColors.HeaderSeparator + local MenuColors = GProfiler.MenuColors + local RNDX = GProfiler.RNDX local MenuBackground = vgui.Create("DFrame") MenuBackground:SetSize(ScrW(), ScrH()) @@ -38,7 +30,10 @@ function GProfiler.Menu:Open() MenuBackground:SetTitle("") MenuBackground:MakePopup() MenuBackground:SetMouseInputEnabled(false) - MenuBackground.Paint = function(s) Derma_DrawBackgroundBlur(s) end + MenuBackground.Paint = function(s, w, h) + -- RNDX.DrawBlur(0, 0, w, h, nil, nil, nil, nil, nil, (ScrW() - (ScrW() * 0.79)) / 2) + RNDX.Draw(4, 0, 0, w, h, Color(0, 0, 0, 100)) -- better than eating fps + end if GProfiler.Config.MenuCommands.Closekey then MenuBackground.Think = function(s) if input.IsKeyDown(GProfiler.Config.MenuCommands.Closekey) then @@ -48,197 +43,152 @@ function GProfiler.Menu:Open() end GProfiler.Menu.Background = MenuBackground - local Menu = vgui.Create("DFrame", MenuBackground) - Menu:SetSize(ScrW() * 0.8, ScrH() * 0.8) - Menu:Center() - Menu:SetDraggable(false) - Menu:ShowCloseButton(false) - Menu:SetTitle("") - Menu:MakePopup() - Menu.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, MenuColors.Background) end - Menu.OnClose = function() MenuBackground:Remove() end - - local MenuTopBar = vgui.Create("DPanel", Menu) - MenuTopBar:SetSize(Menu:GetWide(), 40) - MenuTopBar:SetPos(0, 0) - MenuTopBar.Paint = function(s, w, h) - draw.RoundedBoxEx(4, 0, 0, w, h, MenuColors.OpaqueBlack, true, true, false, false) - surface.SetDrawColor(SColor.r, SColor.g, SColor.b, SColor.a) - surface.DrawLine(0, h - 1, w, h - 1) + local Main = vgui.Create("DFrame", MenuBackground) + Main:SetSize(ScrW() * 0.8, ScrH() * 0.8) + Main:Center() + Main:SetDraggable(false) + Main:ShowCloseButton(false) + Main:SetTitle("") + Main:MakePopup() + Main.Paint = function(s, w, h) RNDX.Draw(4, 0, 0, w, h, Color(10, 32, 55, 255)) end + Main.OnClose = function() MenuBackground:Remove() end + + local Header = vgui.Create("DPanel", Main) + Header:SetSize(Main:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(92)) + Header:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(10)) + Header.Paint = function(s, w, h) + RNDX.Draw(4, 0, 0, w, h, Color(24, 45, 67, 255)) + + surface.SetFont("GProfiler.HeaderTitle") + local TitleWidth, TitleHeight = surface.GetTextSize("GProfiler") + local StartX = GProfiler.GetScaledSize(20) + + draw.SimpleText("GProfiler", "GProfiler.HeaderTitle", StartX, h / 2, Color(191, 237, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText("v" .. GProfiler.Version, "GProfiler.HeaderSubtitle", StartX + 5 + TitleWidth, h / 2 + GProfiler.GetScaledSize(10), Color(210, 210, 210), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end - local MenuTitle = vgui.Create("DLabel", MenuTopBar) - MenuTitle:SetSize(MenuTopBar:GetWide(), MenuTopBar:GetTall()) - MenuTitle:SetPos(0, 0) - MenuTitle:SetFont("GProfiler.Menu.Title") - MenuTitle:SetTextColor(MenuColors.White) - MenuTitle:SetText("GProfiler") - MenuTitle:SizeToContents() - MenuTitle:SetPos(5, MenuTopBar:GetTall() / 2 - MenuTitle:GetTall() / 2) - - GProfiler.Menu.Title = MenuTitle - - local LeftSideBar = vgui.Create("DPanel", Menu) - LeftSideBar:SetSize(250, Menu:GetTall() - MenuTopBar:GetTall() - 35) - LeftSideBar:SetPos(0, MenuTopBar:GetTall()) - LeftSideBar.Paint = function(s, w, h) draw.RoundedBoxEx(4, 0, 0, w, h, MenuColors.OpaqueBlack, false, false, true, false) end - - local UptimeBar = vgui.Create("DPanel", Menu) - UptimeBar:SetSize(LeftSideBar:GetWide(), 35) - UptimeBar:SetPos(0, Menu:GetTall() - UptimeBar:GetTall()) - UptimeBar.Paint = function(s, w, h) - draw.RoundedBoxEx(4, 0, 0, w, h, MenuColors.OpaqueBlack, false, false, false, true) - surface.SetDrawColor(SColor.r, SColor.g, SColor.b, SColor.a) - surface.DrawLine(0, 0, w, 0) + local CloseButton = vgui.Create("DButton", Header) + CloseButton:SetSize(GProfiler.GetScaledSize(56), GProfiler.GetScaledSize(56)) + CloseButton:SetPos(Header:GetWide() - CloseButton:GetWide() - GProfiler.GetScaledSize(20), Header:GetTall() / 2 - CloseButton:GetTall() / 2) + CloseButton:SetText("X") + CloseButton:SetTextColor(Color(255, 215, 215)) + CloseButton:SetFont("GProfiler.HeaderTitle") + CloseButton.Paint = function(s, w, h) + RNDX.Draw(4, 0 ,0, w, h, Color(176, 64, 64)) + if s:IsHovered() then + RNDX.Draw(4, 0, 0, w, h, Color(255, 64, 64)) + end end + CloseButton.DoClick = function() Main:Close() end - local VersionLbl = vgui.Create("DLabel", UptimeBar) - VersionLbl:SetSize(UptimeBar:GetWide(), UptimeBar:GetTall()) - VersionLbl:SetPos(0, 0) - VersionLbl:SetFont("GProfiler.Menu.VersionLbl") - VersionLbl:SetTextColor(MenuColors.White) - VersionLbl:SetText("GProfiler • v" .. GProfiler.Version) - VersionLbl:SetContentAlignment(5) - VersionLbl.Paint = nil - - local CloseButton = vgui.Create("DButton", MenuTopBar) - CloseButton:SetSize(MenuTopBar:GetTall(), MenuTopBar:GetTall()) - CloseButton:SetPos(Menu:GetWide() - CloseButton:GetWide(), 0) - CloseButton:SetText("X") - CloseButton:SetFont("GProfiler.Menu.SectionHeader") - CloseButton:SetTextColor(MenuColors.White) - CloseButton.Paint = nil - CloseButton.DoClick = function() Menu:Close() end - - local MenuContent = vgui.Create("DPanel", Menu) - MenuContent:SetSize(Menu:GetWide() - LeftSideBar:GetWide(), Menu:GetTall() - MenuTopBar:GetTall()) - MenuContent:SetPos(LeftSideBar:GetWide(), MenuTopBar:GetTall()) - MenuContent.Paint = nil - - GProfiler.Menu.Content = MenuContent - - local TabList = vgui.Create("DPanelList", LeftSideBar) - TabList:SetSize(LeftSideBar:GetWide(), LeftSideBar:GetTall()) - TabList:SetPos(0, 0) - TabList:EnableVerticalScrollbar(true) - TabList:SetSpacing(0) - TabList.Paint = nil - - local padding = 10 - - local activeTab = nil - local BottomTabs = {} - - local function AddTab(k, v, alt) - local Tab = vgui.Create("DButton") - Tab.Lerped = 0 - Tab:SetSize(TabList:GetWide(), 50) + local InnerMain = vgui.Create("DPanel", Main) + InnerMain:SetSize(Main:GetWide() - GProfiler.GetScaledSize(20), Main:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(30)) + InnerMain:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(20)) + InnerMain.Paint = nil + + local SidebarBase, ContentBase = GProfiler.Utils.VSplitPanel(InnerMain, GProfiler.GetScaledSize(10), "sb_cnt", 0.185) + SidebarBase.Paint = nil + ContentBase.Paint = nil + + local Sidebar = vgui.Create("DPanel", SidebarBase) + Sidebar:SetSize(GProfiler.GetScaledSize(600), Main:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(30)) + Sidebar.Paint = nil + + local Scroller = vgui.Create("DScrollPanel", Sidebar) + Scroller:SetSize(Sidebar:GetSize()) + + local sbar = Scroller:GetVBar() + sbar:SetWide(GProfiler.GetScaledSize(12)) + sbar:SetHideButtons(true) + sbar.Paint = function(s, w, h) + RNDX.Draw(4, 0, 0, w, h, MenuColors.ScrollBar) + end + sbar.btnGrip.Paint = function(s, w, h) + RNDX.Draw(4, 0, 0, w, h, MenuColors.ScrollBarGrip) + if s:IsHovered() then + RNDX.Draw(4, 0, 0, w, h, MenuColors.ScrollBarGripOutline) + end + end + + local List = vgui.Create("DIconLayout", Scroller) + List:SetSize(Scroller:GetSize()) + List:SetSpaceY(GProfiler.GetScaledSize(10)) + + for k, v in ipairs(Menu.Tabs) do + local Icon = Material(v.Icon, "smooth noclamp") + local IconSize = GProfiler.GetScaledSize(60) + + local Tab = vgui.Create("DButton", List) + Tab:SetSize(Scroller:GetWide(), GProfiler.GetScaledSize(100)) Tab:SetText("") Tab.Paint = function(s, w, h) - if alt then - draw.RoundedBox(4, 0, 0, w, h, MenuColors.HeaderSeparator) - draw.RoundedBox(4, 2, 2, w - 4, h - 4, MenuColors.OpaqueBlack) - - if s:IsHovered() or activeTab == s then - draw.RoundedBox(4, 0, 0, w, h, MenuColors.HeaderSeparator) - end - return + RNDX.Draw(4, 0, 0, w, h, Color(25, 60, 97, 255)) + if Menu.LastTab == k then + RNDX.Draw(4, 0, 0, w, h, Color(30, 90, 152, 255)) + elseif s:IsHovered() then + RNDX.Draw(4, 0, 0, w, h, Color(34, 77, 122, 255)) end - surface.SetDrawColor(SColor.r, SColor.g, SColor.b, SColor.a) - surface.DrawLine(0, h - 1, w, h - 1) - if s:IsHovered() or activeTab == s then - s.Lerped = Lerp(FrameTime() * 5, s.Lerped, w + 2) - else - s.Lerped = Lerp(FrameTime() * 5, s.Lerped, 0) - end + surface.SetDrawColor(191, 237, 255, 255) + surface.SetMaterial(Icon) + surface.DrawTexturedRect(GProfiler.GetScaledSize(20), h / 2 - IconSize / 2, IconSize, IconSize) + + draw.SimpleText(GetTabName(v.Name), "GProfiler.Menu.TabText", GProfiler.GetScaledSize(100), h / 2, MenuColors.White, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - draw.RoundedBox(0, 0, 0, s.Lerped, h, MenuColors.TopBarSeparator) + if not v.BadgeFunc then return end + + local time, isActive = v.BadgeFunc() + if time then + surface.SetFont("GProfiler.Menu.TabText") + local timeWidth, timeHeight = surface.GetTextSize(time) + local badgeWidth = timeWidth + GProfiler.GetScaledSize(10) + local badgeHeight = timeHeight + GProfiler.GetScaledSize(5) + + local badgeX = w - badgeWidth - GProfiler.GetScaledSize(20) + local badgeY = h / 2 - badgeHeight / 2 + + RNDX.Draw(4, badgeX, badgeY, badgeWidth, badgeHeight, isActive and Color(36, 172, 82, 255) or Color(196, 79, 79)) + draw.SimpleText(time, "GProfiler.Menu.TabBadge", badgeX + badgeWidth / 2, badgeY + badgeHeight / 2, MenuColors.White, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end end Tab.DoClick = function() - GProfiler.Menu.OpenTab(v.Name, v.Function) - activeTab = Tab - GProfiler.Menu.LastTab = k + Menu.OpenTab(v.Name, v.Function) + Menu.LastTab = k end - local TabIcon = vgui.Create("DImage", Tab) - TabIcon:SetSize(Tab:GetTall() - padding * 2, Tab:GetTall() - padding * 2) - TabIcon:SetPos(padding, padding) - TabIcon:SetImage(v.Icon) - - local TabText = vgui.Create("DLabel", Tab) - TabText:SetFont("GProfiler.Menu.TabText") - TabText:SetText(GetTabName(v.Name)) - TabText:SetTextColor(MenuColors.White) - TabText:SizeToContents() - TabText:SetPos(TabIcon:GetWide() + padding * 2, Tab:GetTall() / 2 - TabText:GetTall() / 2) - TabText:SetContentAlignment(5) - - if v.BadgeFunc then - local TabBadge = vgui.Create("DLabel", Tab) - TabBadge:SetSize(1, 1) - TabBadge:SetText("") - TabBadge:SetFont("GProfiler.Menu.TabText") - TabBadge:SetPos(Tab:GetWide() - TabBadge:GetWide() - padding, Tab:GetTall() / 2 - TabBadge:GetTall() / 2) - TabBadge:SetContentAlignment(5) - TabBadge.Think = function(s) - local text = v.BadgeFunc() - if not s.CurrentText or s.CurrentText ~= text then - s.CurrentText = text - surface.SetFont(s:GetFont()) - local w, h = surface.GetTextSize(text or "") - if text == "" then - s:SetSize(h / 2, h / 2) - else - s:SetSize(w + 5, h + 5) - end - s:SetPos(Tab:GetWide() - s:GetWide() - padding, Tab:GetTall() / 2 - s:GetTall() / 2) - end - end - TabBadge.Paint = function(s, w, h) - local text, color = v.BadgeFunc() - if text and color then - if text == "" then - draw.RoundedBox(h / 2, 0, 0, w, h, color) - else - draw.RoundedBox(4, 0, 0, w, h, color) - draw.SimpleText(text, "GProfiler.Menu.TabBadge", w / 2, h / 2, MenuColors.White, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) - end - end - end - end + List:Add(Tab) + end - return Tab + local Content = vgui.Create("DPanel", ContentBase) + Content:SetSize(ContentBase:GetSize()) + Content.Paint = function(s, w, h) + RNDX.Draw(8, 0, 0, w, h, Color(18, 48, 74, 255)) end - for k, v in ipairs(GProfiler.Menu.Tabs) do - if v.Weight == 999 then - table.insert(BottomTabs, v) - continue - end + Menu.Content = Content - local Tab = AddTab(k, v) - TabList:AddItem(Tab) - end + local LastTab = Menu.Tabs[Menu.LastTab or 1] + Menu.OpenTab(LastTab.Name, LastTab.Function) - if #BottomTabs > 0 then - local BottomTabList = vgui.Create("DPanelList", LeftSideBar) - BottomTabList:SetSize(LeftSideBar:GetWide() - 6, 50 * #BottomTabs) - BottomTabList:SetPos(3, LeftSideBar:GetTall() - BottomTabList:GetTall() - 5) - BottomTabList:EnableVerticalScrollbar(true) - BottomTabList:SetSpacing(0) - BottomTabList.Paint = nil - - for k, v in ipairs(BottomTabs) do - local Tab = AddTab(k, v, true) - BottomTabList:AddItem(Tab) + SidebarBase.OnHandleMoved = function() + Sidebar:SetSize(SidebarBase:GetWide(), Sidebar:GetTall()) + Scroller:SetSize(Sidebar:GetSize()) + List:SetSize(Scroller:GetSize()) + for k, v in ipairs(List:GetChildren()) do + v:SetSize(Scroller:GetWide(), GProfiler.GetScaledSize(100)) end end - TabList:GetItems()[GProfiler.Menu.LastTab]:DoClick() + ContentBase.OnHandleMoved = function() + Content:SetSize(ContentBase:GetWide(), ContentBase:GetTall()) + if IsValid(Content.Tab) then + if Content.Tab.OnHandleMoved then Content.Tab:OnHandleMoved() end + end + end end +if GProfiler.Ready then Menu:Open() end -function GProfiler.Menu.RegisterTab(name, icon, weight, func, badgeFunc) +function Menu.RegisterTab(name, icon, weight, func, badgeFunc) local tbl = { ["Name"] = name, ["Icon"] = icon, @@ -247,33 +197,34 @@ function GProfiler.Menu.RegisterTab(name, icon, weight, func, badgeFunc) ["BadgeFunc"] = badgeFunc } - for k, v in ipairs(GProfiler.Menu.Tabs) do + for k, v in ipairs(Menu.Tabs) do if v.Name == name then - GProfiler.Menu.Tabs[k] = tbl - table.sort(GProfiler.Menu.Tabs, function(a, b) return a.Weight < b.Weight end) + table.Merge(v, tbl) + table.sort(Menu.Tabs, function(a, b) return a.Weight < b.Weight end) return end end - table.insert(GProfiler.Menu.Tabs, tbl) - table.sort(GProfiler.Menu.Tabs, function(a, b) return a.Weight < b.Weight end) + table.insert(Menu.Tabs, tbl) + table.sort(Menu.Tabs, function(a, b) return a.Weight < b.Weight end) end -function GProfiler.Menu.OpenTab(name, func) - if not IsValid(GProfiler.Menu.Content) then return end +function Menu.OpenTab(name, func) + if not IsValid(Menu.Content) then return end if not name or not func then return end - GProfiler.Menu.Content:Clear() + Menu.Content:Clear() - local Tab = vgui.Create("DPanel", GProfiler.Menu.Content) - Tab:SetSize(GProfiler.Menu.Content:GetWide(), GProfiler.Menu.Content:GetTall()) + local Tab = vgui.Create("DPanel", Menu.Content) + Tab:SetSize(Menu.Content:GetWide(), Menu.Content:GetTall()) Tab.Paint = nil + Menu.Content.Tab = Tab - func(Tab) + func(Tab, Menu.Content) - if GProfiler.Menu.Title then - GProfiler.Menu.Title:SetText("GProfiler - " .. GetTabName(name)) - GProfiler.Menu.Title:SizeToContents() + if Menu.Title then + Menu.Title:SetText("GProfiler - " .. GetTabName(name)) + Menu.Title:SizeToContents() end end @@ -287,21 +238,20 @@ if isstring(GProfiler.Config.MenuCommands.Chat) then else hook.Remove("OnPlayerChat", "GProfiler.MenuCommands.Chat") end if isstring(GProfiler.Config.MenuCommands.Console) then - concommand.Add(GProfiler.Config.MenuCommands.Console, GProfiler.Menu.Open) + concommand.Add(GProfiler.Config.MenuCommands.Console, Menu.Open) end local function CreateFonts() - surface.CreateFont("GProfiler.Menu.Title", { font = "Roboto", size = 26, weight = 500, antialias = true }) - surface.CreateFont("GProfiler.Menu.SectionHeader", { font = "Roboto", size = 18, weight = 500, antialias = true }) - surface.CreateFont("GProfiler.Menu.TabText", { font = "Roboto", size = 20, weight = 400, antialias = true }) - surface.CreateFont("GProfiler.Menu.TabBadge", { font = "Roboto", size = 18, weight = 400, antialias = true }) - surface.CreateFont("GProfiler.Menu.VersionLbl", { font = "Roboto", size = 18, weight = 400, antialias = true }) - surface.CreateFont("GProfiler.Menu.RealmSelector", { font = "Roboto", size = 18, weight = 500, antialias = true }) - surface.CreateFont("GProfiler.Menu.StartButton", { font = "Roboto", size = 16, weight = 500, antialias = true }) - surface.CreateFont("GProfiler.Menu.ListHeader", { font = "Roboto", size = ScreenScale(4), weight = 400, antialias = true }) - surface.CreateFont("GProfiler.Menu.FunctionDetails", { font = "Roboto", size = 16, weight = 400, antialias = true }) - surface.CreateFont("GProfiler.Menu.FocusEntry", { font = "Roboto", size = 16, weight = 500, antialias = true }) - surface.CreateFont("GProfiler.Menu.RowText", { font = "Roboto", size = 16, weight = 400, antialias = true }) + CachedSizes = {} + surface.CreateFont("GProfiler.HeaderTitle", { font = "Inter Bold", size = GProfiler.GetScaledSize(64), weight = 800, antialias = true }) + surface.CreateFont("GProfiler.InnerTitle", { font = "Inter Bold", size = GProfiler.GetScaledSize(44), weight = 800, antialias = true }) + surface.CreateFont("GProfiler.HeaderSubtitle", { font = "Inter", size = GProfiler.GetScaledSize(24), weight = 400, antialias = true }) + surface.CreateFont("GProfiler.Menu.TabText", { font = "Inter Bold", size = GProfiler.GetScaledSize(38), weight = 500, antialias = true }) + surface.CreateFont("GProfiler.Menu.TabBadge", { font = "Inter Bold", size = GProfiler.GetScaledSize(32), weight = 500, antialias = true }) + surface.CreateFont("GProfiler.Code", { font = "Roboto", size = GProfiler.GetScaledSize(22), weight = 500 }) + surface.CreateFont("GProfiler.HeaderInteract", { font = "Inter", size = GProfiler.GetScaledSize(32), weight = 500, antialias = true }) + surface.CreateFont("GProfiler.Inter24", { font = "Inter", size = GProfiler.GetScaledSize(24), weight = 500, antialias = true }) + surface.CreateFont("GProfiler.Inter28", { font = "Inter", size = GProfiler.GetScaledSize(28), weight = 500, antialias = true }) end CreateFonts() hook.Add("OnScreenSizeChanged", "GProfiler.Menu.RescaleFonts", CreateFonts) @@ -319,4 +269,4 @@ net.Receive("GProfiler.SendState", function() Tbl.Realm = "Server" Tbl.StartTime = SysTime() - StartedAt end -end) \ No newline at end of file +end) diff --git a/lua/gprofiler/modules/auto_profile/cl_autoprofile.lua b/lua/gprofiler/modules/auto_profile/cl_autoprofile.lua new file mode 100644 index 0000000..40a1c48 --- /dev/null +++ b/lua/gprofiler/modules/auto_profile/cl_autoprofile.lua @@ -0,0 +1,5 @@ +function GProfiler.AutoProfileTab(Content) + +end + +-- GProfiler.Menu.RegisterTab("Auto Profile", "icon16/map_go.png", 999, GProfiler.AutoProfileTab) \ No newline at end of file diff --git a/lua/gprofiler/modules/auto_profile/sv_autoprofile.lua b/lua/gprofiler/modules/auto_profile/sv_autoprofile.lua new file mode 100644 index 0000000..d1987d5 --- /dev/null +++ b/lua/gprofiler/modules/auto_profile/sv_autoprofile.lua @@ -0,0 +1,85 @@ +-- util.AddNetworkString("GProfiler.AutoProfile.Configure") +-- util.AddNetworkString("GProfiler.AutoProfile.SendState") + +-- local Profilers = { +-- ["Hooks"] = "Hooks", +-- ["Networking"] = "Net", +-- ["Functions"] = "Functions", +-- ["Commands"] = "ConCommands", +-- ["Timers"] = "Timers", +-- -- ["Entity Variables"] = "EntVars", +-- ["Network Variables"] = "NetVars", +-- ["Database"] = "Database" +-- } + +-- local ValidStates = { +-- 0, -- Disabled +-- 1, -- ASAP +-- 2, -- When the gamemode has fully loaded +-- 3 -- When the first player connects +-- } + +-- hook.Add("GProfiler.Loaded", "GProfiler.AutoProfilers", function() +-- sql.Query("CREATE TABLE IF NOT EXISTS gprofiler_autoprofile (profiler TEXT, state INTEGER, PRIMARY KEY(profiler))") + +-- local Data = sql.Query("SELECT * FROM gprofiler_autoprofile") +-- if not table.IsEmpty(Data or {}) then +-- for _, row in ipairs(Data) do +-- row.state = tonumber(row.state) or 0 +-- if row.state == 0 then continue end + +-- local Profiler = GProfiler[Profilers[row.profiler]] +-- if not Profiler then continue end + +-- if row.state == 1 then +-- GProfiler.Log("[Auto Profiler] Starting " .. row.profiler .. " profiler!", 2) +-- Profiler:StartProfiler(Entity(0)) +-- elseif row.state == 2 then +-- GProfiler.Log("[Auto Profiler] Delaying " .. row.profiler .. " profiler until the gamemode has fully loaded!", 2) +-- hook.Add("PostGamemodeLoaded", "GProfiler.AutoProfile." .. row.profiler, function() +-- hook.Remove("PostGamemodeLoaded", "GProfiler.AutoProfile." .. row.profiler) +-- GProfiler.Log("[Auto Profiler] Starting " .. row.profiler .. " profiler (gamemode loaded)!", 2) +-- Profiler:StartProfiler(Entity(0)) +-- end) +-- elseif row.state == 3 then +-- GProfiler.Log("[Auto Profiler] Delaying " .. row.profiler .. " profiler until the first player connects!", 2) +-- hook.Add("PlayerConnect", "GProfiler.AutoProfile." .. row.profiler, function(ply) +-- hook.Remove("PlayerConnect", "GProfiler.AutoProfile." .. row.profiler) +-- GProfiler.Log("[Auto Profiler] Starting " .. row.profiler .. " profiler (player connected)!", 2) +-- Profiler:StartProfiler(ply) +-- end) +-- end +-- end +-- end + +-- sql.Query("DELETE FROM gprofiler_autoprofile") +-- end) + +-- net.Receive("GProfiler.AutoProfile.Configure", function(_, ply) +-- if not GProfiler.Access.HasAccess(ply) then return end + +-- local Profiler = net.ReadString() +-- local State = net.ReadUInt(2) + +-- if not Profilers[Profiler] or not table.HasValue(ValidStates, State) then return end + +-- if State == 0 then +-- sql.Query("DELETE FROM gprofiler_autoprofile WHERE profiler = " .. sql.SQLStr(Profiler)) +-- else +-- sql.Query("REPLACE INTO gprofiler_autoprofile (profiler, state) VALUES (" .. sql.SQLStr(Profiler) .. ", " .. State .. ")") +-- end +-- end) + + +-- hook.Add("PlayerInitialSpawn", "GProfiler.AutoProfiler.SendState", function(ply) +-- local Data = sql.Query("SELECT * FROM gprofiler_autoprofile") +-- if table.IsEmpty(Data or {}) then return end + +-- net.Start("GProfiler.AutoProfile.SendState") +-- net.WriteUInt(table.Count(Data), 4) +-- for _, row in ipairs(Data) do +-- net.WriteString(row.profiler) +-- net.WriteUInt(row.state, 2) +-- end +-- net.Send(ply) +-- end) \ No newline at end of file diff --git a/lua/gprofiler/modules/external/cl_rndx.lua b/lua/gprofiler/modules/external/cl_rndx.lua new file mode 100644 index 0000000..963c643 --- /dev/null +++ b/lua/gprofiler/modules/external/cl_rndx.lua @@ -0,0 +1,897 @@ +--[[ +Copyright (c) 2025 Srlion (https://github.com/Srlion) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] + +-- https://github.com/Srlion/RNDX + +local bit_band = bit.band +local surface_SetDrawColor = surface.SetDrawColor +local surface_SetMaterial = surface.SetMaterial +local surface_DrawTexturedRectUV = surface.DrawTexturedRectUV +local surface_DrawTexturedRect = surface.DrawTexturedRect +local render_CopyRenderTargetToTexture = render.CopyRenderTargetToTexture +local math_min = math.min +local math_max = math.max +local DisableClipping = DisableClipping +local type = type + +local SHADERS_VERSION = "1762971900" +local SHADERS_GMA = [========[R01BRAOHS2tdVNwrAPzQFGkAAAAAAFJORFhfMTc2Mjk3MTkwMAAAdW5rbm93bgABAAAAAQAAAHNoYWRlcnMvZnhjLzE3NjI5NzE5MDBfcm5keF9yb3VuZGVkX2JsdXJfcHMzMC52Y3MAWwUAAAAAAAAAAAAAAgAAAHNoYWRlcnMvZnhjLzE3NjI5NzE5MDBfcm5keF9yb3VuZGVkX3BzMzAudmNzAMgFAAAAAAAAAAAAAAMAAABzaGFkZXJzL2Z4Yy8xNzYyOTcxOTAwX3JuZHhfc2hhZG93c19ibHVyX3BzMzAudmNzAEAFAAAAAAAAAAAAAAQAAABzaGFkZXJzL2Z4Yy8xNzYyOTcxOTAwX3JuZHhfc2hhZG93c19wczMwLnZjcwDkAwAAAAAAAAAAAAAFAAAAc2hhZGVycy9meGMvMTc2Mjk3MTkwMF9ybmR4X3ZlcnRleF92czMwLnZjcwAeAQAAAAAAAAAAAAAAAAAABgAAAAEAAAABAAAAAAAAAAAAAAACAAAAaPB5rwAAAAAwAAAA/////1sFAAAAAAAAIwUAQExaTUHcDgAAEgUAAF0AAAABAABos178gL/sqTCKKmhqvjMGBcspzCTmp/gKUuCPCSeJ6i+BM7QEKYcFW21fRRw+YLGjb6YWXU3Dlwr8WEhzRKa8KwmC/lFMmO69CG1fpOFcygopZ5z40DdKrcnlVZen4TOHrP3hEJCoIJgyo2bogJS03SXW5PQ/G92VoqBr5y4G1Y1aDEaZ3oF+wPYcowySi51s6V9Zp1zAi2573ER3fFq3umlLoSbfrvxgllHGCdEqvOqxBpBMc9iVB2vD2Gr2dGHxwFgOUsnc0TZGh6zvCR+BiDIjOft0J2kttjAVDnPrJLXTOk/inDdGbGvuXcdi6YQsefnG1jCviSZ2OPSCbUfVuV3jgj+hBiVXhkA1RODpepTEIx8Ip7RBjOjckgKijP+kXlvzn+u57PaRYOLCOA3Lv67zHO7uwmM9lT1b7WhFhBZUV6lwoUNue5WZgfGj2TEe4x7ct90aNy2QrIZvRdLjuBNy3YDj2Ixi/uhgCwCxIpvjDVwnPlwpYfqAirwJX6VsjWa2WsHNVdWsSLHoUfK4mUnPtb0BXWrJjnDP0mgiQ9jcqwKlLVyUtF9OJGskkK9G2yqlCBaOPf2ko2C6wXRAzIa3GtPzGCIxXfyety1QBPdtSCNL+i1zc9mTM2/lEOpt1ENwzbFvoD8eyNbpoH1xMXJBjV5ZtSXYPOSLOGeSIKfml0FNIlaO97LLo4lAdQUY6DfBIIg28PYzh9w65QHtrhZm6IlVwSJHkNWBb025SNYVYlHJD0SXSEj3aonN0014SxPr+SGJvspnvZRhkHxU+RctW4G9AW72dTbbMZ1QzhIVREhLScYoh39FyTE7em8i+aQUbxCVC9EqhIhbl+Jv938/zZ7ahjvZz4rESob/utbRJRSwqGSCq3zF37O0Jx8f6uOfQybJrlW91PRfdPBlCBjS076sH9vU1WpPwvAj5GUhRyYZVaPU95Jtk5CflsYh5lsyks8Ogf2iu7KyJ56p+O+9RoDHGgc2WvNVYMaDsYlytO0qJd1TavnMSF4yyzoX8SSGdAUDudJC/g4sO8bmR20VfPLJi1Y9u6EQ9szvClRZKgi5f75penrPHVH54nrKHQKE3ueeKBh4UyQSkwoRsJscJDvFRRsfqohmKGPDaUSsRS7hlhNWXP96waSr3vfmnJMg68pY5z429Own3gEKatY9py3AwaoPyo2L+64RHdUMbnbOICQYgRpU71G3A/Jk+eLYdiWGeG2CG0MliL7CoM46y6nAWv/XfzNHIhZIzI3IovL7pReA1OrL9QOIYeqoDyAM6ZkAtgoWn4nL87JXzMe2lP2ah7WcnbdV08mS/SjcmG8/EAtI8SBdRXe1EOfhWy3YeIAzXcPnisyubzTzTCmzWNzrrtE0sVNzcLrfQQNTSp4qDC+26yRbliSKeOiwMkDQWuLAl5FTI+ouM0l71sR0/ERtCc7BcO2x8FlpXy7417qNSANIafXi4KvmYx49k+inp+8GRbLDaSI+JBgomvgOitAA8uK3MWb3wVpAqr7Xfj8LrW0NO0vftd4isSVXsAvNTxKtcopeRdvOtMb68bTXgmwRKzFPXWFhcPBCHS9s5g7eQi2r19dVbHM/9cbR291EwQY4qD+o/dGcy3X0XEsQDqEJeHIJJCF+YtYJlwGh9Sgt6u9FlmY6cbv3qcgQIDvUeJZhO9dsX0jRTmtECNSFulrGN+ImfVlcvKot+ITSwKcx5xuxch0pLPJVoQD/////BgAAAAEAAAABAAAAAAAAAAAAAAACAAAA1s9wzQAAAAAwAAAA/////8gFAAAAAAAAkAUAQExaTUGYEAAAfwUAAF0AAAABAABool+Af7/sqj/+eFMHhRdm72ukxyRJj20tiWfIyugyDci04ls+0jp7rYtBmuizKvcLzwnIfvwTGprHR3AMKCe7SG3kSgZh5+60tMr52Z4R2Avkd6Fz3L6eZmUlOlDpcZ2FbEiA5iyU/DZNcfaVdA6n2sRJ0DooQo3rHFMTzZwzR3oVTWi9THpaFW+LboMWGiDN56c++DsXipCL5uLjYRW7slLJoKrNxLN85b2C9Ob4k7qvmGveed8u4vRln1/vcQxxbVhem+79qtZEEKX5VpC5gTxL9f49uTJf1kCBJt/E8aGetqXD2hMWQ1YX4MAAN4XMnh9gWfQ690wfyL/5zIRFljXmjYOl6R6TQlf3Sh3b7zmUj1XBawVz2BinXLep2nwRIxihEGk3iZG1m5BUmsK0IHBpXjh5p7qZHGrUSWt2Zj5pLVnYYgPDsgBCbVrLhHN723vMl67+50og/D9UyW5OD7kaFYcaMu6G5z9sAUgrggYOkpoiCdONKslsmP6vae2DVQfWOAreddeitgBLjOZdxTWiiwP1cMeQlzU9rwo9UiQ8a3WuPom3YKKlVL7WNrV+CCe176gUPn/ZjsLr/N+ZSbAN1+WjaPuddjDb6fGIj6N16mQshMzrG1SkktcWDyMmZc7p2XluVdc79c5p7EJbIjQsIE/3KiIh8G/M4ABU5eEeZf6cmBKJciM18WdPgZUWqs0rfD++gcZDl1ELlIgfv2h4WhnAg0MV3HDGzYuv8PA5ShmJtyr7x/AmukGHqsK2EifJ850VSVcx6TnvAATFruf2TA2sZa/mczH9YoEgIzvWelLgDpCVnLn2d4WUK/CXdNUg47KHSOr2U1lEX6Q655wa+T8CtPnwSgDBW6HW3Fbrm/O+oNc/HWpbL3P9N9x7gBtsI2dC2fOjXWqi9ayxGZllhh1SVr6iQEKOcF9InZlQELLYIQtOZlzp3JBuVxjfb+e9OZnvuEIsgePpqrxTbY0PM0dF//uITB0fZQvFnKWRcqKf5nvPt27ofO7tD7akFAo4TlIukMIppNzaPNCfz6O7wPo8VG1d12ZAYkWjjOwQMyKHkuzxDofmS6GdRzXg8zw5Fyhpbi+BPlwOqBPuLqd1rPNMtsYPCh3uyyuDSSsnA0sJQcqJFkPD2JsdaXKxK5hKpCY9sracmLqP6jgzHAYlNYc4et31Gk2svPGK0ybMgjz8Xa+cOoaSaqJ8+sMaIMhT7v+RUWMywwaciyCI7nbL0XJt8ENQ2ftUkXsrSUKt9vdtjdTRCkkvFqz0IhZNFaK11Jb0Wi6qDLnkw11aQZjxopOzCGrpBnUhYZTTCBsoiL3mVKpCsxWN4nPzom1r4AaNGx7HQJj5Etes1YsW6WVLCgZVuZFpJWWLcRizKa2qxhJrDE1u4SI1ai2hvrJ7UvY7GOhGRUqSUORQs7qrQm8lMAUcElgxCN3o+t5orb4zUCGS05jPA4XMgrDJNj29ah3jrX0uZMTuA8MsPbuwlOczI5C7flifdWQxY1EErfbrm7z9d5KvD/seBHaxxq8yt+UK2az6+gFPUnmuaud/MjFns480VCyLV0HAkNQmnVTJ4GQ1Gy1yeYDbtt9icDJ7TYsLSU5M7qhARTamI/nX6ls1oGfdlIfPXH7+lqA8GnsORh8JaH5Vz8C7/E0p5jQprkAcM9sBpypiTcKj3/ApNylklzA2BP3SvpZHNxdZ6Q6bg9Xg2TYsVpTe5VCcizQAVgPZnTeD5mqHDl2dsPGbRqIb5KLvwRgWvICZEGuPuPAzoikf7s1tcSDefXEg0v6BqMHQfqBDEEFIrYD+t28WhQ4ACy+ZHJCTxFranT0C+RaHlQCu6+jORlNrqgII5tYADjum/I5nNXkU8QAA/////wYAAAABAAAAAQAAAAAAAAAAAAAAAgAAAJzyglUAAAAAMAAAAP////9ABQAAAAAAAAgFAEBMWk1BPA4AAPcEAABdAAAAAQAAaItfnIC/7KknxcVFPc7QbqZor3QsQPQUdzflG66hK8OH6waTx1K7zbeuivPeI5Gp87L+/ZIw5yYyIQPxbzOU92vHD7ci1YcrRGTYeSL0O9pGpGE1RhTznrCmz1qJJcfXPX+VZk+3o98JGsV69uIaHKeg6y6r2xPvqqeCq9tyUqYGqJcxq4yrP/96FutryyecmD1V3j1cIMaB3WBkb68Lp9+zlLLShdPmSZAKeT0gsSCsZpCOZsJOGVqwLIFTM/L+Ovi3s9TuCNv1j3BrM3mDRaTpyqBacBeLB4dQHTVpdsEkHSG3RGLL7nLr0sGwWsc4H5SJ65gK8uiREq4a8uVEgcpPn8v5GnpqtTV55+NuRwsFWUAobDNtzJdPhcvg7zROa6S+a2y/33X+slYsAdvXioR6oH4uWqHLBOdCneyzVY41iMj9oJ06xgGz4QplngPpcGSIU+4SyG3m3kw5TGoloWnMnZckaTBf4pr3jCw5Dja7MPLmlhqaS2Mcy0w/pUb6CvnphQQuUfU3Mge08yOLal9G2Qx3oej/TMRhfnVPQG9vF95bTLcIF0JtN2Dd4Smq/u3qtE29P/1BumEPxPfOUV8NvfzmqM9iZFdat2GhEi0H6GRdPaWFHFL2fQcGS5mIvmGRc/7ugh187nIXy6oMnPNREsQB7Kr59aldMYOhqI3txDILtofE55qIvp/kprm+0Ry4pYbGo6TF/MgvsMZzUmeI8l84sg8XV6ADNEvf88lr9eYwcSFFWs2grgIVFmSfLNwGhYv5DHllrMBdACBhLwivjXHFVH7IlaYrXiuQMEK5tVcZXfPqCbKdvQet/SacGPbqDj8CKge0fm0nB04iSELMvL6YpeS+OYb8EX6X3JNq7LjVX4kLYBstjVd9A9zK68rJKkZtjL1cSdTRcUzgwAX4cx879LyDsZlQxMLHpDVrNxqEBeTX+aq7/M/KCDSEafmMHk0gdPYgXjtiwAW6iyYpSydFi4YAGXLhDctOkBcuC1l705plrYUjuUjYSoBRAKmgMlJB6T3qj25znc2iaVHVZqc77TgRv9SHMcMC0Eh2h/TOK9XEMzC0juGZ3yKpYX1Jq9kgcE+2lT3oi29wOEmr6GuqjSXafkA15F0z6VehraBRbVuTAnbwtPMrlkOpF/oAQsw90eJT1LLXMNsjzwNV13uSo2nwoSdbiY92xjFyOu84u/T54NKR/wBHXCBvAEhh/F7J/S50remgEppnqgXvfZuYGPY2+QHEdumQmfQs6y4aYpSta1IVn5e4fR4HUVq0dcSsAnc4iR06pIfZwBWhvCIoQBgaRhQGBpqIy7X5Q1vaeLbZEJw2bxlPO53wqkJbEuCiN3gmteRRet1yCUcprue+m7/mmxG9zyyhBZtm/abR4f7SWqLrvm0YKFAHKDkTKzKmDcqhgfiDXIF+NMlzDc7w+1E9Tp4mVmpBYP7uuKkqJzHJh0ZvB1X4ZRPr6NM+TlJFl0ob+W9H6xiCCq3HnGfMYAh7i4YpXdXMROqKeiDMY0EfBzWmc+hFRAIAUlpdMN/CTpZtxWO2bAT5e+cdlcdMuwbhEQeW/bybYZaR+zKdUE4WSm3S4j7Ijo4sM2IM1yuEYCjDF1uYvJn9StkGGhh3Vf/t9v6N/8S4eJe3FWl9sEDSwbarIz+SMnRtgUo7F2jqUMlyJLlVk1fmaCoDlwAA/////wYAAAABAAAAAQAAAAAAAAAAAAAAAgAAAHgthy4AAAAAMAAAAP/////kAwAAAAAAAKwDAEBMWk1BXAkAAJsDAABdAAAAAQAAaJNe3IM/7KknxcRHY47O9fYyNdc3kY24ieD8FTrqtxFJe67osEaB+xDr9sgDqjs5X5yhoFQ/2qprKt7mID/eRH1zgb8C4z6LiW0mxCypgbau9V6CxI/yfXTrgjsWPOK8WZxTfql/MI8nsS5t7+3q3095QGdU5TUDjLmpV4DaIeiN/lwkHMPlSDittCckryLg/X9mhxwy4EQaIYin2mDrYaTaj5wp3ilELOAmUoNc9RbdeJ/KcyNwACVe26YWJCH5pWDlj5LB77XVel+bujGjfXfsm+DnIjhXljYTUOxvaXH0NKLvWTW2fCPVJ28mqza3hJwrCKousqJxq/UASVB6yVJt3fIHp4qIYMSjG78GKHi5uo+IlpJTQo0aykOb+WeVmZRg6b1Jq0IF2lD9kXSr2IcPixMaNXAPCbS/gHy3gleI9ETS6xps510jCsO8FhihoGr3C3Pc38QIjvZyCksi6W8UOGj5JcFG1YhwT/3dPthPiXriqUTCmwBm8+M4Mp3rck5PjEbUYk94nVjiT2ecHzEgExiETuWmkDsy7rgWBRNQ1J87vZHy3ofHvnURv2yHASLZYmGOmxQAWAPcWb8oq0qqZ2HOClAgyO7yquJSP4MwrqrmUk6eTWzCk/Iy3PDkReKwl4mqPf8GQT9J6qMg3l0gHvNfzYKTsjWnwIQcSCEKhGlw0o+cxaEA+tpQMemJQMki+rwP+/lg7B/WHlWWdAWXLJHxvO6yEhM/5bb81WhYEky06g3aKVH3+eWltCBaAE/yz+RCH0C1e7KrUagIwEs96oujKF78ju44iBEhUGU35QiBNMEgpjIzsHWC77qunQtHBs275LvYwP19KlCOErrjBTWEmpsuvH6lcY9lv30lNt37HP5pl7IBMzutFE4rgrrI9gsh7uIhrbGIOE7WkCS7OmqRrk4Q+EfPbtdkpTKJUDtznwGLYkqm50y/5g/8MM/6FVDjtCIn8YIjRYh/Y7CfBFQ2YGb0SeMTa/qOH2MksY1lRwIJM4EYTd1E/2Gd9SQIbJNjLVTLzIqXE4gUmIfv/YMysmge4k6dW+tMFo+5NM4HQ1YN42DSWMpxY6T0hzf2dAhXXWOos9HYxcJkJYbXYXP2k+ApuVUDyFh6c/3NRL2ugIk02pukuQMLww5w4AD6vOFExw5gH9FB0WfO40XEIHq9eAjmRB5p+VP3eaJywpgjGSpXzeCiI5BVhDxMZZJwLhe1EsAA/////wYAAAABAAAAAQAAAAAAAAAAAAAAAgAAAHdDQpkAAAAAMAAAAP////8eAQAAAAAAAOYAAEBMWk1BZAEAANUAAABdAAAAAQAAaJVd1Ic/7GMZqmFmSkZT5Syb4y1BQfzcRtdcyOB5r7JLn4LwCNmyuJTsWtJr8LdDB+d807YTbmGBRNEYgNCazErHtD6CDDk7YfK7qU+cRg9+q3eO+bdyOPpnVfTY+iJt5kQXhXbw6vmZKQpyqBmTpxuep55WCep8C8P87e4u76dPtUA7J1Gs0FIPXJBVMFlRm0gkua8O4gTbsSjsa7AehgJStVTCBbqrRJuKSTHAR462FrPlswhNs53YmCOGQeRBXbZUlM2KeVFbYANLUT90mfIAAP////8AAAAA]========] +do + local DECODED_SHADERS_GMA = util.Base64Decode(SHADERS_GMA) + if not DECODED_SHADERS_GMA or #DECODED_SHADERS_GMA == 0 then + print("Failed to load shaders!") -- this shouldn't happen + return + end + + file.Write("rndx_shaders_" .. SHADERS_VERSION .. ".gma", DECODED_SHADERS_GMA) + game.MountGMA("data/rndx_shaders_" .. SHADERS_VERSION .. ".gma") +end + +local function GET_SHADER(name) + return SHADERS_VERSION:gsub("%.", "_") .. "_" .. name +end + +local BLUR_RT = GetRenderTargetEx("RNDX" .. SHADERS_VERSION .. SysTime(), + 1024, 1024, + RT_SIZE_LITERAL, + MATERIAL_RT_DEPTH_SEPARATE, + bit.bor(2, 256, 4, 8 --[[4, 8 is clamp_s + clamp-t]]), + 0, + IMAGE_FORMAT_BGRA8888 +) + + +local RAMP_RT = GetRenderTargetEx("RNDX_RAMP" .. SHADERS_VERSION, + 256, 1, + RT_SIZE_LITERAL, + MATERIAL_RT_DEPTH_SEPARATE, + bit.bor(4, 8), + 0, + IMAGE_FORMAT_BGRA8888 +) + +local NEW_FLAG; do + local flags_n = -1 + function NEW_FLAG() + flags_n = flags_n + 1 + return 2 ^ flags_n + end +end + +local NO_TL, NO_TR, NO_BL, NO_BR = NEW_FLAG(), NEW_FLAG(), NEW_FLAG(), NEW_FLAG() + +local SHAPE_CIRCLE, SHAPE_FIGMA, SHAPE_IOS = NEW_FLAG(), NEW_FLAG(), NEW_FLAG() + +local BLUR = NEW_FLAG() + +local RNDX = {} + + +function RNDX.GetSharedRampTexture() + return RAMP_RT +end + +function RNDX.UpdateSharedRampTexture(colA, colB) + if not RAMP_RT then return end + if not colA or not colB then return end + + render.PushRenderTarget(RAMP_RT) + render.Clear(0, 0, 0, 0, true, true) + cam.Start2D() + local w = RAMP_RT:Width() + for x = 0, w - 1 do + local t = x / (w - 1) + local r = Lerp(t, colA.r or 255, colB.r or 255) + local g = Lerp(t, colA.g or 255, colB.g or 255) + local b = Lerp(t, colA.b or 255, colB.b or 255) + local a = Lerp(t, colA.a or 255, colB.a or 255) + surface_SetDrawColor(r, g, b, a) + surface.DrawRect(x, 0, 1, 1) + end + cam.End2D() + render.PopRenderTarget() +end + +local shader_mat = [==[ +screenspace_general +{ + $pixshader "" + $vertexshader "" + + $basetexture "" + $texture1 "" + $texture2 "" + $texture3 "" + + // Mandatory, don't touch + $ignorez 1 + $vertexcolor 1 + $vertextransform 1 + " 1 then + local inv = 1 / k + TL, TR, BL, BR = TL * inv, TR * inv, BL * inv, BR * inv + end + + return clamp0(TL), clamp0(TR), clamp0(BL), clamp0(BR) + end +end + +local function SetupDraw() + local TL, TR, BL, BR = normalize_corner_radii() + + local matrix = MATRIXES[MAT] + MATRIX_SetUnpacked( + matrix, + + BL, W, OUTLINE_THICKNESS or -1, END_ANGLE, + BR, H, SHADOW_INTENSITY, ROTATION, + TR, SHAPE, BLUR_INTENSITY or 1.0, 0, + TL, TEXTURE and 1 or 0, START_ANGLE, 0 + ) + MATERIAL_SetMatrix(MAT, "$viewprojmat", matrix) + + + local mode = GRAD_MODE_FLAG or 0 + local cx = GRAD_CENTER_X or 0.5 + local cy = GRAD_CENTER_Y or 0.5 + local gangle = GRAD_ANGLE or 0 + local sx = GRAD_SCALE_X ~= 0 and GRAD_SCALE_X or W + local sy = GRAD_SCALE_Y ~= 0 and GRAD_SCALE_Y or H + local use_ramp = GRAD_USE_RAMP_TEX and 1 or 0 + local tiling = GRAD_TILING_MODE or 0 + + MATERIAL_SetFloat(MAT, C1_X, cx) + MATERIAL_SetFloat(MAT, C1_Y, cy) + MATERIAL_SetFloat(MAT, C1_Z, gangle) + MATERIAL_SetFloat(MAT, C1_W, mode) + MATERIAL_SetFloat(MAT, C2_X, sx) + MATERIAL_SetFloat(MAT, C2_Y, sy) + MATERIAL_SetFloat(MAT, C2_Z, use_ramp) + MATERIAL_SetFloat(MAT, C2_W, tiling) + + if GRAD_RAMP_TEXTURE then + MATERIAL_SetTexture(MAT, "$texture2", GRAD_RAMP_TEXTURE) + end + + if COL_R then + surface_SetDrawColor(COL_R, COL_G, COL_B, COL_A) + end + + surface_SetMaterial(MAT) +end + +local MANUAL_COLOR = NEW_FLAG() +local DEFAULT_DRAW_FLAGS = DEFAULT_SHAPE + +local function draw_rounded(x, y, w, h, col, flags, tl, tr, bl, br, texture, thickness) + if col and col.a == 0 then + return + end + + RESET_PARAMS() + + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + + local using_blur = bit_band(flags, BLUR) ~= 0 + if using_blur then + return RNDX.DrawBlur(x, y, w, h, flags, tl, tr, bl, br, thickness) + end + + MAT = ROUNDED_MAT; if texture then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", texture) + TEXTURE = texture + end + + W, H = w, h + TL, TR, BL, BR = bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + OUTLINE_THICKNESS = thickness + + if bit_band(flags, MANUAL_COLOR) ~= 0 then + COL_R = nil + elseif col then + COL_R, COL_G, COL_B, COL_A = col.r, col.g, col.b, col.a + else + COL_R, COL_G, COL_B, COL_A = 255, 255, 255, 255 + end + + SetupDraw() + + return surface_DrawTexturedRectUV(x, y, w, h, -0.015625, -0.015625, 1.015625, 1.015625) +end + +function RNDX.Draw(r, x, y, w, h, col, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r) +end + +function RNDX.DrawOutlined(r, x, y, w, h, col, thickness, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r, nil, thickness or 1) +end + +function RNDX.DrawTexture(r, x, y, w, h, col, texture, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r, texture) +end + +function RNDX.DrawMaterial(r, x, y, w, h, col, mat, flags) + local tex = mat:GetTexture("$basetexture") + if tex then + return RNDX.DrawTexture(r, x, y, w, h, col, tex, flags) + end +end + +function RNDX.DrawCircle(x, y, r, col, flags) + return RNDX.Draw(r / 2, x - r / 2, y - r / 2, r, r, col, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleOutlined(x, y, r, col, thickness, flags) + return RNDX.DrawOutlined(r / 2, x - r / 2, y - r / 2, r, r, col, thickness, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleTexture(x, y, r, col, texture, flags) + return RNDX.DrawTexture(r / 2, x - r / 2, y - r / 2, r, r, col, texture, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleMaterial(x, y, r, col, mat, flags) + return RNDX.DrawMaterial(r / 2, x - r / 2, y - r / 2, r, r, col, mat, (flags or 0) + SHAPE_CIRCLE) +end + +local USE_SHADOWS_BLUR = false + +local function draw_blur() + if USE_SHADOWS_BLUR then + MAT = SHADOWS_BLUR_MAT + else + MAT = ROUNDED_BLUR_MAT + end + + COL_R, COL_G, COL_B, COL_A = 255, 255, 255, 255 + SetupDraw() + + render_CopyRenderTargetToTexture(BLUR_RT) + MATERIAL_SetFloat(MAT, BLUR_VERTICAL, 0) + surface_DrawTexturedRect(X, Y, W, H) + + render_CopyRenderTargetToTexture(BLUR_RT) + MATERIAL_SetFloat(MAT, BLUR_VERTICAL, 1) + surface_DrawTexturedRect(X, Y, W, H) +end + +function RNDX.DrawBlur(x, y, w, h, flags, tl, tr, bl, br, thickness) + RESET_PARAMS() + + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + + X, Y = x, y + W, H = w, h + TL, TR, BL, BR = bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + OUTLINE_THICKNESS = thickness + + draw_blur() +end + +local function setup_shadows() + X = X - SHADOW_SPREAD + Y = Y - SHADOW_SPREAD + W = W + (SHADOW_SPREAD * 2) + H = H + (SHADOW_SPREAD * 2) + + TL = TL + (SHADOW_SPREAD * 2) + TR = TR + (SHADOW_SPREAD * 2) + BL = BL + (SHADOW_SPREAD * 2) + BR = BR + (SHADOW_SPREAD * 2) +end + +local function draw_shadows(r, g, b, a) + if USING_BLUR then + USE_SHADOWS_BLUR = true + draw_blur() + USE_SHADOWS_BLUR = false + end + + MAT = SHADOWS_MAT + + if r == false then + COL_R = nil + else + COL_R, COL_G, COL_B, COL_A = r, g, b, a + end + + SetupDraw() + surface_DrawTexturedRectUV(X, Y, W, H, -0.015625, -0.015625, 1.015625, 1.015625) +end + +function RNDX.DrawShadowsEx(x, y, w, h, col, flags, tl, tr, bl, br, spread, intensity, thickness) + if col and col.a == 0 then + return + end + + local OLD_CLIPPING_STATE = DisableClipping(true) + + RESET_PARAMS() + + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + + X, Y = x, y + W, H = w, h + SHADOW_SPREAD = spread or 30 + SHADOW_INTENSITY = intensity or SHADOW_SPREAD * 1.2 + + TL, TR, BL, BR = bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + + OUTLINE_THICKNESS = thickness + + setup_shadows() + + USING_BLUR = bit_band(flags, BLUR) ~= 0 + + if bit_band(flags, MANUAL_COLOR) ~= 0 then + draw_shadows(false, nil, nil, nil) + elseif col then + draw_shadows(col.r, col.g, col.b, col.a) + else + draw_shadows(0, 0, 0, 255) + end + + DisableClipping(OLD_CLIPPING_STATE) +end + +function RNDX.DrawShadows(r, x, y, w, h, col, spread, intensity, flags) + return RNDX.DrawShadowsEx(x, y, w, h, col, flags, r, r, r, r, spread, intensity) +end + +function RNDX.DrawShadowsOutlined(r, x, y, w, h, col, thickness, spread, intensity, flags) + return RNDX.DrawShadowsEx(x, y, w, h, col, flags, r, r, r, r, spread, intensity, thickness or 1) +end + +local BASE_FUNCS; BASE_FUNCS = { + Rad = function(self, rad) + TL, TR, BL, BR = rad, rad, rad, rad + return self + end, + Radii = function(self, tl, tr, bl, br) + TL, TR, BL, BR = tl or 0, tr or 0, bl or 0, br or 0 + return self + end, + Texture = function(self, texture) + TEXTURE = texture + return self + end, + Material = function(self, mat) + local tex = mat:GetTexture("$basetexture") + if tex then + TEXTURE = tex + end + return self + end, + Outline = function(self, thickness) + OUTLINE_THICKNESS = thickness + return self + end, + Shape = function(self, shape) + SHAPE = SHAPES[shape] or 2.2 + return self + end, + Color = function(self, col_or_r, g, b, a) + if type(col_or_r) == "number" then + COL_R, COL_G, COL_B, COL_A = col_or_r, g or 255, b or 255, a or 255 + else + COL_R, COL_G, COL_B, COL_A = col_or_r.r, col_or_r.g, col_or_r.b, col_or_r.a + end + return self + end, + Blur = function(self, intensity) + if not intensity then + intensity = 1.0 + end + intensity = math_max(intensity, 0) + USING_BLUR, BLUR_INTENSITY = true, intensity + return self + end, + GradientNone = function(self) + GRAD_MODE_FLAG = 0 + GRAD_RAMP_TEXTURE = nil + GRAD_USE_RAMP_TEX = false + return self + end, + GradientTiling = function(self, mode) + GRAD_TILING_MODE = mode or 0 + return self + end, + GradientLinear = function(self, cx, cy, angle_degrees, scale) + GRAD_MODE_FLAG = 1 + GRAD_CENTER_X, GRAD_CENTER_Y = cx or 0.5, cy or 0.5 + GRAD_ANGLE = math.rad(angle_degrees or 0) + GRAD_SCALE_X = scale or 0 + GRAD_SCALE_Y = 0 + return self + end, + GradientRadial = function(self, cx, cy, scale_x, scale_y) + GRAD_MODE_FLAG = 2 + GRAD_CENTER_X, GRAD_CENTER_Y = cx or 0.5, cy or 0.5 + GRAD_SCALE_X = scale_x or 0 + GRAD_SCALE_Y = scale_y or GRAD_SCALE_X + return self + end, + GradientConic = function(self, cx, cy, angle_degrees) + GRAD_MODE_FLAG = 3 + GRAD_CENTER_X, GRAD_CENTER_Y = cx or 0.5, cy or 0.5 + GRAD_ANGLE = math.rad(angle_degrees or 0) + return self + end, + GradientTexture = function(self, texture) + GRAD_USE_RAMP_TEX = texture ~= nil + GRAD_RAMP_TEXTURE = texture or nil + return self + end, + Colors = function(self, ...) + local argn = select('#', ...) + local ncolors = 0 + local flat = COLORS_FLAT + + if argn == 1 and type(select(1, ...)) == 'table' then + local t = select(1, ...) + ncolors = #t + for i = 1, ncolors do + local c = t[i] + local base = (i - 1) * 4 + flat[base + 1] = (c.r or 255) + flat[base + 2] = (c.g or 255) + flat[base + 3] = (c.b or 255) + flat[base + 4] = (c.a or 255) + end + else + local first = select(1, ...) + if type(first) == 'number' then + ncolors = math.floor(argn / 4) + for i = 1, ncolors do + local off = (i - 1) * 4 + local r = select(off + 1, ...) + local g = select(off + 2, ...) + local b = select(off + 3, ...) + local a = select(off + 4, ...) + local base = (i - 1) * 4 + flat[base + 1] = r or 255 + flat[base + 2] = g or 255 + flat[base + 3] = b or 255 + flat[base + 4] = a or 255 + end + else + ncolors = argn + for i = 1, ncolors do + local c = select(i, ...) + local base = (i - 1) * 4 + flat[base + 1] = (c.r or 255) + flat[base + 2] = (c.g or 255) + flat[base + 3] = (c.b or 255) + flat[base + 4] = (c.a or 255) + end + end + end + + if ncolors == 0 then return self end + + if RAMP_RT then + render.PushRenderTarget(RAMP_RT) + render.Clear(0, 0, 0, 0, true, true) + cam.Start2D() + local w = RAMP_RT:Width() + local last = ncolors - 1 + local floor = math.floor + local lerp = Lerp + local setcol = surface_SetDrawColor + local drawrect = surface.DrawRect + + if ncolors == 1 then + local r = flat[1] + local g = flat[2] + local b = flat[3] + local a = flat[4] + for x = 0, w - 1 do + setcol(r, g, b, a) + drawrect(x, 0, 1, 1) + end + else + for x = 0, w - 1 do + local t = x / (w - 1) + local pos = t * last + local idx = floor(pos) + local frac = pos - idx + if idx >= last then + local base = last * 4 + setcol(flat[base + 1], flat[base + 2], flat[base + 3], flat[base + 4]) + else + local aBase = idx * 4 + local bBase = (idx + 1) * 4 + local rr = lerp(frac, flat[aBase + 1], flat[bBase + 1]) + local gg = lerp(frac, flat[aBase + 2], flat[bBase + 2]) + local bb = lerp(frac, flat[aBase + 3], flat[bBase + 3]) + local aa = lerp(frac, flat[aBase + 4], flat[bBase + 4]) + setcol(rr, gg, bb, aa) + end + drawrect(x, 0, 1, 1) + end + end + + cam.End2D() + render.PopRenderTarget() + + GRAD_USE_RAMP_TEX = true + GRAD_RAMP_TEXTURE = RAMP_RT + end + + return self + end, + Rotation = function(self, angle) + ROTATION = math.rad(angle or 0) + return self + end, + StartAngle = function(self, angle) + START_ANGLE = angle or 0 + return self + end, + EndAngle = function(self, angle) + END_ANGLE = angle or 360 + return self + end, + Shadow = function(self, spread, intensity) + SHADOW_ENABLED, SHADOW_SPREAD, SHADOW_INTENSITY = true, spread or 30, intensity or (spread or 30) * 1.2 + return self + end, + Clip = function(self, pnl) + CLIP_PANEL = pnl + return self + end, + Flags = function(self, flags) + flags = flags or 0 + + -- Corner flags + if bit_band(flags, NO_TL) ~= 0 then + TL = 0 + end + if bit_band(flags, NO_TR) ~= 0 then + TR = 0 + end + if bit_band(flags, NO_BL) ~= 0 then + BL = 0 + end + if bit_band(flags, NO_BR) ~= 0 then + BR = 0 + end + + -- Shape flags + local shape_flag = bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS) + if shape_flag ~= 0 then + SHAPE = SHAPES[shape_flag] or SHAPES[DEFAULT_SHAPE] + end + + -- Blur flag + if bit_band(flags, BLUR) ~= 0 then + BASE_FUNCS.Blur(self) + end + + -- Manual color flag + if bit_band(flags, MANUAL_COLOR) ~= 0 then + COL_R = nil + end + + return self + end, + +} + +local RECT = { + Rad = BASE_FUNCS.Rad, + Radii = BASE_FUNCS.Radii, + Texture = BASE_FUNCS.Texture, + Material = BASE_FUNCS.Material, + Outline = BASE_FUNCS.Outline, + Shape = BASE_FUNCS.Shape, + Color = BASE_FUNCS.Color, + Blur = BASE_FUNCS.Blur, + GradientNone = BASE_FUNCS.GradientNone, + GradientTiling = BASE_FUNCS.GradientTiling, + GradientLinear = BASE_FUNCS.GradientLinear, + GradientRadial = BASE_FUNCS.GradientRadial, + GradientConic = BASE_FUNCS.GradientConic, + GradientTexture = BASE_FUNCS.GradientTexture, + Colors = BASE_FUNCS.Colors, + Rotation = BASE_FUNCS.Rotation, + StartAngle = BASE_FUNCS.StartAngle, + EndAngle = BASE_FUNCS.EndAngle, + Clip = BASE_FUNCS.Clip, + Shadow = BASE_FUNCS.Shadow, + Flags = BASE_FUNCS.Flags, + + Draw = function(self) + if START_ANGLE == END_ANGLE then + return -- nothing to draw + end + + local OLD_CLIPPING_STATE + if SHADOW_ENABLED or CLIP_PANEL then + OLD_CLIPPING_STATE = DisableClipping(true) + end + + if CLIP_PANEL then + local sx, sy = CLIP_PANEL:LocalToScreen(0, 0) + local sw, sh = CLIP_PANEL:GetSize() + render.SetScissorRect(sx, sy, sx + sw, sy + sh, true) + end + + if SHADOW_ENABLED then + setup_shadows() + draw_shadows(COL_R, COL_G, COL_B, COL_A) + elseif USING_BLUR then + draw_blur() + else + if TEXTURE then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", TEXTURE) + end + + SetupDraw() + surface_DrawTexturedRectUV(X, Y, W, H, -0.015625, -0.015625, 1.015625, 1.015625) + end + + if CLIP_PANEL then + render.SetScissorRect(0, 0, 0, 0, false) + end + + if SHADOW_ENABLED or CLIP_PANEL then + DisableClipping(OLD_CLIPPING_STATE) + end + end, + + GetMaterial = function(self) + if SHADOW_ENABLED or USING_BLUR then + error("You can't get the material of a shadowed or blurred rectangle!") + end + + if TEXTURE then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", TEXTURE) + end + SetupDraw() + + return MAT + end, +} + +local CIRCLE = { + Texture = BASE_FUNCS.Texture, + Material = BASE_FUNCS.Material, + Outline = BASE_FUNCS.Outline, + Color = BASE_FUNCS.Color, + Blur = BASE_FUNCS.Blur, + GradientNone = BASE_FUNCS.GradientNone, + GradientTiling = BASE_FUNCS.GradientTiling, + GradientLinear = BASE_FUNCS.GradientLinear, + GradientRadial = BASE_FUNCS.GradientRadial, + GradientConic = BASE_FUNCS.GradientConic, + GradientTexture = BASE_FUNCS.GradientTexture, + Colors = BASE_FUNCS.Colors, + Rotation = BASE_FUNCS.Rotation, + StartAngle = BASE_FUNCS.StartAngle, + EndAngle = BASE_FUNCS.EndAngle, + Clip = BASE_FUNCS.Clip, + Shadow = BASE_FUNCS.Shadow, + Flags = BASE_FUNCS.Flags, + Draw = RECT.Draw, + GetMaterial = RECT.GetMaterial, +} + +local TYPES = { + Rect = function(x, y, w, h) + RESET_PARAMS() + MAT = ROUNDED_MAT + X, Y, W, H = x, y, w, h + return RECT + end, + Circle = function(x, y, r) + RESET_PARAMS() + MAT = ROUNDED_MAT + SHAPE = SHAPES[SHAPE_CIRCLE] + X, Y, W, H = x - r / 2, y - r / 2, r, r + r = r / 2 + TL, TR, BL, BR = r, r, r, r + return CIRCLE + end +} + +setmetatable(RNDX, { + __call = function() + return TYPES + end +}) + +-- Flags +RNDX.NO_TL = NO_TL +RNDX.NO_TR = NO_TR +RNDX.NO_BL = NO_BL +RNDX.NO_BR = NO_BR + +RNDX.SHAPE_CIRCLE = SHAPE_CIRCLE +RNDX.SHAPE_FIGMA = SHAPE_FIGMA +RNDX.SHAPE_IOS = SHAPE_IOS + +RNDX.BLUR = BLUR +RNDX.MANUAL_COLOR = MANUAL_COLOR + +function RNDX.SetFlag(flags, flag, bool) + flag = RNDX[flag] or flag + if tobool(bool) then + return bit.bor(flags, flag) + else + return bit.band(flags, bit.bnot(flag)) + end +end + +function RNDX.SetDefaultShape(shape) + DEFAULT_SHAPE = shape or SHAPE_FIGMA + DEFAULT_DRAW_FLAGS = DEFAULT_SHAPE +end + +GProfiler.RNDX = RNDX \ No newline at end of file diff --git a/lua/gprofiler/modules/overview/cl_init.lua b/lua/gprofiler/modules/overview/cl_init.lua new file mode 100644 index 0000000..a3ada97 --- /dev/null +++ b/lua/gprofiler/modules/overview/cl_init.lua @@ -0,0 +1,9 @@ +GProfiler.Overview = GProfiler.Overview or {} + +function GProfiler.Overview.DoTab(Base) + +end + +GProfiler.Menu.RegisterTab("Overview", "gprofiler/home.png", 0, GProfiler.Overview.DoTab, function() + +end) \ No newline at end of file diff --git a/lua/gprofiler/modules/utils/cl_syntax.lua b/lua/gprofiler/modules/utils/cl_syntax.lua new file mode 100644 index 0000000..ba2ef48 --- /dev/null +++ b/lua/gprofiler/modules/utils/cl_syntax.lua @@ -0,0 +1,287 @@ +GProfiler.SyntaxColors = { + keyword = Color(86, 156, 214), + library = Color(78, 201, 176), + funcCall = Color(220, 220, 170), + funcName = Color(220, 220, 170), + string = Color(206, 145, 120), + comment = Color(106, 153, 85), + number = Color(181, 206, 168), + operator = Color(212, 212, 212), + punctuation= Color(212, 212, 212), + boolean = Color(86, 156, 214), + selfVar = Color(86, 156, 214), + nilVal = Color(86, 156, 214), + default = Color(212, 212, 212), + background = Color(30, 30, 30), + lineNumber = Color(133, 133, 133), + lineSep = Color(70, 70, 70) +} + +local keywords = { + ["function"] = true, ["if"] = true, ["then"] = true, ["else"] = true, ["elseif"] = true, ["end"] = true, + ["for"] = true, ["while"] = true, ["do"] = true, ["repeat"] = true, ["until"] = true, + ["local"] = true, ["return"] = true, ["break"] = true, ["in"] = true, ["and"] = true, ["or"] = true, ["not"] = true, + ["goto"] = true, ["continue"] = true +} + +local operators = { + ["+"] = true, ["-"] = true, ["*"] = true, ["/"] = true, ["%"] = true, ["^"] = true, + ["="] = true, ["<"] = true, [">"] = true, ["#"] = true, ["!"] = true +} + +local function InsertColor(richText, col) richText:InsertColorChange(col.r, col.g, col.b, col.a) end + +function GProfiler.SyntaxHighlight(richText, code, startLine) + local colors = GProfiler.SyntaxColors + richText:SetText("") + local i = 1 + local len = #code + local state = "default" + local stringChar = "" + local maxIter = len * 2 + local iter = 0 + + local _, totalLines = code:gsub("\n", "\n") + totalLines = totalLines + 1 + local padWidth = #tostring(totalLines) + local lineNum = startLine or 1 + + local function EmitLineNumber() + InsertColor(richText, colors.lineNumber) + local numStr = string.rep(" ", padWidth - #tostring(lineNum)) .. tostring(lineNum) + richText:AppendText(numStr) + InsertColor(richText, colors.lineSep) + richText:AppendText(" | ") + lineNum = lineNum + 1 + end + + EmitLineNumber() + + while i <= len do + iter = iter + 1 + if iter > maxIter then break end + + local c = code:sub(i, i) + + if c == "\n" then + richText:AppendText("\n") + i = i + 1 + if state == "comment" then state = "default" end + EmitLineNumber() + elseif state == "blockcomment" then + if code:sub(i, i + 1) == "]]" then + richText:AppendText("]]") + i = i + 2 + state = "default" + else + richText:AppendText(c) + i = i + 1 + end + + elseif state == "blockstring" then + if code:sub(i, i + 1) == "]]" then + richText:AppendText("]]") + i = i + 2 + state = "default" + else + richText:AppendText(c) + i = i + 1 + end + + elseif state == "comment" then + richText:AppendText(c) + i = i + 1 + + elseif state == "string" then + richText:AppendText(c) + if c == "\\" then + if i + 1 <= len then + i = i + 1 + richText:AppendText(code:sub(i, i)) + end + elseif c == stringChar then + state = "default" + end + i = i + 1 + + else + if code:sub(i, i + 3) == "--[[" then + InsertColor(richText, colors.comment) + richText:AppendText("--[[") + state = "blockcomment" + i = i + 4 + + elseif code:sub(i, i + 1) == "--" then + InsertColor(richText, colors.comment) + richText:AppendText("--") + state = "comment" + i = i + 2 + elseif code:sub(i, i + 1) == "//" then + InsertColor(richText, colors.comment) + richText:AppendText("//") + state = "comment" + i = i + 2 + + elseif code:sub(i, i + 1) == "[[" then + InsertColor(richText, colors.string) + richText:AppendText("[[") + state = "blockstring" + i = i + 2 + + elseif c == "\"" or c == "'" then + InsertColor(richText, colors.string) + richText:AppendText(c) + stringChar = c + state = "string" + i = i + 1 + + elseif code:sub(i, i + 1) == "==" or code:sub(i, i + 1) == "~=" or code:sub(i, i + 1) == "!=" or code:sub(i, i + 1) == ">=" or code:sub(i, i + 1) == "<=" or code:sub(i, i + 1) == ".." or code:sub(i, i + 1) == "&&" or code:sub(i, i + 1) == "||" then + InsertColor(richText, colors.operator) + richText:AppendText(code:sub(i, i + 1)) + i = i + 2 + + elseif operators[c] then + InsertColor(richText, colors.operator) + richText:AppendText(c) + i = i + 1 + + elseif c == "(" or c == ")" or c == "{" or c == "}" or c == "[" or c == "]" or c == "," or c == ";" then + InsertColor(richText, colors.punctuation) + richText:AppendText(c) + i = i + 1 + + elseif c:match("%d") or (c == "." and i + 1 <= len and code:sub(i + 1, i + 1):match("%d")) then + local startI = i + if c == "0" and i + 1 <= len and code:sub(i + 1, i + 1):lower() == "x" then + i = i + 2 + while i <= len and code:sub(i, i):match("[%da-fA-F]") do i = i + 1 end + else + while i <= len and code:sub(i, i):match("[%d%.]") do i = i + 1 end + if i <= len and code:sub(i, i):lower() == "e" then + i = i + 1 + if i <= len and (code:sub(i, i) == "+" or code:sub(i, i) == "-") then i = i + 1 end + while i <= len and code:sub(i, i):match("%d") do i = i + 1 end + end + end + InsertColor(richText, colors.number) + richText:AppendText(code:sub(startI, i - 1)) + + elseif c:match("[%a_]") then + local startI = i + while i <= len and code:sub(i, i):match("[%w_]") do + i = i + 1 + end + local word = code:sub(startI, i - 1) + + local afterWord = i + while afterWord <= len and code:sub(afterWord, afterWord):match("%s") do + afterWord = afterWord + 1 + end + local nextChar = afterWord <= len and code:sub(afterWord, afterWord) or "" + local isCall = nextChar == "(" + local isLibrary = nextChar == "." or nextChar == ":" + + local beforeStart = startI - 1 + while beforeStart >= 1 and code:sub(beforeStart, beforeStart):match("%s") do + beforeStart = beforeStart - 1 + end + local isFuncDecl = beforeStart >= 7 and code:sub(beforeStart - 7, beforeStart) == "function" + + if word == "true" or word == "false" then + InsertColor(richText, colors.boolean) + elseif word == "nil" then + InsertColor(richText, colors.nilVal) + elseif word == "self" then + InsertColor(richText, colors.selfVar) + elseif keywords[word] then + InsertColor(richText, colors.keyword) + elseif isFuncDecl then + InsertColor(richText, colors.funcName) + elseif isLibrary then + InsertColor(richText, colors.library) + elseif isCall and word == "Color" then + local colorMatch = code:sub(afterWord):match("^%((%s*%d+%s*,%s*%d+%s*,%s*%d+%s*,?%s*%d*%s*)%)") + if colorMatch then + local nums = {} + for n in colorMatch:gmatch("%d+") do + nums[#nums + 1] = tonumber(n) + end + if #nums >= 3 then + local r, g, b, a = nums[1], nums[2], nums[3], nums[4] or 255 + InsertColor(richText, colors.funcCall) + richText:AppendText(word) + InsertColor(richText, colors.punctuation) + richText:AppendText("(") + if a < 128 then + richText:InsertColorChange(255, 255, 255, 255) + else + richText:InsertColorChange(r, g, b, 255) + end + richText:AppendText(colorMatch) + InsertColor(richText, colors.punctuation) + richText:AppendText(")") + i = afterWord + #colorMatch + 2 + goto continued + end + end + InsertColor(richText, colors.funcCall) + elseif isCall then + InsertColor(richText, colors.funcCall) + else + InsertColor(richText, colors.default) + end + richText:AppendText(word) + + ::continued:: + + elseif c == ":" or c == "." then + local nextI = i + 1 + if nextI <= len and code:sub(nextI, nextI):match("[%a_]") then + local startI = nextI + while nextI <= len and code:sub(nextI, nextI):match("[%w_]") do + nextI = nextI + 1 + end + local member = code:sub(startI, nextI - 1) + local afterMember = nextI + while afterMember <= len and code:sub(afterMember, afterMember):match("%s") do + afterMember = afterMember + 1 + end + local isMemberCall = afterMember <= len and code:sub(afterMember, afterMember) == "(" + + InsertColor(richText, colors.punctuation) + richText:AppendText(c) + + if isMemberCall then + InsertColor(richText, colors.funcCall) + else + InsertColor(richText, colors.default) + end + richText:AppendText(member) + i = nextI + else + InsertColor(richText, colors.punctuation) + richText:AppendText(c) + i = i + 1 + end + + else + richText:AppendText(c) + i = i + 1 + end + end + end + + richText:InvalidateLayout() + + local scrollFrames = 10 + local oldThink = richText.Think + richText.Think = function(self) + if oldThink then oldThink(self) end + if scrollFrames > 0 then + scrollFrames = scrollFrames - 1 + self:GotoTextStart() + else + richText.Think = oldThink + end + end +end \ No newline at end of file diff --git a/lua/gprofiler/modules/utils/cl_vgui.lua b/lua/gprofiler/modules/utils/cl_vgui.lua new file mode 100644 index 0000000..184f36c --- /dev/null +++ b/lua/gprofiler/modules/utils/cl_vgui.lua @@ -0,0 +1,19 @@ +local PANEL = {} + +function PANEL:SortByColumn(ColumnID, Desc) + table.sort(self.Sorted, function(a, b) + if Desc then a, b = b, a end + local aval = a:GetSortValue( ColumnID ) || a:GetColumnText( ColumnID ) + local bval = b:GetSortValue( ColumnID ) || b:GetColumnText( ColumnID ) + if isnumber(aval) and isnumber(bval) then return aval < bval end + return tostring(aval) < tostring(bval) + end) + + self:SetDirty(true) + self:InvalidateLayout() + + self.SortedBy = ColumnID + self.SortedDescending = Desc +end + +derma.DefineControl("GP.ListView", "", PANEL, "DListView") diff --git a/lua/gprofiler/modules/utils/ui/cl_graphs.lua b/lua/gprofiler/modules/utils/ui/cl_graphs.lua new file mode 100644 index 0000000..dcda04f --- /dev/null +++ b/lua/gprofiler/modules/utils/ui/cl_graphs.lua @@ -0,0 +1,125 @@ +-- TODO + credits to whoever posted this in gmod discord + +-- local MAX_DEBUG_ITEMS = 512 +-- local function ConstantLengthNumericalQueue(capacity) +-- local obj = {} +-- local pointer = 0 +-- local length = 0 +-- local startat = 0 +-- local backing = {} +-- for i = 1, capacity do +-- backing[i - 1] = 0 +-- end +-- function obj:Add(item) +-- if length < capacity then +-- length = length + 1 +-- else +-- startat = startat + 1 +-- if startat >= capacity then +-- startat = 0 +-- end +-- end + +-- if pointer >= capacity then pointer = 0 end +-- backing[pointer] = item +-- pointer = pointer + 1 +-- end +-- function obj:Get(i) +-- return backing[(i + startat) % capacity] +-- end + +-- function obj:Length() return length end + + +-- function obj:Start() return startat end + +-- function obj:Min() +-- local ret = 0 +-- for i = 1, length do +-- ret = math.min(ret, backing[i - 1]) +-- end +-- return ret +-- end + +-- function obj:Max() +-- local ret = 0 +-- for i = 1, length do +-- ret = math.max(ret, backing[i - 1]) +-- end +-- return ret +-- end + +-- function obj:Average() +-- local ret = 0 +-- for i = 1, length do +-- ret = ret + backing[i - 1] +-- end +-- return ret / length +-- end + +-- return obj +-- end + +-- local perfgraph = ConstantLengthNumericalQueue(MAX_DEBUG_ITEMS) + +-- -- during frames call perfgraph:Add(v) + +-- local v1, v2 = Vector(), Vector() +-- function draw.Line(startX, startY, endX, endY, thickness, color) +-- if not startX then return end +-- if not startY then return end +-- if not endX then return end +-- if not endY then return end + +-- thickness = thickness or 1 +-- color = color or color_white + +-- local x, y = endX - startX, endY - startY +-- local cx, cy = (startX + endX) / 2, (startY + endY) / 2 +-- local dist = math.sqrt((x^2) + (y^2)) + +-- local a = -math.atan2(y, x) +-- local s, c = math.sin(a), math.cos(a) + +-- v1:SetUnpacked(cx, cy, 0) +-- v2:SetUnpacked(s, c, -thickness) +-- mesh.Begin(MATERIAL_QUADS, 1) +-- xpcall(function() +-- mesh.QuadEasy(v1, v2, dist, thickness, color) +-- mesh.End() +-- end, function() mesh.End() print(debug.traceback(err)) end) +-- end + +-- local color_grey = Color(173, 173, 173) +-- local formatString = "%.2f" +-- local formatString2 = "avg: %.2f" +-- local function DrawGraph(data, label, x, y, c) +-- local w, h = 450, 64 +-- surface.SetDrawColor(20, 25, 35, 200) +-- surface.DrawRect(x, y, w, h) + +-- local xPadding = 48 + +-- local count, min, max, avg = data:Length(), data:Min(), data:Max(), data:Average() +-- draw.Line(x + xPadding, y + 4, x + xPadding, y + h - 4, 2, color_grey) +-- draw.Line(x + xPadding, y + h - 4, x + w - 4, y + h - 4, 2, color_grey) +-- for i = 0, MAX_DEBUG_ITEMS - 1 do +-- if i + 1 >= count then break end + +-- local finalPos = i + 1 +-- local x1 = x + xPadding + 4 + math.Remap(i, 0, MAX_DEBUG_ITEMS, 0, w - xPadding - 8) +-- local x2 = x + xPadding + 4 + math.Remap(finalPos, 0, MAX_DEBUG_ITEMS, 0, w - xPadding - 8) +-- local y1 = data:Get(i) +-- local y2 = data:Get(finalPos) + +-- draw.Line( +-- x1, y + math.Remap(y1, min, max, h - 4, 16), +-- x2, y + math.Remap(y2, min, max, h - 4, 16), +-- 3, c) +-- end + +-- draw.SimpleText(label, "DebugFixed", x + (w / 2), y, color_white, TEXT_ALIGN_CENTER) +-- draw.SimpleText(formatString:format(max), "DebugFixed", x + xPadding - 4, y, color_white, TEXT_ALIGN_RIGHT) +-- draw.SimpleText(formatString:format(min), "DebugFixed", x + xPadding - 4, y + h, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM) +-- draw.SimpleText(formatString2:format(avg), "DebugFixed", x + w - 4, y, color_white, TEXT_ALIGN_RIGHT) +-- end \ No newline at end of file diff --git a/lua/gprofiler/modules/utils/ui/cl_splitpanels.lua b/lua/gprofiler/modules/utils/ui/cl_splitpanels.lua new file mode 100644 index 0000000..830e2ae --- /dev/null +++ b/lua/gprofiler/modules/utils/ui/cl_splitpanels.lua @@ -0,0 +1,164 @@ +local function DefaultSplitPaint(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, Color(38, 63, 89, 255)) +end + +local function CreateSplitPanel(parent, isVertical, spacing, name, initialPercentage) + parent.Paint = nil + spacing = spacing or GProfiler.GetScaledSize(10) + local handleThickness = spacing + + local panel1 = vgui.Create("DPanel", parent) + local panel2 = vgui.Create("DPanel", parent) + local handle = vgui.Create("DPanel", parent) + + panel1.Paint = DefaultSplitPaint + panel2.Paint = DefaultSplitPaint + + handle:SetCursor(isVertical and "sizewe" or "sizens") + handle.isDragging = false + handle.startPos = 0 + handle.startHandlePos = 0 + handle.Fraction = nil + + local minSize = 50 + + if name then + local saved = cookie.GetNumber("gprofiler_" .. name) + if saved and saved > 0 and saved <= 1 then + handle.Fraction = saved + end + end + + local function updateLayout() + local w, h = parent:GetSize() + + if w == 0 or h == 0 then return end + + local totalSize = isVertical and w or h + + if not handle.Fraction then + handle.Fraction = initialPercentage or 0.5 + end + + local currentPos = (totalSize - spacing) * handle.Fraction + local handlePos = math.Clamp(currentPos, minSize, totalSize - minSize - spacing) + + if isVertical then + handle:SetSize(handleThickness, h) + handle:SetPos(handlePos + (spacing - handleThickness) / 2, 0) + panel1:SetSize(handlePos, h) + panel1:SetPos(0, 0) + panel2:SetSize(w - handlePos - spacing, h) + panel2:SetPos(handlePos + spacing, 0) + else + handle:SetSize(w, handleThickness) + handle:SetPos(0, handlePos + (spacing - handleThickness) / 2) + panel1:SetSize(w, handlePos) + panel1:SetPos(0, 0) + panel2:SetSize(w, h - handlePos - spacing) + panel2:SetPos(0, handlePos + spacing) + end + + if panel1.OnHandleMoved then panel1:OnHandleMoved() end + if panel2.OnHandleMoved then panel2:OnHandleMoved() end + end + + handle.Paint = function(s, w, h) + if not s:IsHovered() and not s.isDragging then return end + local MenuColors = GProfiler.MenuColors + if isVertical then + GProfiler.RNDX.Draw(4, 2, 0, w - 4, h, Color(26, 53, 80)) + else + GProfiler.RNDX.Draw(4, 0, 2, w, h - 4, Color(26, 53, 80)) + end + end + + handle.OnMousePressed = function(s, mousecode) + if mousecode == MOUSE_LEFT then + s.isDragging = true + local mx, my = parent:ScreenToLocal(gui.MouseX(), gui.MouseY()) + s.startPos = isVertical and mx or my + + local w, h = parent:GetSize() + local totalSize = isVertical and w or h + local currentPos = (totalSize - spacing) * (s.Fraction or initialPercentage or 0.5) + s.startHandlePos = currentPos + + s:MouseCapture(true) + elseif mousecode == MOUSE_RIGHT then + if name then + cookie.Set("gprofiler_" .. name, nil) + end + handle.Fraction = nil + updateLayout() + end + end + + handle.OnMouseReleased = function(s, mousecode) + if mousecode == MOUSE_LEFT then + s.isDragging = false + s:MouseCapture(false) + if name and s.Fraction then + cookie.Set("gprofiler_" .. name, s.Fraction) + end + end + end + + handle.Think = function(s) + if s.isDragging then + local mx, my = parent:ScreenToLocal(gui.MouseX(), gui.MouseY()) + local currentPos = isVertical and mx or my + local delta = currentPos - s.startPos + + local w, h = parent:GetSize() + local totalSize = isVertical and w or h + local availableSize = totalSize - spacing + + if availableSize < minSize * 2 then return end + + local newHandlePos = s.startHandlePos + delta + newHandlePos = math.Clamp(newHandlePos, minSize, availableSize - minSize) + + local newFraction = newHandlePos / availableSize + if newFraction ~= s.Fraction then + s.Fraction = newFraction + updateLayout() + end + end + end + + local oldPerformLayout = parent.PerformLayout + parent.PerformLayout = function(pnl, w, h) + if oldPerformLayout then oldPerformLayout(pnl, w, h) end + updateLayout() + end + + updateLayout() + + return panel1, panel2 +end + +function GProfiler.Utils.VSplitPanel(parent, spacing, name, initialPercentage) + if isstring(spacing) then + if isnumber(name) then + initialPercentage = name + end + name = spacing + spacing = nil + end + + return CreateSplitPanel(parent, true, spacing, name, initialPercentage) +end + +function GProfiler.Utils.HSplitPanel(parent, spacing, name, initialPercentage) + if isstring(spacing) then + if isnumber(name) then + initialPercentage = name + end + + name = spacing + spacing = nil + end + + return CreateSplitPanel(parent, false, spacing, name, initialPercentage) +end \ No newline at end of file diff --git a/lua/gprofiler/profilers/auto_profile/cl_autoprofile.lua b/lua/gprofiler/profilers/auto_profile/cl_autoprofile.lua deleted file mode 100644 index 52c2acb..0000000 --- a/lua/gprofiler/profilers/auto_profile/cl_autoprofile.lua +++ /dev/null @@ -1,102 +0,0 @@ -local ProfilerList = { - "Hooks", "Networking", "Functions", "Commands", "Timers", - --[["Entity Variables",]] "Network Variables", "Database" -} - -local CurrentStates = {} -local DropdownOptions = { - ["Disabled"] = 0, - ["As soon as possible"] = 1, - ["When the gamemode is fully loaded"] = 2, - ["When a player joins the server"] = 3, -} - -local Black50 = Color(0, 0, 0, 50) - -local MenuColors = GProfiler.MenuColors -function GProfiler.AutoProfileTab(Content) - local Header = vgui.Create("DPanel", Content) - Header:SetSize(Content:GetWide() - 10, 150) - Header:SetPos(5, 10) - Header.Paint = nil - - local Text = [[ - Here you can configure profilers to start automatically! - You can choose to have the profiler start as soon as possible (when GProfiler loads), when the gamemode is fully loaded, or when a player joins the server. - - Currently, this is limited to the Server Realm. - ]] - - local TextLabel = vgui.Create("DLabel", Header) - TextLabel:SetFont("GProfiler.Menu.TabText") - TextLabel:SetText(Text) - TextLabel:SetWrap(true) - TextLabel:SetAutoStretchVertical(true) - TextLabel:SizeToContents() - TextLabel:SetWide(Header:GetWide() - 20) - TextLabel:SetPos(10, 10) - TextLabel:SetTextColor(MenuColors.White) - - local TabContent = vgui.Create("DPanel", Content) - TabContent:SetSize(Content:GetWide() - 10, Content:GetTall() - Header:GetTall() - 25) - TabContent:SetPos(5, Header:GetTall() + 20) - TabContent.Paint = nil - - local Profilers = vgui.Create("DPanelList", TabContent) - Profilers:SetSize(TabContent:GetWide(), TabContent:GetTall()) - Profilers:EnableVerticalScrollbar(true) - Profilers:EnableHorizontal(false) - Profilers:SetSpacing(5) - Profilers:SetPadding(5) - - for k, v in ipairs(ProfilerList) do - local Profiler = vgui.Create("DPanel", Profilers) - Profiler:SetSize(Profilers:GetWide(), 70) - Profiler.Paint = function(s, w, h) - draw.RoundedBox(4, 2, 2, w - 4, h - 4, MenuColors.DListRowBackground) - draw.RoundedBox(4, 4, 4, w - 8, h - 8, Black50) - - draw.SimpleText(v, "GProfiler.Menu.Title", 10, h / 2, MenuColors.White, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - end - - surface.SetFont("GProfiler.Menu.RealmSelector") - local textWidth, textHeight = surface.GetTextSize("When the gamemode is fully loaded") - - local Dropdown = vgui.Create("DComboBox", Profiler) - Dropdown:SetSize(textWidth + 20, 30) - Dropdown:SetPos(Profiler:GetWide() - Dropdown:GetWide() - 40, Profiler:GetTall() / 2 - Dropdown:GetTall() / 2) - Dropdown:SetValue(CurrentStates[v] or "Disabled") - Dropdown:SetTextColor(MenuColors.White) - Dropdown:SetFont("GProfiler.Menu.RealmSelector") - Dropdown:SetTall(30) - Dropdown:SetWide(Dropdown:GetWide() + 10) - Dropdown:SetSortItems(false) - Dropdown.OnSelect = function(s, index, value, data) - net.Start("GProfiler.AutoProfile.Configure") - net.WriteString(v) - net.WriteUInt(DropdownOptions[value], 2) - net.SendToServer() - - CurrentStates[v] = value - end - - for option, index in SortedPairsByValue(DropdownOptions) do - Dropdown:AddChoice(option, index) - end - - GProfiler.StyleDropdown(Dropdown) - - Profilers:AddItem(Profiler) - end -end - -GProfiler.Menu.RegisterTab("Auto Profile", "icon16/map_go.png", 999, GProfiler.AutoProfileTab) - -net.Receive("GProfiler.AutoProfile.SendState", function() - for i = 1, net.ReadUInt(4) do - local profiler = net.ReadString() - local state = net.ReadUInt(2) - - CurrentStates[profiler] = table.KeyFromValue(DropdownOptions, state) - end -end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/auto_profile/sv_autoprofile.lua b/lua/gprofiler/profilers/auto_profile/sv_autoprofile.lua deleted file mode 100644 index d47041a..0000000 --- a/lua/gprofiler/profilers/auto_profile/sv_autoprofile.lua +++ /dev/null @@ -1,85 +0,0 @@ -util.AddNetworkString("GProfiler.AutoProfile.Configure") -util.AddNetworkString("GProfiler.AutoProfile.SendState") - -local Profilers = { - ["Hooks"] = "Hooks", - ["Networking"] = "Net", - ["Functions"] = "Functions", - ["Commands"] = "ConCommands", - ["Timers"] = "Timers", - -- ["Entity Variables"] = "EntVars", - ["Network Variables"] = "NetVars", - ["Database"] = "Database" -} - -local ValidStates = { - 0, -- Disabled - 1, -- ASAP - 2, -- When the gamemode has fully loaded - 3 -- When the first player connects -} - -hook.Add("GProfiler.Loaded", "GProfiler.AutoProfilers", function() - sql.Query("CREATE TABLE IF NOT EXISTS gprofiler_autoprofile (profiler TEXT, state INTEGER, PRIMARY KEY(profiler))") - - local Data = sql.Query("SELECT * FROM gprofiler_autoprofile") - if not table.IsEmpty(Data or {}) then - for _, row in ipairs(Data) do - row.state = tonumber(row.state) or 0 - if row.state == 0 then continue end - - local Profiler = GProfiler[Profilers[row.profiler]] - if not Profiler then continue end - - if row.state == 1 then - GProfiler.Log("[Auto Profiler] Starting " .. row.profiler .. " profiler!", 2) - Profiler:StartProfiler(Entity(0)) - elseif row.state == 2 then - GProfiler.Log("[Auto Profiler] Delaying " .. row.profiler .. " profiler until the gamemode has fully loaded!", 2) - hook.Add("PostGamemodeLoaded", "GProfiler.AutoProfile." .. row.profiler, function() - hook.Remove("PostGamemodeLoaded", "GProfiler.AutoProfile." .. row.profiler) - GProfiler.Log("[Auto Profiler] Starting " .. row.profiler .. " profiler (gamemode loaded)!", 2) - Profiler:StartProfiler(Entity(0)) - end) - elseif row.state == 3 then - GProfiler.Log("[Auto Profiler] Delaying " .. row.profiler .. " profiler until the first player connects!", 2) - hook.Add("PlayerConnect", "GProfiler.AutoProfile." .. row.profiler, function(ply) - hook.Remove("PlayerConnect", "GProfiler.AutoProfile." .. row.profiler) - GProfiler.Log("[Auto Profiler] Starting " .. row.profiler .. " profiler (player connected)!", 2) - Profiler:StartProfiler(ply) - end) - end - end - end - - sql.Query("DELETE FROM gprofiler_autoprofile") -end) - -net.Receive("GProfiler.AutoProfile.Configure", function(_, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - local Profiler = net.ReadString() - local State = net.ReadUInt(2) - - if not Profilers[Profiler] or not table.HasValue(ValidStates, State) then return end - - if State == 0 then - sql.Query("DELETE FROM gprofiler_autoprofile WHERE profiler = " .. sql.SQLStr(Profiler)) - else - sql.Query("REPLACE INTO gprofiler_autoprofile (profiler, state) VALUES (" .. sql.SQLStr(Profiler) .. ", " .. State .. ")") - end -end) - - -hook.Add("PlayerInitialSpawn", "GProfiler.AutoProfiler.SendState", function(ply) - local Data = sql.Query("SELECT * FROM gprofiler_autoprofile") - if table.IsEmpty(Data or {}) then return end - - net.Start("GProfiler.AutoProfile.SendState") - net.WriteUInt(table.Count(Data), 4) - for _, row in ipairs(Data) do - net.WriteString(row.profiler) - net.WriteUInt(row.state, 2) - end - net.Send(ply) -end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/concommands/cl_concommands.lua b/lua/gprofiler/profilers/concommands/cl_concommands.lua index 216cefd..eaa0f0e 100644 --- a/lua/gprofiler/profilers/concommands/cl_concommands.lua +++ b/lua/gprofiler/profilers/concommands/cl_concommands.lua @@ -1,265 +1,9 @@ GProfiler.ConCommands = GProfiler.ConCommands or {} -GProfiler.ConCommands.ProfileActive = GProfiler.ConCommands.ProfileActive or false -GProfiler.ConCommands.StartTime = GProfiler.ConCommands.StartTime or 0 -GProfiler.ConCommands.EndTime = GProfiler.ConCommands.EndTime or 0 -GProfiler.ConCommands.ProfileActive = GProfiler.ConCommands.ProfileActive or false -GProfiler.ConCommands.Realm = GProfiler.ConCommands.Realm or "Client" - -local TabPadding = 10 -local MenuColors = GProfiler.MenuColors - -local function GetCommandList(realm, callback) - if realm == "Client" then - local commands = concommand.GetTable() - local commandList = {} - - for k, v in pairs(commands) do - local source, lineStart, lineEnd = GProfiler.ConCommands.GetFunction(k, commands) - commandList[k] = {Source = source, Lines = {lineStart, lineEnd}} - end - - callback(commandList) - elseif realm == "Server" then - net.Start("GProfiler_ConCommands_CommandList") - net.SendToServer() - - net.Receive("GProfiler_ConCommands_CommandList", function() - local commandList = {} - for i = 1, net.ReadUInt(32) do - local command = net.ReadString() - local source = net.ReadString() - local lineStart = net.ReadUInt(16) - local lineEnd = net.ReadUInt(16) - commandList[command] = {Source = source, Lines = {lineStart, lineEnd}} - end - - callback(commandList) - end) - end -end function GProfiler.ConCommands.DoTab(Content) - local Header = vgui.Create("DPanel", Content) - Header:SetSize(Content:GetWide(), 40) - Header:SetPos(0, 10) - Header.Paint = nil - - local RealmSelector = GProfiler.Menu.CreateRealmSelector(Header, "ConCommands", Header:GetWide() - 110 - TabPadding, Header:GetTall() / 2 - 30 / 2, function(s, _, value) - GProfiler.ConCommands.Realm = value - GProfiler.Menu.OpenTab("Commands", GProfiler.ConCommands.DoTab) - end) - RealmSelector:SetPos(Header:GetWide() - RealmSelector:GetWide() - TabPadding, Header:GetTall() / 2 - RealmSelector:GetTall() / 2) - - local StartButton = vgui.Create("DButton", Header) - StartButton:SetText(GProfiler.ConCommands.ProfileActive and GProfiler.Language.GetPhrase("profiler_stop") or GProfiler.Language.GetPhrase("profiler_start")) - StartButton:SetTextColor(MenuColors.White) - StartButton:SetFont("GProfiler.Menu.StartButton") - StartButton:SizeToContents() - StartButton:SetTall(RealmSelector:GetTall()) - StartButton:SetPos(Header:GetWide() - StartButton:GetWide() - RealmSelector:GetWide() - TabPadding * 2, Header:GetTall() / 2 - StartButton:GetTall() / 2) - StartButton.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - - StartButton.DoClick = function() - if GProfiler.ConCommands.ProfileActive then - GProfiler.ConCommands.EndTime = SysTime() - GProfiler.ConCommands.Override = GProfiler.TimeRunning(GProfiler.ConCommands.StartTime, SysTime(), GProfiler.ConCommands.ProfileActive) .. "s" - if GProfiler.ConCommands.Realm == "Server" then - net.Start("GProfiler_ConCommands_ToggleServerProfile") - net.WriteBool(false) - net.SendToServer() - else - GProfiler.ConCommands:RestoreCommands() - GProfiler.ConCommands.ProfileActive = false - GProfiler.Menu.OpenTab("Commands", GProfiler.ConCommands.DoTab) - end - else - GProfiler.ConCommands.StartTime = SysTime() - GProfiler.ConCommands.EndTime = 0 - GProfiler.ConCommands.Override = nil - if GProfiler.ConCommands.Realm == "Server" then - net.Start("GProfiler_ConCommands_ToggleServerProfile") - net.WriteBool(true) - net.SendToServer() - else - GProfiler.ConCommands:StartProfiler() - GProfiler.ConCommands.ProfileActive = true - StartButton:SetText(GProfiler.Language.GetPhrase("profiler_stop")) - end - end - end - - local TimeRunning = vgui.Create("DLabel", Header) - TimeRunning:SetFont("GProfiler.Menu.SectionHeader") - TimeRunning:SetText(GProfiler.TimeRunning(GProfiler.ConCommands.StartTime, GProfiler.ConCommands.EndTime, GProfiler.ConCommands.ProfileActive) .. "s") - TimeRunning:SizeToContents() - TimeRunning:SetPos(Header:GetWide() - TimeRunning:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - TimeRunning:GetTall() / 2) - TimeRunning:SetTextColor(MenuColors.White) - function TimeRunning:Think() - if GProfiler.ConCommands.ProfileActive then - self:SetText(GProfiler.TimeRunning(GProfiler.ConCommands.StartTime, 0, GProfiler.ConCommands.ProfileActive) .. "s") - self:SizeToContents() - self:SetPos(Header:GetWide() - self:GetWide() - StartButton:GetWide() - RealmSelector:GetWide() - TabPadding * 3, Header:GetTall() / 2 - self:GetTall() / 2) - end - end - - local SectionHeader = vgui.Create("DPanel", Content) - SectionHeader:SetSize(Content:GetWide(), 40) - SectionHeader:SetPos(0, Header:GetTall()) - SectionHeader.Paint = nil - - local leftFraction = .7 - local rightFraction = .3 - - local LeftHeader, LeftHeaderText = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("profiler_results"), 0, 0, SectionHeader:GetWide() * leftFraction - 5, SectionHeader:GetTall()) - local RightHeader, RightHeaderText = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("command_function"), LeftHeader:GetWide() + 10, 0, SectionHeader:GetWide() * rightFraction - 5, LeftHeader:GetTall()) - - local LeftContent = vgui.Create("DPanel", Content) - LeftContent:SetSize(LeftHeader:GetWide(), Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - LeftContent:SetPos(0, SectionHeader:GetTall() + Header:GetTall()) - LeftContent.Paint = nil - - local RightContent = vgui.Create("DPanel", Content) - RightContent:SetSize(RightHeader:GetWide(), Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - RightContent:SetPos(LeftContent:GetWide() + 10, SectionHeader:GetTall() + Header:GetTall()) - RightContent.Paint = nil - local FunctionDetailsBackground = vgui.Create("DPanel", RightContent) - FunctionDetailsBackground:SetSize(RightContent:GetWide() - TabPadding * 2, RightContent:GetTall() - TabPadding * 2) - FunctionDetailsBackground:SetPos(TabPadding, TabPadding) - FunctionDetailsBackground.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, MenuColors.CodeBackground) end - - local FunctionDetails = vgui.Create("DTextEntry", FunctionDetailsBackground) - FunctionDetails:Dock(FILL) - FunctionDetails:SetMultiline(true) - FunctionDetails:SetKeyboardInputEnabled(false) - FunctionDetails:SetVerticalScrollbarEnabled(true) - FunctionDetails:SetDrawBackground(false) - FunctionDetails:SetTextColor(MenuColors.White) - FunctionDetails:SetFont("GProfiler.Menu.FunctionDetails") - FunctionDetails:SetText(GProfiler.Language.GetPhrase("command_select")) - - local ProfilerResults = vgui.Create("DListView", LeftContent) - ProfilerResults:SetSize(LeftContent:GetWide() - TabPadding * 2, (LeftContent:GetTall() - TabPadding * 2) / 2) - ProfilerResults:SetPos(TabPadding, TabPadding) - ProfilerResults:SetMultiSelect(false) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("command")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("file")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("times_run")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("total_time")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("average_time")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("longest_time")) - - local Wide = ProfilerResults:GetWide() - ProfilerResults.Columns[1]:SetWidth(Wide * .20) - ProfilerResults.Columns[2]:SetWidth(Wide * .24) - ProfilerResults.Columns[3]:SetWidth(Wide * .14) - ProfilerResults.Columns[4]:SetWidth(Wide * .16) - ProfilerResults.Columns[5]:SetWidth(Wide * .16) - ProfilerResults.Columns[6]:SetWidth(Wide * .16) - - for k, v in pairs(GProfiler.ConCommands.ProfileData or {}) do - local Line = ProfilerResults:AddLine(k, v.Source, v.Count, v.Time, v.AverageTime, v.LongestTime) - Line.OnMousePressed = function(s, l) - if l == 108 then - local menu = DermaMenu() - menu:AddOption(GProfiler.CopyLang("command"), function() SetClipboardText(k) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("file"), function() SetClipboardText(v.Function) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("times_run"), function() SetClipboardText(v.Count) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("total_time"), function() SetClipboardText(v.Time) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("average_time"), function() SetClipboardText(v.AverageTime) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("longest_time"), function() SetClipboardText(v.LongestTime) end):SetIcon("icon16/page_copy.png") - menu:Open() - end - - for k, v in pairs(ProfilerResults.Lines) do v:SetSelected(false) end - Line:SetSelected(true) - - FunctionDetails:SetText(GProfiler.Language.GetPhrase("requesting_source")) - GProfiler.RequestFunctionSource(v.Source, tonumber(v.Lines[1]), tonumber(v.Lines[2]), function(source) - if not IsValid(FunctionDetails) then return end - FunctionDetails:SetText(table.concat(source, "\n")) - end) - end - end - - local CommandList = vgui.Create("DListView", LeftContent) - CommandList:SetSize(LeftContent:GetWide() - TabPadding * 2, (LeftContent:GetTall() - TabPadding * 2) / 2 - 10) - CommandList:SetPos(TabPadding, TabPadding + ProfilerResults:GetTall() + TabPadding) - CommandList:SetMultiSelect(false) - CommandList:AddColumn(GProfiler.Language.GetPhrase("command")) - CommandList:AddColumn(GProfiler.Language.GetPhrase("file")) - - GetCommandList(GProfiler.ConCommands.Realm, function(list) - if not IsValid(CommandList) then return end - CommandList:Clear() - - for k, v in pairs(list) do - local Line = CommandList:AddLine(k, v.Source) - Line.OnMousePressed = function(s, l) - if l == 108 then - local menu = DermaMenu() - menu:AddOption(GProfiler.CopyLang("command"), function() SetClipboardText(k) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("file"), function() SetClipboardText(v.Source) end):SetIcon("icon16/page_copy.png") - menu:Open() - end - - for k, v in pairs(CommandList.Lines) do v:SetSelected(false) end - Line:SetSelected(true) - - FunctionDetails:SetText(GProfiler.Language.GetPhrase("requesting_source")) - GProfiler.RequestFunctionSource(v.Source, tonumber(v.Lines[1]), tonumber(v.Lines[2]), function(source) - if not IsValid(FunctionDetails) then return end - FunctionDetails:SetText(table.concat(source, "\n")) - end) - end - end - - GProfiler.StyleDListView(CommandList) - end) - - GProfiler.StyleDListView(ProfilerResults) - GProfiler.StyleDListView(CommandList) end -GProfiler.Menu.RegisterTab("Commands", "icon16/application_xp_terminal.png", 4, GProfiler.ConCommands.DoTab, function() - if GProfiler.ConCommands.ProfileActive then - return GProfiler.TimeRunning(GProfiler.ConCommands.StartTime, 0, GProfiler.ConCommands.ProfileActive) .. "s", MenuColors.ActiveProfile - elseif GProfiler.ConCommands.Override then - return GProfiler.ConCommands.Override, MenuColors.InactiveProfile - end -end) - -net.Receive("GProfiler_ConCommands_ServerProfileStatus", function() - local status = net.ReadBool() - local ply = net.ReadEntity() - GProfiler.ConCommands.ProfileActive = status +GProfiler.Menu.RegisterTab("Commands", "gprofiler/commands.png", 4, GProfiler.ConCommands.DoTab, function() - if ply == LocalPlayer() then - GProfiler.Menu.OpenTab("Commands", GProfiler.ConCommands.DoTab) - end end) - -net.Receive("GProfiler_ConCommands_SendData", function() - local data = {} - for i = 1, net.ReadUInt(32) do - local cmd = net.ReadString() - data[cmd] = { - Count = net.ReadUInt(32), - Time = net.ReadFloat(), - AverageTime = net.ReadFloat(), - LongestTime = net.ReadFloat(), - Source = net.ReadString(), - Lines = {net.ReadUInt(16), net.ReadUInt(16)} - } - end - - GProfiler.ConCommands.ProfileData = data - GProfiler.Menu.OpenTab("Commands", GProfiler.ConCommands.DoTab) -end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/database/cl_database.lua b/lua/gprofiler/profilers/database/cl_database.lua index 439c473..6f9b13e 100644 --- a/lua/gprofiler/profilers/database/cl_database.lua +++ b/lua/gprofiler/profilers/database/cl_database.lua @@ -1,749 +1,9 @@ GProfiler.Database = GProfiler.Database or {} -GProfiler.Database.ProfileActive = GProfiler.Database.ProfileActive or false -GProfiler.Database.StartTime = GProfiler.Database.StartTime or 0 -GProfiler.Database.EndTime = GProfiler.Database.EndTime or 0 -GProfiler.Database.ProfileActive = GProfiler.Database.ProfileActive or false - -local TabPadding = 10 -local MenuColors = GProfiler.MenuColors - -local function QueryTimeScore(query, queryTime) - if queryTime > 0.5 then - return Color(238, 95, 91, 255) - elseif queryTime > 0.1 then - return Color(250, 167, 50, 255) - end - return Color(94, 185, 94, 255) -end - - -local function CreateTimelinePanel(parent, w, h, data, currentQuery) - local minWidth = 10 - local TimelinePanel = vgui.Create("DPanel", parent) - TimelinePanel:SetSize(w, h) - TimelinePanel.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.OpaqueBlack2) - - if s.ShowData then -- hack: reduces freezes, don't question what works - s:ShowData() - end - end - - function TimelinePanel:ShowData() - TimelinePanel.ShowData = nil - - local totalTime = 0 - for _, entry in ipairs(data) do - totalTime = totalTime + entry.Time - end - - local startX = 0 - local SpaceBetweenEntries = 1 - local allocatedWidth = 0 - local widths = {} - - for _, entry in ipairs(data) do - local entryWidth = (w * entry.Time / totalTime) - if entryWidth < minWidth then entryWidth = minWidth end - table.insert(widths, entryWidth) - allocatedWidth = allocatedWidth + entryWidth - end - - local scale = w / allocatedWidth - - for k, entry in ipairs(data) do - local entryWidth = widths[k] * scale - SpaceBetweenEntries - if entryWidth < 1 then entryWidth = 1 end - local entryColor = QueryTimeScore(entry.Query, entry.Time) - if entry.Query != currentQuery then entryColor.a = 25 end - - local entryPanel = vgui.Create("DPanel", TimelinePanel) - if k == #data then entryPanel:SetSize(w - startX, h) - else entryPanel:SetSize(entryWidth, h) end - entryPanel:SetPos(startX, 0) - local HoverColor = table.Copy(entryColor) - entryPanel.Lerp = 0 - entryPanel.Paint = function(s, w, h) - draw.RoundedBox(2, 0, 1, w - 2, h - 2, entryColor) - - if s:IsHovered() then - s.Lerp = Lerp(FrameTime() * 10, s.Lerp, 1) - else - s.Lerp = Lerp(FrameTime() * 10, s.Lerp, 0) - end - - if s.Lerp > 0 then - HoverColor.a = (230 * s.Lerp) - draw.RoundedBox(2, 0, 1, w - 2, h - 2, HoverColor) - end - end - entryPanel:SetCursor("hand") - entryPanel.OnMousePressed = function() - print("Clicked on entry:", entry.QueryId) - GProfiler.Database.SelectedQuery = entry.QueryId - end - - startX = startX + entryPanel:GetWide() + SpaceBetweenEntries - end - end - - return TimelinePanel -end - function GProfiler.Database.DoTab(Content) - local Header = vgui.Create("EditablePanel", Content) - Header:SetSize(Content:GetWide(), 40) - Header:SetMouseInputEnabled(true) - Header:SetPos(0, 10) - Header.Paint = nil - - local StartButton = vgui.Create("DButton", Header) - StartButton:SetText(GProfiler.Database.ProfileActive and GProfiler.Language.GetPhrase("profiler_stop") or GProfiler.Language.GetPhrase("profiler_start")) - StartButton:SetTextColor(MenuColors.White) - StartButton:SetFont("GProfiler.Menu.StartButton") - StartButton:SizeToContents() - StartButton:SetTall(Header:GetTall() - 6) - StartButton:SetPos(Header:GetWide() - StartButton:GetWide() - TabPadding * 2, Header:GetTall() / 2 - StartButton:GetTall() / 2) - StartButton.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - - StartButton.DoClick = function() - if GProfiler.Database.ProfileActive then - GProfiler.Database.EndTime = SysTime() - GProfiler.Database.Override = GProfiler.TimeRunning(GProfiler.Database.StartTime, SysTime(), GProfiler.Database.ProfileActive) .. "s" - - net.Start("GProfiler_Database_ToggleServerProfile") - net.WriteBool(false) - net.SendToServer() - else - GProfiler.Database.StartTime = SysTime() - GProfiler.Database.EndTime = 0 - GProfiler.Database.Override = nil - net.Start("GProfiler_Database_ToggleServerProfile") - net.WriteBool(true) - net.SendToServer() - end - end - - local TimeRunning = vgui.Create("DLabel", Header) - TimeRunning:SetFont("GProfiler.Menu.SectionHeader") - TimeRunning:SetText(GProfiler.TimeRunning(GProfiler.Database.StartTime, GProfiler.Database.EndTime, GProfiler.Database.ProfileActive) .. "s") - TimeRunning:SizeToContents() - TimeRunning:SetPos(Header:GetWide() - TimeRunning:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - TimeRunning:GetTall() / 2) - TimeRunning:SetTextColor(MenuColors.White) - function TimeRunning:Think() - if GProfiler.Database.ProfileActive then - self:SetText(GProfiler.TimeRunning(GProfiler.Database.StartTime, 0, GProfiler.Database.ProfileActive) .. "s") - self:SizeToContents() - self:SetPos(Header:GetWide() - self:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - self:GetTall() / 2) - end - end - - local ReceivingData = vgui.Create("DLabel", Header) - ReceivingData:SetFont("GProfiler.Menu.SectionHeader") - ReceivingData:SetText("Receiving data... ") - ReceivingData:SizeToContents() - ReceivingData:SetPos(Header:GetWide() - ReceivingData:GetWide() - StartButton:GetWide() - TimeRunning:GetWide() - TabPadding * 3, Header:GetTall() / 2 - ReceivingData:GetTall() / 2) - ReceivingData:SetTextColor(Color(225, 66, 66)) - function ReceivingData:Think() - if GProfiler.Database.ReceivingData then - self:SetVisible(true) - else - self:SetVisible(false) - end - end - - local ContentArea = vgui.Create("DPanel", Content) - ContentArea:SetSize(Content:GetWide() - 10, Content:GetTall() - Header:GetTall() - 20) - ContentArea:SetPos(5, Header:GetTall() + 15) - ContentArea.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.DListBackground) - end - - local List = vgui.Create("DPanelList", ContentArea) - List:SetSize(ContentArea:GetWide(), ContentArea:GetTall()) - List:SetPos(0, 0) - List:SetSpacing(5) - List:EnableHorizontal(false) - List:EnableVerticalScrollbar(true) - - local ScrollBar = List.VBar - ScrollBar:SetWide(10) - ScrollBar:SetHideButtons(true) - ScrollBar.Paint = function(s, w, h) draw.RoundedBox(4, 2, 0, w - 2, h, MenuColors.ScrollBar) end - ScrollBar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(4, 2, 0, w - 2, h, MenuColors.ScrollBarGrip) end - - if not GProfiler.Database.ProfileData or table.Count(GProfiler.Database.ProfileData) == 0 then - local NoData = vgui.Create("DLabel", ContentArea) - NoData:SetText(GProfiler.Language.GetPhrase("profiler_no_data")) - NoData:SetFont("GProfiler.Menu.SectionHeader") - NoData:SizeToContents() - NoData:SetPos(ContentArea:GetWide() / 2 - NoData:GetWide() / 2, ContentArea:GetTall() / 2 - NoData:GetTall() / 2) - NoData:SetTextColor(MenuColors.White) - return - end - - local Data = GProfiler.Database.ProfileData - local Explains = GProfiler.Database.Explains or {} - - local TimelineData = {} - for k, queryData in pairs(Data) do - local query = queryData.Query - local time = queryData.AverageTime or 0 - local color = QueryTimeScore(query, time) - table.insert(TimelineData, {Query = query, Time = time, Color = color, QueryId = k}) - end - - local function CreateRow(List, id, queryData) - local Row = vgui.Create("DPanel", List) - Row:SetSize(List:GetWide() --[[/ 2]] - 10, 30) - Row.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.DListRowBackground) - end - Row.Think = function(s) - if GProfiler.Database.SelectedQuery == s.QueryId then - GProfiler.Database.SelectedQuery = nil - List:ScrollToChild(s) - end - end - Row.QueryId = id - - surface.SetFont("GProfiler.Menu.RowText") - local Label = vgui.Create("DLabel", Row) - Label:SetText(string.format("%d. Query Time:", id)) - Label:SetFont("GProfiler.Menu.RowText") - Label:SizeToContents() - Label:SetPos(10, 10) - - local time = string.format("%.2fms", queryData.AverageTime * 1000) - local badgeW, badgeH = surface.GetTextSize(time) - local TimeBadge = vgui.Create("DPanel", Row) - TimeBadge:SetSize(badgeW + 10, badgeH + 4) - TimeBadge:SetPos(10 + Label:GetWide() + 5, 10) - local ScoreCol = QueryTimeScore(queryData.Query, queryData.AverageTime) - TimeBadge.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, ScoreCol) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack2) - draw.SimpleText(time, "GProfiler.Menu.RowText", w / 2, h / 2, MenuColors.White, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) - end - - local TimesRun = vgui.Create("DLabel", Row) - TimesRun:SetText(string.format("x%d", queryData.Count)) - TimesRun:SetFont("GProfiler.Menu.RowText") - TimesRun:SizeToContents() - TimesRun:SetSize(TimesRun:GetWide() + 10, TimesRun:GetTall() + 4) - TimesRun:SetPos(Row:GetWide() - TimesRun:GetWide() - 10, 10) - TimesRun:SetTextColor(color_white) - TimesRun:SetContentAlignment(5) - TimesRun.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.OpaqueBlack2) - end - - local TypeBadge = vgui.Create("DLabel", Row) - TypeBadge:SetText(queryData.Type or "Unknown") - TypeBadge:SetFont("GProfiler.Menu.RowText") - TypeBadge:SizeToContents() - TypeBadge:SetSize(TypeBadge:GetWide() + 10, TypeBadge:GetTall() + 4) - TypeBadge:SetPos(Row:GetWide() - TimesRun:GetWide() - TypeBadge:GetWide() - 20, 10) - TypeBadge:SetTextColor(MenuColors.White) - TypeBadge:SetContentAlignment(5) - TypeBadge.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.OpaqueBlack2) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack) - end - - local CopyQuery = vgui.Create("DButton", Row) - CopyQuery:SetText(GProfiler.Language.GetPhrase("profiler_copy_query")) - CopyQuery:SetFont("GProfiler.Menu.RowText") - CopyQuery:SizeToContents() - CopyQuery:SetSize(CopyQuery:GetWide() + 10, TimesRun:GetTall()) - CopyQuery:SetPos(Row:GetWide() - TimesRun:GetWide() - TypeBadge:GetWide() - CopyQuery:GetWide() - 30, 10) - CopyQuery:SetTextColor(MenuColors.White) - CopyQuery.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.OpaqueBlack2) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - CopyQuery.DoClick = function() - SetClipboardText(queryData.Query) - end - - Row:SetTall(TimeBadge:GetTall() + 20) - - local QueryParser = GProfiler.Database.QueryParser.New() - local template = QueryParser:ParseTemplate(queryData.Query) - local templateString = "" - for _, v in ipairs(template) do - if v[1] == "\n" then - templateString = templateString .. "\n" - else - templateString = templateString .. "" .. v[1] - end - end - - local function GetRealTextHeight(text, font, maxW) - surface.SetFont(font) - local lines = string.Explode("\n", text) - local totalHeight = 0 - for _, line in ipairs(lines) do - local lineWidth, lineHeight = surface.GetTextSize(line) - if lineWidth > maxW then - local words = string.Explode(" ", line) - local currentLine = "" - for _, word in ipairs(words) do - if surface.GetTextSize(currentLine .. " " .. word) <= maxW then - currentLine = currentLine .. " " .. word - else - totalHeight = totalHeight + lineHeight - currentLine = word - if surface.GetTextSize(currentLine) > maxW*2 then - totalHeight = totalHeight + lineHeight - end - end - end - totalHeight = totalHeight + lineHeight - else - totalHeight = totalHeight + lineHeight - end - end - - return totalHeight - end - - local queryH = GetRealTextHeight(templateString, "GProfiler.Menu.RowText", Row:GetWide() - 50) - local TextArea = vgui.Create("DTextEntry", Row) - TextArea:SetPos(10, 15 + TimeBadge:GetTall()) - TextArea:SetText(queryData.Query) - TextArea:SetFont("GProfiler.Menu.RowText") - TextArea:SetMultiline(true) - TextArea:SetEditable(false) - TextArea:SetSize(Row:GetWide() - 20, queryH + 10) - TextArea:SetMouseInputEnabled(false) - TextArea:SizeToContentsY() - TextArea:SetPaintBackground(false) - TextArea:SetTextColor(Color(255, 255, 255, 255)) - TextArea.Paint = function(s, w, h) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack) - draw.SimpleText(s:GetText(), "GProfiler.Menu.RowText", 5, 5, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) - end - - local TextAreaCover = vgui.Create("DPanel", Row) - TextAreaCover:SetPos(10, 15 + TimeBadge:GetTall()) - TextAreaCover:SetSize(TextArea:GetWide(), TextArea:GetTall()) - TextAreaCover.Paint = nil - local fHeight = draw.GetFontHeight("GProfiler.Menu.RowText") - local function CreateTemplate() - if TextAreaCover.TemplateCreated then return end - TextAreaCover.TemplateCreated = true - TextArea.Paint = function(s, w, h) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack) - end - - local xoff, yoff = 5, 5 - local maxw = TextArea:GetWide() - 20 - for _, v in ipairs(template) do - if v[1] == "\n" and v[2] == 12 then - xoff = 5 - yoff = yoff + fHeight - continue - end - - local cat = GProfiler.Database.TokenCategories[v[2]] or { name = "Unknown", description = "Unknown category" } - local label = vgui.Create("DLabel", TextAreaCover) - label:SetText(v[2] ~= 12 and ("" .. v[1] .. "") or " ") - label:SetFont("GProfiler.Menu.RowText") - label:SizeToContents() - label:SetPos(xoff, yoff) - if v[2] == 1 and not GProfiler.Database.MySQLKeywords[v[1]] then v[1] = v[1]:upper() end - label:SetToolTip((v[2] == 1 and (v[1] .. " - " .. (GProfiler.Database.MySQLKeywords[v[1]])) or "Token") .. (cat and ("\n" .. (cat.name or "?") .. ": " .. (cat.description or "?")) or "")) - label:SetMouseInputEnabled(true) - label:SetTextColor(color_white) - label.hoverlerp = 0 - label.Paint = function(self, w, h) - if self:IsHovered() then - self.hoverlerp = Lerp(FrameTime() * 10, self.hoverlerp, 1) - else - self.hoverlerp = Lerp(FrameTime() * 10, self.hoverlerp, 0) - end - - if self.hoverlerp > 0 then - draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 10 * self.hoverlerp)) - end - end - - local lw = label:GetWide() - xoff = xoff + lw - if xoff + lw > maxw then - xoff = 5 - yoff = yoff + fHeight - end - end - end - TextAreaCover.OnCursorEntered = function() CreateTemplate() end - if string.find(queryData.Query, "\n") then CreateTemplate() end - if queryH > draw.GetFontHeight("GProfiler.Menu.RowText") then CreateTemplate() end - - Row:SetTall(Row:GetTall() + TextArea:GetTall() + 10) - - local TimelinePanel = CreateTimelinePanel(Row, Row:GetWide() - 20, 15, TimelineData, queryData.Query) - TimelinePanel:SetPos(10, 15 + TimeBadge:GetTall() + TextArea:GetTall() + 5) - - Row:SetTall(Row:GetTall() + TimelinePanel:GetTall() + 5) - - local Collapses = {} - - local function CreateCollapse(Name, Id, RighText) - local Collapse = vgui.Create("DCollapsibleCategory", Row) - Collapse:SetSize(Row:GetWide() - 20, fHeight * 2) - Collapse:SetPos(10, 25 + TimeBadge:GetTall() + TextArea:GetTall() + TimelinePanel:GetTall() + (Id - 1) * (fHeight * 2 + 5)) - Collapse:SetLabel(GProfiler.Language.GetPhrase(Name)) - Collapse:SetExpanded(false) - Collapse:SetAnimTime(0) - Collapse.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.DListColumnBackground) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack2) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack2) - end - - if RighText then - draw.SimpleText(RighText, "GProfiler.Menu.RowText", w - 10, (fHeight * 2) / 2, Color(255, 255, 255, 200), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) - end - end - Collapse.Header:SetFont("GProfiler.Menu.RowText") - Collapse.Header:SetTall(fHeight * 2) - - Row:SetTall(Row:GetTall() + Collapse.Header:GetTall() + 2) - local BaseHeight = Row:GetTall() - local CollapsePanel = vgui.Create("DPanel", Collapse) - CollapsePanel:SetSize(Collapse:GetWide(), Collapse:GetTall()) - CollapsePanel.Paint = function(s, w, h) - draw.RoundedBoxEx(4, 2, 2, w - 4, h - 4, MenuColors.OpaqueBlack, false, false, true, true) - end - CollapsePanel:SetTall(0) - Collapse:SetContents(CollapsePanel) - Collapse.OnToggle = function(self, expanded) - if expanded then - Row:SetTall(Row:GetTall() + CollapsePanel:GetTall()) - if Id == 1 then - Collapses[2]:SetPos(10, Collapses[2]:GetY() + CollapsePanel:GetTall()) - Collapses[3]:SetPos(10, Collapses[3]:GetY() + CollapsePanel:GetTall()) - elseif Id == 2 then - Collapses[3]:SetPos(10, Collapses[3]:GetY() + CollapsePanel:GetTall()) - end - else - Row:SetTall(Row:GetTall() - CollapsePanel:GetTall()) - if Id == 1 then - Collapses[2]:SetPos(10, Collapses[2]:GetY() - CollapsePanel:GetTall()) - Collapses[3]:SetPos(10, Collapses[3]:GetY() - CollapsePanel:GetTall()) - elseif Id == 2 then - Collapses[3]:SetPos(10, Collapses[3]:GetY() - CollapsePanel:GetTall()) - end - end - - if CollapsePanel.OnToggle then - CollapsePanel:OnToggle(expanded) - end - end - - table.insert(Collapses, Collapse) - - return Collapse, CollapsePanel - end - - local ExplainCollapse, ExplainPanel = CreateCollapse("Explain", 1) - local InternalCollapse, InternalPanel = CreateCollapse("Profile", 2) - local SourceCollapse, SourcePanel = CreateCollapse("Source", 3, string.format("%s (%s - %s)", queryData.Source.File, queryData.Source.Line1, queryData.Source.Line2)) - - if not Explains[id] or (Explains[id])["noExplain"] then - local NoExplain = vgui.Create("DLabel", ExplainPanel) - NoExplain:SetText(GProfiler.Language.GetPhrase("profiler_no_explain")) - NoExplain:SetFont("GProfiler.Menu.RowText") - NoExplain:SizeToContents() - NoExplain:SetSize(NoExplain:GetWide() + 10, NoExplain:GetTall() + 20) - NoExplain:SetContentAlignment(5) - NoExplain:SetTextColor(Color(255, 255, 255, 200)) - else - local Columns = {"Select Type", "Table", "Type", "Possible Keys", "Key", "Key Len", "Ref", "Rows", "Extra"} - local HeaderPanel = vgui.Create("DPanel", ExplainPanel) - HeaderPanel:SetSize(ExplainPanel:GetWide(), fHeight * 2) - HeaderPanel.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.DListColumnBackground) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack2) - end - - for i, col in ipairs(Columns) do - local HeaderLabel = vgui.Create("DLabel", HeaderPanel) - HeaderLabel:SetText(col) - HeaderLabel:SetFont("GProfiler.Menu.RowText") - HeaderLabel:SizeToContents() - HeaderLabel:SetPos((i - 1) * (HeaderPanel:GetWide() / #Columns) + 5, fHeight / 2 - HeaderLabel:GetTall() / 2 + 8) - HeaderLabel:SetTextColor(MenuColors.White) - HeaderLabel:SetContentAlignment(5) - end - - for i, explain in ipairs(Explains[id] or {}) do - local ExplainRow = vgui.Create("DPanel", ExplainPanel) - ExplainRow:SetSize(ExplainPanel:GetWide(), fHeight * 2) - ExplainRow:SetPos(0, (i) * (fHeight * 2)) - ExplainRow.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.DListColumnBackground) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack) - end - - for j, col in ipairs(Columns) do - local Value = explain[col:lower():gsub(" ", "_")] or "N/A" - local ValueLabel = vgui.Create("DLabel", ExplainRow) - ValueLabel:SetToolTip(Value) - if j < #Columns then - local maxW = (ExplainRow:GetWide() / #Columns) - 10 - if surface.GetTextSize(Value) > maxW then - Value = string.sub(Value, 1, math.floor(maxW / surface.GetTextSize("a") * 0.8)) .. "..." - end - elseif j == #Columns then - local maxW = (ExplainRow:GetWide() / #Columns) - 10 - if surface.GetTextSize(Value) > maxW then - Value = string.sub(Value, 1, math.floor(maxW / surface.GetTextSize("a"))) .. "..." - end - end - ValueLabel:SetText(Value) - ValueLabel:SetFont("GProfiler.Menu.RowText") - ValueLabel:SizeToContents() - ValueLabel:SetPos((j - 1) * (ExplainRow:GetWide() / #Columns) + 5, fHeight / 2 - ValueLabel:GetTall() / 2 + 8) - ValueLabel:SetTextColor(MenuColors.White) - ValueLabel:SetContentAlignment(5) - ValueLabel:SetMouseInputEnabled(true) - end - - ExplainPanel:Add(ExplainRow) - end - end - - local Columns = {"Status", "Duration", "Percentage"} - local InternalHeaderPanel = vgui.Create("DPanel", InternalPanel) - InternalHeaderPanel:SetSize(InternalPanel:GetWide(), fHeight * 2) - InternalHeaderPanel.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.DListColumnBackground) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack2) - end - - local colWidth = (InternalHeaderPanel:GetWide() - 10) / #Columns - for i, col in ipairs(Columns) do - local HeaderLabel = vgui.Create("DLabel", InternalHeaderPanel) - HeaderLabel:SetText(col) - HeaderLabel:SetFont("GProfiler.Menu.RowText") - HeaderLabel:SizeToContents() - HeaderLabel:SetPos((i - 1) * colWidth + 5, fHeight / 2 - HeaderLabel:GetTall() / 2 + 8) - HeaderLabel:SetTextColor(MenuColors.White) - HeaderLabel:SetContentAlignment(5) - end - - local internalData = queryData.InternalData - if internalData then - for i, data in ipairs(internalData) do - local InternalRow = vgui.Create("DPanel", InternalPanel) - InternalRow:SetSize(InternalPanel:GetWide(), fHeight * 2) - InternalRow:SetPos(0, (i) * (fHeight * 2)) - InternalRow.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.DListColumnBackground) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.OpaqueBlack2) - end - - if s.PaintColor then - draw.RoundedBox(100, w - h - 8, h / 4, h / 2, h / 2, s.PaintColor) - end - end - - for j, col in ipairs(Columns) do - local Value = data[col] - if col == "Percentage" then - local totalTime = queryData.AverageTime or 0 - Value = totalTime > 0 and (data.Duration or 0) / totalTime or 0 - end - local ValueLabel = vgui.Create("DLabel", InternalRow) - ValueLabel:SetToolTip(Value) - if j < #Columns then - local maxW = (InternalRow:GetWide() / #Columns) - 10 - if surface.GetTextSize(Value) > maxW then - Value = string.sub(Value, 1, math.floor(maxW / surface.GetTextSize("a") * 0.8)) .. "..." - end - end - ValueLabel:SetText(j == 1 and (Value or "N/A") or j == 2 and string.format("%.3fms", (Value or 0) * 1000) or (string.format("%.2f%%", (Value or 0) * 100))) - ValueLabel:SetFont("GProfiler.Menu.RowText") - ValueLabel:SizeToContents() - ValueLabel:SetPos((j - 1) * (InternalRow:GetWide() / #Columns) + 5, fHeight / 2 - ValueLabel:GetTall() / 2 + 8) - ValueLabel:SetTextColor(MenuColors.White) - ValueLabel:SetContentAlignment(5) - ValueLabel:SetMouseInputEnabled(true) - - if j == 2 then - local duration = (data.Duration or 0) * 1000 - local durationColor = duration > 1000 and Color(255, 0, 0) or (duration > 500 and Color(255, 165, 0) or Color(0, 255, 0)) - InternalRow.PaintColor = durationColor - end - end - InternalPanel:Add(InternalRow) - end - else - local NoProfile = vgui.Create("DLabel", InternalPanel) - NoProfile:SetText(GProfiler.Language.GetPhrase("profiler_no_profile")) - NoProfile:SetFont("GProfiler.Menu.RowText") - NoProfile:SizeToContents() - NoProfile:SetSize(NoProfile:GetWide() + 10, NoProfile:GetTall() + 20) - NoProfile:SetContentAlignment(5) - NoProfile:SetTextColor(Color(255, 255, 255, 200)) - InternalPanel:Add(NoProfile) - InternalHeaderPanel:SetVisible(false) - end - - local SourceText = vgui.Create("DTextEntry", SourcePanel) - SourceText:SetMultiline(true) - SourceText:SetKeyboardInputEnabled(false) - SourceText:SetVerticalScrollbarEnabled(true) - SourceText:SetDrawBackground(false) - SourceText:SetTextColor(MenuColors.White) - SourceText:SetFont("GProfiler.Menu.FunctionDetails") - SourceText:SetText(GProfiler.Language.GetPhrase("requesting_source")) - SourceText:SizeToContentsY() - SourceText:SetSize(SourcePanel:GetWide() - 20, SourceText:GetTall() + 6) - SourceText:SetPos(5, 6) - SourcePanel:Add(SourceText) - - local RequestedSource = false - function SourcePanel:OnToggle(expanded) - if RequestedSource then return end - RequestedSource = true - - GProfiler.RequestFunctionSource(queryData.Source.File, queryData.Source.Line1, queryData.Source.Line2, function(source) - if not IsValid(SourceText) then return end - SourceText:SetText(table.concat(source, "\n")) - local lines = string.Explode("\n", SourceText:GetText()) - local lineHeight = draw.GetFontHeight("GProfiler.Menu.FunctionDetails") - SourceText:SetTall(math.min(#lines * lineHeight + 10, 400)) - SourceText:SetSize(SourcePanel:GetWide() - 20, SourceText:GetTall() + 6) - SourceCollapse:Toggle() - SourceCollapse:Toggle() - end) - end - - return Row - end - - for id, queryData in ipairs(Data) do - local Row = CreateRow(List, id, queryData) - List:AddItem(Row) - end end -GProfiler.Menu.RegisterTab("Database", "icon16/database_connect.png", 8, GProfiler.Database.DoTab, function() - if GProfiler.Database.ProfileActive then - return GProfiler.TimeRunning(GProfiler.Database.StartTime, 0, GProfiler.Database.ProfileActive) .. "s", MenuColors.ActiveProfile - elseif GProfiler.Database.Override then - return GProfiler.Database.Override, MenuColors.InactiveProfile - end -end) - -net.Receive("GProfiler_Database_ServerProfileStatus", function() - local status = net.ReadBool() - local ply = net.ReadEntity() - GProfiler.Database.ProfileActive = status - - if ply == LocalPlayer() then - GProfiler.Menu.OpenTab("Database", GProfiler.Database.DoTab) - end -end) - -local TypeLookup = { - [1] = "mysqloo", - [2] = "tmysql4", - [3] = "goobie_mysql", - [4] = "sqlite" -} - -net.Receive("GProfiler_Database_SendData", function() - local isFirstChunk = net.ReadBool() - local isLastChunk = net.ReadBool() - - if isFirstChunk then - GProfiler.Database.ReceivingData = true - GProfiler.Database.ProfileData = {} - GProfiler.Database.Explains = {} - end - - local count = net.ReadUInt(14) - for i = 1, count do - local id = net.ReadUInt(14) - local typeId = net.ReadUInt(3) - GProfiler.Database.ProfileData[id] = { - Type = TypeLookup[typeId] or "unknown", - Count = net.ReadUInt(14), - Time = net.ReadFloat(), - AverageTime = net.ReadFloat(), - LongestTime = net.ReadFloat(), - Query = net.ReadString(), - Source = { - File = net.ReadString(), - Line1 = net.ReadUInt(16), - Line2 = net.ReadUInt(16) - } - } - - local hasInt = net.ReadBool() - if hasInt then - local data = {} - local internalCount = net.ReadUInt(7) - for j = 1, internalCount do - data[j] = { - Duration = net.ReadFloat(), - Status = net.ReadString() - } - end - GProfiler.Database.ProfileData[id].InternalData = data - else - GProfiler.Database.ProfileData[id].InternalData = false - end - end - - count = net.ReadUInt(14) - for i = 1, count do - local id = net.ReadUInt(14) - if net.ReadBool() then - GProfiler.Database.Explains[id] = {noExplain = true} - else - local explainCount = net.ReadUInt(6) - GProfiler.Database.Explains[id] = {} - for j = 1, explainCount do - GProfiler.Database.Explains[id][j] = { - select_type = net.ReadString(), - table = net.ReadString(), - type = net.ReadString(), - possible_keys = net.ReadString(), - key = net.ReadString(), - key_len = net.ReadString(), - ref = net.ReadString(), - rows = net.ReadUInt(32), - extra = net.ReadString() - } - end - end - end +GProfiler.Menu.RegisterTab("Database", "gprofiler/database.png", 8, GProfiler.Database.DoTab, function() - if isLastChunk then - GProfiler.Database.ReceivingData = false - GProfiler.Menu.OpenTab("Database", GProfiler.Database.DoTab) - end end) diff --git a/lua/gprofiler/profilers/entvars/cl_entvars.lua b/lua/gprofiler/profilers/entvars/cl_entvars.lua index 7c1ec46..687c56b 100644 --- a/lua/gprofiler/profilers/entvars/cl_entvars.lua +++ b/lua/gprofiler/profilers/entvars/cl_entvars.lua @@ -1,144 +1,9 @@ GProfiler.EntVars = GProfiler.EntVars or {} -GProfiler.EntVars.ProfileActive = GProfiler.EntVars.ProfileActive or false -GProfiler.EntVars.StartTime = GProfiler.EntVars.StartTime or 0 -GProfiler.EntVars.EndTime = GProfiler.EntVars.EndTime or 0 - -local TabPadding = 10 -local MenuColors = GProfiler.MenuColors function GProfiler.EntVars.DoTab(Content) - local Header = vgui.Create("DPanel", Content) - Header:SetSize(Content:GetWide(), 40) - Header:SetPos(0, 10) - Header.Paint = nil - - local StartButton = vgui.Create("DButton", Header) - StartButton:SetText(GProfiler.EntVars.ProfileActive and GProfiler.Language.GetPhrase("profiler_stop") or GProfiler.Language.GetPhrase("profiler_start")) - StartButton:SetTextColor(MenuColors.White) - StartButton:SetFont("GProfiler.Menu.StartButton") - StartButton:SizeToContents() - StartButton:SetTall(30) - StartButton:SetPos(Header:GetWide() - StartButton:GetWide() - TabPadding * 2, Header:GetTall() / 2 - StartButton:GetTall() / 2) - StartButton.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - - function StartButton:DoClick() - if GProfiler.EntVars.ProfileActive then - GProfiler.EntVars.ProfileActive = false - GProfiler.EntVars.EndTime = SysTime() - GProfiler.EntVars.Override = GProfiler.TimeRunning(GProfiler.EntVars.StartTime, SysTime(), GProfiler.EntVars.ProfileActive) .. "s" - GProfiler.Menu.OpenTab("Entity Variables", GProfiler.EntVars.DoTab) - self:SetText(GProfiler.Language.GetPhrase("profiler_start")) - else - GProfiler.EntVars.ProfileData = {} - GProfiler.EntVars.Override = nil - GProfiler.EntVars.ProfileActive = true - GProfiler.EntVars.StartTime = SysTime() - self:SetText(GProfiler.Language.GetPhrase("profiler_stop")) - end - end - - local TimeRunning = vgui.Create("DLabel", Header) - TimeRunning:SetFont("GProfiler.Menu.SectionHeader") - TimeRunning:SetText(GProfiler.TimeRunning(GProfiler.EntVars.StartTime, GProfiler.EntVars.EndTime, GProfiler.EntVars.ProfileActive) .. "s") - TimeRunning:SizeToContents() - TimeRunning:SetPos(Header:GetWide() - TimeRunning:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - TimeRunning:GetTall() / 2) - TimeRunning:SetTextColor(MenuColors.White) - function TimeRunning:Think() - if GProfiler.EntVars.ProfileActive then - self:SetText(GProfiler.TimeRunning(GProfiler.EntVars.StartTime, 0, GProfiler.EntVars.ProfileActive) .. "s") - self:SizeToContents() - self:SetPos(Header:GetWide() - self:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - self:GetTall() / 2) - end - end - - local SectionHeader = vgui.Create("DPanel", Content) - SectionHeader:SetSize(Content:GetWide(), 40) - SectionHeader:SetPos(0, Header:GetTall()) - SectionHeader.Paint = nil - - local Header, HeaderText = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("profiler_results"), 0, 0, SectionHeader:GetWide() - 5, SectionHeader:GetTall()) - - local ProfilerContent = vgui.Create("DPanel", Content) - ProfilerContent:SetSize(Content:GetWide() - 5, Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - ProfilerContent:SetPos(0, SectionHeader:GetTall() + Header:GetTall()) - ProfilerContent.Paint = nil - - local ProfilerResults = vgui.Create("DListView", ProfilerContent) - ProfilerResults:SetSize(ProfilerContent:GetWide() - TabPadding * 2, ProfilerContent:GetTall() - TabPadding * 2) - ProfilerResults:SetPos(TabPadding, TabPadding) - ProfilerResults:SetMultiSelect(false) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("entity")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("variable")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("times_updated")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("current_value")) - - for k, v in pairs(GProfiler.EntVars.ProfileData or {}) do - if not v.GProfiler_SavedEnt then continue end - for var, val in pairs(v) do - if var == "GProfiler_SavedEnt" or var == "GProfiler_CurrentValues" then continue end - local Line = ProfilerResults:AddLine(v.GProfiler_SavedEnt, var, val, v.GProfiler_CurrentValues[var] or "Unknown") - Line.OnRightClick = function() - local menu = DermaMenu() - menu:AddOption(GProfiler.CopyLang("entity"), function() SetClipboardText(v.GProfiler_SavedEnt) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("variable"), function() SetClipboardText(var) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("times_updated"), function() SetClipboardText(val) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("current_value"), function() SetClipboardText(v.GProfiler_CurrentValues[var] or "Unknown") end):SetIcon("icon16/page_copy.png") - menu:Open() - end - end - end - ProfilerResults:SortByColumn(3, true) - - GProfiler.StyleDListView(ProfilerResults) end -GProfiler.Menu.RegisterTab("Entity Variables", "icon16/database_edit.png", 6, GProfiler.EntVars.DoTab, function() - if GProfiler.EntVars.ProfileActive then - return GProfiler.TimeRunning(GProfiler.EntVars.StartTime, 0, GProfiler.EntVars.ProfileActive) .. "s", MenuColors.ActiveProfile - elseif GProfiler.EntVars.Override then - return GProfiler.EntVars.Override, MenuColors.InactiveProfile - end -end) - -function GProfiler.EntVars.CollectData(ent, var, _, val) - if not GProfiler.EntVars.ProfileActive then return end - - if not GProfiler.EntVars.ProfileData[ent] then - GProfiler.EntVars.ProfileData[ent] = { - GProfiler_SavedEnt = tostring(ent), - GProfiler_CurrentValues = {} - } - end - - GProfiler.EntVars.ProfileData[ent][var] = (GProfiler.EntVars.ProfileData[ent][var] or 0) + 1 - GProfiler.EntVars.ProfileData[ent].GProfiler_CurrentValues[var] = tostring(val) -end - -local function CaptureEnt(ent, attempts) - if not IsValid(ent) then return end - if not ent.GetNetworkVars then - if attempts and attempts > 5 then return end - timer.Simple(.5, function() CaptureEnt(ent, (attempts or 0) + 1) end) - return - end - - for k, v in pairs(ent:GetNetworkVars() or {}) do - local GProfilerIdent = string.format("GProfiler.%s", k) - if ent[GProfilerIdent] then continue end - ent[GProfilerIdent] = true - ent:NetworkVarNotify(k, GProfiler.EntVars.CollectData) - end -end +GProfiler.Menu.RegisterTab("Entity Variables", "gprofiler/entvars.png", 6, GProfiler.EntVars.DoTab, function() -hook.Add("OnEntityCreated", "GProfiler.EntVars.CaptureEnt", function(ent) timer.Simple(0, function() CaptureEnt(ent) end) end) -hook.Add("InitPostEntity", "GProfiler.EntVars.CaptureEnts", function() - for k, v in ipairs(ents.GetAll()) do CaptureEnt(v) end end) diff --git a/lua/gprofiler/profilers/functions/cl_functions.lua b/lua/gprofiler/profilers/functions/cl_functions.lua index fafcf2f..329d1a9 100644 --- a/lua/gprofiler/profilers/functions/cl_functions.lua +++ b/lua/gprofiler/profilers/functions/cl_functions.lua @@ -1,460 +1,9 @@ GProfiler.Functions = GProfiler.Functions or {} -local FunctionsProfiler = GProfiler.Functions -FunctionsProfiler.Realm = FunctionsProfiler.Realm or "Client" -FunctionsProfiler.ProfileActive = FunctionsProfiler.ProfileActive or false -FunctionsProfiler.StartTime = FunctionsProfiler.StartTime or 0 -FunctionsProfiler.EndTime = FunctionsProfiler.EndTime or 0 -FunctionsProfiler.ActiveFocus = FunctionsProfiler.ActiveFocus or {} -FunctionsProfiler.SourceFilter = FunctionsProfiler.SourceFilter or "" -local TabPadding = 10 -local MenuColors = GProfiler.MenuColors +function GProfiler.Functions.Tab(Content) -local function ValidateFocus(foc) - return string.StartWith(foc or "", "function: 0x") or string.StartWith(foc or "", "0x") end -local FocusColors = { - Valid = Color(0, 255, 0), - Invalid = Color(255, 0, 0) -} - -local function bytesToReadable(bytes) - if bytes < 1024 then - return string.format("%d B", bytes) - elseif bytes < 1048576 then - return string.format("%.2f KB", bytes / 1024) - else - return string.format("%.2f MB", bytes / 1048576) - end -end - -function FunctionsProfiler.DoTab(Content) - local Header = vgui.Create("DPanel", Content) - Header:SetSize(Content:GetWide(), 40) - Header:SetPos(0, 10) - Header.Paint = nil - - local RealmSelector = GProfiler.Menu.CreateRealmSelector(Header, "Functions", Header:GetWide() - 110 - TabPadding, Header:GetTall() / 2 - 30 / 2, function(s, _, value) - FunctionsProfiler.Realm = value - GProfiler.Menu.OpenTab("Functions", FunctionsProfiler.DoTab) - end) - RealmSelector:SetPos(Header:GetWide() - RealmSelector:GetWide() - TabPadding, Header:GetTall() / 2 - RealmSelector:GetTall() / 2) - - local StartButton = vgui.Create("DButton", Header) - StartButton:SetText(FunctionsProfiler.ProfileActive and GProfiler.Language.GetPhrase("profiler_stop") or GProfiler.Language.GetPhrase("profiler_start")) - StartButton:SetTextColor(MenuColors.White) - StartButton:SetFont("GProfiler.Menu.StartButton") - StartButton:SizeToContents() - StartButton:SetTall(RealmSelector:GetTall()) - StartButton:SetPos(Header:GetWide() - StartButton:GetWide() - RealmSelector:GetWide() - TabPadding * 2, Header:GetTall() / 2 - StartButton:GetTall() / 2) - StartButton.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - - local FunctionTimeRunning = vgui.Create("DLabel", Header) - FunctionTimeRunning:SetFont("GProfiler.Menu.SectionHeader") - FunctionTimeRunning:SetText(GProfiler.TimeRunning(FunctionsProfiler.StartTime, FunctionsProfiler.EndTime, FunctionsProfiler.ProfileActive) .. "s") - FunctionTimeRunning:SizeToContents() - FunctionTimeRunning:SetPos(Header:GetWide() - FunctionTimeRunning:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - FunctionTimeRunning:GetTall() / 2) - FunctionTimeRunning:SetTextColor(MenuColors.White) - function FunctionTimeRunning:Think() - if FunctionsProfiler.ProfileActive then - self:SetText(FunctionsProfiler.Override or GProfiler.TimeRunning(FunctionsProfiler.StartTime, 0, FunctionsProfiler.ProfileActive) .. "s") - self:SizeToContents() - self:SetPos(Header:GetWide() - self:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - self:GetTall() / 2) - end - end - - local ReceivingData = vgui.Create("DLabel", Header) - ReceivingData:SetFont("GProfiler.Menu.SectionHeader") - ReceivingData:SetText("Receiving data... ") - ReceivingData:SizeToContents() - ReceivingData:SetPos(Header:GetWide() - ReceivingData:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - FunctionTimeRunning:GetWide() - TabPadding * 3, Header:GetTall() / 2 - ReceivingData:GetTall() / 2) - ReceivingData:SetTextColor(Color(225, 66, 66)) - function ReceivingData:Think() - if FunctionsProfiler.ReceivingData then - self:SetVisible(true) - else - self:SetVisible(false) - end - end - - StartButton.DoClick = function() - if FunctionsProfiler.ProfileActive then - FunctionsProfiler.EndTime = SysTime() - FunctionsProfiler.Override = GProfiler.TimeRunning(FunctionsProfiler.StartTime, SysTime(), FunctionsProfiler.ProfileActive) .. "s" - if FunctionsProfiler.Realm == "Server" then - net.Start("GProfiler_Functions_ToggleServerProfile") - net.WriteBool(false) - net.SendToServer() - FunctionsProfiler.ReceivingData = true - else - GProfiler.Functions:RestoreFunctions() - FunctionsProfiler.ProfileActive = false - GProfiler.Menu.OpenTab("Functions", FunctionsProfiler.DoTab) - end - else - FunctionsProfiler.Override = nil - FunctionsProfiler.StartTime = SysTime() - FunctionsProfiler.EndTime = 0 - if FunctionsProfiler.Realm == "Server" then - net.Start("GProfiler_Functions_ToggleServerProfile") - net.WriteBool(true) - if not table.IsEmpty(FunctionsProfiler.ActiveFocus) then - net.WriteBool(true) - net.WriteUInt(#FunctionsProfiler.ActiveFocus, 5) - for k, v in ipairs(FunctionsProfiler.ActiveFocus) do - net.WriteString(v) - end - else - net.WriteBool(false) - end - net.SendToServer() - else - FunctionsProfiler.Focus = {} - for k, v in pairs(FunctionsProfiler.ActiveFocus or {}) do - FunctionsProfiler.Focus[v] = true - end - - if table.IsEmpty(FunctionsProfiler.ActiveFocus) then - FunctionsProfiler.Focus = false - end - - GProfiler.Functions:StartProfiler() - FunctionsProfiler.ProfileActive = true - StartButton:SetText(GProfiler.Language.GetPhrase("profiler_stop")) - end - end - end - - local SectionHeader = vgui.Create("DPanel", Content) - SectionHeader:SetSize(Content:GetWide(), 40) - SectionHeader:SetPos(0, Header:GetTall()) - SectionHeader.Paint = nil - - local leftFraction = .75 - local rightFraction = .25 - - local LeftHeader, LeftHeaderText = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("profiler_results"), 0, 0, SectionHeader:GetWide() * leftFraction - 5, SectionHeader:GetTall()) - local RightHeader, RightHeaderText = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("function_details"), LeftHeader:GetWide() + 10, 0, SectionHeader:GetWide() * rightFraction - 5, LeftHeader:GetTall()) - - local FilterFileLabel, FilterFile = GProfiler.Menu.CreateLabeledInput(LeftHeader, GProfiler.Language.GetPhrase("filter_source") .. ":", 0, 0, 150, Header:GetTall() - TabPadding * 1.5) - FilterFileLabel:SetX(LeftHeader:GetWide() - FilterFileLabel:GetWide() - FilterFile:GetWide() - 15) - FilterFile:SetX(LeftHeader:GetWide() - FilterFile:GetWide() - TabPadding) - - local FunctionsFocusLabel, AddFocus = GProfiler.Menu.CreateLabeledInput(Header, GProfiler.Language.GetPhrase("focus") .. ":", TabPadding, Header:GetTall() / 2 - (Header:GetTall() - TabPadding * 1.5) / 2, 150, Header:GetTall() - TabPadding * 1.5, 10) - - local FocusList = vgui.Create("DIconLayout", Header) - FocusList:SetSpaceX(5) - FocusList:SetSize(Header:GetWide() - AddFocus:GetWide() - FunctionsFocusLabel:GetWide() - StartButton:GetWide() - RealmSelector:GetWide() - TabPadding * 5, Header:GetTall() - TabPadding) - FocusList:SetPos(AddFocus:GetWide() + FunctionsFocusLabel:GetWide() + FunctionsFocusLabel:GetPos() + 10, Header:GetTall() / 2 - FocusList:GetTall() / 2) - FocusList.Paint = nil - - local function AddFocusToList(value) - local Pnl = FocusList:Add("DPanel") - Pnl:SetSize(20, FocusList:GetTall()) - Pnl.Paint = function(s, w, h) - draw.RoundedBox(4, 2, 2, w - 4, h - 4, MenuColors.RealmSelectorBackground) - end - - local lbl = vgui.Create("DLabel", Pnl) - lbl:SetFont("GProfiler.Menu.RealmSelector") - lbl:SetText(string.Split(value, "function: ")[2]) - lbl:SizeToContents() - lbl:SetPos(5, FocusList:GetTall() / 2 - lbl:GetTall() / 2) - lbl:SetTextColor(MenuColors.White) - - local remove = vgui.Create("DButton", Pnl) - remove:SetSize(20, 20) - remove:SetPos(lbl:GetWide() + 10, FocusList:GetTall() / 2 - remove:GetTall() / 2) - remove:SetText("X") - remove:SetTextColor(MenuColors.White) - remove:SetFont("GProfiler.Menu.RealmSelector") - remove.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - remove.DoClick = function() - table.RemoveByValue(FunctionsProfiler.ActiveFocus, value) - end - - Pnl:SizeToChildren(true, false) - Pnl:SetWide(Pnl:GetWide() + 5) - end - - for k, v in ipairs(FunctionsProfiler.ActiveFocus) do - AddFocusToList(v) - end - - AddFocus.OnEnter = function() - if ValidateFocus(AddFocus:GetText()) and not table.HasValue(FunctionsProfiler.ActiveFocus, AddFocus:GetText()) then - if !string.StartWith(AddFocus:GetText(), "function: ") then AddFocus:SetText("function: " .. AddFocus:GetText()) end - table.insert(FunctionsProfiler.ActiveFocus, AddFocus:GetText()) - AddFocus:SetText("") - end - end - - local IsValidInput = false - local OldPaint = AddFocus.Paint - function AddFocus:Paint(w, h) - OldPaint(self, w, h) - draw.RoundedBox(4, 1, 1, 10, h - 2, IsValidInput and FocusColors.Valid or FocusColors.Invalid) - end - - function AddFocus:OnTextChanged() - IsValidInput = ValidateFocus(self:GetText()) - end - - local prevAmount = 0 - FocusList.Think = function() -- fixme: wtf was I thinking here? - if prevAmount != #FunctionsProfiler.ActiveFocus then - prevAmount = #FunctionsProfiler.ActiveFocus - FocusList:Clear() - FocusList:SetSize(Header:GetWide() - AddFocus:GetWide() - FunctionsFocusLabel:GetWide() - StartButton:GetWide() - RealmSelector:GetWide() - TabPadding * 5, Header:GetTall() - TabPadding) - FocusList:SetPos(AddFocus:GetWide() + FunctionsFocusLabel:GetWide() + FunctionsFocusLabel:GetPos() + 10, Header:GetTall() / 2 - FocusList:GetTall() / 2) - for k, v in ipairs(FunctionsProfiler.ActiveFocus) do - AddFocusToList(v) - end - end - end - - local LeftContent = vgui.Create("DPanel", Content) - LeftContent:SetSize(Content:GetWide() * leftFraction - 5, Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - LeftContent:SetPos(0, SectionHeader:GetTall() + Header:GetTall()) - LeftContent.Paint = nil - - local RightContent = vgui.Create("DPanel", Content) - RightContent:SetSize(Content:GetWide() * rightFraction - 5, Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - RightContent:SetPos(LeftContent:GetWide() + 10, SectionHeader:GetTall() + Header:GetTall()) - RightContent.Paint = nil - - local FunctionDetailsBackground = vgui.Create("DPanel", RightContent) - FunctionDetailsBackground:SetSize(RightContent:GetWide() - TabPadding * 2, RightContent:GetTall() - TabPadding * 2 - 50) - FunctionDetailsBackground:SetPos(TabPadding, TabPadding) - FunctionDetailsBackground.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, MenuColors.CodeBackground) end - - local FunctionDetails = vgui.Create("DTextEntry", FunctionDetailsBackground) - FunctionDetails:Dock(FILL) - FunctionDetails:SetMultiline(true) - FunctionDetails:SetKeyboardInputEnabled(false) - FunctionDetails:SetVerticalScrollbarEnabled(true) - FunctionDetails:SetDrawBackground(false) - FunctionDetails:SetTextColor(MenuColors.White) - FunctionDetails:SetFont("GProfiler.Menu.FunctionDetails") - FunctionDetails:SetText(GProfiler.Language.GetPhrase("function_select")) - - local FunctionDetailsSeparator = vgui.Create("DPanel", RightContent) - FunctionDetailsSeparator:SetSize(RightContent:GetWide() - TabPadding * 2, 1) - FunctionDetailsSeparator:SetPos(TabPadding, FunctionDetailsBackground:GetTall() + TabPadding * 2) - FunctionDetailsSeparator.Paint = function(self, w, h) draw.RoundedBox(4, 0, 0, w, h, MenuColors.HeaderSeparator) end - - local BottomSection = vgui.Create("DPanel", RightContent) - BottomSection:SetSize(RightContent:GetWide() - TabPadding * 2, RightContent:GetTall() - FunctionDetailsBackground:GetTall() - FunctionDetailsSeparator:GetTall() - TabPadding * 3) - BottomSection:SetPos(TabPadding, FunctionDetailsBackground:GetTall() + FunctionDetailsSeparator:GetTall() + TabPadding * 3) - BottomSection.Paint = nil - - local SelectedProfile = nil - local Buttons = { - [GProfiler.Language.GetPhrase("focus")] = function() - if not SelectedProfile then return end - if table.HasValue(FunctionsProfiler.ActiveFocus, SelectedProfile.focus) then - table.RemoveByValue(FunctionsProfiler.ActiveFocus, SelectedProfile.focus) - else - table.insert(FunctionsProfiler.ActiveFocus, SelectedProfile.focus) - end - end, - [GProfiler.Language.GetPhrase("print_details")] = function(b) - if not SelectedProfile then return end - - MsgC(MenuColors.Blue, GProfiler.Language.GetPhrase("function"), ": ", MenuColors.White, SelectedProfile.name, "\n") - MsgC(MenuColors.Blue, GProfiler.Language.GetPhrase("source"), ": ", MenuColors.White, SelectedProfile.source, "\n") - MsgC(MenuColors.Blue, GProfiler.Language.GetPhrase("lines"), ": ", MenuColors.White, SelectedProfile.lines, "\n") - MsgC(MenuColors.Blue, GProfiler.Language.GetPhrase("times_called"), ": ", MenuColors.White, SelectedProfile.calls, "\n") - MsgC(MenuColors.Blue, GProfiler.Language.GetPhrase("total_time"), ": ", MenuColors.White, SelectedProfile.time, "\n") - MsgC(MenuColors.Blue, GProfiler.Language.GetPhrase("average_time"), ": ", MenuColors.White, SelectedProfile.average, "\n") - - b:SetText(GProfiler.Language.GetPhrase("printed")) - timer.Simple(2, function() - if not IsValid(b) then return end - b:SetText(GProfiler.Language.GetPhrase("print_details")) - end) - end - } - - local ButtonWidth = BottomSection:GetWide() / table.Count(Buttons) - local ButtonHeight = BottomSection:GetTall() - TabPadding - - local i = 0 - for k, v in pairs(Buttons) do - local Button = vgui.Create("DButton", BottomSection) - Button:SetSize(ButtonWidth - 5, ButtonHeight) - Button:SetPos(i * ButtonWidth + (i * 5), 0) - Button:SetText(k) - Button:SetTextColor(MenuColors.White) - Button:SetFont("GProfiler.Menu.RealmSelector") - Button.Paint = function(self, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if self:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - Button.DoClick = v - i = i + 1 - end - - local FunctionProfiler = vgui.Create("DListView", LeftContent) - FunctionProfiler:SetSize(LeftContent:GetWide() - TabPadding * 2, LeftContent:GetTall() - TabPadding * 2) - FunctionProfiler:SetPos(TabPadding, TabPadding) - FunctionProfiler:SetMultiSelect(false) - FunctionProfiler:AddColumn(GProfiler.Language.GetPhrase("function")) - FunctionProfiler:AddColumn(GProfiler.Language.GetPhrase("source")) - FunctionProfiler:AddColumn(GProfiler.Language.GetPhrase("times_called")) - FunctionProfiler:AddColumn(string.format("%s (ms)", GProfiler.Language.GetPhrase("total_time"))) - FunctionProfiler:AddColumn(string.format("%s (ms)", GProfiler.Language.GetPhrase("average_time"))) - -- FunctionProfiler:AddColumn("Garbage"):SetWide(5) -- Not confident on its accuracy yet - - for k, v in pairs(FunctionsProfiler.ProfileData) do - if FunctionsProfiler.ProfileActive and FunctionsProfiler.Realm == "Client" then break end - local line = FunctionProfiler:AddLine(v.name or "Unknown", string.format("%s (%s)", v.source, v.lines), v.calls, v.time, v.average, bytesToReadable(v.garbage or 0)) - line:SetSortValue(6, v.garbage or 0) - line.OnMousePressed = function(s, l) - if l == 108 then - local menu = DermaMenu() - menu:AddOption(GProfiler.Language.GetPhrase("focus"), function() - if table.HasValue(FunctionsProfiler.ActiveFocus, v.focus) then - table.RemoveByValue(FunctionsProfiler.ActiveFocus, v.focus) - else - table.insert(FunctionsProfiler.ActiveFocus, v.focus) - end - end):SetIcon("icon16/zoom.png") - menu:AddOption(GProfiler.CopyLang("name"), function() SetClipboardText(v.name) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("source"), function() SetClipboardText(v.source) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("times_called"), function() SetClipboardText(v.calls) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("total_time"), function() SetClipboardText(v.time) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("average_time"), function() SetClipboardText(v.average) end):SetIcon("icon16/page_copy.png") - menu:Open() - return - end - - SelectedProfile = v - for k, v in ipairs(FunctionProfiler.Lines) do - v:SetSelected(false) - end - line:SetSelected(true) - - local lines = string.Split(v.lines, " - ") - GProfiler.RequestFunctionSource(v.source, lines[1], lines[2], function(source) - if not IsValid(FunctionDetails) then return end - FunctionDetails:SetText(table.concat(source, "\n")) - end) - end - end - - FilterFile.OnTextChanged = function() - local filterText = FilterFile:GetText():lower() - FunctionsProfiler.SourceFilter = FilterFile:GetText() - for k, v in ipairs(FunctionProfiler.Lines) do - local source = v:GetColumnText(2):lower() - if string.find(source, filterText, 1, true) then - v:SetVisible(true) - else - v:SetVisible(false) - end - end - FunctionProfiler:DataLayout() - FunctionProfiler:InvalidateLayout() - end - - FunctionProfiler:SortByColumn(5, true) - GProfiler.StyleDListView(FunctionProfiler) - - timer.Simple(0, function() - if FunctionsProfiler.SourceFilter != "" then - FilterFile:SetText(FunctionsProfiler.SourceFilter) - FilterFile:OnTextChanged() - end - end) -end - -GProfiler.Menu.RegisterTab("Functions", "icon16/bug.png", 3, FunctionsProfiler.DoTab, function() - if FunctionsProfiler.ProfileActive then - return GProfiler.TimeRunning(FunctionsProfiler.StartTime, 0, FunctionsProfiler.ProfileActive) .. "s", MenuColors.ActiveProfile - elseif FunctionsProfiler.Override then - return FunctionsProfiler.Override, MenuColors.InactiveProfile - end -end) - -net.Receive("GProfiler_Functions_ServerProfileStatus", function() - local status = net.ReadBool() - local ply = net.ReadEntity() - FunctionsProfiler.ProfileActive = status - - if ply == LocalPlayer() then - GProfiler.Menu.OpenTab("Functions", FunctionsProfiler.DoTab) - end -end) - -net.Receive("GProfiler_Functions_SendData", function(len, ply) - local first = net.ReadBool() - - if first then - FunctionsProfiler.ProfileData = {} - end - - local last = net.ReadBool() - local count = net.ReadUInt(32) - for i = 1, count do - local name = net.ReadString() - local source = net.ReadString() - local lines = net.ReadString() - local calls = net.ReadUInt(22) - local time = net.ReadFloat() - local average = net.ReadFloat() - local focus = net.ReadString() - local garbage = net.ReadFloat() - - if not FunctionsProfiler.ProfileData[name] then - FunctionsProfiler.ProfileData[name] = { - name = name, - source = source, - lines = lines, - calls = 0, - time = 0, - average = 0, - focus = focus, - garbage = 0 - } - end - - local Dat = FunctionsProfiler.ProfileData[name] - Dat.calls = Dat.calls + calls - Dat.time = Dat.time + time - Dat.average = Dat.average + average - Dat.garbage = Dat.garbage + garbage - end - - if last then - GProfiler.Menu.OpenTab("Functions", FunctionsProfiler.DoTab) - FunctionsProfiler.ReceivingData = false - end -end) - -hook.Add("ExpressLoaded", "GProfiler_Functions", function() - express.Receive("GProfiler_Functions_SendData", function(data) - FunctionsProfiler.ReceivingData = false - FunctionsProfiler.ProfileData = data - GProfiler.Menu.OpenTab("Functions", FunctionsProfiler.DoTab) - end) +GProfiler.Menu.RegisterTab("Functions", "gprofiler/functions.png", 3, GProfiler.Functions.Tab, function() + return "00:00", false end) diff --git a/lua/gprofiler/profilers/hooks/cl_hooks.lua b/lua/gprofiler/profilers/hooks/cl_hooks.lua index 3c125ca..047134c 100644 --- a/lua/gprofiler/profilers/hooks/cl_hooks.lua +++ b/lua/gprofiler/profilers/hooks/cl_hooks.lua @@ -1,341 +1,9 @@ GProfiler.Hooks = GProfiler.Hooks or {} -GProfiler.Hooks.Realm = GProfiler.Hooks.Realm or "Client" -GProfiler.Hooks.ProfileActive = GProfiler.Hooks.ProfileActive or false -GProfiler.Hooks.StartTime = GProfiler.Hooks.StartTime or 0 -GProfiler.Hooks.EndTime = GProfiler.Hooks.EndTime or 0 - -local TabPadding = 10 -local MenuColors = GProfiler.MenuColors - -local function GetHookTable(realm, callback) - if realm == "Server" then - net.Start("GProfiler_Hooks_HookTbl") - net.SendToServer() - net.Receive("GProfiler_Hooks_HookTbl", function() - local hookCount = net.ReadUInt(15) - local hookTable = {} - for i = 1, hookCount do - hookTable[net.ReadString()] = net.ReadUInt(10) - end - callback(hookTable) - end) - else - local hookTbl = {} - local hooks = hook.GetTable() - for hookName, hookReceivers in pairs(hooks) do - hookTbl[hookName] = table.Count(hookReceivers) - end - - callback(hookTbl) - end -end function GProfiler.Hooks.DoTab(Content) - local Header = vgui.Create("DPanel", Content) - Header:SetSize(Content:GetWide(), 40) - Header:SetPos(0, 10) - Header.Paint = nil - - local RealmSelector = GProfiler.Menu.CreateRealmSelector(Header, "Hooks", Header:GetWide() - TabPadding - 110, Header:GetTall() / 2 - 30 / 2, function(s, _, value) - GProfiler.Hooks.Realm = value - GProfiler.Menu.OpenTab("Hooks", GProfiler.Hooks.DoTab) - end) - RealmSelector:SetPos(Header:GetWide() - RealmSelector:GetWide() - TabPadding, Header:GetTall() / 2 - RealmSelector:GetTall() / 2) - - local StartButton = vgui.Create("DButton", Header) - StartButton:SetText(GProfiler.Hooks.ProfileActive and GProfiler.Language.GetPhrase("profiler_stop") or GProfiler.Language.GetPhrase("profiler_start")) - StartButton:SetTextColor(MenuColors.White) - StartButton:SetFont("GProfiler.Menu.StartButton") - StartButton:SizeToContents() - StartButton:SetTall(RealmSelector:GetTall()) - StartButton:SetPos(Header:GetWide() - StartButton:GetWide() - RealmSelector:GetWide() - TabPadding * 2, Header:GetTall() / 2 - StartButton:GetTall() / 2) - StartButton.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - - local HookTimeRunning = vgui.Create("DLabel", Header) - HookTimeRunning:SetFont("GProfiler.Menu.SectionHeader") - HookTimeRunning:SetText(GProfiler.TimeRunning(GProfiler.Hooks.StartTime, GProfiler.Hooks.EndTime, GProfiler.Hooks.ProfileActive) .. "s") - HookTimeRunning:SizeToContents() - HookTimeRunning:SetPos(Header:GetWide() - HookTimeRunning:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - HookTimeRunning:GetTall() / 2) - HookTimeRunning:SetTextColor(MenuColors.White) - function HookTimeRunning:Think() - if GProfiler.Hooks.ProfileActive then - self:SetText(GProfiler.Hooks.Override or GProfiler.TimeRunning(GProfiler.Hooks.StartTime, 0, GProfiler.Hooks.ProfileActive) .. "s") - self:SizeToContents() - self:SetPos(Header:GetWide() - self:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - self:GetTall() / 2) - end - end - - local ReceivingData = vgui.Create("DLabel", Header) - ReceivingData:SetFont("GProfiler.Menu.SectionHeader") - ReceivingData:SetText("Receiving data... ") - ReceivingData:SizeToContents() - ReceivingData:SetPos(Header:GetWide() - ReceivingData:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - HookTimeRunning:GetWide() - TabPadding * 3, Header:GetTall() / 2 - ReceivingData:GetTall() / 2) - ReceivingData:SetTextColor(Color(225, 66, 66)) - function ReceivingData:Think() - if GProfiler.Hooks.ReceivingData then - self:SetVisible(true) - else - self:SetVisible(false) - end - end - - - StartButton.DoClick = function() - if GProfiler.Hooks.ProfileActive then - GProfiler.Hooks.EndTime = SysTime() - GProfiler.Hooks.Override = GProfiler.TimeRunning(GProfiler.Hooks.StartTime, SysTime(), GProfiler.Hooks.ProfileActive) .. "s" - if GProfiler.Hooks.Realm == "Server" then - net.Start("GProfiler_Hooks_ToggleServerProfile") - net.WriteBool(false) - net.SendToServer() - GProfiler.Hooks.ReceivingData = true - else - GProfiler.Hooks:RestoreHooks() - GProfiler.Hooks.ProfileActive = false - GProfiler.Menu.OpenTab("Hooks", GProfiler.Hooks.DoTab) - end - else - GProfiler.Hooks.StartTime = SysTime() - GProfiler.Hooks.EndTime = 0 - GProfiler.Hooks.Override = nil - if GProfiler.Hooks.Realm == "Server" then - net.Start("GProfiler_Hooks_ToggleServerProfile") - net.WriteBool(true) - net.SendToServer() - else - GProfiler.Hooks:StartProfiler() - GProfiler.Hooks.ProfileActive = true - StartButton:SetText(GProfiler.Language.GetPhrase("profiler_stop")) - end - end - end - - local SectionHeader = vgui.Create("DPanel", Content) - SectionHeader:SetSize(Content:GetWide(), 40) - SectionHeader:SetPos(0, Header:GetTall()) - SectionHeader.Paint = nil - - local leftFraction = .7 - local rightFraction = .3 - - local ResultsHeader, ResultsHeaderText = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("profiler_results"), 0, 0, SectionHeader:GetWide() * leftFraction - 5, SectionHeader:GetTall()) - local RightHeader, RightHeaderText = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("Hook Function"), ResultsHeader:GetWide() + 10, 0, SectionHeader:GetWide() * rightFraction - 5, ResultsHeader:GetTall()) - - local Results = vgui.Create("DPanel", Content) - Results:SetSize(ResultsHeader:GetWide(), (Content:GetTall() / 1.5) - SectionHeader:GetTall() - Header:GetTall()) - Results:SetPos(0, SectionHeader:GetTall() + Header:GetTall()) - Results.Paint = nil - - local List = vgui.Create("DPanel", Content) - List:SetSize(ResultsHeader:GetWide() - TabPadding * 2, Content:GetTall() - Results:GetTall() - SectionHeader:GetTall() - Header:GetTall() - TabPadding) - List:SetPos(TabPadding, Results:GetTall() + SectionHeader:GetTall() + Header:GetTall()) - List.Paint = nil - - local ListHeader, ListHeaderText = GProfiler.Menu.CreateHeader(List, GProfiler.Language.GetPhrase("hook_list"), 0, 0, List:GetWide(), ResultsHeader:GetTall(), true) - - local ListSearchLabel, ListSearch = GProfiler.Menu.CreateLabeledInput(ListHeader, "Search:", ListHeader:GetWide() - 150 - TabPadding, 5, 150, ListHeader:GetTall() - 15) - ListSearchLabel:SetX(ListHeader:GetWide() - ListSearchLabel:GetWide() - ListSearch:GetWide() - 10) - ListSearch:SetX(ListHeader:GetWide() - ListSearch:GetWide() - 5) - - local ResultsFilterLabel, ResultsFilter = GProfiler.Menu.CreateLabeledInput(ResultsHeader, "Filter Source:", ResultsHeader:GetWide() - 150 - TabPadding, 5, 150, ResultsHeader:GetTall() - 15) - ResultsFilterLabel:SetX(ResultsHeader:GetWide() - ResultsFilterLabel:GetWide() - ResultsFilter:GetWide() - 15) - ResultsFilter:SetX(ResultsHeader:GetWide() - ResultsFilter:GetWide() - TabPadding) - - local RightContent = vgui.Create("DPanel", Content) - RightContent:SetSize(RightHeader:GetWide(), Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - RightContent:SetPos(Results:GetWide() + 10, SectionHeader:GetTall() + Header:GetTall()) - RightContent.Paint = nil - local HookProfiler = vgui.Create("DListView", Results) - HookProfiler:SetSize(Results:GetWide() - TabPadding * 2, Results:GetTall() - TabPadding * 2) - HookProfiler:SetPos(TabPadding, TabPadding) - HookProfiler:SetMultiSelect(false) - HookProfiler:AddColumn(GProfiler.Language.GetPhrase("name")) - HookProfiler:AddColumn(GProfiler.Language.GetPhrase("receiver")) - HookProfiler:AddColumn(GProfiler.Language.GetPhrase("source")) - HookProfiler:AddColumn(GProfiler.Language.GetPhrase("total_time")) - HookProfiler:AddColumn(GProfiler.Language.GetPhrase("times_called")) - - local HookList = vgui.Create("DListView", List) - HookList:SetSize(List:GetWide(), List:GetTall() - ListHeader:GetTall() - 10) - HookList:SetPos(0, ListHeader:GetTall() + 10) - HookList:SetMultiSelect(false) - HookList:AddColumn(GProfiler.Language.GetPhrase("name")) - HookList:AddColumn(GProfiler.Language.GetPhrase("receivers")) - - local FunctionDetailsBackground = vgui.Create("DPanel", RightContent) - FunctionDetailsBackground:SetSize(RightContent:GetWide() - TabPadding * 2, RightContent:GetTall() - TabPadding * 2) - FunctionDetailsBackground:SetPos(TabPadding, TabPadding) - FunctionDetailsBackground.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, MenuColors.CodeBackground) end - - local FunctionDetails = vgui.Create("DTextEntry", FunctionDetailsBackground) - FunctionDetails:Dock(FILL) - FunctionDetails:SetMultiline(true) - FunctionDetails:SetKeyboardInputEnabled(false) - FunctionDetails:SetVerticalScrollbarEnabled(true) - FunctionDetails:SetDrawBackground(false) - FunctionDetails:SetTextColor(MenuColors.White) - FunctionDetails:SetFont("GProfiler.Menu.FunctionDetails") - FunctionDetails:SetText(GProfiler.Language.GetPhrase("hook_select")) - - table.sort(GProfiler.Hooks.ProfileData, function(a, b) return a.t > b.t end) - local LastSelected = "" - for k, v in pairs(GProfiler.Hooks.ProfileData) do - if v.c == 0 then continue end - local Line = HookProfiler:AddLine(v.h, v.r, v.FullSource, v.t, v.c) - Line.OnRightClick = function() - local menu = DermaMenu() - menu:AddOption(GProfiler.CopyLang("name"), function() SetClipboardText(v.h) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("receiver"), function() SetClipboardText(v.r) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("source"), function() SetClipboardText(v.FullSource) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("total_time"), function() SetClipboardText(v.t) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("times_called"), function() SetClipboardText(v.c) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.Language.GetPhrase("remove"), function() - if GProfiler.Hooks.Realm == "Server" then - net.Start("GProfiler_Hooks_RemoveHook") - net.WriteString(v.h) - net.WriteString(v.r) - net.SendToServer() - HookProfiler:RemoveLine(Line:GetID()) - else - hook.Remove(v.h, v.r) - HookProfiler:RemoveLine(Line:GetID()) - end - end):SetIcon("icon16/delete.png") - menu:Open() - end - - Line.OnSelect = function() - if LastSelected == v.h..v.r then return end - LastSelected = v.h..v.r - - FunctionDetails:SetText(GProfiler.Language.GetPhrase("requesting_source")) - GProfiler.RequestFunctionSource(v.Source, tonumber(v.Lines[1]), tonumber(v.Lines[2]), function(source) - if not IsValid(FunctionDetails) then return end - FunctionDetails:SetText(table.concat(source, "\n")) - end) - end - end - - HookProfiler:SortByColumn(3, true) - - local function UpdateLists() - GProfiler.StyleDListView(HookList) - GProfiler.StyleDListView(HookProfiler) - end - UpdateLists() - - GetHookTable(GProfiler.Hooks.Realm, function(hookTable) - if not IsValid(HookList) then return end - local hookTableSorted = {} - for k, v in pairs(hookTable) do table.insert(hookTableSorted, {k, v}) end - table.sort(hookTableSorted, function(a, b) return a[2] > b[2] end) - - for k, v in pairs(hookTableSorted) do - local Line = HookList:AddLine(v[1], v[2]) - Line.OnRightClick = function() - local menu = DermaMenu() - menu:AddOption(GProfiler.CopyLang("name"), function() SetClipboardText(v[1]) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("receivers"), function() SetClipboardText(v[2]) end):SetIcon("icon16/page_copy.png") - menu:Open() - end - end - - UpdateLists() - end) - - ListSearch.OnTextChanged = function() - local filterText = ListSearch:GetText():lower() - GProfiler.Hooks.ListSearchText = filterText - for k, v in ipairs(HookList.Lines) do - local source = v:GetColumnText(1):lower() - if string.find(source, filterText, 1, true) then - v:SetVisible(true) - else - v:SetVisible(false) - end - end - HookList:DataLayout() - HookList:InvalidateLayout() - end - - ResultsFilter.OnTextChanged = function() - local filterText = ResultsFilter:GetText():lower() - GProfiler.Hooks.ResultsFilterText = filterText - for k, v in ipairs(HookProfiler.Lines) do - local source = v:GetColumnText(3):lower() - if string.find(source, filterText, 1, true) then - v:SetVisible(true) - else - v:SetVisible(false) - end - end - HookProfiler:DataLayout() - HookProfiler:InvalidateLayout() - end - - timer.Simple(0, function() - if GProfiler.Hooks.ListSearchText then - ListSearch:SetText(GProfiler.Hooks.ListSearchText) - ListSearch:OnTextChanged() - end - - if GProfiler.Hooks.ResultsFilterText then - ResultsFilter:SetText(GProfiler.Hooks.ResultsFilterText) - ResultsFilter:OnTextChanged() - end - end) end -GProfiler.Menu.RegisterTab("Hooks", "icon16/bricks.png", 1, GProfiler.Hooks.DoTab, function() - if GProfiler.Hooks.ProfileActive then - return GProfiler.TimeRunning(GProfiler.Hooks.StartTime, 0, GProfiler.Hooks.ProfileActive) .. "s", MenuColors.ActiveProfile - elseif GProfiler.Hooks.Override then - return GProfiler.Hooks.Override, MenuColors.InactiveProfile - end -end) - -net.Receive("GProfiler_Hooks_ServerProfileStatus", function() - local status = net.ReadBool() - local ply = net.ReadEntity() - GProfiler.Hooks.ProfileActive = status +GProfiler.Menu.RegisterTab("Hooks", "gprofiler/hooks.png", 1, GProfiler.Hooks.DoTab, function() - if ply == LocalPlayer() and not status then - GProfiler.Menu.OpenTab("Hooks", GProfiler.Hooks.DoTab) - end end) - -net.Receive("GProfiler_Hooks_SendData", function() - local data = {} - for i = 1, net.ReadUInt(20) do - local hookName = net.ReadString() - data[hookName] = { - h = net.ReadString(), - r = hookName, - c = net.ReadUInt(32), - t = net.ReadFloat(), - Source = net.ReadString(), - Lines = {net.ReadUInt(16), net.ReadUInt(16)} - } - local data = data[hookName] - data.FullSource = string.format("%s (%d - %d)", data.Source, data.Lines[1], data.Lines[2]) - end - GProfiler.Hooks.ProfileData = data - GProfiler.Hooks.ReceivingData = false - GProfiler.Menu.OpenTab("Hooks", GProfiler.Hooks.DoTab) -end) - -hook.Add("ExpressLoaded", "GProfiler_Hooks", function() - express.Receive("GProfiler_Hooks_SendData", function(data) - GProfiler.Hooks.ProfileData = data - GProfiler.Hooks.ReceivingData = false - GProfiler.Menu.OpenTab("Hooks", GProfiler.Hooks.DoTab) - end) -end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/net/cl_net.lua b/lua/gprofiler/profilers/net/cl_net.lua index 3d2b1ae..adbb8ba 100644 --- a/lua/gprofiler/profilers/net/cl_net.lua +++ b/lua/gprofiler/profilers/net/cl_net.lua @@ -1,301 +1,706 @@ GProfiler.Net = GProfiler.Net or {} -GProfiler.Net.Realm = GProfiler.Net.Realm or "Client" -GProfiler.Net.ProfileActive = GProfiler.Net.ProfileActive or false -GProfiler.Net.StartTime = GProfiler.Net.StartTime or 0 -GProfiler.Net.EndTime = GProfiler.Net.EndTime or 0 - -local TabPadding = 10 -local MenuColors = GProfiler.MenuColors - -local function FormatBites(bites) - if bites < 1024 then - return bites .. "b" - elseif bites < 1024 * 1024 then - return math.Round(bites / 1024, 2) .. "kb" - else - return math.Round(bites / 1024 / 1024, 2) .. "mb" +local Net = GProfiler.Net + +-- Net.StartTime = Net.StartTime or 0 +-- Net.EndTime = Net.EndTime or 0 +-- Net.Realm = Net.Realm or "Client" +Net.StartTime = 0 +Net.EndTime = 0 +Net.Realm = "Client" + +function GProfiler.Net.DoTab(Base, Outer) + local Header = GProfiler.Utils.SetupHeader(Outer, "Networking", "gprofiler/network.png") + local StartStop = Header:SetupStartStop(Net.ProfileActive) + local RealmSelector = Header:SetupRealmSelector(Net.Realm == "Client") + local Timer = Header:SetupTimer(Net) + + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Header.OnHandleMoved() end -end -local function GetReceiverTable(realm, callback) - if realm == "Server" then - net.Start("GProfiler_Net_ReceiverTbl") - net.SendToServer() - net.Receive("GProfiler_Net_ReceiverTbl", function() - local receiverCount = net.ReadUInt(32) - local receiverTbl = {} - for i = 1, receiverCount do - receiverTbl[net.ReadString()] = { - net.ReadString(), - net.ReadString(), - net.ReadUInt(16), - net.ReadUInt(16) - } + function StartStop:OnStateChanged(Running) + Net.ProfileActive = Running + + if not Net.ProfileActive then + Net.EndTime = SysTime() + + if Net.Realm == "Client" then + GProfiler.Net:RestoreNet() + else + net.Start("GProfiler_Net_ToggleServerProfile") + net.WriteBool(false) + net.SendToServer() + end + else + Net.StartTime = SysTime() + Net.EndTime = 0 + + if Net.Realm == "Client" then + GProfiler.Net:StartProfiler() + else + net.Start("GProfiler_Net_ToggleServerProfile") + net.WriteBool(true) + net.SendToServer() + GProfiler.Net.ProfileData = {} end - callback(receiverTbl) - end) - else - local receiverTbl = {} - for k, v in pairs(net.Receivers) do - local Source = debug.getinfo(v, "S") or {short_src = "", linedefined = 0, lastlinedefined = 0} - receiverTbl[k] = { - string.format("%s (%s)", tostring(v), GProfiler.GetFunctionLocation(v)), - Source.short_src, - Source.linedefined, - Source.lastlinedefined - } end - callback(receiverTbl) end -end -function GProfiler.Net.DoTab(Content) - local Header = vgui.Create("DPanel", Content) - Header:SetSize(Content:GetWide(), 40) - Header:SetPos(0, 10) - Header.Paint = nil + function RealmSelector:OnStateChanged(state) Net.Realm = state end - local RealmSelector = GProfiler.Menu.CreateRealmSelector(Header, "Net", Header:GetWide() - TabPadding - 110, Header:GetTall() / 2 - 30 / 2, function(s, _, value) - GProfiler.Net.Realm = value - GProfiler.Menu.OpenTab("Networking", GProfiler.Net.DoTab) - end) - RealmSelector:SetPos(Header:GetWide() - RealmSelector:GetWide() - TabPadding, Header:GetTall() / 2 - RealmSelector:GetTall() / 2) - - local StartButton = vgui.Create("DButton", Header) - StartButton:SetText(GProfiler.Net.ProfileActive and GProfiler.Language.GetPhrase("profiler_stop") or GProfiler.Language.GetPhrase("profiler_start")) - StartButton:SetTextColor(MenuColors.White) - StartButton:SetFont("GProfiler.Menu.StartButton") - StartButton:SizeToContents() - StartButton:SetTall(RealmSelector:GetTall()) - StartButton:SetPos(Header:GetWide() - StartButton:GetWide() - RealmSelector:GetWide() - TabPadding * 2, Header:GetTall() / 2 - StartButton:GetTall() / 2) - StartButton.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) + local left, right = GProfiler.Utils.VSplitPanel(Base, GProfiler.GetScaledSize(10), "net_lr", 0.65) + local Results, Receivers = GProfiler.Utils.HSplitPanel(left, GProfiler.GetScaledSize(10), "net_l_bt", 0.65) + local ResultsSent, ResultsReceived = GProfiler.Utils.HSplitPanel(Results, GProfiler.GetScaledSize(10), "net_results_split", 0.5) + local Source, Breakdown = GProfiler.Utils.HSplitPanel(right, GProfiler.GetScaledSize(10), "net_r_bt", 0.75) + local ClientReceivers, ServerReceivers = GProfiler.Utils.VSplitPanel(Receivers, GProfiler.GetScaledSize(10), "net_lb_lr", 0.5) + + Source.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) + end + + local RichText = vgui.Create("RichText", Source) + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(10)) + RichText:SetVerticalScrollbarEnabled(true) + function RichText:PerformLayout() + self:SetFontInternal("GProfiler.Code") + end + + Source.OnHandleMoved = function() + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(10)) + RichText:InvalidateLayout() + end + + local BreakdownPanel = vgui.Create("DScrollPanel", Breakdown) + BreakdownPanel:Dock(FILL) + + local function FormatBits(bits) + if not bits or bits == 0 then return "0 Bytes" end + if bits < 8 then + return bits .. (bits == 1 and " Bit" or " Bits") end + return string.NiceSize(bits / 8) end - local NetTimeRunning = vgui.Create("DLabel", Header) - NetTimeRunning:SetFont("GProfiler.Menu.SectionHeader") - NetTimeRunning:SetText(GProfiler.TimeRunning(GProfiler.Net.StartTime, GProfiler.Net.EndTime, GProfiler.Net.ProfileActive) .. "s") - NetTimeRunning:SizeToContents() - NetTimeRunning:SetPos(Header:GetWide() - NetTimeRunning:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - NetTimeRunning:GetTall() / 2) - NetTimeRunning:SetTextColor(MenuColors.White) - function NetTimeRunning:Think() - if GProfiler.Net.ProfileActive then - self:SetText(GProfiler.Net.Override or GProfiler.TimeRunning(GProfiler.Net.StartTime, 0, GProfiler.Net.ProfileActive) .. "s") - self:SizeToContents() - self:SetPos(Header:GetWide() - self:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - self:GetTall() / 2) + local SentHeader = GProfiler.Utils.SetupHeader(ResultsSent, "Messages Sent", nil, true) + local ReceivedHeader = GProfiler.Utils.SetupHeader(ResultsReceived, "Messages Received", nil, true) + + local function CreateList(Parent, Columns) + local ResultsList = vgui.Create("GP.ListView", Parent) + ResultsList:SetSize(Parent:GetWide(), Parent:GetTall() - SentHeader:GetTall()) + ResultsList:SetPos(0, SentHeader:GetTall()) + ResultsList:SetMultiSelect(false) + for _, col in ipairs(Columns) do + ResultsList:AddColumn(col) + end + ResultsList:SetHeaderHeight(GProfiler.GetScaledSize(30)) + ResultsList:SetDataHeight(GProfiler.GetScaledSize(draw.GetFontHeight("GProfiler.Inter24") + GProfiler.GetScaledSize(10))) + ResultsList.Paint = nil + + local sbar = ResultsList.VBar + sbar:SetWide(GProfiler.GetScaledSize(12)) + sbar:SetHideButtons(true) + sbar.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) + end + sbar.btnGrip.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + for k, v in ipairs(ResultsList.Columns) do + v.Header:SetFont("GProfiler.Inter28") + v.Header:SetTextColor(color_white) + local isLast = v == ResultsList.Columns[#ResultsList.Columns] + v.Header.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(64, 105, 146), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + if not isLast then + surface.SetDrawColor(Color(255, 255, 255, 20)) + surface.DrawRect(w - 1, 0, 1, h) + end + + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + end + + if ResultsList.SortedBy == k then + draw.SimpleText(ResultsList.SortedDescending and "▼" or "▲", "GProfiler.Inter24", w - GProfiler.GetScaledSize(20), h / 2, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + + local oldAddLine = ResultsList.AddLine + ResultsList.AddLine = function(self, ...) + local line = oldAddLine(self, ...) + line.Paint = function(s, w, h) + local isEven = false + for i, v in ipairs(self.Sorted) do + if v == line then + isEven = i % 2 == 0 + break + end + end + GProfiler.RNDX.Draw(0, 0, 0, w, h, isEven and Color(255, 255, 255, 10) or Color(255, 255, 255, 2)) + + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + if s:IsLineSelected() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 30)) + end + end + for _, col in pairs(line.Columns) do + col:SetFont("GProfiler.Inter24") + col:SetTextColor(Color(255, 255, 255, 200)) + end + return line end + + local sbar = ResultsList.VBar + sbar:SetWide(GProfiler.GetScaledSize(12)) + sbar:SetHideButtons(true) + sbar.Paint = function(s, w, h) GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) end + sbar.btnGrip.Paint = function(s, w, h) GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) end + + return ResultsList end - local ReceivingData = vgui.Create("DLabel", Header) - ReceivingData:SetFont("GProfiler.Menu.SectionHeader") - ReceivingData:SetText("Receiving data... ") - ReceivingData:SizeToContents() - ReceivingData:SetPos(Header:GetWide() - ReceivingData:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - NetTimeRunning:GetWide() - TabPadding * 3, Header:GetTall() / 2 - ReceivingData:GetTall() / 2) - ReceivingData:SetTextColor(Color(225, 66, 66)) - function ReceivingData:Think() - if GProfiler.Net.ReceivingData then - self:SetVisible(true) - else - self:SetVisible(false) + local ResultsList = CreateList(ResultsSent, {"Name", "Count", "Size", "Total", "Avg Time"}) + local ReceivedList = CreateList(ResultsReceived, {"Name", "Count", "Size", "Total", "Avg Time"}) + + Results.OnHandleMoved = function() + ResultsList:SetSize(Results:GetWide(), Results:GetTall() - SentHeader:GetTall()) + ResultsList:SetPos(0, SentHeader:GetTall()) + ReceivedList:SetSize(Results:GetWide(), Results:GetTall() - ReceivedHeader:GetTall()) + ReceivedList:SetPos(0, ReceivedHeader:GetTall()) + SentHeader:SetSize(Results:GetWide(), SentHeader:GetTall()) + ReceivedHeader:SetSize(Results:GetWide(), ReceivedHeader:GetTall()) + end + + local function PopulateBreakdown(name, nodes, totalSize) + if not IsValid(BreakdownPanel) then return end + + BreakdownPanel:Clear() + + local Canvas = vgui.Create("DPanel", BreakdownPanel) + Canvas:Dock(TOP) + Canvas:SetTall(0) + Canvas.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, GProfiler.SyntaxColors.background) + end + + local function GetParts(funcName, arg, size) + local parts = {} + table.insert(parts, {"net", GProfiler.SyntaxColors.library}) + table.insert(parts, {".", GProfiler.SyntaxColors.punctuation}) + table.insert(parts, {funcName, GProfiler.SyntaxColors.funcCall}) + table.insert(parts, {"(", GProfiler.SyntaxColors.punctuation}) + if arg then + table.insert(parts, {arg, GProfiler.SyntaxColors.string}) + end + table.insert(parts, {")", GProfiler.SyntaxColors.punctuation}) + if size and size > 0 then + table.insert(parts, {" -- Size: " .. FormatBits(size), GProfiler.SyntaxColors.comment}) + end + return parts + end + + local Lines = {} + + table.insert(Lines, { + Parts = GetParts("Start", '"' .. name .. '"', totalSize), + Depth = 0, + Expanded = true, + Children = nodes + }) + + local function AddChildren(children, depth) + for _, child in ipairs(children) do + local line = { + Parts = GetParts(child.Func, nil, child.Size), + Depth = depth, + Expanded = true, + Children = child.Children, + IsNode = true, + ParentNode = children + } + table.insert(Lines, line) + if child.Children and #child.Children > 0 then + AddChildren(child.Children, depth + 1) + end + end + end + + local RootNodes = {} + + local startNode = { + Parts = GetParts("Start", '"' .. name .. '"', totalSize), + Children = {}, + Depth = 0, + Expanded = true + } + table.insert(RootNodes, startNode) + + local hiddenFuncs = { + ["Start"] = true, + ["Send"] = true, + ["Broadcast"] = true, + ["SendOmit"] = true, + ["SendPVS"] = true, + ["SendPAS"] = true + } + + local function BuildTree(parentList, dataChildren, depth) + for _, child in ipairs(dataChildren) do + if hiddenFuncs[child.Func] then continue end + + local node = { + Parts = GetParts(child.Func, nil, child.Size), + Children = {}, + Depth = depth, + Expanded = true + } + table.insert(parentList, node) + if child.Children and #child.Children > 0 then + BuildTree(node.Children, child.Children, depth + 1) + end + end end + + BuildTree(startNode.Children, nodes, 1) + + local sendNode = { + Parts = GetParts("Send"), + Children = {}, + Depth = 0, + Expanded = true + } + table.insert(RootNodes, sendNode) + + local lineHeight = GProfiler.GetScaledSize(22) + local indentSize = GProfiler.GetScaledSize(20) + local iconSize = GProfiler.GetScaledSize(16) + + Canvas.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) + + draw.RoundedBox(0, 0, 0, GProfiler.GetScaledSize(30), h, GProfiler.SyntaxColors.background) + surface.SetDrawColor(GProfiler.SyntaxColors.lineSep) + surface.DrawRect(GProfiler.GetScaledSize(30), 0, 1, h) + + local y = 0 + local mouseX, mouseY = s:LocalCursorPos() + local clicked = s.MousePressed + local targetNode = nil + + if clicked then + local function checkClick(nodeList, currentY) + for _, node in ipairs(nodeList) do + local rowY= currentY + currentY = currentY + lineHeight + + local x = GProfiler.GetScaledSize(40) + (node.Depth * indentSize) + local expanderX = x - indentSize + + if #node.Children > 0 then + if mouseX >= expanderX and mouseX <= expanderX + iconSize and mouseY >= rowY and mouseY <= rowY + lineHeight then + node.Expanded = not node.Expanded + s:InvalidateLayout() + return currentY, true + end + end + + if node.Expanded and #node.Children > 0 then + local newY, handled = checkClick(node.Children, currentY) + currentY = newY + if handled then return currentY, true end + end + end + return currentY, false + end + checkClick(RootNodes, 0) + s.MousePressed = false + end + + local lineNum = 1 + local function DrawNodes(nodeList, currentY) + for _, node in ipairs(nodeList) do + local rowY = currentY + + draw.SimpleText(lineNum, "GProfiler.Code", GProfiler.GetScaledSize(25), rowY + lineHeight/2, GProfiler.SyntaxColors.lineNumber, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + lineNum = lineNum + 1 + + local x = GProfiler.GetScaledSize(40) + (node.Depth * indentSize) + + if #node.Children > 0 and node.Depth > 0 then + local expanderX = x - indentSize + local expanderY = rowY + (lineHeight - iconSize)/2 + + surface.SetDrawColor(GProfiler.SyntaxColors.lineNumber) + draw.NoTexture() + + if node.Expanded then + surface.DrawPoly({ + { x = expanderX, y = expanderY + iconSize/4 }, + { x = expanderX + iconSize/2, y = expanderY + iconSize/4 }, + { x = expanderX + iconSize/4, y = expanderY + iconSize/2 + iconSize/4 } + }) + else + surface.DrawPoly({ + { x = expanderX, y = expanderY + iconSize/4 }, + { x = expanderX + iconSize/2, y = expanderY + iconSize/2 }, + { x = expanderX, y = expanderY + iconSize/2 + iconSize/4 } + }) + end + end + + surface.SetFont("GProfiler.Code") + local textX = x + for _, part in ipairs(node.Parts) do + surface.SetTextColor(part[2]) + surface.SetTextPos(textX, rowY + (lineHeight - surface.GetTextSize("A"))/2 - 4) + surface.DrawText(part[1]) + textX = textX + surface.GetTextSize(part[1]) + end + + currentY = currentY + lineHeight + + if node.Expanded and #node.Children > 0 then + currentY = DrawNodes(node.Children, currentY) + end + end + return currentY + end + + DrawNodes(RootNodes, 0) + end + + Canvas.PerformLayout = function(s) + local h = 0 + local function CalcHeight(nodeList) + for _, node in ipairs(nodeList) do + h = h + lineHeight + if node.Expanded and #node.Children > 0 then + CalcHeight(node.Children) + end + end + end + CalcHeight(RootNodes) + s:SetTall(h + GProfiler.GetScaledSize(10)) + end + + Canvas.OnMousePressed = function(s, code) + if code == MOUSE_LEFT then + s.MousePressed = true + end + end + + Canvas:InvalidateLayout() end - StartButton.DoClick = function() - if GProfiler.Net.ProfileActive then - GProfiler.Net.EndTime = SysTime() - GProfiler.Net.Override = GProfiler.TimeRunning(GProfiler.Net.StartTime, SysTime(), GProfiler.Net.ProfileActive) .. "s" - if GProfiler.Net.Realm == "Server" then - net.Start("GProfiler_Net_ToggleServerProfile") - net.WriteBool(false) - net.SendToServer() - GProfiler.Net.ReceivingData = true + GProfiler.Net.UpdateBreakdownUI = function(name) + local data = GProfiler.Net.Breakdowns[name] + if data then + PopulateBreakdown(name, data.Nodes, data.Size) + end + end + + function ResultsList:OnRowSelected(rowIndex, row) + local name = row:GetColumnText(1) + local data = GProfiler.Net.ProfileData.Out and GProfiler.Net.ProfileData.Out[name] + + if data then + BreakdownPanel:Clear() + + local file = data.Source or data[4] + local lineDefined = data.LineDefined or data[5] or 0 + local lastLineDefined = data.LastLineDefined or data[6] or 0 + + if file and file ~= "" then + file = string.match(file, "@?(.+)") + if not file then file = data.Source or data[4] end + + GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), lineDefined) + else + RichText:SetText("Failed to load source") + end + end) else - GProfiler.Net.Overridxe = GProfiler.TimeRunning(GProfiler.Net.StartTime, SysTime(), GProfiler.Net.ProfileActive) .. "s" - GProfiler.Net:RestoreNet() - GProfiler.Net.ProfileActive = false - GProfiler.Menu.OpenTab("Networking", GProfiler.Net.DoTab) + RichText:SetText("Failed to load source (2)") end - else - GProfiler.Net.StartTime = SysTime() - GProfiler.Net.EndTime = 0 - GProfiler.Net.Override = nil - if GProfiler.Net.Realm == "Server" then - net.Start("GProfiler_Net_ToggleServerProfile") - net.WriteBool(true) + + if Net.Realm == "Client" then + local breakdownData = GProfiler.Net.Breakdowns and GProfiler.Net.Breakdowns[name] + if breakdownData then + PopulateBreakdown(name, breakdownData.Children or {}, data.Size or data[7] or 0) + end + else + net.Start("GProfiler_Net_RequestServerBreakdown") + net.WriteString(name) net.SendToServer() + end + end + end + + function ReceivedList:OnRowSelected(rowIndex, row) + local name = row:GetColumnText(1) + local data = GProfiler.Net.ProfileData.Inc and GProfiler.Net.ProfileData.Inc[name] + + if data then + BreakdownPanel:Clear() + + local file = data.Source or data[4] + local lineDefined = data.LineDefined or data[5] or 0 + local lastLineDefined = data.LastLineDefined or data[6] or 0 + + if file and file ~= "" then + file = string.match(file, "@?(.+)") + if not file then file = data.Source or data[4] end + + GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), lineDefined) + else + RichText:SetText("Failed to load source") + end + end) else - GProfiler.Net:StartProfiler() - GProfiler.Net.ProfileActive = true - StartButton:SetText(GProfiler.Language.GetPhrase("profiler_stop")) + RichText:SetText("Failed to load source (2)") end end end - local SectionHeader = vgui.Create("DPanel", Content) - SectionHeader:SetSize(Content:GetWide(), 40) - SectionHeader:SetPos(0, Header:GetTall()) - SectionHeader.Paint = nil - - local leftFraction = .7 - local rightFraction = .3 - - local LeftHeader = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("profiler_results"), 0, 0, SectionHeader:GetWide() * leftFraction - 5, SectionHeader:GetTall()) - local RightHeader = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("Receiver Function"), LeftHeader:GetWide() + 10, 0, SectionHeader:GetWide() * rightFraction - 5, LeftHeader:GetTall()) - - local LeftContent = vgui.Create("DPanel", Content) - LeftContent:SetSize(LeftHeader:GetWide(), Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - LeftContent:SetPos(0, SectionHeader:GetTall() + Header:GetTall()) - LeftContent.Paint = nil - - local RightContent = vgui.Create("DPanel", Content) - RightContent:SetSize(RightHeader:GetWide(), Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - RightContent:SetPos(LeftContent:GetWide() + 10, SectionHeader:GetTall() + Header:GetTall()) - RightContent.Paint = nil - - local ProfilerResults = vgui.Create("DListView", LeftContent) - ProfilerResults:SetSize(LeftContent:GetWide() - TabPadding * 2, (LeftContent:GetTall() - TabPadding * 2) / 2 - 10) - ProfilerResults:SetPos(TabPadding, TabPadding) - ProfilerResults:SetMultiSelect(false) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("receiver")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("times_received")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("largest_size")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("total_size")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("total_time")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("average_time")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("longest_time")) - - local ReceiversList = vgui.Create("DListView", LeftContent) - ReceiversList:SetSize(ProfilerResults:GetWide(), ProfilerResults:GetTall()) - ReceiversList:SetPos(TabPadding, ProfilerResults:GetTall() + TabPadding * 2) - ReceiversList:SetMultiSelect(false) - ReceiversList:AddColumn(GProfiler.Language.GetPhrase("name")):SetFixedWidth(ReceiversList:GetWide() / 3) - ReceiversList:AddColumn(GProfiler.Language.GetPhrase("function")) - - local FunctionDetailsBackground = vgui.Create("DPanel", RightContent) - FunctionDetailsBackground:SetSize(RightContent:GetWide() - TabPadding * 2, RightContent:GetTall() - TabPadding * 2) - FunctionDetailsBackground:SetPos(TabPadding, TabPadding) - FunctionDetailsBackground.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, MenuColors.CodeBackground) end - - local FunctionDetails = vgui.Create("DTextEntry", FunctionDetailsBackground) - FunctionDetails:Dock(FILL) - FunctionDetails:SetMultiline(true) - FunctionDetails:SetKeyboardInputEnabled(false) - FunctionDetails:SetVerticalScrollbarEnabled(true) - FunctionDetails:SetDrawBackground(false) - FunctionDetails:SetTextColor(MenuColors.White) - FunctionDetails:SetFont("GProfiler.Menu.FunctionDetails") - FunctionDetails:SetText(GProfiler.Language.GetPhrase("receiver_select")) - - local LastSelected = "" - table.sort(GProfiler.Net.ProfileData or {}, function(a, b) return a.t > b.t end) - for k, v in pairs(GProfiler.Net.ProfileData or {}) do - local Line = ProfilerResults:AddLine(k, v[1], string.format("%s (%s)", v[2], FormatBites(v[2])), string.format("%s (%s)", v[3], FormatBites(v[3])), v[7], v[8], v[9]) - Line.OnRightClick = function() - local menu = DermaMenu() - menu:AddOption(GProfiler.CopyLang("receiver"), function() SetClipboardText(k) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("times_received"), function() SetClipboardText(v[1]) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("largest_size"), function() SetClipboardText(v[2]) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("total_size"), function() SetClipboardText(v[3]) end):SetIcon("icon16/page_copy.png") - menu:Open() + local function PopulateResults() + if not IsValid(ResultsList) or not IsValid(ReceivedList) then return end + + ResultsList:Clear() + if GProfiler.Net.ProfileData.Out then + for name, data in pairs(GProfiler.Net.ProfileData.Out) do + local count = data.Count or data[1] or 0 + local maxSize = data.MaxSize or data[2] or 0 + local totalSize = data.TotalSize or data[3] or 0 + local avgTime = data.AverageTime or data[9] or 0 + + ResultsList:AddLine(name, count, FormatBits(maxSize), FormatBits(totalSize), math.Round(avgTime * 1000, 3) .. "ms") + end end - Line.OnSelect = function() - if not v[4] or LastSelected == v then return end - LastSelected = v + ReceivedList:Clear() + if GProfiler.Net.ProfileData.Inc then + for name, data in pairs(GProfiler.Net.ProfileData.Inc) do + local count = data.Count or data[1] or 0 + local maxSize = data.MaxSize or data[2] or 0 + local totalSize = data.TotalSize or data[3] or 0 + local avgTime = data.AverageTime or data[9] or 0 - FunctionDetails:SetText(GProfiler.Language.GetPhrase("requesting_source")) - GProfiler.RequestFunctionSource(v[4], tonumber(v[5]), tonumber(v[6]), function(source) - if not IsValid(FunctionDetails) then return end - FunctionDetails:SetText(table.concat(source, "\n")) - end) + ReceivedList:AddLine(name, count, FormatBits(maxSize), FormatBits(totalSize), math.Round(avgTime * 1000, 3) .. "ms") + end end end + GProfiler.Net.RefreshUI = PopulateResults + PopulateResults() - ProfilerResults:SortByColumn(2, true) - - local function UpdateLists() - GProfiler.StyleDListView(ProfilerResults) - GProfiler.StyleDListView(ReceiversList) + Base.OnRemove = function() + GProfiler.Net.RefreshUI = nil + GProfiler.Net.UpdateBreakdownUI = nil end - UpdateLists() - - GetReceiverTable(GProfiler.Net.Realm, function(receiverTbl) - if not IsValid(ReceiversList) then return end - for k, v in pairs(receiverTbl) do - local Line = ReceiversList:AddLine(k, v[1]) - Line.OnRightClick = function() - local menu = DermaMenu() - menu:AddOption(GProfiler.CopyLang("name"), function() SetClipboardText(k) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("function"), function() SetClipboardText(v) end):SetIcon("icon16/page_copy.png") - menu:Open() + + local function CreateReceiverList(Parent, Receivers, Title) + Parent:Clear() + + local HeaderPanel = vgui.Create("DPanel", Parent) + HeaderPanel:SetSize(Parent:GetWide(), GProfiler.GetScaledSize(50)) + HeaderPanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, Color(34, 77, 122), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + draw.SimpleText(Title, "GProfiler.Inter28", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local RefreshButton = vgui.Create("DButton", HeaderPanel) + RefreshButton:SetSize(GProfiler.GetScaledSize(80), GProfiler.GetScaledSize(30)) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + RefreshButton:SetText("Refresh") + RefreshButton:SetFont("GProfiler.Inter24") + RefreshButton:SetTextColor(Color(0,0,0,0)) + RefreshButton.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + if s:IsHovered() then + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) end + draw.SimpleText("Refresh", "GProfiler.Inter24", w / 2, h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + RefreshButton.DoClick = function() + net.Start("GProfiler_Net_ReceiverTbl") + net.SendToServer() + end - Line.OnSelect = function() - if not IsValid(FunctionDetails) then return end - FunctionDetails:SetText(GProfiler.Language.GetPhrase("requesting_source")) - GProfiler.RequestFunctionSource(v[2], tonumber(v[3]), tonumber(v[4]), function(source) - if not IsValid(FunctionDetails) then return end - FunctionDetails:SetText(table.concat(source, "\n")) - end) + local List = vgui.Create("DPanelList", Parent) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + List:EnableVerticalScrollbar() + + local ScrollBar = List.VBar + ScrollBar:SetWide(GProfiler.GetScaledSize(12)) + ScrollBar:SetHideButtons(true) + ScrollBar.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + end + ScrollBar.btnGrip.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + for k, Receiver in ipairs(Receivers) do + local i = k + local Item = vgui.Create("DButton", List) + Item:SetSize(List:GetWide(), GProfiler.GetScaledSize(30)) + Item:SetText(Receiver.Name) + Item:SetFont("GProfiler.Inter24") + Item:SizeToContentsY() + Item:SetTall(Item:GetTall() + GProfiler.GetScaledSize(10)) + Item:SetContentAlignment(1) + Item:SetTextColor(Color(0,0,0,0)) + Item.Paint = function(s, w, h) + if i % 2 == 0 then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + else + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 2)) + end + + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + draw.SimpleText(Receiver.Name, "GProfiler.Inter24", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + List:AddItem(Item) + end + + Parent.OnHandleMoved = function() + HeaderPanel:SetSize(Parent:GetWide(), HeaderPanel:GetTall()) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + for k, v in pairs(List:GetItems()) do + v:SetSize(List:GetWide(), v:GetTall()) end end - UpdateLists() - end) -end -GProfiler.Menu.RegisterTab("Networking", "icon16/connect.png", 2, GProfiler.Net.DoTab, function() - if GProfiler.Net.ProfileActive then - return GProfiler.TimeRunning(GProfiler.Net.StartTime, 0, GProfiler.Net.ProfileActive) .. "s", MenuColors.ActiveProfile - elseif GProfiler.Net.Override then - return GProfiler.Net.Override, MenuColors.InactiveProfile end -end) -net.Receive("GProfiler_Net_ServerProfileStatus", function() - local status = net.ReadBool() - local ply = net.ReadEntity() - GProfiler.Net.ProfileActive = status + local CLReceivers = {} + local SVReceivers = {} - if ply == LocalPlayer() and not status then - GProfiler.Menu.OpenTab("Networking", GProfiler.Net.DoTab) + for name, func in pairs(net.Receivers) do + local Source = debug.getinfo(func, "S") or {} + local ReceiverInfo = { + ["Name"] = name, + ["Source"] = Source.short_src or "", + ["LineDefined"] = Source.linedefined or 0, + ["LastLineDefined"] = Source.lastlinedefined or 0 + } + table.insert(CLReceivers, ReceiverInfo) end + + local clr = CreateReceiverList(ClientReceivers, CLReceivers, string.format("Client Receivers (%d)", #CLReceivers)) + + net.Receive("GProfiler_Net_ReceiverTbl", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + SVReceivers = {} + local Count = net.ReadUInt(32) + for i = 1, Count do + local name = net.ReadString() + local source = net.ReadString() + local lineDefined = net.ReadUInt(16) + local lastLineDefined = net.ReadUInt(16) + + local ReceiverInfo = { + ["Name"] = name, + ["Source"] = source, + ["LineDefined"] = lineDefined, + ["LastLineDefined"] = lastLineDefined + } + table.insert(SVReceivers, ReceiverInfo) + end + + local svr = CreateReceiverList(ServerReceivers, SVReceivers, string.format("Server Receivers (%d)", #SVReceivers)) + end) + + net.Start("GProfiler_Net_ReceiverTbl") + net.SendToServer() +end +GProfiler.Menu.RegisterTab("Networking", "gprofiler/network.png", 2, GProfiler.Net.DoTab, function() + return "00:00", true end) net.Receive("GProfiler_Net_SendData", function() - GProfiler.Net.ProfileData = {} - for i = 1, net.ReadUInt(32) do - GProfiler.Net.ProfileData[net.ReadString()] = { - net.ReadUInt(32), - net.ReadUInt(32), - net.ReadUInt(32), - net.ReadString(), - net.ReadUInt(16), - net.ReadUInt(16), - net.ReadFloat(), - net.ReadFloat(), - net.ReadFloat() - } + local isIncoming = net.ReadBool() + local count = net.ReadUInt(32) + + if not GProfiler.Net.ProfileData.Inc then GProfiler.Net.ProfileData.Inc = {} end + if not GProfiler.Net.ProfileData.Out then GProfiler.Net.ProfileData.Out = {} end + + local target = isIncoming and GProfiler.Net.ProfileData.Inc or GProfiler.Net.ProfileData.Out + table.Empty(target) + + for i=1, count do + local name = net.ReadString() + local data = {} + data.Count = net.ReadUInt(32) + data.MaxSize = net.ReadUInt(32) + data.TotalSize = net.ReadDouble() + data.Source = net.ReadString() + data.LineDefined = net.ReadUInt(16) + data.LastLineDefined = net.ReadUInt(16) + data.TotalTime = net.ReadFloat() + data.LongestTime = net.ReadFloat() + data.AverageTime = net.ReadFloat() + target[name] = data + end + + if GProfiler.Net.RefreshUI then + GProfiler.Net.RefreshUI() end - GProfiler.Net.ReceivingData = false - GProfiler.Menu.OpenTab("Networking", GProfiler.Net.DoTab) end) -hook.Add("ExpressLoaded", "GProfiler.Net", function() - express.Receive("GProfiler_Net_SendData", function(data) - GProfiler.Net.ProfileData = {} - for k, v in pairs(data) do - GProfiler.Net.ProfileData[k] = { - v.Count, v.MaxSize, v.TotalSize, - v.Source, v.LineStart, v.LineEnd, - v.TotalTime, v.LongestTime, v.AverageTime - } +net.Receive("GProfiler_Net_SendBreakdown", function() + local name = net.ReadString() + local found = net.ReadBool() + if not found then return end + + local totalSize = net.ReadUInt(32) + + local function ReadNode() + local node = {} + node.Func = net.ReadString() + node.Size = net.ReadUInt(32) + local childCount = net.ReadUInt(16) + node.Children = {} + for i=1, childCount do + node.Children[i] = ReadNode() end + return node + end - GProfiler.Menu.OpenTab("Networking", GProfiler.Net.DoTab) - GProfiler.Net.ReceivingData = false - end) + local rootChildren = {} + local count = net.ReadUInt(16) + for i=1, count do + table.insert(rootChildren, ReadNode()) + end + + GProfiler.Net.Breakdowns = GProfiler.Net.Breakdowns or {} + GProfiler.Net.Breakdowns[name] = { + Nodes = rootChildren, + Size = totalSize + } + + if GProfiler.Net.UpdateBreakdownUI then + GProfiler.Net.UpdateBreakdownUI(name) + end end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/net/sh_net.lua b/lua/gprofiler/profilers/net/sh_net.lua index 28681b2..ba82715 100644 --- a/lua/gprofiler/profilers/net/sh_net.lua +++ b/lua/gprofiler/profilers/net/sh_net.lua @@ -1,16 +1,172 @@ GProfiler.Net = GProfiler.Net or {} GProfiler.Net.IsDetoured = GProfiler.Net.IsDetoured or false GProfiler.Net.ProfileData = GProfiler.Net.ProfileData or {} +GProfiler.Net.ProfileData.Inc = GProfiler.Net.ProfileData.Inc or {} +GProfiler.Net.ProfileData.Out = GProfiler.Net.ProfileData.Out or {} +GProfiler.Net.Breakdowns = GProfiler.Net.Breakdowns or {} local netReadHeader = net.ReadHeader local util = util local max = math.max +local netWriteFunctions = { + "WriteAngle", "WriteBit", "WriteBool", "WriteColor", "WriteData", + "WriteDouble", "WriteEntity", "WriteFloat", "WriteInt", "WriteMatrix", + "WriteNormal", "WriteString", "WriteTable", "WriteType", "WriteUInt", + "WriteUInt64", "WriteVector" +} + +GProfiler.Net.OriginalWrites = GProfiler.Net.OriginalWrites or {} + +local netSendFunctions = { "Send" } +if SERVER then + table.insert(netSendFunctions, "Broadcast") + table.insert(netSendFunctions, "SendOmit") + table.insert(netSendFunctions, "SendPVS") + table.insert(netSendFunctions, "SendPAS") +else + table.insert(netSendFunctions, "SendToServer") +end + +local function DetourOutgoing() + GProfiler.Net.OriginalWrites = {} + + local function AddEntry(funcName, size, ...) + if not GProfiler.Net.CurrentMsg then return end + + local entry = { + Func = funcName, + Children = {}, + Size = size or 0 + } + + local stack = GProfiler.Net.CurrentMsg.Stack + local parent = stack[#stack] + if parent then + table.insert(parent.Children, entry) + end + return entry + end + + GProfiler.Net.OriginalWrites["Start"] = net.Start + function net.Start(name, ...) + local nameLower = name:lower() + + GProfiler.Net.CurrentMsg = { + Name = nameLower, + Stack = {}, + Root = { Children = {} } + } + GProfiler.Net.CurrentMsg.Stack[1] = GProfiler.Net.CurrentMsg.Root + + AddEntry("Start", 0) + + return GProfiler.Net.OriginalWrites["Start"](name, ...) + end + + local function FinishMsg() + if not GProfiler.Net.CurrentMsg then return end + local name = GProfiler.Net.CurrentMsg.Name + + if not GProfiler.Net.ProfileData.Out[name] then + GProfiler.Net.ProfileData.Out[name] = {0, 0, 0, nil, nil, nil, 0, 0, 0} + end + + local d = GProfiler.Net.ProfileData.Out[name] + local bytes, bits = net.BytesWritten() + local size = bits or (bytes * 8) + + d[1] = d[1] + 1 + d[2] = max(d[2], size) + d[3] = d[3] + size + + if not d[4] then + local src = debug.getinfo(3, "S") + if src then + d[4] = src.short_src + d[5] = src.linedefined + d[6] = src.lastlinedefined + end + end + + GProfiler.Net.Breakdowns[name] = { + Nodes = GProfiler.Net.CurrentMsg.Root.Children, + Size = size + } + + GProfiler.Net.CurrentMsg = nil + end + + for _, funcName in ipairs(netSendFunctions) do + if not net[funcName] then continue end + GProfiler.Net.OriginalWrites[funcName] = net[funcName] + + net[funcName] = function(...) + if GProfiler.Net.CurrentMsg then + AddEntry(funcName, 0) + end + + FinishMsg() + return GProfiler.Net.OriginalWrites[funcName](...) + end + end + + for _, funcName in ipairs(netWriteFunctions) do + if not net[funcName] then continue end + + GProfiler.Net.OriginalWrites[funcName] = net[funcName] + net[funcName] = function(...) + if not GProfiler.Net.CurrentMsg then + return GProfiler.Net.OriginalWrites[funcName](...) + end + + local entry = AddEntry(funcName, nil, ...) + + table.insert(GProfiler.Net.CurrentMsg.Stack, entry) + + local startB, startBits = net.BytesWritten() + local ret = {GProfiler.Net.OriginalWrites[funcName](...)} + local endB, endBits = net.BytesWritten() + + startBits = startBits or (startB * 8) + endBits = endBits or (endB * 8) + entry.Size = endBits - startBits + + table.remove(GProfiler.Net.CurrentMsg.Stack) + + return unpack(ret) + end + end +end + +local function RestoreOutgoing() + if not GProfiler.Net.OriginalWrites["Start"] then return end + + net.Start = GProfiler.Net.OriginalWrites["Start"] + + for _, funcName in ipairs(netSendFunctions) do + if GProfiler.Net.OriginalWrites[funcName] then + net[funcName] = GProfiler.Net.OriginalWrites[funcName] + end + end + + for _, funcName in ipairs(netWriteFunctions) do + if GProfiler.Net.OriginalWrites[funcName] then + net[funcName] = GProfiler.Net.OriginalWrites[funcName] + end + end + + GProfiler.Net.OriginalWrites = {} + GProfiler.Net.CurrentMsg = nil +end + function GProfiler.Net:StartProfiler(ply) if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or GProfiler.Net.IsDetoured then return end GProfiler.Log((SERVER and "Server" or "Client") .. " net profiler started!", 2) - GProfiler.Net.ProfileData = {} + GProfiler.Net.ProfileData = { Inc = {}, Out = {} } + GProfiler.Net.Breakdowns = {} + GProfiler.Net.IsDetoured = true GProfiler.Net.ProfileStarted = SysTime() @@ -21,19 +177,20 @@ function GProfiler.Net:StartProfiler(ply) local strName = util.NetworkIDToString(i) if not strName then return end + local nameLower = strName:lower() - if not GProfiler.Net.ProfileData[strName] then - GProfiler.Net.ProfileData[strName] = {0, 0, 0, nil, nil, nil, 0, 0, 0} + if not GProfiler.Net.ProfileData.Inc[nameLower] then + GProfiler.Net.ProfileData.Inc[nameLower] = {0, 0, 0, nil, nil, nil, 0, 0, 0} end len = len - 16 - local d = GProfiler.Net.ProfileData[strName] + local d = GProfiler.Net.ProfileData.Inc[nameLower] d[1] = d[1] + 1 d[2] = max(d[2], len) d[3] = d[3] + len - local func = net.Receivers[strName:lower()] + local func = net.Receivers[nameLower] if not func then return end if not d[4] then @@ -48,10 +205,13 @@ function GProfiler.Net:StartProfiler(ply) local start = SysTime() func(len, client) local endT = SysTime() + d[7] = d[7] + (endT - start) d[8] = max(d[8], endT - start) d[9] = d[7] / d[1] end + + DetourOutgoing() end function GProfiler.Net:RestoreNet(ply) @@ -62,45 +222,55 @@ function GProfiler.Net:RestoreNet(ply) GProfiler.Net.ProfileStarted = nil net.Incoming = GProfiler.Net.OriginalIncoming + RestoreOutgoing() if CLIENT then return end - local Count = table.Count(GProfiler.Net.ProfileData) - if GProfiler.ExpressAvailable() and Count > GProfiler.Config.ExpressMinimumResults-1 and Count > 0 then - local Data = {} - for k, v in pairs(GProfiler.Net.ProfileData) do - Data[tostring(k)] = { - Count = v[1], MaxSize = v[2], TotalSize = v[3], - Source = v[4], LineStart = v[5], LineEnd = v[6], - TotalTime = v[7], LongestTime = v[8], AverageTime = v[9] - } - end + local function SendData(dataMap, isIncoming) + local count = table.Count(dataMap) + if GProfiler.ExpressAvailable() and count > GProfiler.Config.ExpressMinimumResults-1 and count > 0 then + local Data = {} + for k, v in pairs(dataMap) do + Data[tostring(k)] = { + Count = v[1], MaxSize = v[2], TotalSize = v[3], + Source = v[4], LineStart = v[5], LineEnd = v[6], + TotalTime = v[7], LongestTime = v[8], AverageTime = v[9] + } + end - express.Send("GProfiler_Net_SendData", Data, ply) - else - net.Start("GProfiler_Net_SendData") - net.WriteUInt(Count, 32) - for name, data in pairs(GProfiler.Net.ProfileData) do - net.WriteString(name) - net.WriteUInt(data[1], 32) - net.WriteUInt(data[2], 32) - net.WriteUInt(data[3], 32) - net.WriteString(data[4] or "") - net.WriteUInt(data[5] or 0, 16) - net.WriteUInt(data[6] or 0, 16) - net.WriteFloat(data[7]) - net.WriteFloat(data[8]) - net.WriteFloat(data[9]) + express.Send("GProfiler_Net_SendData", { IsIncoming = isIncoming, Data = Data }, ply) + else + net.Start("GProfiler_Net_SendData") + net.WriteBool(isIncoming) + net.WriteUInt(count, 32) + for name, data in pairs(dataMap) do + net.WriteString(name) + net.WriteUInt(data[1], 32) + net.WriteUInt(data[2], 32) + net.WriteDouble(data[3]) + net.WriteString(data[4] or "") + net.WriteUInt(data[5] or 0, 16) + net.WriteUInt(data[6] or 0, 16) + net.WriteFloat(data[7]) + net.WriteFloat(data[8]) + net.WriteFloat(data[9]) + end + net.Send(ply) end - net.Send(ply) end + + SendData(GProfiler.Net.ProfileData.Inc, true) + SendData(GProfiler.Net.ProfileData.Out, false) end if SERVER then util.AddNetworkString("GProfiler_Net_ToggleServerProfile") util.AddNetworkString("GProfiler_Net_ServerProfileStatus") util.AddNetworkString("GProfiler_Net_SendData") + util.AddNetworkString("GProfiler_Net_RequestBreakdown") + util.AddNetworkString("GProfiler_Net_SendBreakdown") util.AddNetworkString("GProfiler_Net_ReceiverTbl") + util.AddNetworkString("GProfiler_NetTest") net.Receive("GProfiler_Net_ToggleServerProfile", function(len, ply) if not GProfiler.Access.HasAccess(ply) then return end @@ -128,11 +298,77 @@ if SERVER then for name, func in pairs(net.Receivers) do local Source = debug.getinfo(func, "S") or {} net.WriteString(name) - net.WriteString(string.format("%s (%s)", tostring(func), GProfiler.GetFunctionLocation(func))) net.WriteString(Source.short_src or "") net.WriteUInt(Source.linedefined or 0, 16) net.WriteUInt(Source.lastlinedefined or 0, 16) end net.Send(ply) end) -end + + net.Receive("GProfiler_Net_RequestBreakdown", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + local name = net.ReadString() + local breakdownData = GProfiler.Net.Breakdowns[name] + + net.Start("GProfiler_Net_SendBreakdown") + net.WriteString(name) + if breakdownData then + net.WriteBool(true) + net.WriteUInt(breakdownData.Size, 32) + + local function WriteNode(node) + net.WriteString(node.Func) + net.WriteUInt(node.Size, 32) + net.WriteUInt(#node.Children, 16) + for _, child in ipairs(node.Children) do + WriteNode(child) + end + end + + local nodes = breakdownData.Nodes + net.WriteUInt(#nodes, 16) + for _, node in ipairs(nodes) do + WriteNode(node) + end + else + net.WriteBool(false) + end + net.Send(ply) + end) + + concommand.Add("gprofiler_nettest", function(ply, cmd, args) + if IsValid(ply) and not GProfiler.Access.HasAccess(ply) then return end + + GProfiler.Net:StartProfiler(ply) + + timer.Simple(0, function() + net.Start("GProfiler_NetTest") + net.WriteAngle(Angle(1,2,3)) + net.WriteBit(1) + net.WriteBool(true) + net.WriteColor(Color(255, 0, 0, 255)) + net.WriteData("test", 4) + net.WriteDouble(1.23) + net.WriteFloat(4.56) + net.WriteInt(123, 32) + net.WriteString("a") + net.WriteType("string") + net.WriteUInt(456, 32) + net.WriteUInt64(ply:SteamID64()) + net.WriteVector(Vector(7,8,9)) + for i=1, 100 do + net.WriteString("test" .. i) + end + net.Broadcast() + end) + + timer.Simple(3.1, function() + GProfiler.Net:RestoreNet(ply) + end) + end) + else + net.Receive("gprofiler_nettest", function() + print("got net test") + end) + end diff --git a/lua/gprofiler/profilers/netvars/cl_netvars.lua b/lua/gprofiler/profilers/netvars/cl_netvars.lua index 5691bd7..9e05cec 100644 --- a/lua/gprofiler/profilers/netvars/cl_netvars.lua +++ b/lua/gprofiler/profilers/netvars/cl_netvars.lua @@ -1,149 +1,9 @@ GProfiler.NetVars = GProfiler.NetVars or {} -GProfiler.NetVars.ProfileActive = GProfiler.NetVars.ProfileActive or false -GProfiler.NetVars.ProfileData = GProfiler.NetVars.ProfileData or {} -GProfiler.NetVars.StartTime = GProfiler.NetVars.StartTime or 0 -GProfiler.NetVars.EndTime = GProfiler.NetVars.EndTime or 0 - -local TabPadding = 10 -local MenuColors = GProfiler.MenuColors function GProfiler.NetVars.DoTab(Content) - local Header = vgui.Create("DPanel", Content) - Header:SetSize(Content:GetWide(), 40) - Header:SetPos(0, 10) - Header.Paint = nil - - local StartButton = vgui.Create("DButton", Header) - StartButton:SetText(GProfiler.NetVars.ProfileActive and GProfiler.Language.GetPhrase("profiler_stop") or GProfiler.Language.GetPhrase("profiler_start")) - StartButton:SetTextColor(MenuColors.White) - StartButton:SetFont("GProfiler.Menu.StartButton") - StartButton:SizeToContents() - StartButton:SetTall(30) - StartButton:SetPos(Header:GetWide() - StartButton:GetWide() - TabPadding * 2, Header:GetTall() / 2 - StartButton:GetTall() / 2) - StartButton.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - - function StartButton:DoClick() - if GProfiler.NetVars.ProfileActive then - GProfiler.NetVars.ProfileActive = false - GProfiler.NetVars.EndTime = SysTime() - GProfiler.NetVars.Override = GProfiler.TimeRunning(GProfiler.NetVars.StartTime, SysTime(), GProfiler.NetVars.ProfileActive) .. "s" - net.Start("GProfiler_NetVars_ToggleServerProfile") - net.WriteBool(false) - net.SendToServer() - self:SetText(GProfiler.Language.GetPhrase("profiler_start")) - else - GProfiler.NetVars.ProfileActive = true - GProfiler.NetVars.Override = nil - GProfiler.NetVars.StartTime = SysTime() - net.Start("GProfiler_NetVars_ToggleServerProfile") - net.WriteBool(true) - net.SendToServer() - self:SetText(GProfiler.Language.GetPhrase("profiler_stop")) - end - end - - local TimeRunning = vgui.Create("DLabel", Header) - TimeRunning:SetFont("GProfiler.Menu.SectionHeader") - TimeRunning:SetText(GProfiler.TimeRunning(GProfiler.NetVars.StartTime, GProfiler.NetVars.EndTime, GProfiler.NetVars.ProfileActive) .. "s") - TimeRunning:SizeToContents() - TimeRunning:SetPos(Header:GetWide() - TimeRunning:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - TimeRunning:GetTall() / 2) - TimeRunning:SetTextColor(MenuColors.White) - function TimeRunning:Think() - if GProfiler.NetVars.ProfileActive then - self:SetText(GProfiler.TimeRunning(GProfiler.NetVars.StartTime, 0, GProfiler.NetVars.ProfileActive) .. "s") - self:SizeToContents() - self:SetPos(Header:GetWide() - self:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - self:GetTall() / 2) - end - end - - local SectionHeader = vgui.Create("DPanel", Content) - SectionHeader:SetSize(Content:GetWide(), 40) - SectionHeader:SetPos(0, Header:GetTall()) - SectionHeader.Paint = nil - - local Header, HeaderText = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("profiler_results"), 0, 0, SectionHeader:GetWide() - 5, SectionHeader:GetTall()) - local ProfilerContent = vgui.Create("DPanel", Content) - ProfilerContent:SetSize(Content:GetWide() - 5, Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - ProfilerContent:SetPos(0, SectionHeader:GetTall() + Header:GetTall()) - ProfilerContent.Paint = nil - - local ProfilerResults = vgui.Create("DListView", ProfilerContent) - ProfilerResults:SetSize(ProfilerContent:GetWide() - TabPadding * 2, ProfilerContent:GetTall() - TabPadding * 2) - ProfilerResults:SetPos(TabPadding, TabPadding) - ProfilerResults:SetMultiSelect(false) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("entity")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("variable")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("type")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("times_updated")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("current_value")) - - for ent, vars in pairs(GProfiler.NetVars.ProfileData) do - for var, types in pairs(vars) do - for type, data in pairs(types) do - local Line = ProfilerResults:AddLine(ent, var, type, data.TimesUpdated, data.CurValue) - Line.OnMousePressed = function(s, l) - local menu = DermaMenu() - menu:AddOption(GProfiler.CopyLang("entity"), function() SetClipboardText(ent) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("variable"), function() SetClipboardText(var) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("type"), function() SetClipboardText(type) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("times_updated"), function() SetClipboardText(data.TimesUpdated) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("current_value"), function() SetClipboardText(data.CurValue) end):SetIcon("icon16/page_copy.png") - menu:Open() - end - end - end - end - - ProfilerResults:SortByColumn(3, true) - - GProfiler.StyleDListView(ProfilerResults) end -GProfiler.Menu.RegisterTab("Network Variables", "icon16/table_edit.png", 7, GProfiler.NetVars.DoTab, function() - if GProfiler.NetVars.ProfileActive then - return GProfiler.TimeRunning(GProfiler.NetVars.StartTime, 0, GProfiler.NetVars.ProfileActive) .. "s", MenuColors.ActiveProfile - elseif GProfiler.NetVars.Override then - return GProfiler.NetVars.Override, MenuColors.InactiveProfile - end -end) +GProfiler.Menu.RegisterTab("Network Variables", "gprofiler/netvars.png", 7, GProfiler.NetVars.DoTab, function() -net.Receive("GProfiler_NetVars_SendData", function(len) - local data = {} - local numEnts = net.ReadUInt(32) - for i = 1, numEnts do - local ent = net.ReadString() - data[ent] = {} - local numVars = net.ReadUInt(32) - for i = 1, numVars do - local name = net.ReadString() - data[ent][name] = {} - local numTypes = net.ReadUInt(32) - for i = 1, numTypes do - local type = net.ReadString() - data[ent][name][type] = { - TimesUpdated = net.ReadUInt(32), - CurValue = net.ReadString() - } - end - end - end - GProfiler.NetVars.ProfileData = data end) - -net.Receive("GProfiler_NetVars_ServerProfileStatus", function() - local status = net.ReadBool() - local ply = net.ReadEntity() - GProfiler.NetVars.ProfileActive = status - - if ply == LocalPlayer() then - GProfiler.Menu.OpenTab("Network Variables", GProfiler.NetVars.DoTab) - end -end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/netvars/sv_netvars.lua b/lua/gprofiler/profilers/netvars/sv_netvars.lua index 42fc285..2e84531 100644 --- a/lua/gprofiler/profilers/netvars/sv_netvars.lua +++ b/lua/gprofiler/profilers/netvars/sv_netvars.lua @@ -1,101 +1,101 @@ -GProfiler.NetVars = GProfiler.NetVars or {} -GProfiler.NetVars.ProfileActive = GProfiler.NetVars.ProfileActive or false -GProfiler.NetVars.ProfileData = GProfiler.NetVars.ProfileData or {} +-- GProfiler.NetVars = GProfiler.NetVars or {} +-- GProfiler.NetVars.ProfileActive = GProfiler.NetVars.ProfileActive or false +-- GProfiler.NetVars.ProfileData = GProfiler.NetVars.ProfileData or {} -util.AddNetworkString("GProfiler_NetVars_ToggleServerProfile") -util.AddNetworkString("GProfiler_NetVars_ServerProfileStatus") -util.AddNetworkString("GProfiler_NetVars_SendData") +-- util.AddNetworkString("GProfiler_NetVars_ToggleServerProfile") +-- util.AddNetworkString("GProfiler_NetVars_ServerProfileStatus") +-- util.AddNetworkString("GProfiler_NetVars_SendData") -local NetVarTypes = {"Angle", "Bool", "Entity", "Float", "Int", "String", "Vector"} -local EntityMeta = FindMetaTable("Entity") -local PlayerMeta = FindMetaTable("Player") +-- local NetVarTypes = {"Angle", "Bool", "Entity", "Float", "Int", "String", "Vector"} +-- local EntityMeta = FindMetaTable("Entity") +-- local PlayerMeta = FindMetaTable("Player") -hook.Add("Initialize", "GProfiler_NetVars", function() - for _, type in ipairs(NetVarTypes) do - for _, prefix in ipairs({"", "2"}) do - local funcName = string.format("SetNW%s%s", prefix, type) - local funcNameDetour = string.format("GProfiler_NetVars_%s%s", prefix, type) +-- hook.Add("Initialize", "GProfiler_NetVars", function() +-- for _, type in ipairs(NetVarTypes) do +-- for _, prefix in ipairs({"", "2"}) do +-- local funcName = string.format("SetNW%s%s", prefix, type) +-- local funcNameDetour = string.format("GProfiler_NetVars_%s%s", prefix, type) - if not EntityMeta[funcNameDetour] then - EntityMeta[funcNameDetour] = EntityMeta[funcName] - EntityMeta[funcName] = function(ent, name, value) - GProfiler.NetVars.CollectData(ent, name, value, type, prefix == "2") - return ent[funcNameDetour](ent, name, value) - end - end +-- if not EntityMeta[funcNameDetour] then +-- EntityMeta[funcNameDetour] = EntityMeta[funcName] +-- EntityMeta[funcName] = function(ent, name, value) +-- GProfiler.NetVars.CollectData(ent, name, value, type, prefix == "2") +-- return ent[funcNameDetour](ent, name, value) +-- end +-- end - if not PlayerMeta[funcNameDetour] then - PlayerMeta[funcNameDetour] = PlayerMeta[funcName] - PlayerMeta[funcName] = function(ply, name, value) - GProfiler.NetVars.CollectData(ply, name, value, type, prefix == "2") - return ply[funcNameDetour](ply, name, value) - end - end - end - end -end) +-- if not PlayerMeta[funcNameDetour] then +-- PlayerMeta[funcNameDetour] = PlayerMeta[funcName] +-- PlayerMeta[funcName] = function(ply, name, value) +-- GProfiler.NetVars.CollectData(ply, name, value, type, prefix == "2") +-- return ply[funcNameDetour](ply, name, value) +-- end +-- end +-- end +-- end +-- end) -function GProfiler.NetVars.CollectData(ent, name, value, type, nw2) - if not GProfiler.NetVars.ProfileActive then return end +-- function GProfiler.NetVars.CollectData(ent, name, value, type, nw2) +-- if not GProfiler.NetVars.ProfileActive then return end - local ent = tostring(ent) - local type = string.format("(NW%s) %s", nw2 and "2" or "", type) +-- local ent = tostring(ent) +-- local type = string.format("(NW%s) %s", nw2 and "2" or "", type) - GProfiler.NetVars.ProfileData[ent] = GProfiler.NetVars.ProfileData[ent] or {} - GProfiler.NetVars.ProfileData[ent][name] = GProfiler.NetVars.ProfileData[ent][name] or {} - GProfiler.NetVars.ProfileData[ent][name][type] = GProfiler.NetVars.ProfileData[ent][name][type] or { TimesUpdated = 0 } - GProfiler.NetVars.ProfileData[ent][name][type].TimesUpdated = GProfiler.NetVars.ProfileData[ent][name][type].TimesUpdated + 1 - GProfiler.NetVars.ProfileData[ent][name][type].CurValue = value -end +-- GProfiler.NetVars.ProfileData[ent] = GProfiler.NetVars.ProfileData[ent] or {} +-- GProfiler.NetVars.ProfileData[ent][name] = GProfiler.NetVars.ProfileData[ent][name] or {} +-- GProfiler.NetVars.ProfileData[ent][name][type] = GProfiler.NetVars.ProfileData[ent][name][type] or { TimesUpdated = 0 } +-- GProfiler.NetVars.ProfileData[ent][name][type].TimesUpdated = GProfiler.NetVars.ProfileData[ent][name][type].TimesUpdated + 1 +-- GProfiler.NetVars.ProfileData[ent][name][type].CurValue = value +-- end -function GProfiler.NetVars:StartProfiler() - if GProfiler.NetVars.ProfileActive then return end +-- function GProfiler.NetVars:StartProfiler() +-- if GProfiler.NetVars.ProfileActive then return end - GProfiler.Log((SERVER and "Server" or "Client") .. " network variables profiler started!", 2) - GProfiler.NetVars.ProfileData = {} - GProfiler.NetVars.ProfileActive = true - GProfiler.NetVars.ProfileStarted = SysTime() -end +-- GProfiler.Log((SERVER and "Server" or "Client") .. " network variables profiler started!", 2) +-- GProfiler.NetVars.ProfileData = {} +-- GProfiler.NetVars.ProfileActive = true +-- GProfiler.NetVars.ProfileStarted = SysTime() +-- end -function GProfiler.NetVars:RestoreNetVars(ply) - if not GProfiler.NetVars.ProfileActive then return end +-- function GProfiler.NetVars:RestoreNetVars(ply) +-- if not GProfiler.NetVars.ProfileActive then return end - GProfiler.Log((SERVER and "Server" or "Client") .. " network variables profile stopped, sending data!", 2) - GProfiler.NetVars.ProfileActive = false - GProfiler.NetVars.ProfileStarted = nil +-- GProfiler.Log((SERVER and "Server" or "Client") .. " network variables profile stopped, sending data!", 2) +-- GProfiler.NetVars.ProfileActive = false +-- GProfiler.NetVars.ProfileStarted = nil - net.Start("GProfiler_NetVars_SendData") - net.WriteUInt(table.Count(GProfiler.NetVars.ProfileData), 32) - for ent, data in pairs(GProfiler.NetVars.ProfileData) do - net.WriteString(ent) - net.WriteUInt(table.Count(data), 32) - for name, types in pairs(data) do - net.WriteString(name) - net.WriteUInt(table.Count(types), 32) - for type, data in pairs(types) do - net.WriteString(type) - net.WriteUInt(data.TimesUpdated, 32) - net.WriteString(tostring(data.CurValue or "")) - end - end - end - net.Send(ply) -end +-- net.Start("GProfiler_NetVars_SendData") +-- net.WriteUInt(table.Count(GProfiler.NetVars.ProfileData), 32) +-- for ent, data in pairs(GProfiler.NetVars.ProfileData) do +-- net.WriteString(ent) +-- net.WriteUInt(table.Count(data), 32) +-- for name, types in pairs(data) do +-- net.WriteString(name) +-- net.WriteUInt(table.Count(types), 32) +-- for type, data in pairs(types) do +-- net.WriteString(type) +-- net.WriteUInt(data.TimesUpdated, 32) +-- net.WriteString(tostring(data.CurValue or "")) +-- end +-- end +-- end +-- net.Send(ply) +-- end -net.Receive("GProfiler_NetVars_ToggleServerProfile", function(len, ply) - if not GProfiler.Access.HasAccess(ply) then return end +-- net.Receive("GProfiler_NetVars_ToggleServerProfile", function(len, ply) +-- if not GProfiler.Access.HasAccess(ply) then return end - if net.ReadBool() then - GProfiler.NetVars:StartProfiler() - net.Start("GProfiler_NetVars_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() - else - GProfiler.NetVars:RestoreNetVars(ply) - net.Start("GProfiler_NetVars_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() - end -end) \ No newline at end of file +-- if net.ReadBool() then +-- GProfiler.NetVars:StartProfiler() +-- net.Start("GProfiler_NetVars_ServerProfileStatus") +-- net.WriteBool(true) +-- net.WriteEntity(ply) +-- net.Broadcast() +-- else +-- GProfiler.NetVars:RestoreNetVars(ply) +-- net.Start("GProfiler_NetVars_ServerProfileStatus") +-- net.WriteBool(false) +-- net.WriteEntity(ply) +-- net.Broadcast() +-- end +-- end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/timers/cl_timers.lua b/lua/gprofiler/profilers/timers/cl_timers.lua index 5385416..899dad2 100644 --- a/lua/gprofiler/profilers/timers/cl_timers.lua +++ b/lua/gprofiler/profilers/timers/cl_timers.lua @@ -1,215 +1,9 @@ GProfiler.Timers = GProfiler.Timers or {} -GProfiler.Timers.Realm = GProfiler.Timers.Realm or "Client" -GProfiler.Timers.ProfileActive = GProfiler.Timers.ProfileActive or false -GProfiler.Timers.StartTime = GProfiler.Timers.StartTime or 0 -GProfiler.Timers.EndTime = GProfiler.Timers.EndTime or 0 - -local TabPadding = 10 -local MenuColors = GProfiler.MenuColors function GProfiler.Timers.DoTab(Content) - local Header = vgui.Create("DPanel", Content) - Header:SetSize(Content:GetWide(), 40) - Header:SetPos(0, 10) - Header.Paint = nil - - local RealmSelector = GProfiler.Menu.CreateRealmSelector(Header, "Timers", Header:GetWide() - TabPadding - 110, Header:GetTall() / 2 - 30 / 2, function(s, _, value) - GProfiler.Timers.Realm = value - GProfiler.Menu.OpenTab("Timers", GProfiler.Timers.DoTab) - end) - RealmSelector:SetPos(Header:GetWide() - RealmSelector:GetWide() - TabPadding, Header:GetTall() / 2 - RealmSelector:GetTall() / 2) - - local StartButton = vgui.Create("DButton", Header) - StartButton:SetText(GProfiler.Timers.ProfileActive and GProfiler.Language.GetPhrase("profiler_stop") or GProfiler.Language.GetPhrase("profiler_start")) - StartButton:SetTextColor(MenuColors.White) - StartButton:SetFont("GProfiler.Menu.StartButton") - StartButton:SizeToContents() - StartButton:SetTall(RealmSelector:GetTall()) - StartButton:SetPos(Header:GetWide() - StartButton:GetWide() - RealmSelector:GetWide() - TabPadding * 2, Header:GetTall() / 2 - StartButton:GetTall() / 2) - StartButton.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - - local TimersTimeRunning = vgui.Create("DLabel", Header) - TimersTimeRunning:SetFont("GProfiler.Menu.SectionHeader") - TimersTimeRunning:SetText(GProfiler.TimeRunning(GProfiler.Timers.StartTime, GProfiler.Timers.EndTime, GProfiler.Timers.ProfileActive) .. "s") - TimersTimeRunning:SizeToContents() - TimersTimeRunning:SetPos(Header:GetWide() - TimersTimeRunning:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - TimersTimeRunning:GetTall() / 2) - TimersTimeRunning:SetTextColor(MenuColors.White) - function TimersTimeRunning:Think() - if GProfiler.Timers.ProfileActive then - self:SetText(GProfiler.TimeRunning(GProfiler.Timers.StartTime, 0, GProfiler.Timers.ProfileActive) .. "s") - self:SizeToContents() - self:SetPos(Header:GetWide() - self:GetWide() - RealmSelector:GetWide() - StartButton:GetWide() - TabPadding * 3, Header:GetTall() / 2 - self:GetTall() / 2) - end - end - - StartButton.DoClick = function() - if GProfiler.Timers.ProfileActive then - GProfiler.Timers.EndTime = SysTime() - if GProfiler.Timers.Realm == "Server" then - GProfiler.Timers.Override = GProfiler.TimeRunning(GProfiler.Timers.StartTime, SysTime(), GProfiler.Timers.ProfileActive) .. "s" - net.Start("GProfiler_Timers_ToggleServerProfile") - net.WriteBool(false) - net.SendToServer() - else - GProfiler.Timers:Stop() - GProfiler.Timers.ProfileActive = false - GProfiler.Menu.OpenTab("Timers", GProfiler.Timers.DoTab) - end - - if timer.Exists("GProfiler.Timers.Time") then - timer.Remove("GProfiler.Timers.Time") - end - else - GProfiler.Timers.StartTime = SysTime() - GProfiler.Timers.EndTime = 0 - GProfiler.Timers.Override = nil - if GProfiler.Timers.Realm == "Server" then - net.Start("GProfiler_Timers_ToggleServerProfile") - net.WriteBool(true) - net.SendToServer() - else - GProfiler.Timers:StartProfiler() - GProfiler.Timers.ProfileActive = true - StartButton:SetText(GProfiler.Language.GetPhrase("profiler_stop")) - end - end - end - - local SectionHeader = vgui.Create("DPanel", Content) - SectionHeader:SetSize(Content:GetWide(), 40) - SectionHeader:SetPos(0, Header:GetTall()) - SectionHeader.Paint = nil - - local leftFraction = .7 - local rightFraction = .3 - - local LeftHeader, LeftHeaderText = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("profiler_results"), 0, 0, SectionHeader:GetWide() * leftFraction - 5, SectionHeader:GetTall()) - local RightHeader, RightHeaderText = GProfiler.Menu.CreateHeader(SectionHeader, GProfiler.Language.GetPhrase("timer_function"), LeftHeader:GetWide() + 10, 0, SectionHeader:GetWide() * rightFraction - 5, LeftHeader:GetTall()) - local LeftContent = vgui.Create("DPanel", Content) - LeftContent:SetSize(LeftHeader:GetWide(), Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - LeftContent:SetPos(0, SectionHeader:GetTall() + Header:GetTall()) - LeftContent.Paint = nil - - local RightContent = vgui.Create("DPanel", Content) - RightContent:SetSize(RightHeader:GetWide(), Content:GetTall() - SectionHeader:GetTall() - Header:GetTall()) - RightContent:SetPos(LeftContent:GetWide() + 10, SectionHeader:GetTall() + Header:GetTall()) - RightContent.Paint = nil - - local FunctionDetailsBackground = vgui.Create("DPanel", RightContent) - FunctionDetailsBackground:SetSize(RightContent:GetWide() - TabPadding * 2, RightContent:GetTall() - TabPadding * 2) - FunctionDetailsBackground:SetPos(TabPadding, TabPadding) - FunctionDetailsBackground.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, MenuColors.CodeBackground) end - - local FunctionDetails = vgui.Create("DTextEntry", FunctionDetailsBackground) - FunctionDetails:Dock(FILL) - FunctionDetails:SetMultiline(true) - FunctionDetails:SetKeyboardInputEnabled(false) - FunctionDetails:SetVerticalScrollbarEnabled(true) - FunctionDetails:SetDrawBackground(false) - FunctionDetails:SetTextColor(MenuColors.White) - FunctionDetails:SetFont("GProfiler.Menu.FunctionDetails") - FunctionDetails:SetText(GProfiler.Language.GetPhrase("timer_select")) - - local ProfilerResults = vgui.Create("DListView", LeftContent) - ProfilerResults:SetSize(LeftContent:GetWide() - TabPadding * 2, LeftContent:GetTall() - TabPadding * 2) - ProfilerResults:SetPos(TabPadding, TabPadding) - ProfilerResults:SetMultiSelect(false) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("timer")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("file")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("delay")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("times_run")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("total_time")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("longest_time")) - ProfilerResults:AddColumn(GProfiler.Language.GetPhrase("average_time")) - - local ProfileData = table.Merge(GProfiler.Timers.Simple, GProfiler.Timers.Create) - for k, v in pairs(ProfileData or {}) do - local line = ProfilerResults:AddLine(v.Type == "Simple" and "Simple Timer" or tostring(k), v.Source or "Unknown", math.Round(v.Delay, 4), v.Count, v.TotalTime, v.LongestTime, v.AverageTime) - line.OnMousePressed = function(s, l) - if l == 108 then - local menu = DermaMenu() - menu:AddOption(GProfiler.CopyLang("receiver"), function() SetClipboardText(k) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("times_received"), function() SetClipboardText(v.Count) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("largest_size"), function() SetClipboardText(v.LongestTime) end):SetIcon("icon16/page_copy.png") - menu:AddOption(GProfiler.CopyLang("total_size"), function() SetClipboardText(v.TotalTime) end):SetIcon("icon16/page_copy.png") - menu:Open() - end - - for k, v in pairs(ProfilerResults.Lines) do - v:SetSelected(false) - end - line:SetSelected(true) - - GProfiler.RequestFunctionSource(v.Source, v.Lines[1], v.Lines[2], function(source) - if not IsValid(FunctionDetails) then return end - FunctionDetails:SetText(table.concat(source, "\n")) - end) - end - end - - local Wide = ProfilerResults:GetWide() - ProfilerResults.Columns[1]:SetWide(Wide * 0.2) - ProfilerResults.Columns[2]:SetWide(Wide * 0.2) - ProfilerResults.Columns[3]:SetWide(Wide * 0.075) - ProfilerResults.Columns[4]:SetWide(Wide * 0.075) - ProfilerResults.Columns[5]:SetWide(Wide * 0.17) - ProfilerResults.Columns[6]:SetWide(Wide * 0.17) - ProfilerResults.Columns[7]:SetWide(Wide * 0.17) - ProfilerResults:SortByColumn(5, true) - - local function UpdateLists() - GProfiler.StyleDListView(ProfilerResults) - end - UpdateLists() end -GProfiler.Menu.RegisterTab("Timers", "icon16/time.png", 5, GProfiler.Timers.DoTab, function() - if GProfiler.Timers.ProfileActive then - return GProfiler.TimeRunning(GProfiler.Timers.StartTime, 0, GProfiler.Timers.ProfileActive) .. "s", MenuColors.ActiveProfile - elseif GProfiler.Timers.Override then - return GProfiler.Timers.Override, MenuColors.InactiveProfile - end -end) - -net.Receive("GProfiler_Timers_ServerProfileStatus", function() - local status = net.ReadBool() - local ply = net.ReadEntity() - GProfiler.Timers.ProfileActive = status - if ply == LocalPlayer() and not GProfiler.Timers.ProfileActive then - GProfiler.Menu.OpenTab("Timers", GProfiler.Timers.DoTab) - end -end) +GProfiler.Menu.RegisterTab("Timers", "gprofiler/timers.png", 5, GProfiler.Timers.DoTab, function() -net.Receive("GProfiler_Timers_SendData", function(len) - local firstChunk = net.ReadBool() - if firstChunk then - GProfiler.Timers.Simple = {} - GProfiler.Timers.Create = {} - end - local lastChunk = net.ReadBool() - for i = 1, net.ReadUInt(32) do - local type = net.ReadString() - local name = net.ReadString() - GProfiler.Timers[type][name] = { - Count = net.ReadUInt(15), - Delay = net.ReadFloat(), - TotalTime = net.ReadFloat(), - LongestTime = net.ReadFloat(), - AverageTime = net.ReadFloat(), - Source = net.ReadString(), - Lines = {net.ReadUInt(14), net.ReadUInt(14)}, - Type = type - } - end - if lastChunk then - GProfiler.Menu.OpenTab("Timers", GProfiler.Timers.DoTab) - end end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/timers/sh_timers.lua b/lua/gprofiler/profilers/timers/sh_timers.lua index 0e898e5..e8979ad 100644 --- a/lua/gprofiler/profilers/timers/sh_timers.lua +++ b/lua/gprofiler/profilers/timers/sh_timers.lua @@ -1,200 +1,200 @@ --- For timers, we must detour instantly, as there is no way to get timers created before the detour was created. --- rubat please timer.GetList - -GProfiler.Timers = GProfiler.Timers or {} -GProfiler.Timers.Simple = GProfiler.Timers.Simple or {} -GProfiler.Timers.Create = GProfiler.Timers.Create or {} -GProfiler.Timers.IsDetoured = GProfiler.Timers.IsDetoured or false -GProfiler.Timers.OldSimpleTimer = GProfiler.Timers.OldSimpleTimer or timer.Simple -GProfiler.Timers.OldCreateTimer = GProfiler.Timers.OldCreateTimer or timer.Create -GProfiler.ActiveTimers = GProfiler.ActiveTimers or { Simple = {}, Create = {} } - -local chunkSizeLimit = 65535 - -function GProfiler.Timers:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) then return end - - if GProfiler.Timers.IsDetoured then return end - - GProfiler.Log((SERVER and "Server" or "Client") .. " timer profiler started!", 2) - GProfiler.Timers.IsDetoured = true - GProfiler.Timers.ProfileStarted = SysTime() - - GProfiler.Timers.Simple = {} - GProfiler.Timers.Create = {} -end - -function GProfiler.Timers:Stop(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) then return end - - if not GProfiler.Timers.IsDetoured then return end - - GProfiler.Log((SERVER and "Server" or "Client") .. " timer profile stopped, sending data!", 2) - GProfiler.Timers.IsDetoured = false - GProfiler.Timers.ProfileStarted = nil - - if SERVER then - local ProfileData = table.Merge(GProfiler.Timers.Simple, GProfiler.Timers.Create) - local chunkCount = 1 - local currentChunkSize = 0 - local chunks = {} - for k, v in pairs(ProfileData) do - local chunkSize = 146 + string.len(v.Type) + string.len(tostring(k)) + string.len(v.Source) - if currentChunkSize + chunkSize > chunkSizeLimit then - chunkCount = chunkCount + 1 - currentChunkSize = 0 - end - - if not chunks[chunkCount] then chunks[chunkCount] = {} end - currentChunkSize = currentChunkSize + chunkSize - table.insert(chunks[chunkCount], {k, v}) - end - - for k, v in ipairs(chunks) do - net.Start("GProfiler_Timers_SendData") - net.WriteBool(k == 1) - net.WriteBool(k == table.Count(chunks)) - net.WriteUInt(table.Count(v), 32) - for _, data in ipairs(v) do - local dat = data[2] - net.WriteString(dat.Type) - net.WriteString(tostring(data[1])) - net.WriteUInt(dat.Count, 15) - net.WriteFloat(dat.Delay) - net.WriteFloat(dat.TotalTime) - net.WriteFloat(dat.LongestTime) - net.WriteFloat(dat.AverageTime) - net.WriteString(dat.Source) - net.WriteUInt(dat.Lines[1], 14) - net.WriteUInt(dat.Lines[2], 14) - end - net.Send(ply) - end - - if table.Count(chunks) == 0 then - net.Start("GProfiler_Timers_SendData") - net.WriteBool(true) - net.WriteBool(true) - net.WriteUInt(0, 32) - net.Send(ply) - end - end -end - -function GProfiler.Timers.CollectTimerData(type, name, delay, func, funcTime) - if not GProfiler.Timers.IsDetoured then return end - - if not GProfiler.Timers[type][name] then - local dbgInfo = debug.getinfo(func, "S") - GProfiler.Timers[type][name] = { - Count = 0, - TotalTime = 0, - LongestTime = 0, - AverageTime = 0, - Func = func, - Delay = delay, - Source = dbgInfo.short_src, - Lines = {dbgInfo.linedefined, dbgInfo.lastlinedefined}, - Type = type - } - end - - local tbl = GProfiler.Timers[type][name] - tbl.Count = tbl.Count + 1 - tbl.TotalTime = tbl.TotalTime + funcTime - tbl.AverageTime = tbl.TotalTime / tbl.Count - tbl.LongestTime = math.max(tbl.LongestTime, funcTime) -end - -local function assertType(value, check, num, expect) - assert(check(value), string.format("bad argument #%d (%s expected, got %s)", num, expect, type(value))) - return true -end - -timer.Simple = function(delay, func, ...) - if not assertType(delay, isnumber, 1, "number") or not assertType(func, isfunction, 2, "function") then return end - - local Index = delay != 0 and table.insert(GProfiler.ActiveTimers.Simple, {NextRun = SysTime() + delay, Source = debug.getinfo(2)}) - - local args = {...} - GProfiler.Timers.OldSimpleTimer(delay, function() - local start = SysTime() - func(unpack(args)) - GProfiler.Timers.CollectTimerData("Simple", func, delay, func, SysTime() - start) - if Index then table.remove(GProfiler.ActiveTimers.Simple, Index) end - end) -end - -timer.Create = function(name, delay, reps, func) - if not ( - assertType(name, isstring, 1, "string") and - assertType(delay, isnumber, 2, "number") and - assertType(reps, isnumber, 3, "number") and - assertType(func, isfunction, 4, "function") - ) then return end - - name = tostring(name) - - for k, v in ipairs(GProfiler.ActiveTimers.Create) do - if v.Name == name then - table.remove(GProfiler.ActiveTimers.Create, k) - break - end - end - table.insert(GProfiler.ActiveTimers.Create, { Name = name, Reps = reps, Source = debug.getinfo(2) }) - - GProfiler.Timers.OldCreateTimer(name, delay, reps, function() - local start = SysTime() - func() - local endtime = SysTime() - start - GProfiler.Timers.CollectTimerData("Create", name, delay, func, endtime) - if timer.RepsLeft(name) == 0 then - for i, data in ipairs(GProfiler.ActiveTimers.Create) do - if data.Name == name then - table.remove(GProfiler.ActiveTimers.Create, i) - break - end - end - end - end) -end - -timer.Create("GProfiler_ClearTimerList", 2, 0, function() -- because apparently table.remove does NOT want to work sometimes?? fixme - for i = #GProfiler.ActiveTimers.Create, 1, -1 do - local data = GProfiler.ActiveTimers.Create[i] - if not timer.Exists(data.Name) then - table.remove(GProfiler.ActiveTimers.Create, i) - end - end - - for i = #GProfiler.ActiveTimers.Simple, 1, -1 do - local data = GProfiler.ActiveTimers.Simple[i] - if SysTime() >= data.NextRun then - table.remove(GProfiler.ActiveTimers.Simple, i) - end - end -end) - -if SERVER then - util.AddNetworkString("GProfiler_Timers_ToggleServerProfile") - util.AddNetworkString("GProfiler_Timers_ServerProfileStatus") - util.AddNetworkString("GProfiler_Timers_SendData") - - net.Receive("GProfiler_Timers_ToggleServerProfile", function(len, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - if net.ReadBool() then - GProfiler.Timers:StartProfiler(ply) - net.Start("GProfiler_Timers_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() - else - GProfiler.Timers:Stop(ply) - net.Start("GProfiler_Timers_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() - end - end) -end \ No newline at end of file +-- -- For timers, we must detour instantly, as there is no way to get timers created before the detour was created. +-- -- rubat please timer.GetList + +-- GProfiler.Timers = GProfiler.Timers or {} +-- GProfiler.Timers.Simple = GProfiler.Timers.Simple or {} +-- GProfiler.Timers.Create = GProfiler.Timers.Create or {} +-- GProfiler.Timers.IsDetoured = GProfiler.Timers.IsDetoured or false +-- GProfiler.Timers.OldSimpleTimer = GProfiler.Timers.OldSimpleTimer or timer.Simple +-- GProfiler.Timers.OldCreateTimer = GProfiler.Timers.OldCreateTimer or timer.Create +-- GProfiler.ActiveTimers = GProfiler.ActiveTimers or { Simple = {}, Create = {} } + +-- local chunkSizeLimit = 65535 + +-- function GProfiler.Timers:StartProfiler(ply) +-- if not GProfiler.Access.HasAccess(ply or LocalPlayer()) then return end + +-- if GProfiler.Timers.IsDetoured then return end + +-- GProfiler.Log((SERVER and "Server" or "Client") .. " timer profiler started!", 2) +-- GProfiler.Timers.IsDetoured = true +-- GProfiler.Timers.ProfileStarted = SysTime() + +-- GProfiler.Timers.Simple = {} +-- GProfiler.Timers.Create = {} +-- end + +-- function GProfiler.Timers:Stop(ply) +-- if not GProfiler.Access.HasAccess(ply or LocalPlayer()) then return end + +-- if not GProfiler.Timers.IsDetoured then return end + +-- GProfiler.Log((SERVER and "Server" or "Client") .. " timer profile stopped, sending data!", 2) +-- GProfiler.Timers.IsDetoured = false +-- GProfiler.Timers.ProfileStarted = nil + +-- if SERVER then +-- local ProfileData = table.Merge(GProfiler.Timers.Simple, GProfiler.Timers.Create) +-- local chunkCount = 1 +-- local currentChunkSize = 0 +-- local chunks = {} +-- for k, v in pairs(ProfileData) do +-- local chunkSize = 146 + string.len(v.Type) + string.len(tostring(k)) + string.len(v.Source) +-- if currentChunkSize + chunkSize > chunkSizeLimit then +-- chunkCount = chunkCount + 1 +-- currentChunkSize = 0 +-- end + +-- if not chunks[chunkCount] then chunks[chunkCount] = {} end +-- currentChunkSize = currentChunkSize + chunkSize +-- table.insert(chunks[chunkCount], {k, v}) +-- end + +-- for k, v in ipairs(chunks) do +-- net.Start("GProfiler_Timers_SendData") +-- net.WriteBool(k == 1) +-- net.WriteBool(k == table.Count(chunks)) +-- net.WriteUInt(table.Count(v), 32) +-- for _, data in ipairs(v) do +-- local dat = data[2] +-- net.WriteString(dat.Type) +-- net.WriteString(tostring(data[1])) +-- net.WriteUInt(dat.Count, 15) +-- net.WriteFloat(dat.Delay) +-- net.WriteFloat(dat.TotalTime) +-- net.WriteFloat(dat.LongestTime) +-- net.WriteFloat(dat.AverageTime) +-- net.WriteString(dat.Source) +-- net.WriteUInt(dat.Lines[1], 14) +-- net.WriteUInt(dat.Lines[2], 14) +-- end +-- net.Send(ply) +-- end + +-- if table.Count(chunks) == 0 then +-- net.Start("GProfiler_Timers_SendData") +-- net.WriteBool(true) +-- net.WriteBool(true) +-- net.WriteUInt(0, 32) +-- net.Send(ply) +-- end +-- end +-- end + +-- function GProfiler.Timers.CollectTimerData(type, name, delay, func, funcTime) +-- if not GProfiler.Timers.IsDetoured then return end + +-- if not GProfiler.Timers[type][name] then +-- local dbgInfo = debug.getinfo(func, "S") +-- GProfiler.Timers[type][name] = { +-- Count = 0, +-- TotalTime = 0, +-- LongestTime = 0, +-- AverageTime = 0, +-- Func = func, +-- Delay = delay, +-- Source = dbgInfo.short_src, +-- Lines = {dbgInfo.linedefined, dbgInfo.lastlinedefined}, +-- Type = type +-- } +-- end + +-- local tbl = GProfiler.Timers[type][name] +-- tbl.Count = tbl.Count + 1 +-- tbl.TotalTime = tbl.TotalTime + funcTime +-- tbl.AverageTime = tbl.TotalTime / tbl.Count +-- tbl.LongestTime = math.max(tbl.LongestTime, funcTime) +-- end + +-- local function assertType(value, check, num, expect) +-- assert(check(value), string.format("bad argument #%d (%s expected, got %s)", num, expect, type(value))) +-- return true +-- end + +-- timer.Simple = function(delay, func, ...) +-- if not assertType(delay, isnumber, 1, "number") or not assertType(func, isfunction, 2, "function") then return end + +-- local Index = delay != 0 and table.insert(GProfiler.ActiveTimers.Simple, {NextRun = SysTime() + delay, Source = debug.getinfo(2)}) + +-- local args = {...} +-- GProfiler.Timers.OldSimpleTimer(delay, function() +-- local start = SysTime() +-- func(unpack(args)) +-- GProfiler.Timers.CollectTimerData("Simple", func, delay, func, SysTime() - start) +-- if Index then table.remove(GProfiler.ActiveTimers.Simple, Index) end +-- end) +-- end + +-- timer.Create = function(name, delay, reps, func) +-- if not ( +-- assertType(name, isstring, 1, "string") and +-- assertType(delay, isnumber, 2, "number") and +-- assertType(reps, isnumber, 3, "number") and +-- assertType(func, isfunction, 4, "function") +-- ) then return end + +-- name = tostring(name) + +-- for k, v in ipairs(GProfiler.ActiveTimers.Create) do +-- if v.Name == name then +-- table.remove(GProfiler.ActiveTimers.Create, k) +-- break +-- end +-- end +-- table.insert(GProfiler.ActiveTimers.Create, { Name = name, Reps = reps, Source = debug.getinfo(2) }) + +-- GProfiler.Timers.OldCreateTimer(name, delay, reps, function() +-- local start = SysTime() +-- func() +-- local endtime = SysTime() - start +-- GProfiler.Timers.CollectTimerData("Create", name, delay, func, endtime) +-- if timer.RepsLeft(name) == 0 then +-- for i, data in ipairs(GProfiler.ActiveTimers.Create) do +-- if data.Name == name then +-- table.remove(GProfiler.ActiveTimers.Create, i) +-- break +-- end +-- end +-- end +-- end) +-- end + +-- timer.Create("GProfiler_ClearTimerList", 2, 0, function() -- because apparently table.remove does NOT want to work sometimes?? fixme +-- for i = #GProfiler.ActiveTimers.Create, 1, -1 do +-- local data = GProfiler.ActiveTimers.Create[i] +-- if not timer.Exists(data.Name) then +-- table.remove(GProfiler.ActiveTimers.Create, i) +-- end +-- end + +-- for i = #GProfiler.ActiveTimers.Simple, 1, -1 do +-- local data = GProfiler.ActiveTimers.Simple[i] +-- if SysTime() >= data.NextRun then +-- table.remove(GProfiler.ActiveTimers.Simple, i) +-- end +-- end +-- end) + +-- if SERVER then +-- util.AddNetworkString("GProfiler_Timers_ToggleServerProfile") +-- util.AddNetworkString("GProfiler_Timers_ServerProfileStatus") +-- util.AddNetworkString("GProfiler_Timers_SendData") + +-- net.Receive("GProfiler_Timers_ToggleServerProfile", function(len, ply) +-- if not GProfiler.Access.HasAccess(ply) then return end + +-- if net.ReadBool() then +-- GProfiler.Timers:StartProfiler(ply) +-- net.Start("GProfiler_Timers_ServerProfileStatus") +-- net.WriteBool(true) +-- net.WriteEntity(ply) +-- net.Broadcast() +-- else +-- GProfiler.Timers:Stop(ply) +-- net.Start("GProfiler_Timers_ServerProfileStatus") +-- net.WriteBool(false) +-- net.WriteEntity(ply) +-- net.Broadcast() +-- end +-- end) +-- end \ No newline at end of file diff --git a/lua/gprofiler/sh_access.lua b/lua/gprofiler/sh_access.lua index 5f0d194..e126b8b 100644 --- a/lua/gprofiler/sh_access.lua +++ b/lua/gprofiler/sh_access.lua @@ -102,6 +102,7 @@ hook.Add("Initialize", "GProfiler.Access.Register", function() end) function GProfiler.Access.HasAccess(ply) + if true then return true end if ply:EntIndex() == 0 then return true end -- Console if GProfiler.Config.AllowedSteamIDs[ply:SteamID64()] or GProfiler.Config.AllowedSteamIDs[ply:SteamID()] then return true end if not GProfiler.Access.AdminSystem then return false end diff --git a/lua/gprofiler/sh_config.lua b/lua/gprofiler/sh_config.lua index edaf64e..6ee48b4 100644 --- a/lua/gprofiler/sh_config.lua +++ b/lua/gprofiler/sh_config.lua @@ -1,4 +1,4 @@ -GProfiler.Version = "1.10.0" +GProfiler.Version = "2.0.0a" -- Available languages: english, french, german, dutch, russian, italian, turkish -- Some languages may only have partial support. diff --git a/lua/gprofiler/sh_utils.lua b/lua/gprofiler/sh_utils.lua index c042343..6caac6a 100644 --- a/lua/gprofiler/sh_utils.lua +++ b/lua/gprofiler/sh_utils.lua @@ -1,271 +1,253 @@ if CLIENT then - GProfiler.Menu = GProfiler.Menu or {} - - local MenuColors = GProfiler.MenuColors - local BorderColor = MenuColors.DListColumnOutline - local TabPadding = 10 - - local draw = draw - local table = table - local ipairs = ipairs - local string = string - local surface = surface - - local function PaintColumn(s, w, h) - surface.SetDrawColor(BorderColor.r, BorderColor.g, BorderColor.b, BorderColor.a) - surface.DrawRect(w - 2, 0, 2, h) - end - - local function PaintLine(s, w, h) - if s:IsHovered() then - draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListRowHover) - else - draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListRowBackground) - end - - if s:IsLineSelected() then - draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListRowSelected) - end - end - - local function PaintHeader(s, w, h) - draw.RoundedBox(1, 0, 0, w, h, MenuColors.DListColumnOutline) - draw.RoundedBox(1, 1, 1, w - 2, h - 2, MenuColors.DListColumnBackground) - - if s:IsHovered() then - draw.RoundedBox(1, 0, 0, w, h, MenuColors.DListColumnOutline) - end - end - - function GProfiler.StyleDListView(v) - local Columns = v.Columns - for k, v1 in ipairs(Columns) do - v1.Header:SetFont("GProfiler.Menu.ListHeader") - v1.Header.Paint = PaintHeader - v1.Header:SetTextColor(MenuColors.White) - end - - local Lines = v.Lines - for k, v in ipairs(Lines) do - local columnCount = table.Count(v.Columns) - for k, v in ipairs(v.Columns) do - v:SetTextColor(MenuColors.DListRowTextColor) - v.Paint = PaintColumn - end - v.Paint = PaintLine - end - - GProfiler.StyleScrollbar(v) - - function v:Paint(w, h) - draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListBackground) - surface.SetDrawColor(BorderColor.r, BorderColor.g, BorderColor.b, BorderColor.a) - surface.DrawOutlinedRect(0, 0, w, h) - end - end - - local function PaintGrip(s, w, h) - draw.RoundedBox(2, 0, 0, w, h, MenuColors.ScrollBarGripOutline) - draw.RoundedBox(2, 1, 1, w - 2, h - 2, MenuColors.ScrollBarGrip) - - if s:IsHovered() or s.Depressed then - draw.RoundedBox(2, 0, 0, w, h, MenuColors.ScrollBarGripOutline) - end - end - - local function PaintScrollbar(s, w, h) - draw.RoundedBox(0, 0, 0, w, h, MenuColors.ScrollBar) - end - - function GProfiler.StyleScrollbar(v) - local ScrollBar = v.VBar or (v.GetVBar and v:GetVBar()) or nil - if not IsValid(ScrollBar) then return end - ScrollBar.btnUp:SetVisible(false) - ScrollBar.btnDown:SetVisible(false) - ScrollBar.Paint = PaintScrollbar - ScrollBar.btnGrip.Paint = PaintGrip - ScrollBar.PerformLayout = function() - local wide = ScrollBar:GetWide() - local scroll = ScrollBar:GetScroll() / ScrollBar.CanvasSize - local barSize = math.max(ScrollBar:BarScale() * (ScrollBar:GetTall() - (wide * 2)), 10) - local track = ScrollBar:GetTall() - (wide * 2) - barSize - - ScrollBar.btnGrip:SetPos(0, (wide + (scroll * (track + 3))) - 16) - ScrollBar.btnGrip:SetSize(wide, barSize + 30) - end - end - - function GProfiler.StyleDropdown(v) - v.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - end - - local function PaintSeperator(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.HeaderSeparator) - end - - function GProfiler.Menu.CreateHeader(parent, text, x, y, w, h, noPadding) - local TabPadding = noPadding and 0 or TabPadding - local header = vgui.Create("DPanel", parent) - header:SetSize(w, h) - header:SetPos(x, y) - header.Paint = nil - - local headerText = vgui.Create("DLabel", header) - headerText:SetFont("GProfiler.Menu.SectionHeader") - headerText:SetText(text) - headerText:SizeToContents() - headerText:SetPos(TabPadding, header:GetTall() / 2 - headerText:GetTall() / 2) - headerText:SetTextColor(MenuColors.White) - - local separator = vgui.Create("DPanel", header) - separator:SetSize(header:GetWide() - TabPadding * 2, 1) - separator:SetPos(TabPadding, header:GetTall() - 1) - separator.Paint = PaintSeperator - - return header, headerText - end - - local function PaintRealmSelector(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - if s:IsHovered() then - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) - end - end - - function GProfiler.Menu.CreateLabeledInput(parent, labelText, x, y, w, h, textInset) - textInset = textInset or 0 - local lbl = vgui.Create("DLabel", parent) - lbl:SetFont("GProfiler.Menu.SectionHeader") - lbl:SetText(labelText) - lbl:SizeToContents() - lbl:SetTextColor(MenuColors.White) - lbl:SetPos(x, y + 3) - - local input = vgui.Create("DTextEntry", parent) - input:SetFont("GProfiler.Menu.SectionHeader") - input:SetText("") - input:SetSize(w, h) - input:SetPos(x + lbl:GetWide() + 5, y) - input:SetTextColor(MenuColors.White) - function input:Paint(w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.RealmSelectorOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.RealmSelectorBackground) - local x = draw.SimpleText(self:GetText(), "GProfiler.Menu.FocusEntry", textInset + 5, h / 2, MenuColors.White, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - if self:IsEditing() and (x < w - 15) and (SysTime() % 1 > 0.5) then - local caretPos = self:GetCaretPos() - local text = self:GetText() - surface.SetFont("GProfiler.Menu.FocusEntry") - local textWidth = surface.GetTextSize(text) - local textWidthBeforeCaret = surface.GetTextSize(string.sub(text, 1, caretPos)) - draw.RoundedBox(0, textInset + textWidthBeforeCaret + 5, 1, 2, h - 2, MenuColors.White) - end - end - - return lbl, input - end - - function GProfiler.Menu.CreateRealmSelector(parent, profiler, x, y, onSelect) - local Data = GProfiler[profiler] - local Selected = Data.Realm == "Client" and 1 or 2 - Data.Lerp = Data.Lerp or (Selected - 1) - local SelectorBase = vgui.Create("DPanel", parent) - SelectorBase:SetPos(x, y) - SelectorBase:SetSize(200, parent:GetTall() - 6) - SelectorBase.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) - draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - - local lerp = Lerp(0.1, Data.Lerp, Data.LerpTo or (Selected - 1)) - Data.Lerp = lerp - - draw.RoundedBox(4, 2 + w / 2 * lerp, 2, w / 2 - 4, h - 4, MenuColors.ButtonHover) - end - - local Realms = {"Client", "Server"} - for i = 1, 2 do - local Button = vgui.Create("DButton", SelectorBase) - Button:SetText(i == 1 and "Client" or "Server") - Button:SetFont("GProfiler.Menu.RealmSelector") - Button:SetTextColor(color_white) - Button:SetSize(SelectorBase:GetWide() / 2, SelectorBase:GetTall()) - Button:SetPos(SelectorBase:GetWide() / 2 * (i - 1), 0) - Button.Paint = nil - Button.DoClick = function() - Selected = i - Data.LerpTo = i - 1 - end - Button.Think = function(s) - if Data.ProfileActive then - s:SetEnabled(false) - else - s:SetEnabled(true) - end - - if s:IsEnabled() then - s:SetCursor("hand") - else - s:SetCursor("no") - end - end - - function Button:DoClick() - onSelect(self, nil, Realms[i]) - end - - Button.Text = i == 1 and "Client" or "Server" - end - - return SelectorBase - end - - function GProfiler.TimeRunning(start, endd, profileActive) - local time = 0 - - if profileActive then - time = SysTime() - start - else - time = endd - start - end - - return string.format("%.2f", time) - end +-- GProfiler.Menu = GProfiler.Menu or {} + +-- local MenuColors = GProfiler.MenuColors +-- local BorderColor = MenuColors.DListColumnOutline +-- local TabPadding = 10 + +-- local draw = draw +-- local table = table +-- local ipairs = ipairs +-- local string = string +-- local surface = surface + +-- local function PaintColumn(s, w, h) +-- surface.SetDrawColor(BorderColor.r, BorderColor.g, BorderColor.b, BorderColor.a) +-- surface.DrawRect(w - 2, 0, 2, h) +-- end + +-- local function PaintLine(s, w, h) +-- if s:IsHovered() then +-- draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListRowHover) +-- else +-- draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListRowBackground) +-- end + +-- if s:IsLineSelected() then +-- draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListRowSelected) +-- end +-- end + +-- local function PaintHeader(s, w, h) +-- draw.RoundedBox(1, 0, 0, w, h, MenuColors.DListColumnOutline) +-- draw.RoundedBox(1, 1, 1, w - 2, h - 2, MenuColors.DListColumnBackground) + +-- if s:IsHovered() then +-- draw.RoundedBox(1, 0, 0, w, h, MenuColors.DListColumnOutline) +-- end +-- end + +-- function GProfiler.StyleDListView(v) +-- local Columns = v.Columns +-- for k, v1 in ipairs(Columns) do +-- v1.Header:SetFont("GProfiler.Menu.ListHeader") +-- v1.Header.Paint = PaintHeader +-- v1.Header:SetTextColor(MenuColors.White) +-- end + +-- local Lines = v.Lines +-- for k, v in ipairs(Lines) do +-- local columnCount = table.Count(v.Columns) +-- for k, v in ipairs(v.Columns) do +-- v:SetTextColor(MenuColors.DListRowTextColor) +-- v.Paint = PaintColumn +-- end +-- v.Paint = PaintLine +-- end + +-- GProfiler.StyleScrollbar(v) + +-- function v:Paint(w, h) +-- draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListBackground) +-- surface.SetDrawColor(BorderColor.r, BorderColor.g, BorderColor.b, BorderColor.a) +-- surface.DrawOutlinedRect(0, 0, w, h) +-- end +-- end + +-- local function PaintGrip(s, w, h) +-- draw.RoundedBox(2, 0, 0, w, h, MenuColors.ScrollBarGripOutline) +-- draw.RoundedBox(2, 1, 1, w - 2, h - 2, MenuColors.ScrollBarGrip) + +-- if s:IsHovered() or s.Depressed then +-- draw.RoundedBox(2, 0, 0, w, h, MenuColors.ScrollBarGripOutline) +-- end +-- end + +-- local function PaintScrollbar(s, w, h) +-- draw.RoundedBox(0, 0, 0, w, h, MenuColors.ScrollBar) +-- end + +-- function GProfiler.StyleScrollbar(v) +-- local ScrollBar = v.VBar or (v.GetVBar and v:GetVBar()) or nil +-- if not IsValid(ScrollBar) then return end +-- ScrollBar.btnUp:SetVisible(false) +-- ScrollBar.btnDown:SetVisible(false) +-- ScrollBar.Paint = PaintScrollbar +-- ScrollBar.btnGrip.Paint = PaintGrip +-- ScrollBar.PerformLayout = function() +-- local wide = ScrollBar:GetWide() +-- local scroll = ScrollBar:GetScroll() / ScrollBar.CanvasSize +-- local barSize = math.max(ScrollBar:BarScale() * (ScrollBar:GetTall() - (wide * 2)), 10) +-- local track = ScrollBar:GetTall() - (wide * 2) - barSize + +-- ScrollBar.btnGrip:SetPos(0, (wide + (scroll * (track + 3))) - 16) +-- ScrollBar.btnGrip:SetSize(wide, barSize + 30) +-- end +-- end + +-- function GProfiler.StyleDropdown(v) +-- v.Paint = function(s, w, h) +-- draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) +-- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) + +-- if s:IsHovered() then +-- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) +-- end +-- end +-- end + +-- local function PaintSeperator(s, w, h) +-- draw.RoundedBox(4, 0, 0, w, h, MenuColors.HeaderSeparator) +-- end + +-- function GProfiler.Menu.CreateHeader(parent, text, x, y, w, h, noPadding) +-- local TabPadding = noPadding and 0 or TabPadding +-- local header = vgui.Create("DPanel", parent) +-- header:SetSize(w, h) +-- header:SetPos(x, y) +-- header.Paint = nil + +-- local headerText = vgui.Create("DLabel", header) +-- headerText:SetFont("GProfiler.Menu.SectionHeader") +-- headerText:SetText(text) +-- headerText:SizeToContents() +-- headerText:SetPos(TabPadding, header:GetTall() / 2 - headerText:GetTall() / 2) +-- headerText:SetTextColor(MenuColors.White) + +-- local separator = vgui.Create("DPanel", header) +-- separator:SetSize(header:GetWide() - TabPadding * 2, 1) +-- separator:SetPos(TabPadding, header:GetTall() - 1) +-- separator.Paint = PaintSeperator + +-- return header, headerText +-- end + +-- local function PaintRealmSelector(s, w, h) +-- draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) +-- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) + +-- if s:IsHovered() then +-- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) +-- end +-- end + +-- function GProfiler.Menu.CreateLabeledInput(parent, labelText, x, y, w, h, textInset) +-- textInset = textInset or 0 +-- local lbl = vgui.Create("DLabel", parent) +-- lbl:SetFont("GProfiler.Menu.SectionHeader") +-- lbl:SetText(labelText) +-- lbl:SizeToContents() +-- lbl:SetTextColor(MenuColors.White) +-- lbl:SetPos(x, y + 3) + +-- local input = vgui.Create("DTextEntry", parent) +-- input:SetFont("GProfiler.Menu.SectionHeader") +-- input:SetText("") +-- input:SetSize(w, h) +-- input:SetPos(x + lbl:GetWide() + 5, y) +-- input:SetTextColor(MenuColors.White) +-- function input:Paint(w, h) +-- draw.RoundedBox(4, 0, 0, w, h, MenuColors.RealmSelectorOutline) +-- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.RealmSelectorBackground) +-- local x = draw.SimpleText(self:GetText(), "GProfiler.Menu.FocusEntry", textInset + 5, h / 2, MenuColors.White, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) +-- if self:IsEditing() and (x < w - 15) and (SysTime() % 1 > 0.5) then +-- local caretPos = self:GetCaretPos() +-- local text = self:GetText() +-- surface.SetFont("GProfiler.Menu.FocusEntry") +-- local textWidth = surface.GetTextSize(text) +-- local textWidthBeforeCaret = surface.GetTextSize(string.sub(text, 1, caretPos)) +-- draw.RoundedBox(0, textInset + textWidthBeforeCaret + 5, 1, 2, h - 2, MenuColors.White) +-- end +-- end + +-- return lbl, input +-- end + +-- function GProfiler.Menu.CreateRealmSelector(parent, profiler, x, y, onSelect) +-- local Data = GProfiler[profiler] +-- local Selected = Data.Realm == "Client" and 1 or 2 +-- Data.Lerp = Data.Lerp or (Selected - 1) +-- local SelectorBase = vgui.Create("DPanel", parent) +-- SelectorBase:SetPos(x, y) +-- SelectorBase:SetSize(200, parent:GetTall() - 6) +-- SelectorBase.Paint = function(s, w, h) +-- draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) +-- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) + +-- local lerp = Lerp(0.1, Data.Lerp, Data.LerpTo or (Selected - 1)) +-- Data.Lerp = lerp + +-- draw.RoundedBox(4, 2 + w / 2 * lerp, 2, w / 2 - 4, h - 4, MenuColors.ButtonHover) +-- end + +-- local Realms = {"Client", "Server"} +-- for i = 1, 2 do +-- local Button = vgui.Create("DButton", SelectorBase) +-- Button:SetText(i == 1 and "Client" or "Server") +-- Button:SetFont("GProfiler.Menu.RealmSelector") +-- Button:SetTextColor(color_white) +-- Button:SetSize(SelectorBase:GetWide() / 2, SelectorBase:GetTall()) +-- Button:SetPos(SelectorBase:GetWide() / 2 * (i - 1), 0) +-- Button.Paint = nil +-- Button.DoClick = function() +-- Selected = i +-- Data.LerpTo = i - 1 +-- end +-- Button.Think = function(s) +-- if Data.ProfileActive then +-- s:SetEnabled(false) +-- else +-- s:SetEnabled(true) +-- end + +-- if s:IsEnabled() then +-- s:SetCursor("hand") +-- else +-- s:SetCursor("no") +-- end +-- end + +-- function Button:DoClick() +-- onSelect(self, nil, Realms[i]) +-- end + +-- Button.Text = i == 1 and "Client" or "Server" +-- end + +-- return SelectorBase +-- end + +-- function GProfiler.CopyLang(copy) +-- copy = string.lower(string.Replace(copy, " ", "_")) +-- return string.format("%s %s", GProfiler.Language.GetPhrase("copy"), GProfiler.Language.GetPhrase(copy)) +-- end + + +else + +end - function GProfiler.CopyLang(copy) - copy = string.lower(string.Replace(copy, " ", "_")) - return string.format("%s %s", GProfiler.Language.GetPhrase("copy"), GProfiler.Language.GetPhrase(copy)) - end +-- function GProfiler.GetFunctionLocation(func) +-- local info = debug.getinfo(func, "S") +-- if info.short_src == "[C]" then return "C" end +-- return info.short_src .. ":" .. info.linedefined +-- end - function GProfiler.RequestFunctionSource(file, lineStart, lineEnd, callback) - net.Start("GProfiler_RequestFunctionSource") - net.WriteString(file) - net.WriteUInt(lineStart, 32) - net.WriteUInt(lineEnd, 32) - net.SendToServer() +function GProfiler.ExpressAvailable() return !!((express and express.shSend) and GProfiler.Config.UseExpressNetworking) end - local lines = {} - net.Receive("GProfiler_RequestFunctionSource", function() - local isFirst = net.ReadBool() - local isLast = net.ReadBool() - local count = net.ReadUInt(32) - for i = 1, count do - table.insert(lines, net.ReadString()) - end +GProfiler.Utils = GProfiler.Utils or {} - if isLast then - callback(lines) - end - end) - end -else +if SERVER then util.AddNetworkString("GProfiler_RequestFunctionSource") local chunkSizeLimit = 65535 @@ -281,7 +263,7 @@ else local currentChunkSize = 0 local chunks = {} - if type(res) == "string" then res = {res} end + if isstring(res) then res = {res} end for k, v in ipairs(res) do local str = string.Replace(v, "\t", " ") @@ -323,12 +305,216 @@ else return lines end + + return end -function GProfiler.GetFunctionLocation(func) - local info = debug.getinfo(func, "S") - if info.short_src == "[C]" then return "C" end - return info.short_src .. ":" .. info.linedefined +local function GetTabName(tabName) return GProfiler.Language.GetPhrase(string.format("tab_%s", string.gsub(string.lower(tabName), " ", "_"))) end + +function GProfiler.Utils.SetupHeader(Outer, Title, Icon, Sub) + local Header = vgui.Create("DPanel", Outer) + Header:SetSize(Outer:GetWide(), GProfiler.GetScaledSize(Sub and 50 or 100)) + Header.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, Color(34, 77, 122, 255), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + end + + local IconPanel + if Icon then + local IconMat = Material(Icon, "noclamp smooth") + local IconSize = GProfiler.GetScaledSize(64) * 0.75 + IconPanel = vgui.Create("DPanel", Header) + IconPanel:SetSize(GProfiler.GetScaledSize(64), GProfiler.GetScaledSize(64)) + IconPanel:SetPos(GProfiler.GetScaledSize(20), Header:GetTall() / 2 - IconPanel:GetTall() / 2) + IconPanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(22, 50, 80, 255)) + GProfiler.RNDX.Draw(4, 2, 2, w - 4, h - 4, Color(26, 59, 94, 255)) + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(IconMat) + surface.DrawTexturedRect(w / 2 - IconSize / 2, h / 2 - IconSize / 2, IconSize, IconSize) + end + end + + local TitleLabel = vgui.Create("DLabel", Header) + TitleLabel:SetText(Sub and Title or GetTabName(Title)) + TitleLabel:SetTextColor(color_white) + TitleLabel:SetFont(Sub and "GProfiler.Inter28" or "GProfiler.InnerTitle") + TitleLabel:SizeToContents() + TitleLabel:SetMouseInputEnabled(false) + if Icon then + TitleLabel:SetPos(IconPanel:GetWide() + IconPanel:GetX() + GProfiler.GetScaledSize(20), Header:GetTall() / 2 - TitleLabel:GetTall() / 2) + else + TitleLabel:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() / 2 - TitleLabel:GetTall() / 2) + end + + local ItemsXOffset = Header:GetWide() - GProfiler.GetScaledSize(20) + local RNDX = GProfiler.RNDX + + local Button, Selector, Timer + + function Header:SetupStartStop(state) + local ButtonW = GProfiler.GetScaledSize(155) + local ButtonH = Header:GetTall() * 0.65 + + ItemsXOffset = ItemsXOffset - ButtonW + + Button = vgui.Create("DButton", Header) + Button:SetSize(ButtonW, ButtonH) + Button:SetPos(ItemsXOffset, Header:GetTall() / 2 - ButtonH / 2) + Button:SetTextColor(color_white) + Button:SetFont("GProfiler.HeaderInteract") + Button.Paint = function(s, w, h) + RNDX.Draw(6, 0, 0, w, h, Color(18, 46, 74, 255)) + if s:IsHovered() then + RNDX.Draw(6, 0, 0, w, h, Color(0, 0, 0, 50)) + end + end + + Button.State = state + Button:SetText(state and "Stop" or "Start") + + function Button:DoClick() + self.State = not self.State + self:SetText(self.State and "Stop" or "Start") + if self.OnStateChanged then self:OnStateChanged(self.State) end + + if Selector then + Selector.Enabled = not self.State + for k, v in ipairs(Selector:GetChildren()) do + if v.SetCursor then + if Selector.Enabled then v:SetCursor("hand") + else v:SetCursor("no") end + end + end + end + end + + return Button + end + + function Header:SetupRealmSelector(IsClient) + if IsClient == nil then IsClient = true end + + local Width = GProfiler.GetScaledSize(366) + local Height = Header:GetTall() * 0.65 + + ItemsXOffset = ItemsXOffset - Width - GProfiler.GetScaledSize(10) + + Selector = vgui.Create("DPanel", Header) + Selector:SetSize(Width, Height) + Selector:SetPos(ItemsXOffset, Header:GetTall() / 2 - Height / 2) + + Selector.State = IsClient and "Client" or "Server" + Selector.LerpTo = Selector.State == "Client" and 0 or 1 + Selector.LerpPos = Selector.LerpTo + Selector.IsClient = IsClient + Selector.Enabled = true + + Selector.Paint = function(s, w, h) + RNDX.Draw(6, 0, 0, w, h, Color(18, 46, 74, 255)) + + local lerp = Lerp(0.1, Selector.LerpPos, Selector.LerpTo) + Selector.LerpPos = lerp + local Padding = 6 + local SelectorW = w / 2 - Padding * 2 + RNDX.Draw(6, Padding + (SelectorW * lerp), Padding, SelectorW + (Selector.LerpTo == 1 and Padding * 2 or 0), h - Padding * 2, Color(31, 79, 128, 255)) + end + + local ItemW = Selector:GetWide() / 2 + + local Items = {"Client", "Server"} + for i = 1, 2 do + local Button = vgui.Create("DButton", Selector) + Button:SetSize(ItemW, Selector:GetTall()) + Button:SetPos((i - 1) * ItemW, 0) + Button:SetText(Items[i]) + Button:SetFont("GProfiler.HeaderInteract") + Button:SetTextColor(color_white) + Button.Paint = nil + + Button.DoClick = function() + if not Selector.Enabled then return end + + Selector.LerpTo = i - 1 + Selector.State = Items[i] + Selector.IsClient = Items[i] == "Client" + if Selector.OnStateChanged then Selector:OnStateChanged(Selector.State) end + end + end + + return Selector + end + + function Header:SetupTimer(profiler) + Timer = vgui.Create("DLabel", Header) + Timer:SetFont("GProfiler.HeaderInteract") + Timer:SetTextColor(color_white) + Timer:SetText(GProfiler.TimeRunning(profiler.StartTime or 0, profiler.EndTime or 0, profiler.ProfileActive) .. "s") + Timer:SizeToContents() + Timer:SetPos(ItemsXOffset - Timer:GetWide() - GProfiler.GetScaledSize(10), Header:GetTall() / 2 - Timer:GetTall() / 2) + + local OldSetText = Timer.SetText + function Timer:SetText(text) + OldSetText(self, text) + self:SizeToContents() + self:SetPos(ItemsXOffset - self:GetWide() - GProfiler.GetScaledSize(10), Header:GetTall() / 2 - self:GetTall() / 2) + end + + function Timer:Think() + if not profiler.ProfileActive then return end + self:SetText(GProfiler.TimeRunning(profiler.StartTime or 0, profiler.EndTime or 0, true) .. "s") + end + + return Timer + end + + Header.OnHandleMoved = function() + ItemsXOffset = Header:GetWide() - GProfiler.GetScaledSize(20) + if Button then + Button:SetPos(ItemsXOffset - Button:GetWide(), Button:GetY()) + ItemsXOffset = ItemsXOffset - Button:GetWide() - GProfiler.GetScaledSize(10) + end + if Selector then + Selector:SetPos(ItemsXOffset - Selector:GetWide(), Selector:GetY()) + ItemsXOffset = ItemsXOffset - Selector:GetWide() - GProfiler.GetScaledSize(10) + end + if Timer then + Timer:SetPos(ItemsXOffset - Timer:GetWide() - GProfiler.GetScaledSize(10), Timer:GetY()) + end + end + + return Header end -function GProfiler.ExpressAvailable() return !!((express and express.shSend) and GProfiler.Config.UseExpressNetworking) end +function GProfiler.TimeRunning(startTime, endTime, active) + local time = 0 + + if active then + time = SysTime() - startTime + else + time = endTime - startTime + end + + return string.format("%.2f", time) +end + +function GProfiler.RequestFunctionSource(file, lineStart, lineEnd, callback) + net.Start("GProfiler_RequestFunctionSource") + net.WriteString(file) + net.WriteUInt(lineStart, 32) + net.WriteUInt(lineEnd, 32) + net.SendToServer() + + local lines = {} + net.Receive("GProfiler_RequestFunctionSource", function() + local isFirst = net.ReadBool() + local isLast = net.ReadBool() + local count = net.ReadUInt(32) + for i = 1, count do + table.insert(lines, net.ReadString()) + end + + if isLast then + callback(lines) + end + end) +end \ No newline at end of file diff --git a/lua/gprofiler/sv_init.lua b/lua/gprofiler/sv_init.lua index 8677970..3086426 100644 --- a/lua/gprofiler/sv_init.lua +++ b/lua/gprofiler/sv_init.lua @@ -1,25 +1,25 @@ -util.AddNetworkString("GProfiler.SendState") +-- util.AddNetworkString("GProfiler.SendState") -local Profilers = { - "ConCommands", --[["EntVars",]] "Functions", - "Hooks", "Net", "NetVars", "Timers", "Database" -} +-- local Profilers = { +-- "ConCommands", --[["EntVars",]] "Functions", +-- "Hooks", "Net", "NetVars", "Timers", "Database" +-- } -hook.Add("PlayerInitialSpawn", "GProfiler.SendState", function(ply) - local Active = {} - for _, profiler in ipairs(Profilers) do - if GProfiler[profiler].ProfileStarted then - Active[profiler] = GProfiler[profiler].ProfileStarted - end - end +-- hook.Add("PlayerInitialSpawn", "GProfiler.SendState", function(ply) +-- local Active = {} +-- for _, profiler in ipairs(Profilers) do +-- if GProfiler[profiler].ProfileStarted then +-- Active[profiler] = GProfiler[profiler].ProfileStarted +-- end +-- end - if table.IsEmpty(Active) then return end +-- if table.IsEmpty(Active) then return end - net.Start("GProfiler.SendState") - net.WriteUInt(table.Count(Active), 4) - for profiler, time in pairs(Active) do - net.WriteString(profiler) - net.WriteFloat(SysTime() - time) - end - net.Send(ply) -end) +-- net.Start("GProfiler.SendState") +-- net.WriteUInt(table.Count(Active), 4) +-- for profiler, time in pairs(Active) do +-- net.WriteString(profiler) +-- net.WriteFloat(SysTime() - time) +-- end +-- net.Send(ply) +-- end) From 04738db9730c81545ff7ae315230c2beb1eb9932 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 10 Mar 2026 19:00:49 +0000 Subject: [PATCH 02/16] Sidebar timer --- lua/gprofiler/profilers/functions/cl_functions.lua | 1 - lua/gprofiler/profilers/net/cl_net.lua | 5 +++-- lua/gprofiler/profilers/net/sh_net.lua | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/gprofiler/profilers/functions/cl_functions.lua b/lua/gprofiler/profilers/functions/cl_functions.lua index 329d1a9..33d6a58 100644 --- a/lua/gprofiler/profilers/functions/cl_functions.lua +++ b/lua/gprofiler/profilers/functions/cl_functions.lua @@ -5,5 +5,4 @@ function GProfiler.Functions.Tab(Content) end GProfiler.Menu.RegisterTab("Functions", "gprofiler/functions.png", 3, GProfiler.Functions.Tab, function() - return "00:00", false end) diff --git a/lua/gprofiler/profilers/net/cl_net.lua b/lua/gprofiler/profilers/net/cl_net.lua index adbb8ba..a0c16a0 100644 --- a/lua/gprofiler/profilers/net/cl_net.lua +++ b/lua/gprofiler/profilers/net/cl_net.lua @@ -442,7 +442,7 @@ function GProfiler.Net.DoTab(Base, Outer) PopulateBreakdown(name, breakdownData.Children or {}, data.Size or data[7] or 0) end else - net.Start("GProfiler_Net_RequestServerBreakdown") + net.Start("GProfiler_Net_RequestBreakdown") net.WriteString(name) net.SendToServer() end @@ -636,7 +636,8 @@ function GProfiler.Net.DoTab(Base, Outer) net.SendToServer() end GProfiler.Menu.RegisterTab("Networking", "gprofiler/network.png", 2, GProfiler.Net.DoTab, function() - return "00:00", true + if Net.StartTime == 0 then return end + return GProfiler.TimeRunning(Net.StartTime, Net.EndTime, Net.ProfileActive), Net.ProfileActive end) net.Receive("GProfiler_Net_SendData", function() diff --git a/lua/gprofiler/profilers/net/sh_net.lua b/lua/gprofiler/profilers/net/sh_net.lua index ba82715..18a51f1 100644 --- a/lua/gprofiler/profilers/net/sh_net.lua +++ b/lua/gprofiler/profilers/net/sh_net.lua @@ -368,7 +368,7 @@ if SERVER then end) end) else - net.Receive("gprofiler_nettest", function() - print("got net test") + net.Receive("gprofiler_nettest", function(len) + print("got net test", len) end) end From 57367dfcf5e72bdf6c6f89caba6448c7d64e3105 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 10 Mar 2026 20:30:19 +0000 Subject: [PATCH 03/16] Finish net profiler --- lua/gprofiler/profilers/net/cl_net.lua | 120 ++++++++++++++++++++----- lua/gprofiler/profilers/net/sh_net.lua | 9 +- 2 files changed, 106 insertions(+), 23 deletions(-) diff --git a/lua/gprofiler/profilers/net/cl_net.lua b/lua/gprofiler/profilers/net/cl_net.lua index a0c16a0..b2bd297 100644 --- a/lua/gprofiler/profilers/net/cl_net.lua +++ b/lua/gprofiler/profilers/net/cl_net.lua @@ -58,31 +58,58 @@ function GProfiler.Net.DoTab(Base, Outer) local Source, Breakdown = GProfiler.Utils.HSplitPanel(right, GProfiler.GetScaledSize(10), "net_r_bt", 0.75) local ClientReceivers, ServerReceivers = GProfiler.Utils.VSplitPanel(Receivers, GProfiler.GetScaledSize(10), "net_lb_lr", 0.5) + local SourceHeader = vgui.Create("DLabel", Source) + SourceHeader:SetFont("GProfiler.Inter24") + SourceHeader:SetTextColor(GProfiler.SyntaxColors.comment) + SourceHeader:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(5)) + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + SourceHeader:SetText("Select a network message to view source.") + Source.Paint = function(s, w, h) GProfiler.RNDX.Draw(8, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) end local RichText = vgui.Create("RichText", Source) - RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20)) - RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(10)) + RichText:SetText("") + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) RichText:SetVerticalScrollbarEnabled(true) function RichText:PerformLayout() self:SetFontInternal("GProfiler.Code") end Source.OnHandleMoved = function() - RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20)) - RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(10)) + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) RichText:InvalidateLayout() end local BreakdownPanel = vgui.Create("DScrollPanel", Breakdown) BreakdownPanel:Dock(FILL) + local function SetDefaultBreakdownState() + BreakdownPanel:Clear() + local pnl = vgui.Create("DPanel", BreakdownPanel) + pnl:Dock(FILL) + pnl:DockMargin(0, 0, 0, 0) + pnl.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) + draw.SimpleText("Select a network message to view breakdown", "GProfiler.Inter24", GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(14), GProfiler.SyntaxColors.comment, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + pnl:SetTall(BreakdownPanel:GetTall()) + BreakdownPanel.OnSizeChanged = function(s, w, h) + if IsValid(pnl) then pnl:SetTall(h) end + end + end + SetDefaultBreakdownState() + local function FormatBits(bits) - if not bits or bits == 0 then return "0 Bytes" end + if not bits or bits < 0.01 then return "0 Bytes" end + if not isnumber(bits) then return "oh fiddlesticks, what now?" end if bits < 8 then - return bits .. (bits == 1 and " Bit" or " Bits") + return math.Round(bits, 2) .. (bits == 1 and " Bit" or " Bits") end return string.NiceSize(bits / 8) end @@ -170,8 +197,8 @@ function GProfiler.Net.DoTab(Base, Outer) return ResultsList end - local ResultsList = CreateList(ResultsSent, {"Name", "Count", "Size", "Total", "Avg Time"}) - local ReceivedList = CreateList(ResultsReceived, {"Name", "Count", "Size", "Total", "Avg Time"}) + local ResultsList = CreateList(ResultsSent, {"Name", "Count", "Size (Largest)", "Size (Total)", "Time (Total)", "Time (Avg)", "Time (Longest)"}) + local ReceivedList = CreateList(ResultsReceived, {"Name", "Count", "Size (Largest)", "Size (Total)", "Time (Total)", "Time (Avg)", "Time (Longest)"}) Results.OnHandleMoved = function() ResultsList:SetSize(Results:GetWide(), Results:GetTall() - SentHeader:GetTall()) @@ -391,7 +418,7 @@ function GProfiler.Net.DoTab(Base, Outer) end end CalcHeight(RootNodes) - s:SetTall(h + GProfiler.GetScaledSize(10)) + s:SetTall(math.max(h + GProfiler.GetScaledSize(10), BreakdownPanel:GetTall())) end Canvas.OnMousePressed = function(s, code) @@ -411,11 +438,27 @@ function GProfiler.Net.DoTab(Base, Outer) end function ResultsList:OnRowSelected(rowIndex, row) + ReceivedList:ClearSelection() local name = row:GetColumnText(1) local data = GProfiler.Net.ProfileData.Out and GProfiler.Net.ProfileData.Out[name] if data then BreakdownPanel:Clear() + + local Loading = vgui.Create("DPanel", BreakdownPanel) + Loading:Dock(FILL) + Loading:DockMargin(0, 0, 0, 0) + Loading.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) + draw.SimpleText("Loading breakdown...", "GProfiler.Inter24", GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(14), GProfiler.SyntaxColors.comment, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + Loading:SetTall(BreakdownPanel:GetTall()) + BreakdownPanel.OnSizeChanged = function(s, w, h) + if IsValid(Loading) then Loading:SetTall(h) end + end + + RichText:SetText("Loading source...") + SourceHeader:SetText("") local file = data.Source or data[4] local lineDefined = data.LineDefined or data[5] or 0 @@ -424,6 +467,8 @@ function GProfiler.Net.DoTab(Base, Outer) if file and file ~= "" then file = string.match(file, "@?(.+)") if not file then file = data.Source or data[4] end + + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) if src then @@ -435,13 +480,20 @@ function GProfiler.Net.DoTab(Base, Outer) else RichText:SetText("Failed to load source (2)") end - + if Net.Realm == "Client" then local breakdownData = GProfiler.Net.Breakdowns and GProfiler.Net.Breakdowns[name] if breakdownData then PopulateBreakdown(name, breakdownData.Children or {}, data.Size or data[7] or 0) end else + GProfiler.Net.UpdateBreakdownUI = function(msg) + if msg ~= name then return end + local d = GProfiler.Net.Breakdowns and GProfiler.Net.Breakdowns[name] + if d then PopulateBreakdown(name, d.Nodes, d.Size) end + GProfiler.Net.UpdateBreakdownUI = nil + end + net.Start("GProfiler_Net_RequestBreakdown") net.WriteString(name) net.SendToServer() @@ -450,12 +502,28 @@ function GProfiler.Net.DoTab(Base, Outer) end function ReceivedList:OnRowSelected(rowIndex, row) + ResultsList:ClearSelection() local name = row:GetColumnText(1) local data = GProfiler.Net.ProfileData.Inc and GProfiler.Net.ProfileData.Inc[name] if data then BreakdownPanel:Clear() + local Header = vgui.Create("DPanel", BreakdownPanel) + Header:Dock(FILL) + Header:DockMargin(0, 0, 0, 0) + Header.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) + draw.SimpleText("Breakdown only available for sent messages", "GProfiler.Inter24", GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(14), GProfiler.SyntaxColors.comment, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + Header:SetTall(BreakdownPanel:GetTall()) + BreakdownPanel.OnSizeChanged = function(s, w, h) + if IsValid(Header) then Header:SetTall(h) end + end + + RichText:SetText("Loading source...") + SourceHeader:SetText("") + local file = data.Source or data[4] local lineDefined = data.LineDefined or data[5] or 0 local lastLineDefined = data.LastLineDefined or data[6] or 0 @@ -463,6 +531,8 @@ function GProfiler.Net.DoTab(Base, Outer) if file and file ~= "" then file = string.match(file, "@?(.+)") if not file then file = data.Source or data[4] end + + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) if src then @@ -483,24 +553,30 @@ function GProfiler.Net.DoTab(Base, Outer) ResultsList:Clear() if GProfiler.Net.ProfileData.Out then for name, data in pairs(GProfiler.Net.ProfileData.Out) do - local count = data.Count or data[1] or 0 - local maxSize = data.MaxSize or data[2] or 0 - local totalSize = data.TotalSize or data[3] or 0 - local avgTime = data.AverageTime or data[9] or 0 - - ResultsList:AddLine(name, count, FormatBits(maxSize), FormatBits(totalSize), math.Round(avgTime * 1000, 3) .. "ms") + ResultsList:AddLine( + name, + data.Count or data[1] or 0, + FormatBits(data.MaxSize or data[2] or 0), + FormatBits(data.TotalSize or data[3] or 0), + data.TotalTime or data[7] or 0, + data.AverageTime or data[9] or 0, + data.LongestTime or data[8] or 0 + ) end end ReceivedList:Clear() if GProfiler.Net.ProfileData.Inc then for name, data in pairs(GProfiler.Net.ProfileData.Inc) do - local count = data.Count or data[1] or 0 - local maxSize = data.MaxSize or data[2] or 0 - local totalSize = data.TotalSize or data[3] or 0 - local avgTime = data.AverageTime or data[9] or 0 - - ReceivedList:AddLine(name, count, FormatBits(maxSize), FormatBits(totalSize), math.Round(avgTime * 1000, 3) .. "ms") + ReceivedList:AddLine( + name, + data.Count or data[1] or 0, + FormatBits(data.MaxSize or data[2] or 0), + FormatBits(data.TotalSize or data[3] or 0), + data.TotalTime or data[7] or 0, + data.AverageTime or data[9] or 0, + data.LongestTime or data[8] or 0 + ) end end end diff --git a/lua/gprofiler/profilers/net/sh_net.lua b/lua/gprofiler/profilers/net/sh_net.lua index 18a51f1..250421c 100644 --- a/lua/gprofiler/profilers/net/sh_net.lua +++ b/lua/gprofiler/profilers/net/sh_net.lua @@ -55,7 +55,8 @@ local function DetourOutgoing() GProfiler.Net.CurrentMsg = { Name = nameLower, Stack = {}, - Root = { Children = {} } + Root = { Children = {} }, + StartTime = SysTime() } GProfiler.Net.CurrentMsg.Stack[1] = GProfiler.Net.CurrentMsg.Root @@ -76,10 +77,16 @@ local function DetourOutgoing() local bytes, bits = net.BytesWritten() local size = bits or (bytes * 8) + local dt = SysTime() - (GProfiler.Net.CurrentMsg.StartTime or SysTime()) + d[1] = d[1] + 1 d[2] = max(d[2], size) d[3] = d[3] + size + d[7] = d[7] + dt + d[8] = max(d[8], dt) + d[9] = d[7] / d[1] + if not d[4] then local src = debug.getinfo(3, "S") if src then From c117774572526d965ecdae1f25a9e4f63a82d803 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Wed, 11 Mar 2026 13:24:07 +0000 Subject: [PATCH 04/16] Make sure net ui actually refreshes --- lua/gprofiler/profilers/net/cl_net.lua | 54 +++++++++++++------------- lua/gprofiler/profilers/net/sh_net.lua | 51 +++++------------------- lua/gprofiler/sh_access.lua | 2 +- lua/gprofiler/sv_init.lua | 3 ++ 4 files changed, 42 insertions(+), 68 deletions(-) diff --git a/lua/gprofiler/profilers/net/cl_net.lua b/lua/gprofiler/profilers/net/cl_net.lua index b2bd297..ce8ded8 100644 --- a/lua/gprofiler/profilers/net/cl_net.lua +++ b/lua/gprofiler/profilers/net/cl_net.lua @@ -1,12 +1,24 @@ GProfiler.Net = GProfiler.Net or {} local Net = GProfiler.Net --- Net.StartTime = Net.StartTime or 0 --- Net.EndTime = Net.EndTime or 0 --- Net.Realm = Net.Realm or "Client" -Net.StartTime = 0 -Net.EndTime = 0 -Net.Realm = "Client" +Net.StartTime = Net.StartTime or 0 +Net.EndTime = Net.EndTime or 0 +Net.Realm = Net.Realm or "Client" + +local function FormatBits(bits) + if not bits or bits < 0.01 then return "0 Bytes" end + if not isnumber(bits) then return "oh fiddlesticks, what now?" end + + if bits < 8 then return math.Round(bits, 2) .. (bits == 1 and " Bit" or " Bits") end + + local bytes = bits / 8 + if bytes < 1024 then return math.Round(bytes, 2) .. (bytes == 1 and " Byte" or " Bytes") end + + local kb = bytes / 1024 + if kb < 1024 then return math.Round(kb, 2) .. " KB" end + local mb = kb / 1024 + return math.Round(mb, 2) .. " MB" +end function GProfiler.Net.DoTab(Base, Outer) local Header = GProfiler.Utils.SetupHeader(Outer, "Networking", "gprofiler/network.png") @@ -38,6 +50,7 @@ function GProfiler.Net.DoTab(Base, Outer) else Net.StartTime = SysTime() Net.EndTime = 0 + Net.ProfileData = {} if Net.Realm == "Client" then GProfiler.Net:StartProfiler() @@ -45,9 +58,12 @@ function GProfiler.Net.DoTab(Base, Outer) net.Start("GProfiler_Net_ToggleServerProfile") net.WriteBool(true) net.SendToServer() - GProfiler.Net.ProfileData = {} end end + + if Net.RefreshUI then + Net.RefreshUI() + end end function RealmSelector:OnStateChanged(state) Net.Realm = state end @@ -95,7 +111,7 @@ function GProfiler.Net.DoTab(Base, Outer) pnl:DockMargin(0, 0, 0, 0) pnl.Paint = function(s, w, h) GProfiler.RNDX.Draw(0, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) - draw.SimpleText("Select a network message to view breakdown", "GProfiler.Inter24", GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(14), GProfiler.SyntaxColors.comment, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText("Select a network message to view breakdown.", "GProfiler.Inter24", GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(14), GProfiler.SyntaxColors.comment, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end pnl:SetTall(BreakdownPanel:GetTall()) @@ -105,15 +121,6 @@ function GProfiler.Net.DoTab(Base, Outer) end SetDefaultBreakdownState() - local function FormatBits(bits) - if not bits or bits < 0.01 then return "0 Bytes" end - if not isnumber(bits) then return "oh fiddlesticks, what now?" end - if bits < 8 then - return math.Round(bits, 2) .. (bits == 1 and " Bit" or " Bits") - end - return string.NiceSize(bits / 8) - end - local SentHeader = GProfiler.Utils.SetupHeader(ResultsSent, "Messages Sent", nil, true) local ReceivedHeader = GProfiler.Utils.SetupHeader(ResultsReceived, "Messages Received", nil, true) @@ -444,7 +451,7 @@ function GProfiler.Net.DoTab(Base, Outer) if data then BreakdownPanel:Clear() - + local Loading = vgui.Create("DPanel", BreakdownPanel) Loading:Dock(FILL) Loading:DockMargin(0, 0, 0, 0) @@ -467,7 +474,7 @@ function GProfiler.Net.DoTab(Base, Outer) if file and file ~= "" then file = string.match(file, "@?(.+)") if not file then file = data.Source or data[4] end - + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) @@ -480,7 +487,7 @@ function GProfiler.Net.DoTab(Base, Outer) else RichText:SetText("Failed to load source (2)") end - + if Net.Realm == "Client" then local breakdownData = GProfiler.Net.Breakdowns and GProfiler.Net.Breakdowns[name] if breakdownData then @@ -531,7 +538,7 @@ function GProfiler.Net.DoTab(Base, Outer) if file and file ~= "" then file = string.match(file, "@?(.+)") if not file then file = data.Source or data[4] end - + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) @@ -583,11 +590,6 @@ function GProfiler.Net.DoTab(Base, Outer) GProfiler.Net.RefreshUI = PopulateResults PopulateResults() - Base.OnRemove = function() - GProfiler.Net.RefreshUI = nil - GProfiler.Net.UpdateBreakdownUI = nil - end - local function CreateReceiverList(Parent, Receivers, Title) Parent:Clear() diff --git a/lua/gprofiler/profilers/net/sh_net.lua b/lua/gprofiler/profilers/net/sh_net.lua index 250421c..000af8a 100644 --- a/lua/gprofiler/profilers/net/sh_net.lua +++ b/lua/gprofiler/profilers/net/sh_net.lua @@ -123,8 +123,8 @@ local function DetourOutgoing() GProfiler.Net.OriginalWrites[funcName] = net[funcName] net[funcName] = function(...) - if not GProfiler.Net.CurrentMsg then - return GProfiler.Net.OriginalWrites[funcName](...) + if not GProfiler.Net.CurrentMsg then + return GProfiler.Net.OriginalWrites[funcName](...) end local entry = AddEntry(funcName, nil, ...) @@ -132,7 +132,7 @@ local function DetourOutgoing() table.insert(GProfiler.Net.CurrentMsg.Stack, entry) local startB, startBits = net.BytesWritten() - local ret = {GProfiler.Net.OriginalWrites[funcName](...)} + local ret = {GProfiler.Net.OriginalWrites[funcName](...)} local endB, endBits = net.BytesWritten() startBits = startBits or (startB * 8) @@ -231,6 +231,10 @@ function GProfiler.Net:RestoreNet(ply) net.Incoming = GProfiler.Net.OriginalIncoming RestoreOutgoing() + if GProfiler.Net.RefreshUI then + GProfiler.Net.RefreshUI() + end + if CLIENT then return end local function SendData(dataMap, isIncoming) @@ -319,13 +323,13 @@ if SERVER then local breakdownData = GProfiler.Net.Breakdowns[name] net.Start("GProfiler_Net_SendBreakdown") - net.WriteString(name) + net.WriteString(name) -- TODO: assign an network id for these! if breakdownData then net.WriteBool(true) net.WriteUInt(breakdownData.Size, 32) local function WriteNode(node) - net.WriteString(node.Func) + net.WriteString(node.Func) -- ^ net.WriteUInt(node.Size, 32) net.WriteUInt(#node.Children, 16) for _, child in ipairs(node.Children) do @@ -343,39 +347,4 @@ if SERVER then end net.Send(ply) end) - - concommand.Add("gprofiler_nettest", function(ply, cmd, args) - if IsValid(ply) and not GProfiler.Access.HasAccess(ply) then return end - - GProfiler.Net:StartProfiler(ply) - - timer.Simple(0, function() - net.Start("GProfiler_NetTest") - net.WriteAngle(Angle(1,2,3)) - net.WriteBit(1) - net.WriteBool(true) - net.WriteColor(Color(255, 0, 0, 255)) - net.WriteData("test", 4) - net.WriteDouble(1.23) - net.WriteFloat(4.56) - net.WriteInt(123, 32) - net.WriteString("a") - net.WriteType("string") - net.WriteUInt(456, 32) - net.WriteUInt64(ply:SteamID64()) - net.WriteVector(Vector(7,8,9)) - for i=1, 100 do - net.WriteString("test" .. i) - end - net.Broadcast() - end) - - timer.Simple(3.1, function() - GProfiler.Net:RestoreNet(ply) - end) - end) - else - net.Receive("gprofiler_nettest", function(len) - print("got net test", len) - end) - end +end diff --git a/lua/gprofiler/sh_access.lua b/lua/gprofiler/sh_access.lua index e126b8b..f8c61e3 100644 --- a/lua/gprofiler/sh_access.lua +++ b/lua/gprofiler/sh_access.lua @@ -102,7 +102,7 @@ hook.Add("Initialize", "GProfiler.Access.Register", function() end) function GProfiler.Access.HasAccess(ply) - if true then return true end + if GetGlobalBool("gprofiler_lan", false) then return true end if ply:EntIndex() == 0 then return true end -- Console if GProfiler.Config.AllowedSteamIDs[ply:SteamID64()] or GProfiler.Config.AllowedSteamIDs[ply:SteamID()] then return true end if not GProfiler.Access.AdminSystem then return false end diff --git a/lua/gprofiler/sv_init.lua b/lua/gprofiler/sv_init.lua index 3086426..c4406e8 100644 --- a/lua/gprofiler/sv_init.lua +++ b/lua/gprofiler/sv_init.lua @@ -23,3 +23,6 @@ -- end -- net.Send(ply) -- end) + +local lan = GetConVar("sv_lan") +if lan:GetBool() then SetGlobalBool("gprofiler_lan", true) end \ No newline at end of file From 16d0d34640485ec05bd05998e10af3fad4e45b8d Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Wed, 11 Mar 2026 13:24:17 +0000 Subject: [PATCH 05/16] Overview graphs --- lua/gprofiler/modules/overview/cl_init.lua | 116 ++++++- .../modules/overview/sv_overview.lua | 34 ++ lua/gprofiler/modules/utils/ui/cl_graphs.lua | 313 +++++++++++------- 3 files changed, 336 insertions(+), 127 deletions(-) create mode 100644 lua/gprofiler/modules/overview/sv_overview.lua diff --git a/lua/gprofiler/modules/overview/cl_init.lua b/lua/gprofiler/modules/overview/cl_init.lua index a3ada97..6f7cf7b 100644 --- a/lua/gprofiler/modules/overview/cl_init.lua +++ b/lua/gprofiler/modules/overview/cl_init.lua @@ -1,9 +1,121 @@ GProfiler.Overview = GProfiler.Overview or {} +local function CreateQueue(name, defaultCapacity) + local SavedCapacity = cookie.GetNumber("gprofiler_overview_" .. name .. "_capacity", defaultCapacity) + return GProfiler.Utils.Graphs.ConstantLengthNumericalQueue(SavedCapacity) +end + +hook.Add("GProfiler.Loaded", "GProfiler.Overview.Init", function() + GProfiler.Overview.Data = GProfiler.Overview.Data or { + CL = { + Frametime = CreateQueue("cl_frametime", 512), + Simtime = CreateQueue("cl_simtime", 512), + MemUsage = CreateQueue("cl_memusage", 512), + FPS = CreateQueue("cl_fps", 512) + }, + SV = { + Frametime = CreateQueue("sv_frametime", 512), + Simtime = CreateQueue("sv_simtime", 512), + MemUsage = CreateQueue("sv_memusage", 512) + } + } +end) + function GProfiler.Overview.DoTab(Base) - + GProfiler.Overview.StartUpdating() + + net.Start("GProfiler.OverviewSubscribe") + net.WriteBool(true) + net.SendToServer() + + Base.OnRemove = function(s) + net.Start("GProfiler.OverviewSubscribe") + net.WriteBool(false) + net.SendToServer() + end + + local Container = vgui.Create("DPanel", Base) + Container:Dock(FILL) + Container.Paint = nil + + local TopPnl, BottomPnl = GProfiler.Utils.HSplitPanel(Container, 4, "overview_split", 0.5) + + local function AddHeader(pnl, title, icon) + local header, lbl = GProfiler.Menu.CreateHeader(pnl, title, 0, 0, pnl:GetWide(), 32, true) + header:Dock(TOP) + + if IsValid(lbl) and icon then + local iconImg = vgui.Create("DImage", header) + iconImg:SetImage(icon) + iconImg:SetSize(20, 20) + iconImg:SetPos(8, 6) + + local x, y = lbl:GetPos() + lbl:SetPos(36, y) + end + return header + end + + local function AddGraphEntry(parent, title, queue, color, suffix, formatter) + local g = vgui.Create("GP.Graph", parent) + g:Dock(TOP) + g:SetTall(150) + g:DockMargin(8, 8, 8, 0) + g:SetTitle(title) + g:AddSegment(queue, color, suffix, formatter) + return g + end + + -- AddHeader(TopPnl, "Client Performance", "icon16/monitor.png") + local ClientScroll = vgui.Create("DScrollPanel", TopPnl) + ClientScroll:Dock(FILL) + + AddGraphEntry(ClientScroll, "Frametime", GProfiler.Overview.Data.CL.Frametime, Color(48, 160, 220), " ms", function(v) return string.format("%.2f", v * 1000) end) + AddGraphEntry(ClientScroll, "Simtime", GProfiler.Overview.Data.CL.Simtime, Color(220, 160, 48), " ms", function(v) return string.format("%.2f", v * 1000) end) + AddGraphEntry(ClientScroll, "Memory", GProfiler.Overview.Data.CL.MemUsage, Color(160, 48, 220), " MiB", function(v) return string.format("%.1f", v / 1024) end) + AddGraphEntry(ClientScroll, "FPS", GProfiler.Overview.Data.CL.FPS, Color(48, 220, 160), " FPS", function(v) return string.format("%.0f", v) end) + + -- AddHeader(BottomPnl, "Server Performance", "icon16/server.png") + local ServerScroll = vgui.Create("DScrollPanel", BottomPnl) + ServerScroll:Dock(FILL) + + AddGraphEntry(ServerScroll, "Frametime", GProfiler.Overview.Data.SV.Frametime, Color(220, 80, 80), " ms", function(v) return string.format("%.2f", v * 1000) end) + AddGraphEntry(ServerScroll, "Simtime", GProfiler.Overview.Data.SV.Simtime, Color(220, 140, 60), " ms", function(v) return string.format("%.2f", v * 1000) end) + AddGraphEntry(ServerScroll, "Memory", GProfiler.Overview.Data.SV.MemUsage, Color(140, 60, 220), " MiB", function(v) return string.format("%.1f", v / 1024) end) end GProfiler.Menu.RegisterTab("Overview", "gprofiler/home.png", 0, GProfiler.Overview.DoTab, function() -end) \ No newline at end of file +end) + +net.Receive("GProfiler.OverviewUpdate", function() + GProfiler.Overview.Data.SV.Frametime:Add(net.ReadFloat()) + GProfiler.Overview.Data.SV.Simtime:Add(net.ReadFloat()) + GProfiler.Overview.Data.SV.MemUsage:Add(net.ReadUInt(32)) + + -- print(string.format("Server - Frametime: %.2fms, Simtime: %.2fms, MemUsage: %.2fmb", + -- GProfiler.Overview.Data.SV.Frametime:Average() * 1000, + -- GProfiler.Overview.Data.SV.Simtime:Average() * 1000, + -- GProfiler.Overview.Data.SV.MemUsage:Average() / 1024 + -- )) +end) + +function GProfiler.Overview.StartUpdating() + local lastUpdate = 0 + local updateInterval = .025 + hook.Add("Think", "GProfiler.Overview.UpdateData", function() + if CurTime() - lastUpdate < updateInterval then return end + lastUpdate = CurTime() + GProfiler.Overview.Data.CL.Frametime:Add(FrameTime()) + GProfiler.Overview.Data.CL.Simtime:Add(physenv.GetLastSimulationTime()) + GProfiler.Overview.Data.CL.MemUsage:Add(collectgarbage("count")) + GProfiler.Overview.Data.CL.FPS:Add(1 / FrameTime()) + + -- print(string.format("Client - Frametime: %.2fms, Simtime: %.2fms, MemUsage: %.2fmb, FPS: %.2f", + -- GProfiler.Overview.Data.CL.Frametime:Average() * 1000, + -- GProfiler.Overview.Data.CL.Simtime:Average() * 1000, + -- GProfiler.Overview.Data.CL.MemUsage:Average() / 1024, + -- GProfiler.Overview.Data.CL.FPS:Average() + -- )) + end) +end \ No newline at end of file diff --git a/lua/gprofiler/modules/overview/sv_overview.lua b/lua/gprofiler/modules/overview/sv_overview.lua new file mode 100644 index 0000000..8c99913 --- /dev/null +++ b/lua/gprofiler/modules/overview/sv_overview.lua @@ -0,0 +1,34 @@ +util.AddNetworkString("GProfiler.OverviewSubscribe") +util.AddNetworkString("GProfiler.OverviewUpdate") + +GProfiler.Overview = GProfiler.Overview or {} +GProfiler.Overview.SubscribedPlayers = GProfiler.Overview.SubscribedPlayers or {} + +net.Receive("GProfiler.OverviewSubscribe", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + local subscribe = net.ReadBool() + table[subscribe and "insert" or "RemoveByValue"](GProfiler.Overview.SubscribedPlayers, ply) + + GProfiler.Overview.OnSubscriptionUpdate() +end) + +function GProfiler.Overview.OnSubscriptionUpdate() + if #GProfiler.Overview.SubscribedPlayers == 0 then timer.Remove("GProfiler.Overview.SendData") return end + if timer.Exists("GProfiler.Overview.SendData") then return end + + timer.Create("GProfiler.Overview.SendData", 0, 0, function() + for i = #GProfiler.Overview.SubscribedPlayers, 1, -1 do + if not IsValid(GProfiler.Overview.SubscribedPlayers[i]) then + table.remove(GProfiler.Overview.SubscribedPlayers, i) + end + end + + if #GProfiler.Overview.SubscribedPlayers == 0 then timer.Remove("GProfiler.Overview.SendData") return end + + net.Start("GProfiler.OverviewUpdate") + net.WriteFloat(FrameTime()) + net.WriteFloat(physenv.GetLastSimulationTime()) + net.WriteUInt(collectgarbage("count"), 32) + net.Send(GProfiler.Overview.SubscribedPlayers) + end) +end diff --git a/lua/gprofiler/modules/utils/ui/cl_graphs.lua b/lua/gprofiler/modules/utils/ui/cl_graphs.lua index dcda04f..bb9e95c 100644 --- a/lua/gprofiler/modules/utils/ui/cl_graphs.lua +++ b/lua/gprofiler/modules/utils/ui/cl_graphs.lua @@ -1,125 +1,188 @@ --- TODO + credits to whoever posted this in gmod discord - --- local MAX_DEBUG_ITEMS = 512 --- local function ConstantLengthNumericalQueue(capacity) --- local obj = {} --- local pointer = 0 --- local length = 0 --- local startat = 0 --- local backing = {} --- for i = 1, capacity do --- backing[i - 1] = 0 --- end --- function obj:Add(item) --- if length < capacity then --- length = length + 1 --- else --- startat = startat + 1 --- if startat >= capacity then --- startat = 0 --- end --- end - --- if pointer >= capacity then pointer = 0 end --- backing[pointer] = item --- pointer = pointer + 1 --- end --- function obj:Get(i) --- return backing[(i + startat) % capacity] --- end - --- function obj:Length() return length end - - --- function obj:Start() return startat end - --- function obj:Min() --- local ret = 0 --- for i = 1, length do --- ret = math.min(ret, backing[i - 1]) --- end --- return ret --- end - --- function obj:Max() --- local ret = 0 --- for i = 1, length do --- ret = math.max(ret, backing[i - 1]) --- end --- return ret --- end - --- function obj:Average() --- local ret = 0 --- for i = 1, length do --- ret = ret + backing[i - 1] --- end --- return ret / length --- end - --- return obj --- end - --- local perfgraph = ConstantLengthNumericalQueue(MAX_DEBUG_ITEMS) - --- -- during frames call perfgraph:Add(v) - --- local v1, v2 = Vector(), Vector() --- function draw.Line(startX, startY, endX, endY, thickness, color) --- if not startX then return end --- if not startY then return end --- if not endX then return end --- if not endY then return end - --- thickness = thickness or 1 --- color = color or color_white - --- local x, y = endX - startX, endY - startY --- local cx, cy = (startX + endX) / 2, (startY + endY) / 2 --- local dist = math.sqrt((x^2) + (y^2)) - --- local a = -math.atan2(y, x) --- local s, c = math.sin(a), math.cos(a) - --- v1:SetUnpacked(cx, cy, 0) --- v2:SetUnpacked(s, c, -thickness) --- mesh.Begin(MATERIAL_QUADS, 1) --- xpcall(function() --- mesh.QuadEasy(v1, v2, dist, thickness, color) --- mesh.End() --- end, function() mesh.End() print(debug.traceback(err)) end) --- end - --- local color_grey = Color(173, 173, 173) --- local formatString = "%.2f" --- local formatString2 = "avg: %.2f" --- local function DrawGraph(data, label, x, y, c) --- local w, h = 450, 64 --- surface.SetDrawColor(20, 25, 35, 200) --- surface.DrawRect(x, y, w, h) - --- local xPadding = 48 - --- local count, min, max, avg = data:Length(), data:Min(), data:Max(), data:Average() --- draw.Line(x + xPadding, y + 4, x + xPadding, y + h - 4, 2, color_grey) --- draw.Line(x + xPadding, y + h - 4, x + w - 4, y + h - 4, 2, color_grey) --- for i = 0, MAX_DEBUG_ITEMS - 1 do --- if i + 1 >= count then break end - --- local finalPos = i + 1 --- local x1 = x + xPadding + 4 + math.Remap(i, 0, MAX_DEBUG_ITEMS, 0, w - xPadding - 8) --- local x2 = x + xPadding + 4 + math.Remap(finalPos, 0, MAX_DEBUG_ITEMS, 0, w - xPadding - 8) --- local y1 = data:Get(i) --- local y2 = data:Get(finalPos) - --- draw.Line( --- x1, y + math.Remap(y1, min, max, h - 4, 16), --- x2, y + math.Remap(y2, min, max, h - 4, 16), --- 3, c) --- end - --- draw.SimpleText(label, "DebugFixed", x + (w / 2), y, color_white, TEXT_ALIGN_CENTER) --- draw.SimpleText(formatString:format(max), "DebugFixed", x + xPadding - 4, y, color_white, TEXT_ALIGN_RIGHT) --- draw.SimpleText(formatString:format(min), "DebugFixed", x + xPadding - 4, y + h, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM) --- draw.SimpleText(formatString2:format(avg), "DebugFixed", x + w - 4, y, color_white, TEXT_ALIGN_RIGHT) --- end \ No newline at end of file +GProfiler.Utils.Graphs = GProfiler.Utils.Graphs or {} + +function GProfiler.Utils.Graphs.ConstantLengthNumericalQueue(capacity) + -- Thanks to https://github.com/ACF-Team/ACF-3-DevTools + local obj = {} + obj.Divisor = 1 + + local pointer = 0 + local length = 0 + local startat = 0 + local backing = {} + for i = 1, capacity do backing[i - 1] = 0 end + + function obj:Add(item) + if length < capacity then + length = length + 1 + else + startat = startat + 1 + if startat >= capacity then startat = 0 end + end + + if pointer >= capacity then pointer = pointer % capacity end + + backing[pointer] = item + pointer = pointer + 1 + end + + function obj:Length() return length end + function obj:Start() return startat end + function obj:Get(i) return backing[(i + startat) % capacity] / self.Divisor end + + function obj:Min() + local ret = math.huge + for i = 1, length do ret = math.min(ret, backing[i - 1]) end + return ret / self.Divisor + end + + function obj:Max() + local ret = 0 + for i = 1, length do ret = math.max(ret, backing[i - 1]) end + return ret / self.Divisor + end + + function obj:Average() + local ret = 0 + for i = 1, length do ret = ret + backing[i - 1] end + return ret / length / self.Divisor + end + + function obj:Resize(newCapacity) + if newCapacity == capacity then return end + + local newBacking = {} + for i = 1, newCapacity do newBacking[i - 1] = 0 end + + for i = 1, math.min(length, newCapacity) do + newBacking[i - 1] = backing[(startat + i - 1) % capacity] + end + + backing = newBacking + capacity = newCapacity + startat = 0 + pointer = math.min(length, newCapacity) + length = math.min(length, newCapacity) + end + + return obj +end + +surface.CreateFont("GProfiler.Graph.Title", { font = "Roboto", size = 20, weight = 500, antialias = true }) +surface.CreateFont("GProfiler.Graph.ValueLarge", { font = "Roboto", size = 32, weight = 800, antialias = true }) + +local PANEL = {} + +AccessorFunc(PANEL, "m_strTitle", "Title", FORCE_STRING) +AccessorFunc(PANEL, "m_fVisualMax", "VisualMax", FORCE_NUMBER) +AccessorFunc(PANEL, "m_MainIcon", "Icon") + +function PANEL:Init() + self.Data = {} + self:SetTitle("Graph") + self:SetVisualMax(1) +end + +function PANEL:AddSegment(queue, color, suffix, formatter, icon) + if isstring(icon) then icon = Material(icon) end + table.insert(self.Data, { + queue = queue, + color = color, + textColor = Color(color.r * 1.5, color.g * 1.5, color.b * 1.5), + suffix = suffix or "", + formatter = formatter, + icon = icon + }) +end + +function PANEL:SetMainIcon(iconMat) + if isstring(iconMat) then + self.MainIcon = Material(iconMat) + else + self.MainIcon = iconMat + end +end + +function PANEL:Paint(w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(16, 16, 24)) + + local titleX = 16 + if self.MainIcon then + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(self.MainIcon) + surface.DrawTexturedRect(16, 16, 20, 20) + titleX = 16 + 20 + 8 + end + + draw.SimpleText(self:GetTitle(), "GProfiler.Graph.Title", titleX, 16, Color(220, 220, 220), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + local iconX = w - 16 + for i = #self.Data, 1, -1 do + local seg = self.Data[i] + if seg.icon then + surface.SetDrawColor(seg.color) + surface.SetMaterial(seg.icon) + surface.DrawTexturedRect(iconX - 20, 16, 20, 20) + iconX = iconX - 20 - 8 + end + end + + local currentMax = 0 + for _, seg in ipairs(self.Data) do + local m = seg.queue:Max() + if m > currentMax then currentMax = m end + end + + self:SetVisualMax(Lerp(FrameTime() * 5, self:GetVisualMax(), currentMax)) + local max = self:GetVisualMax() + if max == 0 or max ~= max then max = 1 end + + for _, seg in ipairs(self.Data) do + local queue = seg.queue + local count = queue:Length() + if count < 2 then continue end + + local col = seg.color + surface.SetDrawColor(Color(col.r, col.g, col.b, 150)) + draw.NoTexture() + + for i = 0, count - 2 do + local val1 = queue:Get(i) + local x1 = (i / (count - 1)) * w + local y1 = h - (val1 / max) * (h * 0.60) + local val2 = queue:Get(i + 1) + local x2 = ((i + 1) / (count - 1)) * w + local y2 = h - (val2 / max) * (h * 0.60) + + local quad = { + { x = x1, y = h }, + { x = x1, y = y1 }, + { x = x2, y = y2 }, + { x = x2, y = h } + } + surface.DrawPoly(quad) + end + end + + local leftX = 16 + local rightX = w - 16 + + for i, seg in ipairs(self.Data) do + local val = seg.queue:Get(seg.queue:Length() - 1) + if not val then val = 0 end + + local txt = string.format("%.2f", val) + if seg.formatter then txt = seg.formatter(val) end + + local fullText = txt .. " " .. (seg.suffix or "") + + if i == 1 then + draw.SimpleText(fullText, "GProfiler.Graph.ValueLarge", rightX, 16, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + rightX = rightX - surface.GetTextSize(fullText) - 8 + else -- todo: multiple segments is nice, but needs to look better + draw.SimpleText(fullText, "GProfiler.Graph.ValueLarge", rightX, 16, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + rightX = rightX - surface.GetTextSize(fullText) - 8 + end + end +end + +vgui.Register("GP.Graph", PANEL, "DPanel") \ No newline at end of file From 48ed51034ce5ccfa3cc2804127c7291d880e0646 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Wed, 11 Mar 2026 17:14:27 +0000 Subject: [PATCH 06/16] Massively reduce strain on gc from graphs --- lua/gprofiler/modules/overview/cl_init.lua | 48 ++++++-------------- lua/gprofiler/modules/utils/ui/cl_graphs.lua | 41 +++++++++++------ 2 files changed, 41 insertions(+), 48 deletions(-) diff --git a/lua/gprofiler/modules/overview/cl_init.lua b/lua/gprofiler/modules/overview/cl_init.lua index 6f7cf7b..118b168 100644 --- a/lua/gprofiler/modules/overview/cl_init.lua +++ b/lua/gprofiler/modules/overview/cl_init.lua @@ -38,50 +38,32 @@ function GProfiler.Overview.DoTab(Base) Container:Dock(FILL) Container.Paint = nil - local TopPnl, BottomPnl = GProfiler.Utils.HSplitPanel(Container, 4, "overview_split", 0.5) - - local function AddHeader(pnl, title, icon) - local header, lbl = GProfiler.Menu.CreateHeader(pnl, title, 0, 0, pnl:GetWide(), 32, true) - header:Dock(TOP) - - if IsValid(lbl) and icon then - local iconImg = vgui.Create("DImage", header) - iconImg:SetImage(icon) - iconImg:SetSize(20, 20) - iconImg:SetPos(8, 6) - - local x, y = lbl:GetPos() - lbl:SetPos(36, y) - end - return header - end + local Scroll = vgui.Create("DScrollPanel", Container) + Scroll:Dock(FILL) - local function AddGraphEntry(parent, title, queue, color, suffix, formatter) - local g = vgui.Create("GP.Graph", parent) + local function AddGraph(title) + local g = vgui.Create("GP.Graph", Scroll) g:Dock(TOP) g:SetTall(150) g:DockMargin(8, 8, 8, 0) g:SetTitle(title) - g:AddSegment(queue, color, suffix, formatter) return g end - -- AddHeader(TopPnl, "Client Performance", "icon16/monitor.png") - local ClientScroll = vgui.Create("DScrollPanel", TopPnl) - ClientScroll:Dock(FILL) + local g = AddGraph("Frametime") + g:AddSegment(GProfiler.Overview.Data.SV.Frametime, Color(220, 80, 80), "ms (SV)", function(v) return string.format("%.2f", v * 1000) end, "icon16/server.png") + g:AddSegment(GProfiler.Overview.Data.CL.Frametime, Color(48, 160, 220), "ms (CL)", function(v) return string.format("%.2f", v * 1000) end, "icon16/monitor.png") - AddGraphEntry(ClientScroll, "Frametime", GProfiler.Overview.Data.CL.Frametime, Color(48, 160, 220), " ms", function(v) return string.format("%.2f", v * 1000) end) - AddGraphEntry(ClientScroll, "Simtime", GProfiler.Overview.Data.CL.Simtime, Color(220, 160, 48), " ms", function(v) return string.format("%.2f", v * 1000) end) - AddGraphEntry(ClientScroll, "Memory", GProfiler.Overview.Data.CL.MemUsage, Color(160, 48, 220), " MiB", function(v) return string.format("%.1f", v / 1024) end) - AddGraphEntry(ClientScroll, "FPS", GProfiler.Overview.Data.CL.FPS, Color(48, 220, 160), " FPS", function(v) return string.format("%.0f", v) end) + g = AddGraph("Simulation Time") + g:AddSegment(GProfiler.Overview.Data.CL.Simtime, Color(220, 160, 48), "ms (CL)", function(v) return string.format("%.2f", v * 1000) end, "icon16/monitor.png") + g:AddSegment(GProfiler.Overview.Data.SV.Simtime, Color(255, 120, 0), "ms (SV)", function(v) return string.format("%.2f", v * 1000) end, "icon16/server.png") - -- AddHeader(BottomPnl, "Server Performance", "icon16/server.png") - local ServerScroll = vgui.Create("DScrollPanel", BottomPnl) - ServerScroll:Dock(FILL) + g = AddGraph("Memory Usage") + g:AddSegment(GProfiler.Overview.Data.CL.MemUsage, Color(160, 48, 220), "MiB (CL)", function(v) return string.format("%.1f", v / 1024) end, "icon16/monitor.png") + g:AddSegment(GProfiler.Overview.Data.SV.MemUsage, Color(255, 48, 160), "MiB (SV)", function(v) return string.format("%.1f", v / 1024) end, "icon16/server.png") - AddGraphEntry(ServerScroll, "Frametime", GProfiler.Overview.Data.SV.Frametime, Color(220, 80, 80), " ms", function(v) return string.format("%.2f", v * 1000) end) - AddGraphEntry(ServerScroll, "Simtime", GProfiler.Overview.Data.SV.Simtime, Color(220, 140, 60), " ms", function(v) return string.format("%.2f", v * 1000) end) - AddGraphEntry(ServerScroll, "Memory", GProfiler.Overview.Data.SV.MemUsage, Color(140, 60, 220), " MiB", function(v) return string.format("%.1f", v / 1024) end) + g = AddGraph("Client FPS") + g:AddSegment(GProfiler.Overview.Data.CL.FPS, Color(48, 220, 160), "FPS", function(v) return string.format("%.0f", v) end, "icon16/monitor.png") end GProfiler.Menu.RegisterTab("Overview", "gprofiler/home.png", 0, GProfiler.Overview.DoTab, function() diff --git a/lua/gprofiler/modules/utils/ui/cl_graphs.lua b/lua/gprofiler/modules/utils/ui/cl_graphs.lua index bb9e95c..b683a05 100644 --- a/lua/gprofiler/modules/utils/ui/cl_graphs.lua +++ b/lua/gprofiler/modules/utils/ui/cl_graphs.lua @@ -70,8 +70,16 @@ end surface.CreateFont("GProfiler.Graph.Title", { font = "Roboto", size = 20, weight = 500, antialias = true }) surface.CreateFont("GProfiler.Graph.ValueLarge", { font = "Roboto", size = 32, weight = 800, antialias = true }) -local PANEL = {} +local graphBg = Color(16, 16, 24) +local graphTitle = Color(220, 220, 220) +local poly = { + { x = 0, y = 0 }, + { x = 0, y = 0 }, + { x = 0, y = 0 }, + { x = 0, y = 0 }, +} +local PANEL = {} AccessorFunc(PANEL, "m_strTitle", "Title", FORCE_STRING) AccessorFunc(PANEL, "m_fVisualMax", "VisualMax", FORCE_NUMBER) AccessorFunc(PANEL, "m_MainIcon", "Icon") @@ -103,7 +111,7 @@ function PANEL:SetMainIcon(iconMat) end function PANEL:Paint(w, h) - draw.RoundedBox(8, 0, 0, w, h, Color(16, 16, 24)) + draw.RoundedBox(8, 0, 0, w, h, graphBg) local titleX = 16 if self.MainIcon then @@ -113,7 +121,7 @@ function PANEL:Paint(w, h) titleX = 16 + 20 + 8 end - draw.SimpleText(self:GetTitle(), "GProfiler.Graph.Title", titleX, 16, Color(220, 220, 220), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(self:GetTitle(), "GProfiler.Graph.Title", titleX, 16, graphTitle, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) local iconX = w - 16 for i = #self.Data, 1, -1 do @@ -142,7 +150,7 @@ function PANEL:Paint(w, h) if count < 2 then continue end local col = seg.color - surface.SetDrawColor(Color(col.r, col.g, col.b, 150)) + surface.SetDrawColor(col.r, col.g, col.b, 150) draw.NoTexture() for i = 0, count - 2 do @@ -153,18 +161,21 @@ function PANEL:Paint(w, h) local x2 = ((i + 1) / (count - 1)) * w local y2 = h - (val2 / max) * (h * 0.60) - local quad = { - { x = x1, y = h }, - { x = x1, y = y1 }, - { x = x2, y = y2 }, - { x = x2, y = h } - } - surface.DrawPoly(quad) + poly[1].x = x1 + poly[1].y = h + poly[2].x = x1 + poly[2].y = y1 + poly[3].x = x2 + poly[3].y = y2 + poly[4].x = x2 + poly[4].y = h + + surface.DrawPoly(poly) end end local leftX = 16 - local rightX = w - 16 + local rightX = w - 72 for i, seg in ipairs(self.Data) do local val = seg.queue:Get(seg.queue:Length() - 1) @@ -176,10 +187,10 @@ function PANEL:Paint(w, h) local fullText = txt .. " " .. (seg.suffix or "") if i == 1 then - draw.SimpleText(fullText, "GProfiler.Graph.ValueLarge", rightX, 16, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + draw.SimpleText(fullText, "GProfiler.Graph.Title", rightX, 14, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) rightX = rightX - surface.GetTextSize(fullText) - 8 - else -- todo: multiple segments is nice, but needs to look better - draw.SimpleText(fullText, "GProfiler.Graph.ValueLarge", rightX, 16, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + else + draw.SimpleText(fullText, "GProfiler.Graph.Title", rightX, 14, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) rightX = rightX - surface.GetTextSize(fullText) - 8 end end From 5447b67645636ae41ed68e0c4124742ea48e2039 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 17 Mar 2026 01:40:35 +0000 Subject: [PATCH 07/16] Pinned graphs --- lua/gprofiler/modules/overview/cl_init.lua | 1 + lua/gprofiler/modules/utils/ui/cl_graphs.lua | 198 ++++++++++++++++++- 2 files changed, 189 insertions(+), 10 deletions(-) diff --git a/lua/gprofiler/modules/overview/cl_init.lua b/lua/gprofiler/modules/overview/cl_init.lua index 118b168..1c1bd99 100644 --- a/lua/gprofiler/modules/overview/cl_init.lua +++ b/lua/gprofiler/modules/overview/cl_init.lua @@ -29,6 +29,7 @@ function GProfiler.Overview.DoTab(Base) net.SendToServer() Base.OnRemove = function(s) + if next(GProfiler.Utils.Graphs.Pinned) then return end net.Start("GProfiler.OverviewSubscribe") net.WriteBool(false) net.SendToServer() diff --git a/lua/gprofiler/modules/utils/ui/cl_graphs.lua b/lua/gprofiler/modules/utils/ui/cl_graphs.lua index b683a05..176d1d3 100644 --- a/lua/gprofiler/modules/utils/ui/cl_graphs.lua +++ b/lua/gprofiler/modules/utils/ui/cl_graphs.lua @@ -67,6 +67,112 @@ function GProfiler.Utils.Graphs.ConstantLengthNumericalQueue(capacity) return obj end +function GProfiler.Utils.Graphs.Render(x, y, w, h, title, visualMax, mainIcon, segments, isPinned) + draw.RoundedBox(8, x, y, w, h, Color(16, 16, 24, isPinned and 200 or 255)) + + local titleX = x + 16 + if mainIcon then + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(mainIcon) + surface.DrawTexturedRect(x + 16, y + 16, 20, 20) + titleX = titleX + 20 + 8 + end + + draw.SimpleText(title, "GProfiler.Graph.Title", titleX, y + 16, Color(220, 220, 220), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + local iconX = x + w - 16 + if isPinned then iconX = x + w - 16 end + + for i = #segments, 1, -1 do + local seg = segments[i] + if seg.icon then + surface.SetDrawColor(seg.color) + surface.SetMaterial(seg.icon) + surface.DrawTexturedRect(iconX - 20, y + 16, 20, 20) + iconX = iconX - 20 - 8 + end + end + + local currentMax = 0 + for _, seg in ipairs(segments) do + local m = seg.queue:Max() + if m > currentMax then currentMax = m end + end + + visualMax = Lerp(FrameTime() * 5, visualMax or 1, currentMax) + local max = visualMax + if max == 0 or max ~= max then max = 1 end + + local poly = GProfiler.Utils.Graphs.PolyBuffer or { + { x = 0, y = 0 }, + { x = 0, y = 0 }, + { x = 0, y = 0 }, + { x = 0, y = 0 }, + } + GProfiler.Utils.Graphs.PolyBuffer = poly + + for _, seg in ipairs(segments) do + local queue = seg.queue + local count = queue:Length() + if count < 2 then continue end + + local col = seg.color + surface.SetDrawColor(col.r, col.g, col.b, 150) + draw.NoTexture() + + for i = 0, count - 2 do + local val1 = queue:Get(i) + local x1 = x + (i / (count - 1)) * w + local y1 = y + h - (val1 / max) * (h * 0.60) + local val2 = queue:Get(i + 1) + local x2 = x + ((i + 1) / (count - 1)) * w + local y2 = y + h - (val2 / max) * (h * 0.60) + + poly[1].x = x1 + poly[1].y = y + h + poly[2].x = x1 + poly[2].y = y1 + poly[3].x = x2 + poly[3].y = y2 + poly[4].x = x2 + poly[4].y = y + h + + surface.DrawPoly(poly) + end + end + + local rightX = x + w - 72 + + for i, seg in ipairs(segments) do + local val = seg.queue:Get(seg.queue:Length() - 1) + if not val then val = 0 end + + local txt = string.format("%.2f", val) + if seg.formatter then txt = seg.formatter(val) end + + local fullText = txt .. " " .. (seg.suffix or "") + + draw.SimpleText(fullText, "GProfiler.Graph.Title", rightX, y + 14, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + rightX = rightX - surface.GetTextSize(fullText) - 8 + end + + return visualMax +end + +GProfiler.Utils.Graphs.Pinned = GProfiler.Utils.Graphs.Pinned or {} + +hook.Add("HUDPaint", "GProfiler.Graphs.Pinned", function() -- TODO: Scaling/Only hood when necessary + local width = 512 + local x = ScrW() - width - 20 + local y = 200 + local h = 150 + + for id, data in pairs(GProfiler.Utils.Graphs.Pinned) do + data.visualMax = GProfiler.Utils.Graphs.Render(x, y, width, h, data.title, data.visualMax, data.icon, data.segments, true) + y = y + h + 10 + end +end) + surface.CreateFont("GProfiler.Graph.Title", { font = "Roboto", size = 20, weight = 500, antialias = true }) surface.CreateFont("GProfiler.Graph.ValueLarge", { font = "Roboto", size = 32, weight = 800, antialias = true }) @@ -88,6 +194,75 @@ function PANEL:Init() self.Data = {} self:SetTitle("Graph") self:SetVisualMax(1) + + self.HistorySlider = vgui.Create("DNumSlider", self) + self.HistorySlider:SetText("History") + self.HistorySlider:SetMin(32) + self.HistorySlider:SetMax(1024) + self.HistorySlider:SetDecimals(0) + self.HistorySlider:SizeToContents() + self.HistorySlider:SetPos(16, 40) + self.HistorySlider:SetSize(250, 40) + self.HistorySlider.Label:SetFont("GProfiler.Graph.Title") + self.HistorySlider.Label:SetTextColor(Color(220, 220, 220)) + self.HistorySlider.TextArea:SetFont("GProfiler.Graph.Title") + self.HistorySlider.TextArea:SetTextColor(Color(220, 220, 220)) + self.HistorySlider.OnValueChanged = function(s, val) + self:ResizeQueues(math.Round(val)) + end + self.HistorySlider.Slider.Paint = function(s, w, h) + draw.RoundedBox(4, 0, h / 2 - 2, w, 4, Color(60, 60, 70)) + end + self.HistorySlider.Slider.Knob.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(220, 220, 220)) + end + self.HistorySlider:SetVisible(false) + + self.PinBtn = vgui.Create("DImageButton", self) + self.PinBtn:SetImage("icon16/pin.png") -- TODO + self.PinBtn:SetSize(24, 24) + self.PinBtn:SetPos(16, 16) + self.PinBtn.DoClick = function(s) + self:TogglePin() + end + self.PinBtn:SetVisible(false) +end + +function PANEL:ResizeQueues(size) + for _, seg in ipairs(self.Data) do + seg.queue:Resize(size) + end + cookie.Set("gprofiler_graph_" .. self:GetTitle() .. "_size", size) +end + +function PANEL:TogglePin() + local id = self:GetTitle() + if GProfiler.Utils.Graphs.Pinned[id] then + GProfiler.Utils.Graphs.Pinned[id] = nil + else + GProfiler.Utils.Graphs.Pinned[id] = { + title = self:GetTitle(), + segments = self.Data, + icon = self.MainIcon, + visualMax = self:GetVisualMax() + } + end +end + +function PANEL:Think() + if self.Data[1] and not self.InitialSizeLoaded then + self.InitialSizeLoaded = true + local savedSize = cookie.GetNumber("gprofiler_graph_" .. self:GetTitle() .. "_size", self.Data[1].queue:Length()) + self.HistorySlider:SetValue(savedSize) + self:ResizeQueues(savedSize) + end + + local hover = self:IsHovered() or self:IsChildHovered() + if hover != self.LastHover then + self.LastHover = hover + self.HistorySlider:SetVisible(hover) + self.PinBtn:SetVisible(hover) + end end function PANEL:AddSegment(queue, color, suffix, formatter, icon) @@ -153,6 +328,9 @@ function PANEL:Paint(w, h) surface.SetDrawColor(col.r, col.g, col.b, 150) draw.NoTexture() + poly[1].y = h + poly[4].y = h + for i = 0, count - 2 do local val1 = queue:Get(i) local x1 = (i / (count - 1)) * w @@ -162,19 +340,16 @@ function PANEL:Paint(w, h) local y2 = h - (val2 / max) * (h * 0.60) poly[1].x = x1 - poly[1].y = h poly[2].x = x1 poly[2].y = y1 poly[3].x = x2 poly[3].y = y2 poly[4].x = x2 - poly[4].y = h surface.DrawPoly(poly) end end - local leftX = 16 local rightX = w - 72 for i, seg in ipairs(self.Data) do @@ -186,13 +361,16 @@ function PANEL:Paint(w, h) local fullText = txt .. " " .. (seg.suffix or "") - if i == 1 then - draw.SimpleText(fullText, "GProfiler.Graph.Title", rightX, 14, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) - rightX = rightX - surface.GetTextSize(fullText) - 8 - else - draw.SimpleText(fullText, "GProfiler.Graph.Title", rightX, 14, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) - rightX = rightX - surface.GetTextSize(fullText) - 8 - end + local labelX = rightX + if i == 1 then labelX = rightX end + + draw.SimpleText(fullText, "GProfiler.Graph.Title", labelX, 14, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + rightX = rightX - surface.GetTextSize(fullText) - 8 + end + + if self.PinBtn and self.PinBtn:IsVisible() then + self.PinBtn:SetPos(w - 32, h - 32) + self.HistorySlider:SetPos(16, h - 36) end end From 28ee60a423189b3cead13261bdc8ab8bd37e81a4 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 17 Mar 2026 08:22:40 +0000 Subject: [PATCH 08/16] Some cleanups --- README.md | 3 +- lua/gprofiler/cl_menu.lua | 18 +- lua/gprofiler/{ => modules}/cl_language.lua | 0 lua/gprofiler/{ => modules}/sh_access.lua | 28 +- .../{ => modules/utils}/sh_utils.lua | 250 +----------------- lua/gprofiler/modules/utils/ui/cl_graphs.lua | 2 +- lua/gprofiler/profilers/hooks/sh_hooks.lua | 6 +- lua/gprofiler/sh_config.lua | 60 ++--- 8 files changed, 38 insertions(+), 329 deletions(-) rename lua/gprofiler/{ => modules}/cl_language.lua (100%) rename lua/gprofiler/{ => modules}/sh_access.lua (83%) rename lua/gprofiler/{ => modules/utils}/sh_utils.lua (51%) diff --git a/README.md b/README.md index ea123c5..0db724f 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,4 @@ ![758e7672ed29e3acd633a4826ed5578f](https://github.com/user-attachments/assets/489d4171-4c06-49c2-a481-0330f58bc322) ![fc934fdc3a3f8116a187c6bebae03060](https://github.com/user-attachments/assets/0cfaeead-c9f1-4249-ae9b-30c679b7f42d) ![a690e160c76c9a36b61f675e232cac57](https://github.com/user-attachments/assets/77b19c50-b726-443f-925b-2ff5310f3606) -![b60f6548f7c8c11297aeffa32699886a](https://github.com/user-attachments/assets/62fb0c2d-3224-44dc-9fad-4d60b96ec1dc) - +![b60f6548f7c8c11297aeffa32699886a](https://github.com/user-attachments/assets/62fb0c2d-3224-44dc-9fad-4d60b96ec1dc) \ No newline at end of file diff --git a/lua/gprofiler/cl_menu.lua b/lua/gprofiler/cl_menu.lua index 70de6c4..373b035 100644 --- a/lua/gprofiler/cl_menu.lua +++ b/lua/gprofiler/cl_menu.lua @@ -1,4 +1,5 @@ GProfiler.Menu = GProfiler.Menu or {} + local Menu = GProfiler.Menu Menu.Tabs = Menu.Tabs or {} Menu.Background = Menu.Background or nil @@ -32,7 +33,7 @@ function GProfiler.Menu:Open() MenuBackground:SetMouseInputEnabled(false) MenuBackground.Paint = function(s, w, h) -- RNDX.DrawBlur(0, 0, w, h, nil, nil, nil, nil, nil, (ScrW() - (ScrW() * 0.79)) / 2) - RNDX.Draw(4, 0, 0, w, h, Color(0, 0, 0, 100)) -- better than eating fps + RNDX.Draw(4, 0, 0, w, h, MenuColors.Black100) -- better than eating fps end if GProfiler.Config.MenuCommands.Closekey then MenuBackground.Think = function(s) @@ -255,18 +256,3 @@ local function CreateFonts() end CreateFonts() hook.Add("OnScreenSizeChanged", "GProfiler.Menu.RescaleFonts", CreateFonts) - -net.Receive("GProfiler.SendState", function() - local count = net.ReadUInt(4) - for i = 1, count do - local Profiler = net.ReadString() - local StartedAt = net.ReadFloat() - - local Tbl = GProfiler[Profiler] - if not Tbl then continue end - - Tbl.ProfileActive = true - Tbl.Realm = "Server" - Tbl.StartTime = SysTime() - StartedAt - end -end) diff --git a/lua/gprofiler/cl_language.lua b/lua/gprofiler/modules/cl_language.lua similarity index 100% rename from lua/gprofiler/cl_language.lua rename to lua/gprofiler/modules/cl_language.lua diff --git a/lua/gprofiler/sh_access.lua b/lua/gprofiler/modules/sh_access.lua similarity index 83% rename from lua/gprofiler/sh_access.lua rename to lua/gprofiler/modules/sh_access.lua index f8c61e3..cc883c2 100644 --- a/lua/gprofiler/sh_access.lua +++ b/lua/gprofiler/modules/sh_access.lua @@ -1,10 +1,6 @@ GProfiler.Access.AdminSystem = GProfiler.Access.AdminSystem or false -local function GlobalExists(name) - local exists = false - pcall(function() exists = _G[name] and true or false end) - return exists -end +local function GlobalExists(name) return _G[name] != nil end local AdminSystems = { ["FAdmin"] = { @@ -77,7 +73,6 @@ function GProfiler.Access.FindAdminSystem() for name, system in SortedPairsByMemberValue(AdminSystems, "Priority") do if system.IsAvailable() then GProfiler.Access.AdminSystem = system - GProfiler.Log("Found admin system: " .. name, 2) return end end @@ -85,26 +80,23 @@ function GProfiler.Access.FindAdminSystem() GProfiler.Access.AdminSystem = false end -function GProfiler.Access.RegisterPrivilege(name) - if not GProfiler.Access.AdminSystem then - GProfiler.Log("No admin system found, cannot register privilege: " .. name, 3) - return - end - - if GProfiler.Access.AdminSystem.RegisterPrivilege then - GProfiler.Access.AdminSystem.RegisterPrivilege(name) - end -end - hook.Add("Initialize", "GProfiler.Access.Register", function() GProfiler.Access.FindAdminSystem() - GProfiler.Access.RegisterPrivilege("gprofiler") + + if GProfiler.Access.AdminSystem then + GProfiler.Access.AdminSystem.RegisterPrivilege("gprofiler") + end end) function GProfiler.Access.HasAccess(ply) if GetGlobalBool("gprofiler_lan", false) then return true end + if ply:EntIndex() == 0 then return true end -- Console + + if GProfiler.Config.AllowSuperAdmin and ply:IsSuperAdmin() then return true end if GProfiler.Config.AllowedSteamIDs[ply:SteamID64()] or GProfiler.Config.AllowedSteamIDs[ply:SteamID()] then return true end + if not GProfiler.Access.AdminSystem then return false end + return GProfiler.Access.AdminSystem.CheckAccess(ply, "gprofiler") end diff --git a/lua/gprofiler/sh_utils.lua b/lua/gprofiler/modules/utils/sh_utils.lua similarity index 51% rename from lua/gprofiler/sh_utils.lua rename to lua/gprofiler/modules/utils/sh_utils.lua index 6caac6a..2e9e41e 100644 --- a/lua/gprofiler/sh_utils.lua +++ b/lua/gprofiler/modules/utils/sh_utils.lua @@ -1,252 +1,7 @@ -if CLIENT then --- GProfiler.Menu = GProfiler.Menu or {} - --- local MenuColors = GProfiler.MenuColors --- local BorderColor = MenuColors.DListColumnOutline --- local TabPadding = 10 - --- local draw = draw --- local table = table --- local ipairs = ipairs --- local string = string --- local surface = surface - --- local function PaintColumn(s, w, h) --- surface.SetDrawColor(BorderColor.r, BorderColor.g, BorderColor.b, BorderColor.a) --- surface.DrawRect(w - 2, 0, 2, h) --- end - --- local function PaintLine(s, w, h) --- if s:IsHovered() then --- draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListRowHover) --- else --- draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListRowBackground) --- end - --- if s:IsLineSelected() then --- draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListRowSelected) --- end --- end - --- local function PaintHeader(s, w, h) --- draw.RoundedBox(1, 0, 0, w, h, MenuColors.DListColumnOutline) --- draw.RoundedBox(1, 1, 1, w - 2, h - 2, MenuColors.DListColumnBackground) - --- if s:IsHovered() then --- draw.RoundedBox(1, 0, 0, w, h, MenuColors.DListColumnOutline) --- end --- end - --- function GProfiler.StyleDListView(v) --- local Columns = v.Columns --- for k, v1 in ipairs(Columns) do --- v1.Header:SetFont("GProfiler.Menu.ListHeader") --- v1.Header.Paint = PaintHeader --- v1.Header:SetTextColor(MenuColors.White) --- end - --- local Lines = v.Lines --- for k, v in ipairs(Lines) do --- local columnCount = table.Count(v.Columns) --- for k, v in ipairs(v.Columns) do --- v:SetTextColor(MenuColors.DListRowTextColor) --- v.Paint = PaintColumn --- end --- v.Paint = PaintLine --- end - --- GProfiler.StyleScrollbar(v) - --- function v:Paint(w, h) --- draw.RoundedBox(2, 0, 0, w, h, MenuColors.DListBackground) --- surface.SetDrawColor(BorderColor.r, BorderColor.g, BorderColor.b, BorderColor.a) --- surface.DrawOutlinedRect(0, 0, w, h) --- end --- end - --- local function PaintGrip(s, w, h) --- draw.RoundedBox(2, 0, 0, w, h, MenuColors.ScrollBarGripOutline) --- draw.RoundedBox(2, 1, 1, w - 2, h - 2, MenuColors.ScrollBarGrip) - --- if s:IsHovered() or s.Depressed then --- draw.RoundedBox(2, 0, 0, w, h, MenuColors.ScrollBarGripOutline) --- end --- end - --- local function PaintScrollbar(s, w, h) --- draw.RoundedBox(0, 0, 0, w, h, MenuColors.ScrollBar) --- end - --- function GProfiler.StyleScrollbar(v) --- local ScrollBar = v.VBar or (v.GetVBar and v:GetVBar()) or nil --- if not IsValid(ScrollBar) then return end --- ScrollBar.btnUp:SetVisible(false) --- ScrollBar.btnDown:SetVisible(false) --- ScrollBar.Paint = PaintScrollbar --- ScrollBar.btnGrip.Paint = PaintGrip --- ScrollBar.PerformLayout = function() --- local wide = ScrollBar:GetWide() --- local scroll = ScrollBar:GetScroll() / ScrollBar.CanvasSize --- local barSize = math.max(ScrollBar:BarScale() * (ScrollBar:GetTall() - (wide * 2)), 10) --- local track = ScrollBar:GetTall() - (wide * 2) - barSize - --- ScrollBar.btnGrip:SetPos(0, (wide + (scroll * (track + 3))) - 16) --- ScrollBar.btnGrip:SetSize(wide, barSize + 30) --- end --- end - --- function GProfiler.StyleDropdown(v) --- v.Paint = function(s, w, h) --- draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) --- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - --- if s:IsHovered() then --- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) --- end --- end --- end - --- local function PaintSeperator(s, w, h) --- draw.RoundedBox(4, 0, 0, w, h, MenuColors.HeaderSeparator) --- end - --- function GProfiler.Menu.CreateHeader(parent, text, x, y, w, h, noPadding) --- local TabPadding = noPadding and 0 or TabPadding --- local header = vgui.Create("DPanel", parent) --- header:SetSize(w, h) --- header:SetPos(x, y) --- header.Paint = nil - --- local headerText = vgui.Create("DLabel", header) --- headerText:SetFont("GProfiler.Menu.SectionHeader") --- headerText:SetText(text) --- headerText:SizeToContents() --- headerText:SetPos(TabPadding, header:GetTall() / 2 - headerText:GetTall() / 2) --- headerText:SetTextColor(MenuColors.White) - --- local separator = vgui.Create("DPanel", header) --- separator:SetSize(header:GetWide() - TabPadding * 2, 1) --- separator:SetPos(TabPadding, header:GetTall() - 1) --- separator.Paint = PaintSeperator - --- return header, headerText --- end - --- local function PaintRealmSelector(s, w, h) --- draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) --- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - --- if s:IsHovered() then --- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonHover) --- end --- end - --- function GProfiler.Menu.CreateLabeledInput(parent, labelText, x, y, w, h, textInset) --- textInset = textInset or 0 --- local lbl = vgui.Create("DLabel", parent) --- lbl:SetFont("GProfiler.Menu.SectionHeader") --- lbl:SetText(labelText) --- lbl:SizeToContents() --- lbl:SetTextColor(MenuColors.White) --- lbl:SetPos(x, y + 3) - --- local input = vgui.Create("DTextEntry", parent) --- input:SetFont("GProfiler.Menu.SectionHeader") --- input:SetText("") --- input:SetSize(w, h) --- input:SetPos(x + lbl:GetWide() + 5, y) --- input:SetTextColor(MenuColors.White) --- function input:Paint(w, h) --- draw.RoundedBox(4, 0, 0, w, h, MenuColors.RealmSelectorOutline) --- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.RealmSelectorBackground) --- local x = draw.SimpleText(self:GetText(), "GProfiler.Menu.FocusEntry", textInset + 5, h / 2, MenuColors.White, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) --- if self:IsEditing() and (x < w - 15) and (SysTime() % 1 > 0.5) then --- local caretPos = self:GetCaretPos() --- local text = self:GetText() --- surface.SetFont("GProfiler.Menu.FocusEntry") --- local textWidth = surface.GetTextSize(text) --- local textWidthBeforeCaret = surface.GetTextSize(string.sub(text, 1, caretPos)) --- draw.RoundedBox(0, textInset + textWidthBeforeCaret + 5, 1, 2, h - 2, MenuColors.White) --- end --- end - --- return lbl, input --- end - --- function GProfiler.Menu.CreateRealmSelector(parent, profiler, x, y, onSelect) --- local Data = GProfiler[profiler] --- local Selected = Data.Realm == "Client" and 1 or 2 --- Data.Lerp = Data.Lerp or (Selected - 1) --- local SelectorBase = vgui.Create("DPanel", parent) --- SelectorBase:SetPos(x, y) --- SelectorBase:SetSize(200, parent:GetTall() - 6) --- SelectorBase.Paint = function(s, w, h) --- draw.RoundedBox(4, 0, 0, w, h, MenuColors.ButtonOutline) --- draw.RoundedBox(4, 1, 1, w - 2, h - 2, MenuColors.ButtonBackground) - --- local lerp = Lerp(0.1, Data.Lerp, Data.LerpTo or (Selected - 1)) --- Data.Lerp = lerp - --- draw.RoundedBox(4, 2 + w / 2 * lerp, 2, w / 2 - 4, h - 4, MenuColors.ButtonHover) --- end - --- local Realms = {"Client", "Server"} --- for i = 1, 2 do --- local Button = vgui.Create("DButton", SelectorBase) --- Button:SetText(i == 1 and "Client" or "Server") --- Button:SetFont("GProfiler.Menu.RealmSelector") --- Button:SetTextColor(color_white) --- Button:SetSize(SelectorBase:GetWide() / 2, SelectorBase:GetTall()) --- Button:SetPos(SelectorBase:GetWide() / 2 * (i - 1), 0) --- Button.Paint = nil --- Button.DoClick = function() --- Selected = i --- Data.LerpTo = i - 1 --- end --- Button.Think = function(s) --- if Data.ProfileActive then --- s:SetEnabled(false) --- else --- s:SetEnabled(true) --- end - --- if s:IsEnabled() then --- s:SetCursor("hand") --- else --- s:SetCursor("no") --- end --- end - --- function Button:DoClick() --- onSelect(self, nil, Realms[i]) --- end - --- Button.Text = i == 1 and "Client" or "Server" --- end - --- return SelectorBase --- end - --- function GProfiler.CopyLang(copy) --- copy = string.lower(string.Replace(copy, " ", "_")) --- return string.format("%s %s", GProfiler.Language.GetPhrase("copy"), GProfiler.Language.GetPhrase(copy)) --- end - - -else - -end - --- function GProfiler.GetFunctionLocation(func) --- local info = debug.getinfo(func, "S") --- if info.short_src == "[C]" then return "C" end --- return info.short_src .. ":" .. info.linedefined --- end +GProfiler.Utils = GProfiler.Utils or {} function GProfiler.ExpressAvailable() return !!((express and express.shSend) and GProfiler.Config.UseExpressNetworking) end -GProfiler.Utils = GProfiler.Utils or {} - if SERVER then util.AddNetworkString("GProfiler_RequestFunctionSource") @@ -517,4 +272,5 @@ function GProfiler.RequestFunctionSource(file, lineStart, lineEnd, callback) callback(lines) end end) -end \ No newline at end of file +end + diff --git a/lua/gprofiler/modules/utils/ui/cl_graphs.lua b/lua/gprofiler/modules/utils/ui/cl_graphs.lua index 176d1d3..bbdba4d 100644 --- a/lua/gprofiler/modules/utils/ui/cl_graphs.lua +++ b/lua/gprofiler/modules/utils/ui/cl_graphs.lua @@ -182,7 +182,7 @@ local poly = { { x = 0, y = 0 }, { x = 0, y = 0 }, { x = 0, y = 0 }, - { x = 0, y = 0 }, + { x = 0, y = 0 } } local PANEL = {} diff --git a/lua/gprofiler/profilers/hooks/sh_hooks.lua b/lua/gprofiler/profilers/hooks/sh_hooks.lua index 14e97fb..ca1ec41 100644 --- a/lua/gprofiler/profilers/hooks/sh_hooks.lua +++ b/lua/gprofiler/profilers/hooks/sh_hooks.lua @@ -58,9 +58,7 @@ function GProfiler.Hooks:StartProfiler(ply) end end - hook.Add = function(hookName, receiverName, receiverFunc, ...) - profileHook(hookName, receiverName, receiverFunc, ...) - end + hook.Add = profileHook end function GProfiler.Hooks:RestoreHooks(ply) @@ -74,7 +72,7 @@ function GProfiler.Hooks:RestoreHooks(ply) for hookName, hookReceivers in pairs(hook.GetTable()) do for receiverName, receiverFunc in pairs(hookReceivers) do - if type(receiverName) ~= "string" or type(receiverFunc) ~= "function" then continue end + if not isstring(receiverName) or not isfunction(receiverFunc) then continue end local data = HooksProfiler.ProfileData[string.format("%s_%s", hookName, receiverName)] if data then hook.Add(hookName, receiverName, data.f, unpack(data.extra or {})) diff --git a/lua/gprofiler/sh_config.lua b/lua/gprofiler/sh_config.lua index 6ee48b4..08321b5 100644 --- a/lua/gprofiler/sh_config.lua +++ b/lua/gprofiler/sh_config.lua @@ -11,10 +11,13 @@ GProfiler.Config.LOG_WARNING = true GProfiler.Config.LOG_ERROR = true GProfiler.Config.LOG_LOAD = false +-- Access GProfiler.Config.AllowedSteamIDs = { -- SteamIDs that can access GProfiler ["76561198XXXXXXXXX"] = true } +GProfiler.Config.AllowSuperAdmin = false -- Allow players with superadmin (Player:IsSuperAdmin()) to access GProfiler regardless of other checks. + --[[ If express is available, we use it over gmod's net library This can handle larger data sizes, and we shouldn't need to worry about overflowing the buffer @@ -27,47 +30,22 @@ GProfiler.Config.UseExpressNetworking = true -- Express is not worth using for small amounts, as it will be slower for small data GProfiler.Config.ExpressMinimumResults = 25 -if CLIENT then - GProfiler.MenuColors = { - White = Color(255, 255, 255), - Blue = Color(91, 118, 255), - - -- Menu - Background = Color(8, 27, 48, 220), - OpaqueBlack = Color(0, 0, 0, 200), - OpaqueBlack2 = Color(0, 0, 0, 150), - TopBarSeparator = Color(91, 118, 255, 10), - HeaderSeparator = Color(91, 118, 255, 50), - RealmSelectorBackground = Color(38, 57, 78), - RealmSelectorOutline = Color(88, 107, 138), - ActiveProfile = Color(10, 155, 10), - InactiveProfile = Color(200, 50, 50), - - -- Lists - DListBackground = Color(18, 37, 58), - DListColumnBackground = Color(68, 87, 108), - DListColumnOutline = Color(88, 107, 138), - DListRowBackground = Color(48, 67, 88), - DListRowHover = Color(68, 87, 108), - DListRowTextColor = Color(235, 235, 235), - DListRowSelected = Color(91, 118, 255, 50), +if SERVER then return end - -- Scrollbars - ScrollBar = Color(38, 57, 78), - ScrollBarGrip = Color(68, 87, 108), - ScrollBarGripOutline = Color(88, 107, 138), +GProfiler.MenuColors = { + -- Misc + White = Color(255, 255, 255), + Blue = Color(91, 118, 255), + Black100 = Color(0, 0, 0, 100), - -- Buttons - ButtonOutline = Color(88, 107, 138), - ButtonBackground = Color(38, 57, 78), - ButtonHover = Color(58, 77, 98), - - CodeBackground = Color(45, 45, 45) - } + -- Scrollbars + ScrollBar = Color(38, 57, 78), + ScrollBarGrip = Color(68, 87, 108), + ScrollBarGripOutline = Color(88, 107, 138), +} - GProfiler.Config.MenuCommands = { - Chat = '!gprofiler', -- False to disable - Console = 'gprofiler', -- False to disable - Closekey = KEY_F4 -- False to disable - } -end +GProfiler.Config.MenuCommands = { + Chat = '!gprofiler', -- False to disable + Console = 'gprofiler', -- False to disable + Closekey = KEY_F4 -- False to disable +} \ No newline at end of file From bd8b951707c92b807e4379a127878df47c40f8a2 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 17 Mar 2026 10:05:06 +0000 Subject: [PATCH 09/16] An unncessarily overcomplicated system for realm data persistance :) --- lua/autorun/gprofiler_load.lua | 5 +- lua/gprofiler/modules/sh_profilers.lua | 290 ++++++++++++++++++ lua/gprofiler/modules/utils/sh_utils.lua | 49 +-- .../profilers/concommands/cl_concommands.lua | 7 +- .../profilers/concommands/sh_concommands.lua | 79 +++-- .../profilers/database/cl_database.lua | 2 + .../profilers/database/cl_queryparser.lua | 2 + .../profilers/database/sv_database.lua | 239 +++++++-------- .../profilers/entvars/cl_entvars.lua | 3 + .../profilers/functions/cl_functions.lua | 6 + .../profilers/functions/sh_functions.lua | 67 ++-- lua/gprofiler/profilers/hooks/cl_hooks.lua | 7 +- lua/gprofiler/profilers/hooks/sh_hooks.lua | 54 ++-- lua/gprofiler/profilers/net/cl_net.lua | 85 ++--- lua/gprofiler/profilers/net/sh_net.lua | 69 +++-- .../profilers/netvars/cl_netvars.lua | 3 + lua/gprofiler/profilers/timers/cl_timers.lua | 2 + lua/gprofiler/profilers/timers/sh_timers.lua | 13 +- lua/gprofiler/sv_init.lua | 26 -- 19 files changed, 635 insertions(+), 373 deletions(-) create mode 100644 lua/gprofiler/modules/sh_profilers.lua diff --git a/lua/autorun/gprofiler_load.lua b/lua/autorun/gprofiler_load.lua index 6d56e1d..193e881 100644 --- a/lua/autorun/gprofiler_load.lua +++ b/lua/autorun/gprofiler_load.lua @@ -32,7 +32,7 @@ local function incFolder(folder, subFileOnly, fileOnly) GProfiler.Log(string.format("Loading folder %s", folder), 5) local files, folders = file.Find(folder.."/*", "LUA") - for _, f in ipairs(files) do incFile(string.format("%s/%s", folder, f)) end + for _, f in SortedPairs(files, CLIENT) do incFile(string.format("%s/%s", folder, f)) end if fileOnly then return end for _, f in ipairs(folders) do incFolder(folder.."/"..f, nil, subFileOnly) end @@ -40,10 +40,7 @@ end incFile("gprofiler/sv_init.lua") incFile("gprofiler/sh_config.lua") -incFile("gprofiler/sh_utils.lua") -incFile("gprofiler/cl_language.lua") incFile("gprofiler/cl_menu.lua") -incFile("gprofiler/sh_access.lua") incFolder("gprofiler/modules") incFolder("gprofiler/profilers", true) diff --git a/lua/gprofiler/modules/sh_profilers.lua b/lua/gprofiler/modules/sh_profilers.lua new file mode 100644 index 0000000..f487a00 --- /dev/null +++ b/lua/gprofiler/modules/sh_profilers.lua @@ -0,0 +1,290 @@ +GProfiler.Profilers = GProfiler.Profilers or {} +GProfiler.Profilers.Registered = GProfiler.Profilers.Registered or {} + +local Profilers = GProfiler.Profilers +local SysTime = SysTime + +if SERVER then + util.AddNetworkString("GProfiler.Profiler.Status") + util.AddNetworkString("GProfiler.Profiler.Toggle") + util.AddNetworkString("GProfiler.Profiler.RequestData") + util.AddNetworkString("GProfiler.Profiler.SyncState") +end + +local function CreateStore(name, opts) + local Store = {} + + Store.Name = name + Store.Realms = opts.Realms or { "Client", "Server" } + Store.ServerOnly = opts.ServerOnly or false + + Store.Active = {} + Store.StartTime = {} + Store.EndTime = {} + Store.Data = {} + Store.Version = {} + Store.StartedBy = {} + + for _, realm in ipairs(Store.Realms) do + Store.Active[realm] = false + Store.StartTime[realm] = 0 + Store.EndTime[realm] = 0 + Store.Data[realm] = {} + Store.Version[realm] = 0 + end + + function Store:IsActive(realm) + return self.Active[realm] or false + end + + function Store:GetData(realm) + return self.Data[realm] + end + + function Store:SetData(realm, data) + self.Data[realm] = data + self.Version[realm] = (self.Version[realm] or 0) + 1 + end + + function Store:Start(realm, ply) + if self.Active[realm] then return false end + self.Active[realm] = true + self.StartTime[realm] = SysTime() + self.EndTime[realm] = 0 + self.Data[realm] = {} + self.StartedBy[realm] = ply + return true + end + + function Store:Stop(realm, ply) + if not self.Active[realm] then return false end + self.Active[realm] = false + self.EndTime[realm] = SysTime() + return true + end + + function Store:GetTimerData(realm) + if not realm then realm = "Client" end + return { + StartTime = self.StartTime[realm] or 0, + EndTime = self.EndTime[realm] or 0, + ProfileActive = self.Active[realm] or false + } + end + + return Store +end + +function Profilers.Register(name, opts) + opts = opts or {} + local profiler = { + Name = name, + Store = CreateStore(name, opts), + Realms = opts.Realms or { "Client", "Server" }, + ServerOnly = opts.ServerOnly or false, + OnStart = opts.OnStart, + OnStop = opts.OnStop, + WriteData = opts.WriteData, + ReadData = opts.ReadData, + OnDataReceived = opts.OnDataReceived, + } + + MsgC(string.format("Registered profiler '%s' for realms: %s\n", name, table.concat(profiler.Realms, ", ")), Color(100, 255, 100)) + + Profilers.Registered[name] = profiler + return profiler.Store +end + +function Profilers.Get(name) + return Profilers.Registered[name] +end + +function Profilers.GetStore(name) + local p = Profilers.Registered[name] + return p and p.Store +end + +function Profilers.GetAll() + return Profilers.Registered +end + +if SERVER then + local function BroadcastStatus(name, realm, active, ply) + local store = Profilers.Get(name).Store + net.Start("GProfiler.Profiler.Status") + net.WriteString(name) + net.WriteString(realm) + net.WriteBool(active) + net.WriteEntity(ply) + net.WriteFloat(active and (SysTime() - store.StartTime[realm]) or 0) + net.Broadcast() + end + + net.Receive("GProfiler.Profiler.Toggle", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + local name = net.ReadString() + local realm = net.ReadString() + local start = net.ReadBool() + + local profiler = Profilers.Get(name) + if not profiler then return end + if realm ~= "Server" then return end + + if start then + if not profiler.Store:Start(realm, ply) then return end + if profiler.OnStart then profiler.OnStart(realm, ply) end + BroadcastStatus(name, realm, true, ply) + else + if profiler.OnStop then profiler.OnStop(realm, ply) end + if not profiler.Store:Stop(realm, ply) then return end + BroadcastStatus(name, realm, false, ply) + end + end) + + net.Receive("GProfiler.Profiler.RequestData", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + local name = net.ReadString() + local profiler = Profilers.Get(name) + if not profiler then return end + + if profiler.WriteData then + profiler.WriteData("Server", ply) + end + end) + + net.Receive("GProfiler.Profiler.SyncState", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + local count = 0 + local active = {} + for name, profiler in pairs(Profilers.Registered) do + if profiler.Store:IsActive("Server") then + count = count + 1 + active[name] = profiler + end + end + + net.Start("GProfiler.Profiler.SyncState") + net.WriteUInt(count, 8) + for name, profiler in pairs(active) do + net.WriteString(name) + net.WriteFloat(SysTime() - profiler.Store.StartTime["Server"]) + net.WriteEntity(profiler.Store.StartedBy["Server"] or Entity(0)) + end + net.Send(ply) + end) + + hook.Add("PlayerInitialSpawn", "GProfiler.SyncState", function(ply) + timer.Simple(1, function() + if not IsValid(ply) then return end + if not GProfiler.Access.HasAccess(ply) then return end + + local count = 0 + local active = {} + for name, profiler in pairs(Profilers.Registered) do + if profiler.Store:IsActive("Server") then + count = count + 1 + active[name] = profiler + end + end + + if count == 0 then return end + + net.Start("GProfiler.Profiler.SyncState") + net.WriteUInt(count, 8) + for name, profiler in pairs(active) do + net.WriteString(name) + net.WriteFloat(SysTime() - profiler.Store.StartTime["Server"]) + net.WriteEntity(profiler.Store.StartedBy["Server"] or Entity(0)) + end + net.Send(ply) + end) + end) + + return +end + +net.Receive("GProfiler.Profiler.Status", function() + local name = net.ReadString() + local realm = net.ReadString() + local active = net.ReadBool() + local ply = net.ReadEntity() + local startTime = net.ReadFloat() + + local profiler = Profilers.Get(name) + if not profiler then return end + + if active then + profiler.Store.Active[realm] = true + profiler.Store.StartTime[realm] = SysTime() - startTime + profiler.Store.EndTime[realm] = 0 + profiler.Store.StartedBy[realm] = ply + else + profiler.Store.Active[realm] = false + profiler.Store.EndTime[realm] = SysTime() + end + + hook.Run("GProfiler.Profiler.StatusChanged", name, realm, active, ply) +end) + +net.Receive("GProfiler.Profiler.SyncState", function() + local count = net.ReadUInt(8) + for i = 1, count do + local name = net.ReadString() + local elapsed = net.ReadFloat() + local ply = net.ReadEntity() + + local profiler = Profilers.Get(name) + if not profiler then continue end + + profiler.Store.Active["Server"] = true + profiler.Store.StartTime["Server"] = SysTime() - elapsed + profiler.Store.EndTime["Server"] = 0 + profiler.Store.StartedBy["Server"] = ply + + hook.Run("GProfiler.Profiler.StatusChanged", name, "Server", true, ply) + end +end) + +function Profilers.Toggle(name, realm, start) + local profiler = Profilers.Get(name) + if not profiler then return end + + if realm == "Both" then + Profilers.Toggle(name, "Client", start) + Profilers.Toggle(name, "Server", start) + return + end + + if realm == "Client" then + if start then + if not profiler.Store:Start(realm) then return end + if profiler.OnStart then profiler.OnStart(realm) end + else + if profiler.OnStop then profiler.OnStop(realm) end + profiler.Store:Stop(realm) + end + hook.Run("GProfiler.Profiler.StatusChanged", name, realm, start) + else + net.Start("GProfiler.Profiler.Toggle") + net.WriteString(name) + net.WriteString(realm) + net.WriteBool(start) + net.SendToServer() + end +end + +function Profilers.RequestData(name, callback) + local profiler = Profilers.Get(name) + if not profiler then return end + + if callback then + profiler._DataCallback = callback + end + + net.Start("GProfiler.Profiler.RequestData") + net.WriteString(name) + net.SendToServer() +end diff --git a/lua/gprofiler/modules/utils/sh_utils.lua b/lua/gprofiler/modules/utils/sh_utils.lua index 2e9e41e..579e06e 100644 --- a/lua/gprofiler/modules/utils/sh_utils.lua +++ b/lua/gprofiler/modules/utils/sh_utils.lua @@ -146,10 +146,17 @@ function GProfiler.Utils.SetupHeader(Outer, Title, Icon, Sub) return Button end - function Header:SetupRealmSelector(IsClient) - if IsClient == nil then IsClient = true end + function Header:SetupRealmSelector(currentState, includeBoth) + if isbool(currentState) then + currentState = currentState and "Client" or "Server" + end + currentState = currentState or "Client" + + local Items = {"Client", "Server"} + if includeBoth then table.insert(Items, "Both") end + local numItems = #Items - local Width = GProfiler.GetScaledSize(366) + local Width = GProfiler.GetScaledSize(183) * numItems local Height = Header:GetTall() * 0.65 ItemsXOffset = ItemsXOffset - Width - GProfiler.GetScaledSize(10) @@ -158,10 +165,15 @@ function GProfiler.Utils.SetupHeader(Outer, Title, Icon, Sub) Selector:SetSize(Width, Height) Selector:SetPos(ItemsXOffset, Header:GetTall() / 2 - Height / 2) - Selector.State = IsClient and "Client" or "Server" - Selector.LerpTo = Selector.State == "Client" and 0 or 1 + local initialIndex = 1 + for i, item in ipairs(Items) do + if item == currentState then initialIndex = i; break end + end + + Selector.State = currentState + Selector.LerpTo = (initialIndex - 1) / math.max(1, numItems - 1) Selector.LerpPos = Selector.LerpTo - Selector.IsClient = IsClient + Selector.IsClient = currentState == "Client" Selector.Enabled = true Selector.Paint = function(s, w, h) @@ -170,18 +182,18 @@ function GProfiler.Utils.SetupHeader(Outer, Title, Icon, Sub) local lerp = Lerp(0.1, Selector.LerpPos, Selector.LerpTo) Selector.LerpPos = lerp local Padding = 6 - local SelectorW = w / 2 - Padding * 2 - RNDX.Draw(6, Padding + (SelectorW * lerp), Padding, SelectorW + (Selector.LerpTo == 1 and Padding * 2 or 0), h - Padding * 2, Color(31, 79, 128, 255)) + local SelectorW = (w - Padding * 2) / numItems + local SelectorX = Padding + lerp * (w - SelectorW - Padding * 2) + RNDX.Draw(6, SelectorX, Padding, SelectorW, h - Padding * 2, Color(31, 79, 128, 255)) end - local ItemW = Selector:GetWide() / 2 + local ItemW = Selector:GetWide() / numItems - local Items = {"Client", "Server"} - for i = 1, 2 do + for i, item in ipairs(Items) do local Button = vgui.Create("DButton", Selector) Button:SetSize(ItemW, Selector:GetTall()) Button:SetPos((i - 1) * ItemW, 0) - Button:SetText(Items[i]) + Button:SetText(item) Button:SetFont("GProfiler.HeaderInteract") Button:SetTextColor(color_white) Button.Paint = nil @@ -189,9 +201,9 @@ function GProfiler.Utils.SetupHeader(Outer, Title, Icon, Sub) Button.DoClick = function() if not Selector.Enabled then return end - Selector.LerpTo = i - 1 - Selector.State = Items[i] - Selector.IsClient = Items[i] == "Client" + Selector.LerpTo = (i - 1) / math.max(1, numItems - 1) + Selector.State = item + Selector.IsClient = item == "Client" if Selector.OnStateChanged then Selector:OnStateChanged(Selector.State) end end end @@ -199,7 +211,8 @@ function GProfiler.Utils.SetupHeader(Outer, Title, Icon, Sub) return Selector end - function Header:SetupTimer(profiler) + function Header:SetupTimer(getter) + local profiler = getter() Timer = vgui.Create("DLabel", Header) Timer:SetFont("GProfiler.HeaderInteract") Timer:SetTextColor(color_white) @@ -215,8 +228,8 @@ function GProfiler.Utils.SetupHeader(Outer, Title, Icon, Sub) end function Timer:Think() - if not profiler.ProfileActive then return end - self:SetText(GProfiler.TimeRunning(profiler.StartTime or 0, profiler.EndTime or 0, true) .. "s") + local p = getter() + self:SetText(GProfiler.TimeRunning(p.StartTime or 0, p.EndTime or 0, p.ProfileActive) .. "s") end return Timer diff --git a/lua/gprofiler/profilers/concommands/cl_concommands.lua b/lua/gprofiler/profilers/concommands/cl_concommands.lua index eaa0f0e..8314499 100644 --- a/lua/gprofiler/profilers/concommands/cl_concommands.lua +++ b/lua/gprofiler/profilers/concommands/cl_concommands.lua @@ -1,9 +1,14 @@ GProfiler.ConCommands = GProfiler.ConCommands or {} +local CommandsStore = GProfiler.Profilers.GetStore("Commands") + function GProfiler.ConCommands.DoTab(Content) end GProfiler.Menu.RegisterTab("Commands", "gprofiler/commands.png", 4, GProfiler.ConCommands.DoTab, function() - + if not CommandsStore then return end + local timer = CommandsStore:GetTimerData(GProfiler.ConCommands.Realm) + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) diff --git a/lua/gprofiler/profilers/concommands/sh_concommands.lua b/lua/gprofiler/profilers/concommands/sh_concommands.lua index 587d151..b721e8b 100644 --- a/lua/gprofiler/profilers/concommands/sh_concommands.lua +++ b/lua/gprofiler/profilers/concommands/sh_concommands.lua @@ -15,14 +15,13 @@ function GProfiler.ConCommands.GetFunction(cmd, tbl) return dbgInfo.short_src, dbgInfo.linedefined, dbgInfo.lastlinedefined end -function GProfiler.ConCommands:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or GProfiler.ConCommands.IsDetoured then return end +local function StartDetour() + if GProfiler.ConCommands.IsDetoured then return end GProfiler.Log((SERVER and "Server" or "Client") .. " commands profiler started!", 2) GProfiler.ConCommands.OldRun = GProfiler.ConCommands.OldRun or concommand.Run GProfiler.ConCommands.ProfileData = {} GProfiler.ConCommands.IsDetoured = true - GProfiler.ConCommands.ProfileStarted = SysTime() concommand.Run = function(ply, cmd, ...) local start = SysTime() @@ -51,56 +50,50 @@ function GProfiler.ConCommands:StartProfiler(ply) end end -function GProfiler.ConCommands:RestoreCommands(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or not GProfiler.ConCommands.IsDetoured then return end +local function StopDetour() + if not GProfiler.ConCommands.IsDetoured then return end - GProfiler.Log((SERVER and "Server" or "Client") .. " commands profile stopped, sending data!", 2) + GProfiler.Log((SERVER and "Server" or "Client") .. " commands profile stopped!", 2) GProfiler.ConCommands.IsDetoured = false - GProfiler.ConCommands.ProfileStarted = nil - concommand.Run = GProfiler.ConCommands.OldRun +end - if SERVER then - net.Start("GProfiler_ConCommands_SendData") - net.WriteUInt(table.Count(GProfiler.ConCommands.ProfileData), 32) - for k, v in pairs(GProfiler.ConCommands.ProfileData) do - net.WriteString(k) - net.WriteUInt(v.Count, 32) - net.WriteFloat(v.Time) - net.WriteFloat(v.AverageTime) - net.WriteFloat(v.LongestTime) - net.WriteString(v.Source) - net.WriteUInt(v.Lines[1], 16) - net.WriteUInt(v.Lines[2], 16) - end - net.Send(ply) - end +local function SendData(ply) + net.Start("GProfiler_ConCommands_SendData") + net.WriteUInt(table.Count(GProfiler.ConCommands.ProfileData), 32) + for k, v in pairs(GProfiler.ConCommands.ProfileData) do + net.WriteString(k) + net.WriteUInt(v.Count, 32) + net.WriteFloat(v.Time) + net.WriteFloat(v.AverageTime) + net.WriteFloat(v.LongestTime) + net.WriteString(v.Source) + net.WriteUInt(v.Lines[1], 16) + net.WriteUInt(v.Lines[2], 16) + end + net.Send(ply) end +GProfiler.Profilers.Register("Commands", { + Realms = { "Client", "Server" }, + OnStart = function(realm, ply) + StartDetour() + end, + OnStop = function(realm, ply) + StopDetour() + if SERVER and ply then + SendData(ply) + end + end, + WriteData = function(realm, ply) + SendData(ply) + end +}) + if SERVER then - util.AddNetworkString("GProfiler_ConCommands_ToggleServerProfile") - util.AddNetworkString("GProfiler_ConCommands_ServerProfileStatus") util.AddNetworkString("GProfiler_ConCommands_CommandList") util.AddNetworkString("GProfiler_ConCommands_SendData") - net.Receive("GProfiler_ConCommands_ToggleServerProfile", function(len, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - if net.ReadBool() then - GProfiler.ConCommands:StartProfiler(ply) - net.Start("GProfiler_ConCommands_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() - else - GProfiler.ConCommands:RestoreCommands(ply) - net.Start("GProfiler_ConCommands_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() - end - end) - net.Receive("GProfiler_ConCommands_CommandList", function(_, ply) if not GProfiler.Access.HasAccess(ply) then return end diff --git a/lua/gprofiler/profilers/database/cl_database.lua b/lua/gprofiler/profilers/database/cl_database.lua index 6f9b13e..390711e 100644 --- a/lua/gprofiler/profilers/database/cl_database.lua +++ b/lua/gprofiler/profilers/database/cl_database.lua @@ -1,5 +1,7 @@ GProfiler.Database = GProfiler.Database or {} +local DatabaseStore = GProfiler.Profilers.GetStore("Database") + function GProfiler.Database.DoTab(Content) end diff --git a/lua/gprofiler/profilers/database/cl_queryparser.lua b/lua/gprofiler/profilers/database/cl_queryparser.lua index dd65492..bd8a03b 100644 --- a/lua/gprofiler/profilers/database/cl_queryparser.lua +++ b/lua/gprofiler/profilers/database/cl_queryparser.lua @@ -1,3 +1,5 @@ +GProfiler.Database = GProfiler.Database or {} + local MySQLKeywords = { ["ACCESSIBLE"] = "Ensures table alterations are compatible with storage engines.", ["ACCOUNT"] = "Used in security statements to manage user accounts (e.g., LOCK/UNLOCK).", diff --git a/lua/gprofiler/profilers/database/sv_database.lua b/lua/gprofiler/profilers/database/sv_database.lua index 571e45b..6279b5f 100644 --- a/lua/gprofiler/profilers/database/sv_database.lua +++ b/lua/gprofiler/profilers/database/sv_database.lua @@ -6,6 +6,8 @@ GProfiler.Database.SeenQuery = GProfiler.Database.SeenQuery or {} GProfiler.Database.InternalProfileData = GProfiler.Database.InternalProfileData or {} GProfiler.Database.QueryIdCounter = GProfiler.Database.QueryIdCounter or 0 +local Profilers = GProfiler.Profilers + local ProviderLoader = include("providers/base/sv_provider_loader.lua") local availableProviders = ProviderLoader.Initialize() @@ -16,11 +18,7 @@ local IDLookup = { ["sqlite"] = 4 } --- TODO: Express Networking Support! - -function GProfiler.Database:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or GProfiler.Database.IsDetoured then return end - +local function StartDetour(realm, ply) GProfiler.Log("Server database profiler started!", 2) GProfiler.Database.ProfileData = {} @@ -29,24 +27,48 @@ function GProfiler.Database:StartProfiler(ply) GProfiler.Database.QueryIdCounter = 0 GProfiler.Database.QueryMapping = {} GProfiler.Database.IsDetoured = true - GProfiler.Database.ProfileStarted = SysTime() for name, provider in pairs(availableProviders) do provider:StartProfiling() end end -function GProfiler.Database:StopProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or not GProfiler.Database.IsDetoured then return end - - GProfiler.Log((SERVER and "Server" or "Client") .. " database profiler stopped, sending data!", 2) +local function StopDetour(realm, ply) + GProfiler.Log("Server database profiler stopped!", 2) GProfiler.Database.IsDetoured = false - GProfiler.Database.ProfileStarted = nil for name, provider in pairs(availableProviders) do provider:StopProfiling() end + local providersToWaitFor = 0 + local allProfilingData = {} + + for name, provider in pairs(availableProviders) do + providersToWaitFor = providersToWaitFor + 1 + provider:GetProfilingData(function(data) + providersToWaitFor = providersToWaitFor - 1 + + if data then + for query, profile in pairs(data) do + allProfilingData[query] = profile + end + end + + if providersToWaitFor == 0 then + GProfiler.Database.InternalProfileData = allProfilingData + end + end) + end + + if providersToWaitFor == 0 then + GProfiler.Database.InternalProfileData = allProfilingData + end +end + +local function SendData(realm, ply) + if not IsValid(ply) then return end + if table.Count(GProfiler.Database.ProfileData) == 0 then net.Start("GProfiler_Database_SendData") net.WriteBool(true) @@ -57,119 +79,88 @@ function GProfiler.Database:StopProfiler(ply) return end - local sent = false - local function SendData() - if sent then return end - sent = true - - local function SendChunk(ply, profileData, explains, startIndex, chunkSize) - if not IsValid(ply) then return end - net.Start("GProfiler_Database_SendData") - net.WriteBool(startIndex == 1) - local endIndex = math.min(startIndex + chunkSize - 1, table.Count(profileData)) - net.WriteBool(endIndex == table.Count(profileData)) - net.WriteUInt(endIndex - startIndex + 1, 14) - local index = 0 - for k, v in pairs(profileData) do - index = index + 1 - if index >= startIndex and index <= endIndex then - local FlattenQuery = v.Query:gsub("%s+", " "):Trim():lower() - if FlattenQuery:EndsWith(";") then FlattenQuery = FlattenQuery:sub(1, -2) end - local internalData = GProfiler.Database.InternalProfileData[FlattenQuery] - if not internalData then - for k2, v2 in pairs(GProfiler.Database.InternalProfileData) do - local k2Flat = k2:gsub("%s+", " "):Trim():lower() - if k2Flat:EndsWith(";") then k2Flat = k2Flat:sub(1, -2) end - if FlattenQuery:StartsWith(k2Flat) then - internalData = v2 - break - end + local function SendChunk(ply, profileData, explains, startIndex, chunkSize) + if not IsValid(ply) then return end + net.Start("GProfiler_Database_SendData") + net.WriteBool(startIndex == 1) + local endIndex = math.min(startIndex + chunkSize - 1, table.Count(profileData)) + net.WriteBool(endIndex == table.Count(profileData)) + net.WriteUInt(endIndex - startIndex + 1, 14) + local index = 0 + for k, v in pairs(profileData) do + index = index + 1 + if index >= startIndex and index <= endIndex then + local FlattenQuery = v.Query:gsub("%s+", " "):Trim():lower() + if FlattenQuery:EndsWith(";") then FlattenQuery = FlattenQuery:sub(1, -2) end + local internalData = GProfiler.Database.InternalProfileData[FlattenQuery] + if not internalData then + for k2, v2 in pairs(GProfiler.Database.InternalProfileData) do + local k2Flat = k2:gsub("%s+", " "):Trim():lower() + if k2Flat:EndsWith(";") then k2Flat = k2Flat:sub(1, -2) end + if FlattenQuery:StartsWith(k2Flat) then + internalData = v2 + break end end - net.WriteUInt(k, 14) - net.WriteUInt(IDLookup[v.Type] or 0, 3) - net.WriteUInt(v.Count, 14) - net.WriteFloat(v.Time) - net.WriteFloat(internalData and internalData.Duration or v.AverageTime) - net.WriteFloat(v.LongestTime) - net.WriteString(v.Query or "") - net.WriteString(v.Source[1] or "Unknown") - net.WriteUInt(v.Source[2] or 0, 16) - net.WriteUInt(v.Source[3] or 0, 16) - if internalData then - net.WriteBool(true) - local count = table.Count(internalData.ProfileData) - net.WriteUInt(count, 7) - for k2, v2 in ipairs(internalData.ProfileData) do - net.WriteFloat(v2.Duration or 0) - net.WriteString(v2.Status or "Unknown") - end - else - net.WriteBool(false) + end + net.WriteUInt(k, 14) + net.WriteUInt(IDLookup[v.Type] or 0, 3) + net.WriteUInt(v.Count, 14) + net.WriteFloat(v.Time) + net.WriteFloat(internalData and internalData.Duration or v.AverageTime) + net.WriteFloat(v.LongestTime) + net.WriteString(v.Query or "") + net.WriteString(v.Source[1] or "Unknown") + net.WriteUInt(v.Source[2] or 0, 16) + net.WriteUInt(v.Source[3] or 0, 16) + if internalData then + net.WriteBool(true) + local count = table.Count(internalData.ProfileData) + net.WriteUInt(count, 7) + for k2, v2 in ipairs(internalData.ProfileData) do + net.WriteFloat(v2.Duration or 0) + net.WriteString(v2.Status or "Unknown") end + else + net.WriteBool(false) end end - if endIndex == table.Count(profileData) then - net.WriteUInt(table.Count(explains), 14) - for k, v in pairs(explains) do - net.WriteUInt(k, 14) - if v.noExplain then - net.WriteBool(true) - else - net.WriteBool(false) - net.WriteUInt(table.Count(v), 6) - for i, explain in ipairs(v) do - net.WriteString(explain.select_type or "") - net.WriteString(explain.table or "") - net.WriteString(explain.type or "") - net.WriteString(explain.possible_keys or "") - net.WriteString(explain.key or "") - net.WriteString(explain.key_len or "") - net.WriteString(explain.ref or "") - net.WriteUInt(explain.rows or 0, 32) - net.WriteString(explain.Extra or "") - end + end + if endIndex == table.Count(profileData) then + net.WriteUInt(table.Count(explains), 14) + for k, v in pairs(explains) do + net.WriteUInt(k, 14) + if v.noExplain then + net.WriteBool(true) + else + net.WriteBool(false) + net.WriteUInt(table.Count(v), 6) + for i, explain in ipairs(v) do + net.WriteString(explain.select_type or "") + net.WriteString(explain.table or "") + net.WriteString(explain.type or "") + net.WriteString(explain.possible_keys or "") + net.WriteString(explain.key or "") + net.WriteString(explain.key_len or "") + net.WriteString(explain.ref or "") + net.WriteUInt(explain.rows or 0, 32) + net.WriteString(explain.Extra or "") end end - else - net.WriteUInt(0, 32) end - net.Send(ply) - end - - local chunkSize = 20 - local totalProfiles = table.Count(GProfiler.Database.ProfileData) - for startIndex = 1, totalProfiles, chunkSize do - timer.Simple(0.1, function() - SendChunk(ply, GProfiler.Database.ProfileData, GProfiler.Database.Explains, startIndex, chunkSize) - end) + else + net.WriteUInt(0, 32) end + net.Send(ply) end - local providersToWaitFor = 0 - local allProfilingData = {} - - for name, provider in pairs(availableProviders) do - providersToWaitFor = providersToWaitFor + 1 - provider:GetProfilingData(function(data) - providersToWaitFor = providersToWaitFor - 1 - - if data then - for query, profile in pairs(data) do - allProfilingData[query] = profile - end - end - - if providersToWaitFor == 0 then - GProfiler.Database.InternalProfileData = allProfilingData - SendData() - end + local chunkSize = 20 + local totalProfiles = table.Count(GProfiler.Database.ProfileData) + for startIndex = 1, totalProfiles, chunkSize do + timer.Simple(0.1, function() + SendChunk(ply, GProfiler.Database.ProfileData, GProfiler.Database.Explains, startIndex, chunkSize) end) end - - if providersToWaitFor == 0 then - SendData() - end end function GProfiler.Database:GetAllDatabaseObjects() @@ -190,24 +181,12 @@ function GProfiler.Database:GetAvailableProviders() return availableProviders end -util.AddNetworkString("GProfiler_Database_ToggleServerProfile") -util.AddNetworkString("GProfiler_Database_ServerProfileStatus") util.AddNetworkString("GProfiler_Database_SendData") -net.Receive("GProfiler_Database_ToggleServerProfile", function(len, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - if net.ReadBool() then - GProfiler.Database:StartProfiler(ply) - net.Start("GProfiler_Database_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() - else - GProfiler.Database:StopProfiler(ply) - net.Start("GProfiler_Database_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() - end -end) \ No newline at end of file +Profilers.Register("Database", { -- move to shared when UI for this is done again + Realms = { "Server" }, + ServerOnly = true, + OnStart = StartDetour, + OnStop = StopDetour, + WriteData = SendData +}) \ No newline at end of file diff --git a/lua/gprofiler/profilers/entvars/cl_entvars.lua b/lua/gprofiler/profilers/entvars/cl_entvars.lua index 687c56b..3e31283 100644 --- a/lua/gprofiler/profilers/entvars/cl_entvars.lua +++ b/lua/gprofiler/profilers/entvars/cl_entvars.lua @@ -1,5 +1,8 @@ GProfiler.EntVars = GProfiler.EntVars or {} +GProfiler.Profilers.Register("Entity Variables", {}) +local EntVarsStore = GProfiler.Profilers.GetStore("Entity Variables") + function GProfiler.EntVars.DoTab(Content) end diff --git a/lua/gprofiler/profilers/functions/cl_functions.lua b/lua/gprofiler/profilers/functions/cl_functions.lua index 33d6a58..d9b04fd 100644 --- a/lua/gprofiler/profilers/functions/cl_functions.lua +++ b/lua/gprofiler/profilers/functions/cl_functions.lua @@ -1,8 +1,14 @@ GProfiler.Functions = GProfiler.Functions or {} +local FunctionsStore = GProfiler.Profilers.GetStore("Functions") + function GProfiler.Functions.Tab(Content) end GProfiler.Menu.RegisterTab("Functions", "gprofiler/functions.png", 3, GProfiler.Functions.Tab, function() + if not FunctionsStore then return end + local timer = FunctionsStore:GetTimerData(GProfiler.Functions.Realm) + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) diff --git a/lua/gprofiler/profilers/functions/sh_functions.lua b/lua/gprofiler/profilers/functions/sh_functions.lua index 4cb2892..d575b00 100644 --- a/lua/gprofiler/profilers/functions/sh_functions.lua +++ b/lua/gprofiler/profilers/functions/sh_functions.lua @@ -24,7 +24,6 @@ local function garbage() -- need to experiment more, which is more accurate? bra return collectgarbage("count") * 1024 end --- Hacky solution for functions from the same location but have different signatures local function CombineDuplicates() local combined = {} for k, v in pairs(FunctionsProfiler.ProfileData) do @@ -118,13 +117,12 @@ local function onEvent(event) end end -function GProfiler.Functions:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or FunctionsProfiler.IsDetoured then return end +local function StartDetour() + if FunctionsProfiler.IsDetoured then return end GProfiler.Log((SERVER and "Server" or "Client") .. " function profiler started!", 2) FunctionsProfiler.ProfileData = {} FunctionsProfiler.IsDetoured = true - FunctionsProfiler.ProfileStarted = SysTime() recurse = {} startTimes = {} @@ -132,18 +130,17 @@ function GProfiler.Functions:StartProfiler(ply) debug.sethook(onEvent, "cr") end -function GProfiler.Functions:RestoreFunctions(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or not FunctionsProfiler.IsDetoured then return end +local function StopDetour() + if not FunctionsProfiler.IsDetoured then return end - GProfiler.Log((SERVER and "Server" or "Client") .. " function profile stopped, sending data!", 2) + GProfiler.Log((SERVER and "Server" or "Client") .. " function profile stopped!", 2) FunctionsProfiler.IsDetoured = false - FunctionsProfiler.ProfileStarted = nil debug.sethook() CombineDuplicates() +end - if CLIENT then return end - +local function SendData(ply) local Count = table.Count(FunctionsProfiler.ProfileData) if GProfiler.ExpressAvailable() and Count > (GProfiler.Config.ExpressMinimumResults or 25) - 1 and Count > 0 then local Data = {} @@ -210,38 +207,38 @@ function GProfiler.Functions:RestoreFunctions(ply) end end +GProfiler.Profilers.Register("Functions", { + Realms = { "Client", "Server" }, + OnStart = function(realm, ply) + StartDetour() + end, + OnStop = function(realm, ply) + StopDetour() + if SERVER and ply then + SendData(ply) + end + end, + WriteData = function(realm, ply) + SendData(ply) + end +}) + if SERVER then - util.AddNetworkString("GProfiler_Functions_ToggleServerProfile") - util.AddNetworkString("GProfiler_Functions_ServerProfileStatus") util.AddNetworkString("GProfiler_Functions_SendData") + util.AddNetworkString("GProfiler_Functions_SetFocus") - net.Receive("GProfiler_Functions_ToggleServerProfile", function(len, ply) + net.Receive("GProfiler_Functions_SetFocus", function(len, ply) if not GProfiler.Access.HasAccess(ply) then return end - local startStop = net.ReadBool() - if startStop then - local hasFocus = net.ReadBool() - if hasFocus then - local count = net.ReadUInt(5) - FunctionsProfiler.Focus = {} - for i = 1, count do - FunctionsProfiler.Focus[net.ReadString()] = true - end - else - FunctionsProfiler.Focus = false + local hasFocus = net.ReadBool() + if hasFocus then + local count = net.ReadUInt(5) + FunctionsProfiler.Focus = {} + for i = 1, count do + FunctionsProfiler.Focus[net.ReadString()] = true end - - GProfiler.Functions:StartProfiler(ply) - net.Start("GProfiler_Functions_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() else - GProfiler.Functions:RestoreFunctions(ply) - net.Start("GProfiler_Functions_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() + FunctionsProfiler.Focus = false end end) end diff --git a/lua/gprofiler/profilers/hooks/cl_hooks.lua b/lua/gprofiler/profilers/hooks/cl_hooks.lua index 047134c..ebd49c8 100644 --- a/lua/gprofiler/profilers/hooks/cl_hooks.lua +++ b/lua/gprofiler/profilers/hooks/cl_hooks.lua @@ -1,9 +1,14 @@ GProfiler.Hooks = GProfiler.Hooks or {} +local HooksStore = GProfiler.Profilers.GetStore("Hooks") + function GProfiler.Hooks.DoTab(Content) end GProfiler.Menu.RegisterTab("Hooks", "gprofiler/hooks.png", 1, GProfiler.Hooks.DoTab, function() - + if not HooksStore then return end + local timer = HooksStore:GetTimerData(GProfiler.Hooks.Realm) + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) diff --git a/lua/gprofiler/profilers/hooks/sh_hooks.lua b/lua/gprofiler/profilers/hooks/sh_hooks.lua index ca1ec41..024c5a0 100644 --- a/lua/gprofiler/profilers/hooks/sh_hooks.lua +++ b/lua/gprofiler/profilers/hooks/sh_hooks.lua @@ -9,13 +9,12 @@ local SysTime = SysTime local unpack = unpack local debug = debug -function GProfiler.Hooks:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or HooksProfiler.IsDetoured then return end +local function StartDetour() + if HooksProfiler.IsDetoured then return end GProfiler.Log((SERVER and "Server" or "Client") .. " hook profiler started!", 2) HooksProfiler.ProfileData = {} HooksProfiler.IsDetoured = true - HooksProfiler.ProfileStarted = SysTime() HooksProfiler.AddHook = HooksProfiler.AddHook or hook.Add local function profileHook(hookName, receiverName, receiverFunc, ...) @@ -61,12 +60,11 @@ function GProfiler.Hooks:StartProfiler(ply) hook.Add = profileHook end -function GProfiler.Hooks:RestoreHooks(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or not HooksProfiler.IsDetoured then return end +local function StopDetour() + if not HooksProfiler.IsDetoured then return end - GProfiler.Log((SERVER and "Server" or "Client") .. " hook profile stopped, sending data!", 2) + GProfiler.Log((SERVER and "Server" or "Client") .. " hook profile stopped!", 2) HooksProfiler.IsDetoured = false - HooksProfiler.ProfileStarted = nil hook.Add = HooksProfiler.AddHook @@ -80,16 +78,16 @@ function GProfiler.Hooks:RestoreHooks(ply) end end - if CLIENT then return end - for k, v in pairs(HooksProfiler.ProfileData) do if v.c == 0 then HooksProfiler.ProfileData[k] = nil end end +end +local function SendData(ply) local Count = table.Count(HooksProfiler.ProfileData) - if GProfiler.ExpressAvailable() and Count > GProfiler.Config.ExpressMinimumResults-1 and Count > 0 then + if GProfiler.ExpressAvailable() and Count > GProfiler.Config.ExpressMinimumResults - 1 and Count > 0 then local Data = {} for k, v in pairs(HooksProfiler.ProfileData) do Data[k] = v @@ -116,31 +114,27 @@ function GProfiler.Hooks:RestoreHooks(ply) end end +GProfiler.Profilers.Register("Hooks", { + Realms = { "Client", "Server" }, + OnStart = function(realm, ply) + StartDetour() + end, + OnStop = function(realm, ply) + StopDetour() + if SERVER and ply then + SendData(ply) + end + end, + WriteData = function(realm, ply) + SendData(ply) + end +}) + if SERVER then - util.AddNetworkString("GProfiler_Hooks_ToggleServerProfile") - util.AddNetworkString("GProfiler_Hooks_ServerProfileStatus") util.AddNetworkString("GProfiler_Hooks_SendData") util.AddNetworkString("GProfiler_Hooks_HookTbl") util.AddNetworkString("GProfiler_Hooks_RemoveHook") - net.Receive("GProfiler_Hooks_ToggleServerProfile", function(len, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - if net.ReadBool() then - GProfiler.Hooks:StartProfiler(ply) - net.Start("GProfiler_Hooks_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() - else - GProfiler.Hooks:RestoreHooks(ply) - net.Start("GProfiler_Hooks_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() - end - end) - net.Receive("GProfiler_Hooks_HookTbl", function(len, ply) if not GProfiler.Access.HasAccess(ply) then return end diff --git a/lua/gprofiler/profilers/net/cl_net.lua b/lua/gprofiler/profilers/net/cl_net.lua index ce8ded8..dcc9ddf 100644 --- a/lua/gprofiler/profilers/net/cl_net.lua +++ b/lua/gprofiler/profilers/net/cl_net.lua @@ -1,10 +1,10 @@ GProfiler.Net = GProfiler.Net or {} local Net = GProfiler.Net -Net.StartTime = Net.StartTime or 0 -Net.EndTime = Net.EndTime or 0 Net.Realm = Net.Realm or "Client" +local NetStore = GProfiler.Profilers.GetStore("Networking") + local function FormatBits(bits) if not bits or bits < 0.01 then return "0 Bytes" end if not isnumber(bits) then return "oh fiddlesticks, what now?" end @@ -22,9 +22,13 @@ end function GProfiler.Net.DoTab(Base, Outer) local Header = GProfiler.Utils.SetupHeader(Outer, "Networking", "gprofiler/network.png") - local StartStop = Header:SetupStartStop(Net.ProfileActive) - local RealmSelector = Header:SetupRealmSelector(Net.Realm == "Client") - local Timer = Header:SetupTimer(Net) + local initialActive = Net.Realm == "Both" and (NetStore:IsActive("Client") or NetStore:IsActive("Server")) or NetStore:IsActive(Net.Realm) + local StartStop = Header:SetupStartStop(initialActive) + local RealmSelector = Header:SetupRealmSelector(Net.Realm, true) + local Timer = Header:SetupTimer(function() + local realm = Net.Realm == "Both" and "Client" or Net.Realm + return NetStore:GetTimerData(realm) + end) Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) @@ -35,39 +39,26 @@ function GProfiler.Net.DoTab(Base, Outer) end function StartStop:OnStateChanged(Running) - Net.ProfileActive = Running + GProfiler.Profilers.Toggle("Networking", Net.Realm, Running) - if not Net.ProfileActive then - Net.EndTime = SysTime() + if Net.RefreshUI then + Net.RefreshUI() + end + end - if Net.Realm == "Client" then - GProfiler.Net:RestoreNet() - else - net.Start("GProfiler_Net_ToggleServerProfile") - net.WriteBool(false) - net.SendToServer() - end + function RealmSelector:OnStateChanged(state) + Net.Realm = state + if state == "Both" then + StartStop.State = NetStore:IsActive("Client") or NetStore:IsActive("Server") else - Net.StartTime = SysTime() - Net.EndTime = 0 - Net.ProfileData = {} - - if Net.Realm == "Client" then - GProfiler.Net:StartProfiler() - else - net.Start("GProfiler_Net_ToggleServerProfile") - net.WriteBool(true) - net.SendToServer() - end + StartStop.State = NetStore:IsActive(state) end - + StartStop:SetText(StartStop.State and "Stop" or "Start") if Net.RefreshUI then Net.RefreshUI() end end - function RealmSelector:OnStateChanged(state) Net.Realm = state end - local left, right = GProfiler.Utils.VSplitPanel(Base, GProfiler.GetScaledSize(10), "net_lr", 0.65) local Results, Receivers = GProfiler.Utils.HSplitPanel(left, GProfiler.GetScaledSize(10), "net_l_bt", 0.65) local ResultsSent, ResultsReceived = GProfiler.Utils.HSplitPanel(Results, GProfiler.GetScaledSize(10), "net_results_split", 0.5) @@ -447,7 +438,9 @@ function GProfiler.Net.DoTab(Base, Outer) function ResultsList:OnRowSelected(rowIndex, row) ReceivedList:ClearSelection() local name = row:GetColumnText(1) - local data = GProfiler.Net.ProfileData.Out and GProfiler.Net.ProfileData.Out[name] + local displayRealm = Net.Realm == "Both" and "Client" or Net.Realm + local realmData = NetStore:GetData(displayRealm) or {} + local data = realmData.Out and realmData.Out[name] if data then BreakdownPanel:Clear() @@ -488,7 +481,7 @@ function GProfiler.Net.DoTab(Base, Outer) RichText:SetText("Failed to load source (2)") end - if Net.Realm == "Client" then + if displayRealm == "Client" then local breakdownData = GProfiler.Net.Breakdowns and GProfiler.Net.Breakdowns[name] if breakdownData then PopulateBreakdown(name, breakdownData.Children or {}, data.Size or data[7] or 0) @@ -511,7 +504,9 @@ function GProfiler.Net.DoTab(Base, Outer) function ReceivedList:OnRowSelected(rowIndex, row) ResultsList:ClearSelection() local name = row:GetColumnText(1) - local data = GProfiler.Net.ProfileData.Inc and GProfiler.Net.ProfileData.Inc[name] + local displayRealm = Net.Realm == "Both" and "Client" or Net.Realm + local realmData = NetStore:GetData(displayRealm) or {} + local data = realmData.Inc and realmData.Inc[name] if data then BreakdownPanel:Clear() @@ -557,9 +552,12 @@ function GProfiler.Net.DoTab(Base, Outer) local function PopulateResults() if not IsValid(ResultsList) or not IsValid(ReceivedList) then return end + local displayRealm = Net.Realm == "Both" and "Client" or Net.Realm + local realmData = NetStore:GetData(displayRealm) or {} + ResultsList:Clear() - if GProfiler.Net.ProfileData.Out then - for name, data in pairs(GProfiler.Net.ProfileData.Out) do + if realmData.Out then + for name, data in pairs(realmData.Out) do ResultsList:AddLine( name, data.Count or data[1] or 0, @@ -573,8 +571,8 @@ function GProfiler.Net.DoTab(Base, Outer) end ReceivedList:Clear() - if GProfiler.Net.ProfileData.Inc then - for name, data in pairs(GProfiler.Net.ProfileData.Inc) do + if realmData.Inc then + for name, data in pairs(realmData.Inc) do ReceivedList:AddLine( name, data.Count or data[1] or 0, @@ -714,18 +712,21 @@ function GProfiler.Net.DoTab(Base, Outer) net.SendToServer() end GProfiler.Menu.RegisterTab("Networking", "gprofiler/network.png", 2, GProfiler.Net.DoTab, function() - if Net.StartTime == 0 then return end - return GProfiler.TimeRunning(Net.StartTime, Net.EndTime, Net.ProfileActive), Net.ProfileActive + if not NetStore then return end + local timer = NetStore:GetTimerData(Net.Realm) + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) net.Receive("GProfiler_Net_SendData", function() local isIncoming = net.ReadBool() local count = net.ReadUInt(32) - if not GProfiler.Net.ProfileData.Inc then GProfiler.Net.ProfileData.Inc = {} end - if not GProfiler.Net.ProfileData.Out then GProfiler.Net.ProfileData.Out = {} end + local serverData = NetStore:GetData("Server") or {} + if not serverData.Inc then serverData.Inc = {} end + if not serverData.Out then serverData.Out = {} end - local target = isIncoming and GProfiler.Net.ProfileData.Inc or GProfiler.Net.ProfileData.Out + local target = isIncoming and serverData.Inc or serverData.Out table.Empty(target) for i=1, count do @@ -743,6 +744,8 @@ net.Receive("GProfiler_Net_SendData", function() target[name] = data end + NetStore:SetData("Server", serverData) + if GProfiler.Net.RefreshUI then GProfiler.Net.RefreshUI() end diff --git a/lua/gprofiler/profilers/net/sh_net.lua b/lua/gprofiler/profilers/net/sh_net.lua index 000af8a..3e084a4 100644 --- a/lua/gprofiler/profilers/net/sh_net.lua +++ b/lua/gprofiler/profilers/net/sh_net.lua @@ -167,15 +167,13 @@ local function RestoreOutgoing() GProfiler.Net.CurrentMsg = nil end -function GProfiler.Net:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or GProfiler.Net.IsDetoured then return end +local function StartDetour() + if GProfiler.Net.IsDetoured then return end GProfiler.Log((SERVER and "Server" or "Client") .. " net profiler started!", 2) GProfiler.Net.ProfileData = { Inc = {}, Out = {} } GProfiler.Net.Breakdowns = {} - GProfiler.Net.IsDetoured = true - GProfiler.Net.ProfileStarted = SysTime() GProfiler.Net.OriginalIncoming = GProfiler.Net.OriginalIncoming or net.Incoming @@ -221,12 +219,11 @@ function GProfiler.Net:StartProfiler(ply) DetourOutgoing() end -function GProfiler.Net:RestoreNet(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or not GProfiler.Net.IsDetoured then return end +local function StopDetour() + if not GProfiler.Net.IsDetoured then return end - GProfiler.Log((SERVER and "Server" or "Client") .. " net profile stopped, sending data!", 2) + GProfiler.Log((SERVER and "Server" or "Client") .. " net profile stopped!", 2) GProfiler.Net.IsDetoured = false - GProfiler.Net.ProfileStarted = nil net.Incoming = GProfiler.Net.OriginalIncoming RestoreOutgoing() @@ -234,12 +231,12 @@ function GProfiler.Net:RestoreNet(ply) if GProfiler.Net.RefreshUI then GProfiler.Net.RefreshUI() end +end - if CLIENT then return end - +local function SendNetData(ply) local function SendData(dataMap, isIncoming) local count = table.Count(dataMap) - if GProfiler.ExpressAvailable() and count > GProfiler.Config.ExpressMinimumResults-1 and count > 0 then + if GProfiler.ExpressAvailable() and count > GProfiler.Config.ExpressMinimumResults - 1 and count > 0 then local Data = {} for k, v in pairs(dataMap) do Data[tostring(k)] = { @@ -274,32 +271,36 @@ function GProfiler.Net:RestoreNet(ply) SendData(GProfiler.Net.ProfileData.Out, false) end +GProfiler.Profilers.Register("Networking", { + Realms = { "Client", "Server" }, + OnStart = function(realm, ply) + StartDetour() + end, + OnStop = function(realm, ply) + StopDetour() + if CLIENT then + local NetStore = GProfiler.Profilers.GetStore("Networking") + if NetStore then + NetStore:SetData(realm, { + Inc = GProfiler.Net.ProfileData.Inc, + Out = GProfiler.Net.ProfileData.Out + }) + end + end + if SERVER and ply then + SendNetData(ply) + end + end, + WriteData = function(realm, ply) + SendNetData(ply) + end +}) + if SERVER then - util.AddNetworkString("GProfiler_Net_ToggleServerProfile") - util.AddNetworkString("GProfiler_Net_ServerProfileStatus") util.AddNetworkString("GProfiler_Net_SendData") util.AddNetworkString("GProfiler_Net_RequestBreakdown") util.AddNetworkString("GProfiler_Net_SendBreakdown") util.AddNetworkString("GProfiler_Net_ReceiverTbl") - util.AddNetworkString("GProfiler_NetTest") - - net.Receive("GProfiler_Net_ToggleServerProfile", function(len, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - if net.ReadBool() then - GProfiler.Net:StartProfiler(ply) - net.Start("GProfiler_Net_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() - else - GProfiler.Net:RestoreNet(ply) - net.Start("GProfiler_Net_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() - end - end) net.Receive("GProfiler_Net_ReceiverTbl", function(len, ply) if not GProfiler.Access.HasAccess(ply) then return end @@ -323,13 +324,13 @@ if SERVER then local breakdownData = GProfiler.Net.Breakdowns[name] net.Start("GProfiler_Net_SendBreakdown") - net.WriteString(name) -- TODO: assign an network id for these! + net.WriteString(name) if breakdownData then net.WriteBool(true) net.WriteUInt(breakdownData.Size, 32) local function WriteNode(node) - net.WriteString(node.Func) -- ^ + net.WriteString(node.Func) net.WriteUInt(node.Size, 32) net.WriteUInt(#node.Children, 16) for _, child in ipairs(node.Children) do diff --git a/lua/gprofiler/profilers/netvars/cl_netvars.lua b/lua/gprofiler/profilers/netvars/cl_netvars.lua index 9e05cec..b9f1eb2 100644 --- a/lua/gprofiler/profilers/netvars/cl_netvars.lua +++ b/lua/gprofiler/profilers/netvars/cl_netvars.lua @@ -1,5 +1,8 @@ GProfiler.NetVars = GProfiler.NetVars or {} +GProfiler.Profilers.Register("Network Variables", {}) +local NetVarsStore = GProfiler.Profilers.GetStore("Network Variables") + function GProfiler.NetVars.DoTab(Content) end diff --git a/lua/gprofiler/profilers/timers/cl_timers.lua b/lua/gprofiler/profilers/timers/cl_timers.lua index 899dad2..261e349 100644 --- a/lua/gprofiler/profilers/timers/cl_timers.lua +++ b/lua/gprofiler/profilers/timers/cl_timers.lua @@ -1,5 +1,7 @@ GProfiler.Timers = GProfiler.Timers or {} +local TimersStore = GProfiler.Profilers.GetStore("Timers") + function GProfiler.Timers.DoTab(Content) end diff --git a/lua/gprofiler/profilers/timers/sh_timers.lua b/lua/gprofiler/profilers/timers/sh_timers.lua index e8979ad..58d6d8f 100644 --- a/lua/gprofiler/profilers/timers/sh_timers.lua +++ b/lua/gprofiler/profilers/timers/sh_timers.lua @@ -1,13 +1,6 @@ --- -- For timers, we must detour instantly, as there is no way to get timers created before the detour was created. --- -- rubat please timer.GetList - --- GProfiler.Timers = GProfiler.Timers or {} --- GProfiler.Timers.Simple = GProfiler.Timers.Simple or {} --- GProfiler.Timers.Create = GProfiler.Timers.Create or {} --- GProfiler.Timers.IsDetoured = GProfiler.Timers.IsDetoured or false --- GProfiler.Timers.OldSimpleTimer = GProfiler.Timers.OldSimpleTimer or timer.Simple --- GProfiler.Timers.OldCreateTimer = GProfiler.Timers.OldCreateTimer or timer.Create --- GProfiler.ActiveTimers = GProfiler.ActiveTimers or { Simple = {}, Create = {} } +GProfiler.Timers = GProfiler.Timers or {} + +GProfiler.Profilers.Register("Timers", {}) -- local chunkSizeLimit = 65535 diff --git a/lua/gprofiler/sv_init.lua b/lua/gprofiler/sv_init.lua index c4406e8..b6ed32b 100644 --- a/lua/gprofiler/sv_init.lua +++ b/lua/gprofiler/sv_init.lua @@ -1,28 +1,2 @@ --- util.AddNetworkString("GProfiler.SendState") - --- local Profilers = { --- "ConCommands", --[["EntVars",]] "Functions", --- "Hooks", "Net", "NetVars", "Timers", "Database" --- } - --- hook.Add("PlayerInitialSpawn", "GProfiler.SendState", function(ply) --- local Active = {} --- for _, profiler in ipairs(Profilers) do --- if GProfiler[profiler].ProfileStarted then --- Active[profiler] = GProfiler[profiler].ProfileStarted --- end --- end - --- if table.IsEmpty(Active) then return end - --- net.Start("GProfiler.SendState") --- net.WriteUInt(table.Count(Active), 4) --- for profiler, time in pairs(Active) do --- net.WriteString(profiler) --- net.WriteFloat(SysTime() - time) --- end --- net.Send(ply) --- end) - local lan = GetConVar("sv_lan") if lan:GetBool() then SetGlobalBool("gprofiler_lan", true) end \ No newline at end of file From b7be616ed16028f46cc3fec01b33d444fc62ed9f Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Fri, 20 Mar 2026 17:51:48 +0000 Subject: [PATCH 10/16] Hooks --- lua/gprofiler/modules/sh_profilers.lua | 2 - lua/gprofiler/modules/utils/cl_vgui.lua | 80 +++++++ lua/gprofiler/profilers/hooks/cl_hooks.lua | 257 ++++++++++++++++++++- lua/gprofiler/profilers/hooks/sh_hooks.lua | 9 + lua/gprofiler/profilers/net/cl_net.lua | 84 +------ lua/gprofiler/sh_config.lua | 3 + 6 files changed, 348 insertions(+), 87 deletions(-) diff --git a/lua/gprofiler/modules/sh_profilers.lua b/lua/gprofiler/modules/sh_profilers.lua index f487a00..79ecdf1 100644 --- a/lua/gprofiler/modules/sh_profilers.lua +++ b/lua/gprofiler/modules/sh_profilers.lua @@ -89,8 +89,6 @@ function Profilers.Register(name, opts) OnDataReceived = opts.OnDataReceived, } - MsgC(string.format("Registered profiler '%s' for realms: %s\n", name, table.concat(profiler.Realms, ", ")), Color(100, 255, 100)) - Profilers.Registered[name] = profiler return profiler.Store end diff --git a/lua/gprofiler/modules/utils/cl_vgui.lua b/lua/gprofiler/modules/utils/cl_vgui.lua index 184f36c..3b64bf4 100644 --- a/lua/gprofiler/modules/utils/cl_vgui.lua +++ b/lua/gprofiler/modules/utils/cl_vgui.lua @@ -17,3 +17,83 @@ function PANEL:SortByColumn(ColumnID, Desc) end derma.DefineControl("GP.ListView", "", PANEL, "DListView") + +function GProfiler.Utils.CreateList(Parent, Header, Columns) + local ResultsList = vgui.Create("GP.ListView", Parent) + ResultsList:SetSize(Parent:GetWide(), Parent:GetTall() - Header:GetTall()) + ResultsList:SetPos(0, Header:GetTall()) + ResultsList:SetMultiSelect(false) + for _, col in ipairs(Columns) do + ResultsList:AddColumn(col) + end + ResultsList:SetHeaderHeight(GProfiler.GetScaledSize(30)) + ResultsList:SetDataHeight(GProfiler.GetScaledSize(draw.GetFontHeight("GProfiler.Inter24") + GProfiler.GetScaledSize(10))) + ResultsList.Paint = nil + + local sbar = ResultsList.VBar + sbar:SetWide(GProfiler.GetScaledSize(12)) + sbar:SetHideButtons(true) + sbar.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) + end + sbar.btnGrip.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + for k, v in ipairs(ResultsList.Columns) do + v.Header:SetFont("GProfiler.Inter28") + v.Header:SetTextColor(color_white) + local isLast = v == ResultsList.Columns[#ResultsList.Columns] + v.Header.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(64, 105, 146), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + if not isLast then + surface.SetDrawColor(Color(255, 255, 255, 20)) + surface.DrawRect(w - 1, 0, 1, h) + end + + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + end + + if ResultsList.SortedBy == k then + draw.SimpleText(ResultsList.SortedDescending and "▼" or "▲", "GProfiler.Inter24", w - GProfiler.GetScaledSize(20), h / 2, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + + local oldAddLine = ResultsList.AddLine + ResultsList.AddLine = function(self, ...) + local line = oldAddLine(self, ...) + line.Paint = function(s, w, h) + local isEven = false + for i, v in ipairs(self.Sorted) do + if v == line then + isEven = i % 2 == 0 + break + end + end + GProfiler.RNDX.Draw(0, 0, 0, w, h, isEven and Color(255, 255, 255, 10) or Color(255, 255, 255, 2)) + + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + if s:IsLineSelected() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 30)) + end + end + for _, col in pairs(line.Columns) do + col:SetFont("GProfiler.Inter24") + col:SetTextColor(Color(255, 255, 255, 200)) + end + return line + end + + local sbar = ResultsList.VBar + sbar:SetWide(GProfiler.GetScaledSize(12)) + sbar:SetHideButtons(true) + sbar.Paint = function(s, w, h) GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) end + sbar.btnGrip.Paint = function(s, w, h) GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) end + + return ResultsList +end \ No newline at end of file diff --git a/lua/gprofiler/profilers/hooks/cl_hooks.lua b/lua/gprofiler/profilers/hooks/cl_hooks.lua index ebd49c8..a7aa574 100644 --- a/lua/gprofiler/profilers/hooks/cl_hooks.lua +++ b/lua/gprofiler/profilers/hooks/cl_hooks.lua @@ -1,14 +1,265 @@ GProfiler.Hooks = GProfiler.Hooks or {} +local Hooks = GProfiler.Hooks +Hooks.Realm = Hooks.Realm or "Client" local HooksStore = GProfiler.Profilers.GetStore("Hooks") -function GProfiler.Hooks.DoTab(Content) +function GProfiler.Hooks.DoTab(Base, Outer) +local Header = GProfiler.Utils.SetupHeader(Outer, "Hooks", "gprofiler/hooks.png") + local initialActive = Hooks.Realm == "Both" and (HooksStore:IsActive("Client") or HooksStore:IsActive("Server")) or HooksStore:IsActive(Hooks.Realm) + local StartStop = Header:SetupStartStop(initialActive) + local RealmSelector = Header:SetupRealmSelector(Hooks.Realm, true) + local Timer = Header:SetupTimer(function() + local realm = Hooks.Realm == "Both" and "Client" or Hooks.Realm + return HooksStore:GetTimerData(realm) + end) + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Header.OnHandleMoved() + end + + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Hooks", Hooks.Realm, Running) + + if Hooks.RefreshUI then + Hooks.RefreshUI() + end + end + + function RealmSelector:OnStateChanged(state) + Hooks.Realm = state + if state == "Both" then + StartStop.State = HooksStore:IsActive("Client") or HooksStore:IsActive("Server") + else + StartStop.State = HooksStore:IsActive(state) + end + StartStop:SetText(StartStop.State and "Stop" or "Start") + if Hooks.RefreshUI then + Hooks.RefreshUI() + end + end + + local left, Source = GProfiler.Utils.VSplitPanel(Base, GProfiler.GetScaledSize(10), "hook_lr", 0.65) + local Results, Receivers = GProfiler.Utils.HSplitPanel(left, GProfiler.GetScaledSize(10), "hook_l_bt", 0.65) + local ClientHooks, ServerHooks = GProfiler.Utils.VSplitPanel(Receivers, GProfiler.GetScaledSize(10), "hook_lb_lr", 0.5) + + local SourceHeader = vgui.Create("DLabel", Source) + SourceHeader:SetFont("GProfiler.Inter24") + SourceHeader:SetTextColor(GProfiler.SyntaxColors.comment) + SourceHeader:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(5)) + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + SourceHeader:SetText("Select a hook to view source.") + + Source.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) + end + + local RichText = vgui.Create("RichText", Source) + RichText:SetText("") + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:SetVerticalScrollbarEnabled(true) + function RichText:PerformLayout() + self:SetFontInternal("GProfiler.Code") + end + + Source.OnHandleMoved = function() + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:InvalidateLayout() + end + + local Header = GProfiler.Utils.SetupHeader(Results, "Profiler Results", nil, true) + local ResultsList = GProfiler.Utils.CreateList(Results, Header, {"Hook", "Count", "Time (Total)", "Time (Avg)", "Time (Longest)"}) + + local function PopulateResults() + if not IsValid(ResultsList) then return end + + local displayRealm = Hooks.Realm == "Both" and "Client" or Hooks.Realm + local realmData = HooksStore:GetData(displayRealm) or {} + + ResultsList:Clear() + + for k, v in pairs(realmData) do + local row = ResultsList:AddLine(string.format("%s (%s)", v.r, v.h), v.c, v.t, v.c > 0 and v.t / v.c or 0, v.t) + row.HookData = v + end + end + GProfiler.Hooks.RefreshUI = PopulateResults + PopulateResults() + + function ResultsList:OnRowSelected(rowIndex, row) + local data = row.HookData + if data then + RichText:SetText("Loading source...") + SourceHeader:SetText("") + + local file = data.Source or data[4] + local lineDefined = data.Lines and data.Lines[1] or data[5] or 0 + local lastLineDefined = data.Lines and data.Lines[2] or data[6] or 0 + + if file and file ~= "" then + file = string.match(file, "@?(.+)") + if not file then file = data.Source or data[4] end + + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) + + GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), lineDefined) + else + RichText:SetText("Failed to load source") + end + end) + else + RichText:SetText("Failed to load source (2)") + end + end + end + + local function CreateHookList(Parent, Receivers, Title) + Parent:Clear() + + local HeaderPanel = vgui.Create("DPanel", Parent) + HeaderPanel:SetSize(Parent:GetWide(), GProfiler.GetScaledSize(50)) + HeaderPanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, Color(34, 77, 122), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + draw.SimpleText(Title, "GProfiler.Inter28", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local RefreshButton = vgui.Create("DButton", HeaderPanel) + RefreshButton:SetSize(GProfiler.GetScaledSize(80), GProfiler.GetScaledSize(30)) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + RefreshButton:SetText("Refresh") + RefreshButton:SetFont("GProfiler.Inter24") + RefreshButton:SetTextColor(Color(0,0,0,0)) + RefreshButton.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + if s:IsHovered() then + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + end + draw.SimpleText("Refresh", "GProfiler.Inter24", w / 2, h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + RefreshButton.DoClick = function() + net.Start("GProfiler_Hooks_HookTbl") + net.SendToServer() + end + + local List = vgui.Create("DPanelList", Parent) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + List:EnableVerticalScrollbar() + + local ScrollBar = List.VBar + ScrollBar:SetWide(GProfiler.GetScaledSize(12)) + ScrollBar:SetHideButtons(true) + ScrollBar.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + end + ScrollBar.btnGrip.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + for k, Receiver in ipairs(Receivers) do + local i = k + local Item = vgui.Create("DButton", List) + Item:SetSize(List:GetWide(), GProfiler.GetScaledSize(30)) + Item:SetText(Receiver.Name) + Item:SetFont("GProfiler.Inter24") + Item:SizeToContentsY() + Item:SetTall(Item:GetTall() + GProfiler.GetScaledSize(10)) + Item:SetContentAlignment(1) + Item:SetTextColor(Color(0,0,0,0)) + Item.Paint = function(s, w, h) + if i % 2 == 0 then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + else + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 2)) + end + + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + draw.SimpleText(Receiver.Name, "GProfiler.Inter24", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(Receiver.Hook, "GProfiler.Inter24", w - GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.comment, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + List:AddItem(Item) + end + + Parent.OnHandleMoved = function() + HeaderPanel:SetSize(Parent:GetWide(), HeaderPanel:GetTall()) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + for k, v in pairs(List:GetItems()) do + v:SetSize(List:GetWide(), v:GetTall()) + end + end + end + + local CLHooks = {} + local SVHooks = {} + + for hookName, hookReceivers in pairs(hook.GetTable()) do + for receiverName, _ in pairs(hookReceivers) do + table.insert(CLHooks, { + Name = tostring(receiverName or ""), + Hook = hookName + }) + end + end + + local clr = CreateHookList(ClientHooks, CLHooks, string.format("Client Hooks (%d)", #CLHooks)) + + net.Receive("GProfiler_Hooks_HookTbl", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + SVHooks = {} + for i = 1, net.ReadUInt(15) do + local hookName = net.ReadString() + for j = 1, net.ReadUInt(10) do + local receiverName = net.ReadString() + table.insert(SVHooks, { + Name = tostring(receiverName or ""), + Hook = hookName + }) + end + end + + local svr = CreateHookList(ServerHooks, SVHooks, string.format("Server Hooks (%d)", #SVHooks)) + end) + + net.Start("GProfiler_Hooks_HookTbl") + net.SendToServer() end -GProfiler.Menu.RegisterTab("Hooks", "gprofiler/hooks.png", 1, GProfiler.Hooks.DoTab, function() +GProfiler.Menu.RegisterTab("Hooks", "gprofiler/hooks.png", 1, Hooks.DoTab, function() if not HooksStore then return end - local timer = HooksStore:GetTimerData(GProfiler.Hooks.Realm) + local timer = HooksStore:GetTimerData(Hooks.Realm) if timer.StartTime == 0 then return end return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) + +net.Receive("GProfiler_Hooks_SendData", function() + local Data = {} + for i = 1, net.ReadUInt(20) do + table.insert(Data, { + r = net.ReadString(), + h = net.ReadString(), + c = net.ReadUInt(32), + t = net.ReadFloat(), + Source = net.ReadString(), + Lines = {net.ReadUInt(16), net.ReadUInt(16)} + }) + end + + HooksStore:SetData("Server", Data) + if Hooks.RefreshUI then Hooks.RefreshUI() end +end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/hooks/sh_hooks.lua b/lua/gprofiler/profilers/hooks/sh_hooks.lua index 024c5a0..b045867 100644 --- a/lua/gprofiler/profilers/hooks/sh_hooks.lua +++ b/lua/gprofiler/profilers/hooks/sh_hooks.lua @@ -121,6 +121,12 @@ GProfiler.Profilers.Register("Hooks", { end, OnStop = function(realm, ply) StopDetour() + if CLIENT then + local HookStore = GProfiler.Profilers.GetStore("Hooks") + if HookStore then + HookStore:SetData(realm, HooksProfiler.ProfileData) + end + end if SERVER and ply then SendData(ply) end @@ -144,6 +150,9 @@ if SERVER then for hookName, hookReceivers in pairs(hooks) do net.WriteString(hookName) net.WriteUInt(table.Count(hookReceivers), 10) + for receiverName, _ in pairs(hookReceivers) do + net.WriteString(tostring(receiverName or "")) + end end net.Send(ply) end) diff --git a/lua/gprofiler/profilers/net/cl_net.lua b/lua/gprofiler/profilers/net/cl_net.lua index dcc9ddf..f49b8a8 100644 --- a/lua/gprofiler/profilers/net/cl_net.lua +++ b/lua/gprofiler/profilers/net/cl_net.lua @@ -115,88 +115,8 @@ function GProfiler.Net.DoTab(Base, Outer) local SentHeader = GProfiler.Utils.SetupHeader(ResultsSent, "Messages Sent", nil, true) local ReceivedHeader = GProfiler.Utils.SetupHeader(ResultsReceived, "Messages Received", nil, true) - local function CreateList(Parent, Columns) - local ResultsList = vgui.Create("GP.ListView", Parent) - ResultsList:SetSize(Parent:GetWide(), Parent:GetTall() - SentHeader:GetTall()) - ResultsList:SetPos(0, SentHeader:GetTall()) - ResultsList:SetMultiSelect(false) - for _, col in ipairs(Columns) do - ResultsList:AddColumn(col) - end - ResultsList:SetHeaderHeight(GProfiler.GetScaledSize(30)) - ResultsList:SetDataHeight(GProfiler.GetScaledSize(draw.GetFontHeight("GProfiler.Inter24") + GProfiler.GetScaledSize(10))) - ResultsList.Paint = nil - - local sbar = ResultsList.VBar - sbar:SetWide(GProfiler.GetScaledSize(12)) - sbar:SetHideButtons(true) - sbar.Paint = function(s, w, h) - GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) - end - sbar.btnGrip.Paint = function(s, w, h) - GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) - end - - for k, v in ipairs(ResultsList.Columns) do - v.Header:SetFont("GProfiler.Inter28") - v.Header:SetTextColor(color_white) - local isLast = v == ResultsList.Columns[#ResultsList.Columns] - v.Header.Paint = function(s, w, h) - GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(64, 105, 146), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) - if not isLast then - surface.SetDrawColor(Color(255, 255, 255, 20)) - surface.DrawRect(w - 1, 0, 1, h) - end - - if s:IsHovered() then - GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) - end - - if ResultsList.SortedBy == k then - draw.SimpleText(ResultsList.SortedDescending and "▼" or "▲", "GProfiler.Inter24", w - GProfiler.GetScaledSize(20), h / 2, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) - end - end - end - - local oldAddLine = ResultsList.AddLine - ResultsList.AddLine = function(self, ...) - local line = oldAddLine(self, ...) - line.Paint = function(s, w, h) - local isEven = false - for i, v in ipairs(self.Sorted) do - if v == line then - isEven = i % 2 == 0 - break - end - end - GProfiler.RNDX.Draw(0, 0, 0, w, h, isEven and Color(255, 255, 255, 10) or Color(255, 255, 255, 2)) - - if s:IsHovered() then - GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) - end - - if s:IsLineSelected() then - GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 30)) - end - end - for _, col in pairs(line.Columns) do - col:SetFont("GProfiler.Inter24") - col:SetTextColor(Color(255, 255, 255, 200)) - end - return line - end - - local sbar = ResultsList.VBar - sbar:SetWide(GProfiler.GetScaledSize(12)) - sbar:SetHideButtons(true) - sbar.Paint = function(s, w, h) GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) end - sbar.btnGrip.Paint = function(s, w, h) GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) end - - return ResultsList - end - - local ResultsList = CreateList(ResultsSent, {"Name", "Count", "Size (Largest)", "Size (Total)", "Time (Total)", "Time (Avg)", "Time (Longest)"}) - local ReceivedList = CreateList(ResultsReceived, {"Name", "Count", "Size (Largest)", "Size (Total)", "Time (Total)", "Time (Avg)", "Time (Longest)"}) + local ResultsList = GProfiler.Utils.CreateList(ResultsSent, SentHeader, {"Name", "Count", "Size (Largest)", "Size (Total)", "Time (Total)", "Time (Avg)", "Time (Longest)"}) + local ReceivedList = GProfiler.Utils.CreateList(ResultsReceived, ReceivedHeader, {"Name", "Count", "Size (Largest)", "Size (Total)", "Time (Total)", "Time (Avg)", "Time (Longest)"}) Results.OnHandleMoved = function() ResultsList:SetSize(Results:GetWide(), Results:GetTall() - SentHeader:GetTall()) diff --git a/lua/gprofiler/sh_config.lua b/lua/gprofiler/sh_config.lua index 08321b5..9178c28 100644 --- a/lua/gprofiler/sh_config.lua +++ b/lua/gprofiler/sh_config.lua @@ -18,6 +18,9 @@ GProfiler.Config.AllowedSteamIDs = { -- SteamIDs that can access GProfiler GProfiler.Config.AllowSuperAdmin = false -- Allow players with superadmin (Player:IsSuperAdmin()) to access GProfiler regardless of other checks. +-- todo, requires a lot of changes probably +-- GProfiler.Config.AlwaysAllowClient = false -- Allow clients to use GProfiler (limited to client realm/cannot view sources, etc) + --[[ If express is available, we use it over gmod's net library This can handle larger data sizes, and we shouldn't need to worry about overflowing the buffer From ab847541d8379af1d9cb912a3b72d9f1d8e4165c Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sat, 21 Mar 2026 11:24:21 +0000 Subject: [PATCH 11/16] concommands --- lua/gprofiler/modules/sh_profilers.lua | 2 + .../profilers/concommands/cl_concommands.lua | 251 +++++++++++++++++- .../profilers/concommands/sh_concommands.lua | 6 + lua/gprofiler/profilers/hooks/cl_hooks.lua | 8 +- lua/gprofiler/profilers/hooks/sh_hooks.lua | 1 - 5 files changed, 265 insertions(+), 3 deletions(-) diff --git a/lua/gprofiler/modules/sh_profilers.lua b/lua/gprofiler/modules/sh_profilers.lua index 79ecdf1..465e2f3 100644 --- a/lua/gprofiler/modules/sh_profilers.lua +++ b/lua/gprofiler/modules/sh_profilers.lua @@ -247,6 +247,8 @@ net.Receive("GProfiler.Profiler.SyncState", function() end) function Profilers.Toggle(name, realm, start) + if not GProfiler.Access.HasAccess(LocalPlayer()) then return end + local profiler = Profilers.Get(name) if not profiler then return end diff --git a/lua/gprofiler/profilers/concommands/cl_concommands.lua b/lua/gprofiler/profilers/concommands/cl_concommands.lua index 8314499..5ad3614 100644 --- a/lua/gprofiler/profilers/concommands/cl_concommands.lua +++ b/lua/gprofiler/profilers/concommands/cl_concommands.lua @@ -1,9 +1,236 @@ GProfiler.ConCommands = GProfiler.ConCommands or {} +local ConCommands = GProfiler.ConCommands local CommandsStore = GProfiler.Profilers.GetStore("Commands") -function GProfiler.ConCommands.DoTab(Content) +function GProfiler.ConCommands.DoTab(Base, Outer) +local Header = GProfiler.Utils.SetupHeader(Outer, "Commands", "gprofiler/hooks.png") + local initialActive = ConCommands.Realm == "Both" and (CommandsStore:IsActive("Client") or CommandsStore:IsActive("Server")) or CommandsStore:IsActive(ConCommands.Realm) + local StartStop = Header:SetupStartStop(initialActive) + local RealmSelector = Header:SetupRealmSelector(ConCommands.Realm, true) + local Timer = Header:SetupTimer(function() + local realm = ConCommands.Realm == "Both" and "Client" or ConCommands.Realm + return CommandsStore:GetTimerData(realm) + end) + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Header.OnHandleMoved() + end + + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Commands", ConCommands.Realm, Running) + + if ConCommands.RefreshUI then + ConCommands.RefreshUI() + end + end + + function RealmSelector:OnStateChanged(state) + ConCommands.Realm = state + if state == "Both" then + StartStop.State = CommandsStore:IsActive("Client") or CommandsStore:IsActive("Server") + else + StartStop.State = CommandsStore:IsActive(state) + end + StartStop:SetText(StartStop.State and "Stop" or "Start") + if ConCommands.RefreshUI then + ConCommands.RefreshUI() + end + end + + local left, Source = GProfiler.Utils.VSplitPanel(Base, GProfiler.GetScaledSize(10), "hook_lr", 0.65) + local Results, Receivers = GProfiler.Utils.HSplitPanel(left, GProfiler.GetScaledSize(10), "hook_l_bt", 0.65) + local ClientHooks, ServerHooks = GProfiler.Utils.VSplitPanel(Receivers, GProfiler.GetScaledSize(10), "hook_lb_lr", 0.5) + + local SourceHeader = vgui.Create("DLabel", Source) + SourceHeader:SetFont("GProfiler.Inter24") + SourceHeader:SetTextColor(GProfiler.SyntaxColors.comment) + SourceHeader:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(5)) + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + SourceHeader:SetText("Select a command to view source.") + + Source.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) + end + + local RichText = vgui.Create("RichText", Source) + RichText:SetText("") + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:SetVerticalScrollbarEnabled(true) + function RichText:PerformLayout() + self:SetFontInternal("GProfiler.Code") + end + + Source.OnHandleMoved = function() + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:InvalidateLayout() + end + + local Header = GProfiler.Utils.SetupHeader(Results, "Profiler Results", nil, true) + local ResultsList = GProfiler.Utils.CreateList(Results, Header, {"Command", "Calls", "Total Time", "Average Time", "Longest Time"}) + + local function PopulateResults() + if not IsValid(ResultsList) then return end + + local displayRealm = ConCommands.Realm == "Both" and "Client" or ConCommands.Realm + local realmData = CommandsStore:GetData(displayRealm) or {} + + ResultsList:Clear() + + for k, v in pairs(realmData) do + local line = ResultsList:AddLine(k, v.Count, v.Time, v.AverageTime, v.LongestTime) + line.CmdData = v + end + end + GProfiler.ConCommands.RefreshUI = PopulateResults + PopulateResults() + + function ResultsList:OnRowSelected(rowIndex, row) + local data = row.CmdData + if data then + RichText:SetText("Loading source...") + SourceHeader:SetText("") + + local file = data.Source or data[4] + local lineDefined = data.Lines and data.Lines[1] or data[5] or 0 + local lastLineDefined = data.Lines and data.Lines[2] or data[6] or 0 + + if file and file ~= "" then + file = string.match(file, "@?(.+)") + if not file then file = data.Source or data[4] end + + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) + + GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), lineDefined) + else + RichText:SetText("Failed to load source") + end + end) + else + RichText:SetText("Failed to load source (2)") + end + end + end + + local function CreateCmdList(Parent, Receivers, Title) + Parent:Clear() + + local HeaderPanel = vgui.Create("DPanel", Parent) + HeaderPanel:SetSize(Parent:GetWide(), GProfiler.GetScaledSize(50)) + HeaderPanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, Color(34, 77, 122), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + draw.SimpleText(Title, "GProfiler.Inter28", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local RefreshButton = vgui.Create("DButton", HeaderPanel) + RefreshButton:SetSize(GProfiler.GetScaledSize(80), GProfiler.GetScaledSize(30)) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + RefreshButton:SetText("Refresh") + RefreshButton:SetFont("GProfiler.Inter24") + RefreshButton:SetTextColor(Color(0,0,0,0)) + RefreshButton.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + if s:IsHovered() then + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + end + draw.SimpleText("Refresh", "GProfiler.Inter24", w / 2, h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + RefreshButton.DoClick = function() + net.Start("GProfiler_ConCommands_CommandList") + net.SendToServer() + end + + local List = vgui.Create("DPanelList", Parent) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + List:EnableVerticalScrollbar() + + local ScrollBar = List.VBar + ScrollBar:SetWide(GProfiler.GetScaledSize(12)) + ScrollBar:SetHideButtons(true) + ScrollBar.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + end + ScrollBar.btnGrip.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + for k, Receiver in ipairs(Receivers) do + local i = k + local Item = vgui.Create("DButton", List) + Item:SetSize(List:GetWide(), GProfiler.GetScaledSize(30)) + Item:SetText(Receiver.Name) + Item:SetFont("GProfiler.Inter24") + Item:SizeToContentsY() + Item:SetTall(Item:GetTall() + GProfiler.GetScaledSize(10)) + Item:SetContentAlignment(1) + Item:SetTextColor(Color(0,0,0,0)) + Item.Paint = function(s, w, h) + if i % 2 == 0 then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + else + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 2)) + end + + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + draw.SimpleText(Receiver.Name, "GProfiler.Inter24", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + List:AddItem(Item) + end + + Parent.OnHandleMoved = function() + HeaderPanel:SetSize(Parent:GetWide(), HeaderPanel:GetTall()) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + for k, v in pairs(List:GetItems()) do + v:SetSize(List:GetWide(), v:GetTall()) + end + end + end + + local CLCmds = {} + local SVCmds = {} + + for name, func in pairs(concommand.GetTable()) do + table.insert(CLCmds, { Name = name }) + end + + + local clr = CreateCmdList(ClientHooks, CLCmds, string.format("Client Commands (%d)", #CLCmds)) + + net.Receive("GProfiler_ConCommands_CommandList", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + SVCmds = {} + for i = 1, net.ReadUInt(32) do + table.insert(SVCmds, { Name = net.ReadString(), Source = net.ReadString(), Lines = {net.ReadUInt(16), net.ReadUInt(16)} }) + end + + local svr = CreateCmdList(ServerHooks, SVCmds, string.format("Server Commands (%d)", #SVCmds)) + end) + + net.Start("GProfiler_ConCommands_CommandList") + net.SendToServer() + + Results.OnHandleMoved = function() + ResultsList:SetSize(Results:GetWide(), Results:GetTall() - Header:GetTall()) + ResultsList:SetPos(0, Header:GetTall()) + Header:SetWide(Results:GetWide()) + end end GProfiler.Menu.RegisterTab("Commands", "gprofiler/commands.png", 4, GProfiler.ConCommands.DoTab, function() @@ -12,3 +239,25 @@ GProfiler.Menu.RegisterTab("Commands", "gprofiler/commands.png", 4, GProfiler.Co if timer.StartTime == 0 then return end return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) + +net.Receive("GProfiler_ConCommands_SendData", function() + local count = net.ReadUInt(32) + local data = {} + for i = 1, count do + local name = net.ReadString() + data[name] = { + Count = net.ReadUInt(32), + Time = net.ReadFloat(), + AverageTime = net.ReadFloat(), + LongestTime = net.ReadFloat(), + Source = net.ReadString(), + Lines = {net.ReadUInt(16), net.ReadUInt(16)} + } + end + + CommandsStore:SetData("Server", data) + + if GProfiler.ConCommands.RefreshUI then + GProfiler.ConCommands.RefreshUI() + end +end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/concommands/sh_concommands.lua b/lua/gprofiler/profilers/concommands/sh_concommands.lua index b721e8b..5df4023 100644 --- a/lua/gprofiler/profilers/concommands/sh_concommands.lua +++ b/lua/gprofiler/profilers/concommands/sh_concommands.lua @@ -81,6 +81,12 @@ GProfiler.Profilers.Register("Commands", { end, OnStop = function(realm, ply) StopDetour() + if CLIENT then + local CmdStore = GProfiler.Profilers.GetStore("Commands") + if CmdStore then + CmdStore:SetData(realm, GProfiler.ConCommands.ProfileData) + end + end if SERVER and ply then SendData(ply) end diff --git a/lua/gprofiler/profilers/hooks/cl_hooks.lua b/lua/gprofiler/profilers/hooks/cl_hooks.lua index a7aa574..6e6bac0 100644 --- a/lua/gprofiler/profilers/hooks/cl_hooks.lua +++ b/lua/gprofiler/profilers/hooks/cl_hooks.lua @@ -5,7 +5,7 @@ Hooks.Realm = Hooks.Realm or "Client" local HooksStore = GProfiler.Profilers.GetStore("Hooks") function GProfiler.Hooks.DoTab(Base, Outer) -local Header = GProfiler.Utils.SetupHeader(Outer, "Hooks", "gprofiler/hooks.png") + local Header = GProfiler.Utils.SetupHeader(Outer, "Hooks", "gprofiler/hooks.png") local initialActive = Hooks.Realm == "Both" and (HooksStore:IsActive("Client") or HooksStore:IsActive("Server")) or HooksStore:IsActive(Hooks.Realm) local StartStop = Header:SetupStartStop(initialActive) local RealmSelector = Header:SetupRealmSelector(Hooks.Realm, true) @@ -238,6 +238,12 @@ local Header = GProfiler.Utils.SetupHeader(Outer, "Hooks", "gprofiler/hooks.png" net.Start("GProfiler_Hooks_HookTbl") net.SendToServer() + + Results.OnHandleMoved = function() + ResultsList:SetSize(Results:GetWide(), Results:GetTall() - Header:GetTall()) + ResultsList:SetPos(0, Header:GetTall()) + Header:SetWide(Results:GetWide()) + end end GProfiler.Menu.RegisterTab("Hooks", "gprofiler/hooks.png", 1, Hooks.DoTab, function() diff --git a/lua/gprofiler/profilers/hooks/sh_hooks.lua b/lua/gprofiler/profilers/hooks/sh_hooks.lua index b045867..5790044 100644 --- a/lua/gprofiler/profilers/hooks/sh_hooks.lua +++ b/lua/gprofiler/profilers/hooks/sh_hooks.lua @@ -3,7 +3,6 @@ GProfiler.Hooks = GProfiler.Hooks or {} local HooksProfiler = GProfiler.Hooks HooksProfiler.IsDetoured = HooksProfiler.IsDetoured or false HooksProfiler.ProfileData = HooksProfiler.ProfileData or {} -HooksProfiler.RestoreHookTable = HooksProfiler.RestoreHookTable or {} local SysTime = SysTime local unpack = unpack From 3dad3d2c4d0b6c45651fc8b2470f8bdb599f0d2d Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Sun, 22 Mar 2026 11:26:57 +0000 Subject: [PATCH 12/16] entvars --- .../profilers/concommands/cl_concommands.lua | 3 +- .../profilers/entvars/cl_entvars.lua | 102 +++++++++++++++++- .../profilers/functions/cl_functions.lua | 1 - .../profilers/functions/sh_functions.lua | 6 ++ lua/gprofiler/profilers/hooks/cl_hooks.lua | 1 - lua/gprofiler/profilers/net/cl_net.lua | 1 - 6 files changed, 106 insertions(+), 8 deletions(-) diff --git a/lua/gprofiler/profilers/concommands/cl_concommands.lua b/lua/gprofiler/profilers/concommands/cl_concommands.lua index 5ad3614..9df4a73 100644 --- a/lua/gprofiler/profilers/concommands/cl_concommands.lua +++ b/lua/gprofiler/profilers/concommands/cl_concommands.lua @@ -4,7 +4,7 @@ local ConCommands = GProfiler.ConCommands local CommandsStore = GProfiler.Profilers.GetStore("Commands") function GProfiler.ConCommands.DoTab(Base, Outer) -local Header = GProfiler.Utils.SetupHeader(Outer, "Commands", "gprofiler/hooks.png") +local Header = GProfiler.Utils.SetupHeader(Outer, "Commands", "gprofiler/commands.png") local initialActive = ConCommands.Realm == "Both" and (CommandsStore:IsActive("Client") or CommandsStore:IsActive("Server")) or CommandsStore:IsActive(ConCommands.Realm) local StartStop = Header:SetupStartStop(initialActive) local RealmSelector = Header:SetupRealmSelector(ConCommands.Realm, true) @@ -234,7 +234,6 @@ local Header = GProfiler.Utils.SetupHeader(Outer, "Commands", "gprofiler/hooks.p end GProfiler.Menu.RegisterTab("Commands", "gprofiler/commands.png", 4, GProfiler.ConCommands.DoTab, function() - if not CommandsStore then return end local timer = CommandsStore:GetTimerData(GProfiler.ConCommands.Realm) if timer.StartTime == 0 then return end return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive diff --git a/lua/gprofiler/profilers/entvars/cl_entvars.lua b/lua/gprofiler/profilers/entvars/cl_entvars.lua index 3e31283..fa0d27f 100644 --- a/lua/gprofiler/profilers/entvars/cl_entvars.lua +++ b/lua/gprofiler/profilers/entvars/cl_entvars.lua @@ -1,12 +1,108 @@ GProfiler.EntVars = GProfiler.EntVars or {} -GProfiler.Profilers.Register("Entity Variables", {}) +local EntVars = GProfiler.EntVars +EntVars.ProfileData = EntVars.ProfileData or {} + +GProfiler.Profilers.Register("Entity Variables", { + Realms = {"Client"}, + OnStart = function(realm, ply) + EntVars.ProfileActive = true + EntVars.ProfileData = {} + end, + OnStop = function(realm, ply) + EntVars.ProfileActive = false + local EntVarsStore = GProfiler.Profilers.GetStore("Entity Variables") + if EntVarsStore then + EntVarsStore:SetData(realm, EntVars.ProfileData) + end + if EntVars.RefreshUI then EntVars.RefreshUI() end + end +}) local EntVarsStore = GProfiler.Profilers.GetStore("Entity Variables") -function GProfiler.EntVars.DoTab(Content) +function GProfiler.EntVars.DoTab(Base, Outer) + local Header = GProfiler.Utils.SetupHeader(Outer, "Entity Variables", "gprofiler/entvars.png") + local StartStop = Header:SetupStartStop(EntVarsStore:IsActive("Client")) + local Timer = Header:SetupTimer(function() + return EntVarsStore:GetTimerData("Client") + end) + + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Header.OnHandleMoved() + end + + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Entity Variables", "Client", Running) + if EntVars.RefreshUI then EntVars.RefreshUI() end + end + + local Header = GProfiler.Utils.SetupHeader(Base, "Profiler Results", nil, true) + local ResultsList = GProfiler.Utils.CreateList(Base, Header, {"Entity", "Variable", "Times Changed", "Current Value"}) + ResultsList:SetTall(Base:GetTall() - Header:GetTall()) + ResultsList.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(38, 63, 89, 255)) + end + local function PopulateResults() + if not IsValid(ResultsList) then return end + + local realmData = EntVarsStore:GetData("Client") or {} + + ResultsList:Clear() + + for k, v in pairs(realmData) do + for var, count in pairs(v) do + if var == "GProfiler_SavedEnt" or var == "GProfiler_CurrentValues" then continue end + local currentVal = v.GProfiler_CurrentValues and v.GProfiler_CurrentValues[var] or "N/A" + ResultsList:AddLine(v.GProfiler_SavedEnt or "N/A", var, count, currentVal) + end + end + end + EntVars.RefreshUI = PopulateResults + PopulateResults() end GProfiler.Menu.RegisterTab("Entity Variables", "gprofiler/entvars.png", 6, GProfiler.EntVars.DoTab, function() - + local timer = EntVarsStore:GetTimerData("Client") + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) + +function GProfiler.EntVars.CollectData(ent, var, _, val) + if not GProfiler.EntVars.ProfileActive then return end + + if not GProfiler.EntVars.ProfileData[ent] then + GProfiler.EntVars.ProfileData[ent] = { + GProfiler_SavedEnt = tostring(ent), + GProfiler_CurrentValues = {} + } + end + + GProfiler.EntVars.ProfileData[ent][var] = (GProfiler.EntVars.ProfileData[ent][var] or 0) + 1 + GProfiler.EntVars.ProfileData[ent].GProfiler_CurrentValues[var] = tostring(val) +end + +local function CaptureEnt(ent, attempts) + if not IsValid(ent) then return end + if not ent.GetNetworkVars then + if attempts and attempts > 5 then return end + timer.Simple(.5, function() CaptureEnt(ent, (attempts or 0) + 1) end) + return + end + + for k, v in pairs(ent:GetNetworkVars() or {}) do + local GProfilerIdent = string.format("GProfiler.%s", k) + if ent[GProfilerIdent] then continue end + ent[GProfilerIdent] = true + ent:NetworkVarNotify(k, GProfiler.EntVars.CollectData) + end +end + +hook.Add("OnEntityCreated", "GProfiler.EntVars.CaptureEnt", function(ent) timer.Simple(0, function() CaptureEnt(ent) end) end) +hook.Add("InitPostEntity", "GProfiler.EntVars.CaptureEnts", function() + for k, v in ipairs(ents.GetAll()) do CaptureEnt(v) end +end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/functions/cl_functions.lua b/lua/gprofiler/profilers/functions/cl_functions.lua index d9b04fd..5e6bd06 100644 --- a/lua/gprofiler/profilers/functions/cl_functions.lua +++ b/lua/gprofiler/profilers/functions/cl_functions.lua @@ -7,7 +7,6 @@ function GProfiler.Functions.Tab(Content) end GProfiler.Menu.RegisterTab("Functions", "gprofiler/functions.png", 3, GProfiler.Functions.Tab, function() - if not FunctionsStore then return end local timer = FunctionsStore:GetTimerData(GProfiler.Functions.Realm) if timer.StartTime == 0 then return end return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive diff --git a/lua/gprofiler/profilers/functions/sh_functions.lua b/lua/gprofiler/profilers/functions/sh_functions.lua index d575b00..3a38bb7 100644 --- a/lua/gprofiler/profilers/functions/sh_functions.lua +++ b/lua/gprofiler/profilers/functions/sh_functions.lua @@ -214,6 +214,12 @@ GProfiler.Profilers.Register("Functions", { end, OnStop = function(realm, ply) StopDetour() + if CLIENT then + local FuncStore = GProfiler.Profilers.GetStore("Functions") + if FuncStore then + FuncStore:SetData(realm, FunctionsProfiler.ProfileData) + end + end if SERVER and ply then SendData(ply) end diff --git a/lua/gprofiler/profilers/hooks/cl_hooks.lua b/lua/gprofiler/profilers/hooks/cl_hooks.lua index 6e6bac0..8d4cd62 100644 --- a/lua/gprofiler/profilers/hooks/cl_hooks.lua +++ b/lua/gprofiler/profilers/hooks/cl_hooks.lua @@ -247,7 +247,6 @@ function GProfiler.Hooks.DoTab(Base, Outer) end GProfiler.Menu.RegisterTab("Hooks", "gprofiler/hooks.png", 1, Hooks.DoTab, function() - if not HooksStore then return end local timer = HooksStore:GetTimerData(Hooks.Realm) if timer.StartTime == 0 then return end return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive diff --git a/lua/gprofiler/profilers/net/cl_net.lua b/lua/gprofiler/profilers/net/cl_net.lua index f49b8a8..3ce09b3 100644 --- a/lua/gprofiler/profilers/net/cl_net.lua +++ b/lua/gprofiler/profilers/net/cl_net.lua @@ -632,7 +632,6 @@ function GProfiler.Net.DoTab(Base, Outer) net.SendToServer() end GProfiler.Menu.RegisterTab("Networking", "gprofiler/network.png", 2, GProfiler.Net.DoTab, function() - if not NetStore then return end local timer = NetStore:GetTimerData(Net.Realm) if timer.StartTime == 0 then return end return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive From 42f014e01ac5bc73af6d872251eb056be668aa43 Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Wed, 6 May 2026 13:19:46 +0100 Subject: [PATCH 13/16] Fix potential HasAccess errors --- lua/gprofiler/modules/sh_access.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/gprofiler/modules/sh_access.lua b/lua/gprofiler/modules/sh_access.lua index cc883c2..eef8621 100644 --- a/lua/gprofiler/modules/sh_access.lua +++ b/lua/gprofiler/modules/sh_access.lua @@ -91,7 +91,9 @@ end) function GProfiler.Access.HasAccess(ply) if GetGlobalBool("gprofiler_lan", false) then return true end - if ply:EntIndex() == 0 then return true end -- Console + if CLIENT and ply == nil then ply = LocalPlayer() end + + if SERVER and ply:EntIndex() == 0 then return true end -- Console if GProfiler.Config.AllowSuperAdmin and ply:IsSuperAdmin() then return true end if GProfiler.Config.AllowedSteamIDs[ply:SteamID64()] or GProfiler.Config.AllowedSteamIDs[ply:SteamID()] then return true end From 832f967c51bd370d35b651e772a101f1728d149a Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Wed, 6 May 2026 13:20:02 +0100 Subject: [PATCH 14/16] Timers --- lua/gprofiler/profilers/timers/cl_timers.lua | 262 +++++++++++- lua/gprofiler/profilers/timers/sh_timers.lua | 411 +++++++++++-------- 2 files changed, 497 insertions(+), 176 deletions(-) diff --git a/lua/gprofiler/profilers/timers/cl_timers.lua b/lua/gprofiler/profilers/timers/cl_timers.lua index 261e349..40a9949 100644 --- a/lua/gprofiler/profilers/timers/cl_timers.lua +++ b/lua/gprofiler/profilers/timers/cl_timers.lua @@ -1,11 +1,269 @@ GProfiler.Timers = GProfiler.Timers or {} +local Timers = GProfiler.Timers + +Timers.Realm = Timers.Realm or "Client" local TimersStore = GProfiler.Profilers.GetStore("Timers") -function GProfiler.Timers.DoTab(Content) +function GProfiler.Timers.DoTab(Base, Outer) + local Header = GProfiler.Utils.SetupHeader(Outer, "Timers", "gprofiler/timers.png") + local initialActive = Timers.Realm == "Both" and (TimersStore:IsActive("Client") or TimersStore:IsActive("Server")) or TimersStore:IsActive(Timers.Realm) + local StartStop = Header:SetupStartStop(initialActive) + local RealmSelector = Header:SetupRealmSelector(Timers.Realm, true) + local Timer = Header:SetupTimer(function() + local realm = Timers.Realm == "Both" and "Client" or Timers.Realm + return TimersStore:GetTimerData(realm) + end) + + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Header.OnHandleMoved() + end + + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Timers", Timers.Realm, Running) + + if Timers.RefreshUI then + Timers.RefreshUI() + end + end + + function RealmSelector:OnStateChanged(state) + Timers.Realm = state + if state == "Both" then + StartStop.State = TimersStore:IsActive("Client") or TimersStore:IsActive("Server") + else + StartStop.State = TimersStore:IsActive(state) + end + StartStop:SetText(StartStop.State and "Stop" or "Start") + if Timers.RefreshUI then + Timers.RefreshUI() + end + end + + local left, Source = GProfiler.Utils.VSplitPanel(Base, GProfiler.GetScaledSize(10), "timers_lr", 0.65) + local Results, ActivePanel = GProfiler.Utils.HSplitPanel(left, GProfiler.GetScaledSize(10), "timers_l_bt", 0.65) + local ClientTimers, ServerTimers = GProfiler.Utils.VSplitPanel(ActivePanel, GProfiler.GetScaledSize(10), "timers_lb_lr", 0.5) + + local SourceHeader = vgui.Create("DLabel", Source) + SourceHeader:SetFont("GProfiler.Inter24") + SourceHeader:SetTextColor(GProfiler.SyntaxColors.comment) + SourceHeader:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(5)) + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + SourceHeader:SetText("Select a timer to view source.") + + Source.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) + end + + local RichText = vgui.Create("RichText", Source) + RichText:SetText("") + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:SetVerticalScrollbarEnabled(true) + function RichText:PerformLayout() + self:SetFontInternal("GProfiler.Code") + end + + Source.OnHandleMoved = function() + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:InvalidateLayout() + end + + local Header = GProfiler.Utils.SetupHeader(Results, "Profiler Results", nil, true) + local ResultsList = GProfiler.Utils.CreateList(Results, Header, {"Name", "Type", "Count", "Delay", "Total Time", "Avg Time", "Longest Time"}) + + local function PopulateResults() + if not IsValid(ResultsList) then return end + + local displayRealm = Timers.Realm == "Both" and "Client" or Timers.Realm + local realmData = TimersStore:GetData(displayRealm) or {} + + ResultsList:Clear() + + local merged = table.Merge(realmData.Simple or {}, realmData.Create or {}) + for k, v in pairs(merged) do + local line = ResultsList:AddLine(tostring(k), v.Type, v.Count, v.Delay, v.TotalTime, v.AverageTime, v.LongestTime) + line.TimerData = v + end + end + GProfiler.Timers.RefreshUI = PopulateResults + PopulateResults() + + function ResultsList:OnRowSelected(rowIndex, row) + local data = row.TimerData + if data then + RichText:SetText("Loading source...") + SourceHeader:SetText("") + + local file = data.Source + local lineDefined = data.Lines and data.Lines[1] or 0 + local lastLineDefined = data.Lines and data.Lines[2] or 0 + + if file and file ~= "" then + file = string.match(file, "@?(.+)") + if not file then file = data.Source end + + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) + + GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), lineDefined) + else + RichText:SetText("Failed to load source") + end + end) + else + RichText:SetText("Failed to load source (2)") + end + end + end + local function CreateTimerList(Parent, ActiveList, Title) + Parent:Clear() + + local HeaderPanel = vgui.Create("DPanel", Parent) + HeaderPanel:SetSize(Parent:GetWide(), GProfiler.GetScaledSize(50)) + HeaderPanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, Color(34, 77, 122), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + draw.SimpleText(Title, "GProfiler.Inter28", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local RefreshButton = vgui.Create("DButton", HeaderPanel) + RefreshButton:SetSize(GProfiler.GetScaledSize(80), GProfiler.GetScaledSize(30)) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + RefreshButton:SetText("Refresh") + RefreshButton:SetFont("GProfiler.Inter24") + RefreshButton:SetTextColor(Color(0,0,0,0)) + RefreshButton.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + if s:IsHovered() then + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + end + draw.SimpleText("Refresh", "GProfiler.Inter24", w / 2, h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + RefreshButton.DoClick = function() + net.Start("GProfiler_Timers_ActiveList") + net.SendToServer() + end + + local List = vgui.Create("DPanelList", Parent) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + List:EnableVerticalScrollbar() + + local ScrollBar = List.VBar + ScrollBar:SetWide(GProfiler.GetScaledSize(12)) + ScrollBar:SetHideButtons(true) + ScrollBar.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + end + ScrollBar.btnGrip.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + for k, v in ipairs(ActiveList) do + local i = k + local Item = vgui.Create("DButton", List) + Item:SetSize(List:GetWide(), GProfiler.GetScaledSize(30)) + Item:SetText(v.Name) + Item:SetFont("GProfiler.Inter24") + Item:SizeToContentsY() + Item:SetTall(Item:GetTall() + GProfiler.GetScaledSize(10)) + Item:SetContentAlignment(1) + Item:SetTextColor(Color(0,0,0,0)) + Item.Paint = function(s, w, h) + if i % 2 == 0 then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + else + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 2)) + end + + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + draw.SimpleText(v.Name, "GProfiler.Inter24", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + List:AddItem(Item) + end + + Parent.OnHandleMoved = function() + HeaderPanel:SetSize(Parent:GetWide(), HeaderPanel:GetTall()) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + for k, v in pairs(List:GetItems()) do + v:SetSize(List:GetWide(), v:GetTall()) + end + end + end + + local CLTimers = {} + local SVTimers = {} + + for _, v in ipairs(GProfiler.Timers.ActiveTimers.Create) do + table.insert(CLTimers, { Name = v.Name }) + end + + CreateTimerList(ClientTimers, CLTimers, string.format("Client Timers (%d)", #CLTimers)) + + net.Receive("GProfiler_Timers_ActiveList", function() + SVTimers = {} + for i = 1, net.ReadUInt(16) do + table.insert(SVTimers, { Name = net.ReadString() }) + end + + CreateTimerList(ServerTimers, SVTimers, string.format("Server Timers (%d)", #SVTimers)) + end) + + net.Start("GProfiler_Timers_ActiveList") + net.SendToServer() + + Results.OnHandleMoved = function() + ResultsList:SetSize(Results:GetWide(), Results:GetTall() - Header:GetTall()) + ResultsList:SetPos(0, Header:GetTall()) + Header:SetWide(Results:GetWide()) + end end GProfiler.Menu.RegisterTab("Timers", "gprofiler/timers.png", 5, GProfiler.Timers.DoTab, function() + local timer = TimersStore:GetTimerData(Timers.Realm) + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive +end) + +net.Receive("GProfiler_Timers_SendData", function() + local isFirst = net.ReadBool() + local isLast = net.ReadBool() + local count = net.ReadUInt(32) + + local serverData = isFirst and { Simple = {}, Create = {} } or (TimersStore:GetData("Server") or { Simple = {}, Create = {} }) + + for i = 1, count do + local timerType = net.ReadString() + local name = net.ReadString() + serverData[timerType][name] = { + Type = timerType, + Count = net.ReadUInt(15), + Delay = net.ReadFloat(), + TotalTime = net.ReadFloat(), + LongestTime = net.ReadFloat(), + AverageTime = net.ReadFloat(), + Source = net.ReadString(), + Lines = {net.ReadUInt(14), net.ReadUInt(14)} + } + end + + TimersStore:SetData("Server", serverData) -end) \ No newline at end of file + if isLast and Timers.RefreshUI then + Timers.RefreshUI() + end +end) diff --git a/lua/gprofiler/profilers/timers/sh_timers.lua b/lua/gprofiler/profilers/timers/sh_timers.lua index 58d6d8f..30cf8c6 100644 --- a/lua/gprofiler/profilers/timers/sh_timers.lua +++ b/lua/gprofiler/profilers/timers/sh_timers.lua @@ -1,193 +1,256 @@ GProfiler.Timers = GProfiler.Timers or {} +GProfiler.Timers.ActiveTimers = GProfiler.Timers.ActiveTimers or { Simple = {}, Create = {} } +GProfiler.Timers.OldSimpleTimer = GProfiler.Timers.OldSimpleTimer or timer.Simple +GProfiler.Timers.OldCreateTimer = GProfiler.Timers.OldCreateTimer or timer.Create + +local Timers = GProfiler.Timers +local ActiveTimers = Timers.ActiveTimers GProfiler.Profilers.Register("Timers", {}) --- local chunkSizeLimit = 65535 +local chunkSizeLimit = 65535 --- function GProfiler.Timers:StartProfiler(ply) --- if not GProfiler.Access.HasAccess(ply or LocalPlayer()) then return end +function Timers:StartProfiler(ply) + if not GProfiler.Access.HasAccess(ply or LocalPlayer()) then return end --- if GProfiler.Timers.IsDetoured then return end + if Timers.IsDetoured then return end --- GProfiler.Log((SERVER and "Server" or "Client") .. " timer profiler started!", 2) --- GProfiler.Timers.IsDetoured = true --- GProfiler.Timers.ProfileStarted = SysTime() + GProfiler.Log((SERVER and "Server" or "Client") .. " timer profiler started!", 2) + Timers.IsDetoured = true --- GProfiler.Timers.Simple = {} --- GProfiler.Timers.Create = {} --- end + Timers.Simple = {} + Timers.Create = {} +end --- function GProfiler.Timers:Stop(ply) --- if not GProfiler.Access.HasAccess(ply or LocalPlayer()) then return end - --- if not GProfiler.Timers.IsDetoured then return end - --- GProfiler.Log((SERVER and "Server" or "Client") .. " timer profile stopped, sending data!", 2) --- GProfiler.Timers.IsDetoured = false --- GProfiler.Timers.ProfileStarted = nil - --- if SERVER then --- local ProfileData = table.Merge(GProfiler.Timers.Simple, GProfiler.Timers.Create) --- local chunkCount = 1 --- local currentChunkSize = 0 --- local chunks = {} --- for k, v in pairs(ProfileData) do --- local chunkSize = 146 + string.len(v.Type) + string.len(tostring(k)) + string.len(v.Source) --- if currentChunkSize + chunkSize > chunkSizeLimit then --- chunkCount = chunkCount + 1 --- currentChunkSize = 0 --- end - --- if not chunks[chunkCount] then chunks[chunkCount] = {} end --- currentChunkSize = currentChunkSize + chunkSize --- table.insert(chunks[chunkCount], {k, v}) --- end - --- for k, v in ipairs(chunks) do --- net.Start("GProfiler_Timers_SendData") --- net.WriteBool(k == 1) --- net.WriteBool(k == table.Count(chunks)) --- net.WriteUInt(table.Count(v), 32) --- for _, data in ipairs(v) do --- local dat = data[2] --- net.WriteString(dat.Type) --- net.WriteString(tostring(data[1])) --- net.WriteUInt(dat.Count, 15) --- net.WriteFloat(dat.Delay) --- net.WriteFloat(dat.TotalTime) --- net.WriteFloat(dat.LongestTime) --- net.WriteFloat(dat.AverageTime) --- net.WriteString(dat.Source) --- net.WriteUInt(dat.Lines[1], 14) --- net.WriteUInt(dat.Lines[2], 14) --- end --- net.Send(ply) --- end - --- if table.Count(chunks) == 0 then --- net.Start("GProfiler_Timers_SendData") --- net.WriteBool(true) --- net.WriteBool(true) --- net.WriteUInt(0, 32) --- net.Send(ply) --- end --- end --- end +function Timers:Stop(ply) + if not GProfiler.Access.HasAccess(ply or LocalPlayer()) then return end --- function GProfiler.Timers.CollectTimerData(type, name, delay, func, funcTime) --- if not GProfiler.Timers.IsDetoured then return end - --- if not GProfiler.Timers[type][name] then --- local dbgInfo = debug.getinfo(func, "S") --- GProfiler.Timers[type][name] = { --- Count = 0, --- TotalTime = 0, --- LongestTime = 0, --- AverageTime = 0, --- Func = func, --- Delay = delay, --- Source = dbgInfo.short_src, --- Lines = {dbgInfo.linedefined, dbgInfo.lastlinedefined}, --- Type = type --- } --- end - --- local tbl = GProfiler.Timers[type][name] --- tbl.Count = tbl.Count + 1 --- tbl.TotalTime = tbl.TotalTime + funcTime --- tbl.AverageTime = tbl.TotalTime / tbl.Count --- tbl.LongestTime = math.max(tbl.LongestTime, funcTime) --- end + if not Timers.IsDetoured then return end --- local function assertType(value, check, num, expect) --- assert(check(value), string.format("bad argument #%d (%s expected, got %s)", num, expect, type(value))) --- return true --- end + GProfiler.Log((SERVER and "Server" or "Client") .. " timer profile stopped, sending data!", 2) + Timers.IsDetoured = false +end --- timer.Simple = function(delay, func, ...) --- if not assertType(delay, isnumber, 1, "number") or not assertType(func, isfunction, 2, "function") then return end +local function SendData(ply) + if SERVER then + local ProfileData = table.Merge(Timers.Simple, Timers.Create) + local chunkCount = 1 + local currentChunkSize = 0 + local chunks = {} + for k, v in pairs(ProfileData) do + local chunkSize = 146 + string.len(v.Type) + string.len(tostring(k)) + string.len(v.Source) + if currentChunkSize + chunkSize > chunkSizeLimit then + chunkCount = chunkCount + 1 + currentChunkSize = 0 + end --- local Index = delay != 0 and table.insert(GProfiler.ActiveTimers.Simple, {NextRun = SysTime() + delay, Source = debug.getinfo(2)}) + if not chunks[chunkCount] then chunks[chunkCount] = {} end + currentChunkSize = currentChunkSize + chunkSize + table.insert(chunks[chunkCount], {k, v}) + end --- local args = {...} --- GProfiler.Timers.OldSimpleTimer(delay, function() --- local start = SysTime() --- func(unpack(args)) --- GProfiler.Timers.CollectTimerData("Simple", func, delay, func, SysTime() - start) --- if Index then table.remove(GProfiler.ActiveTimers.Simple, Index) end --- end) --- end + for k, v in ipairs(chunks) do + net.Start("GProfiler_Timers_SendData") + net.WriteBool(k == 1) + net.WriteBool(k == table.Count(chunks)) + net.WriteUInt(table.Count(v), 32) + for _, data in ipairs(v) do + local dat = data[2] + net.WriteString(dat.Type) + net.WriteString(tostring(data[1])) + net.WriteUInt(dat.Count, 15) + net.WriteFloat(dat.Delay) + net.WriteFloat(dat.TotalTime) + net.WriteFloat(dat.LongestTime) + net.WriteFloat(dat.AverageTime) + net.WriteString(dat.Source) + net.WriteUInt(dat.Lines[1], 14) + net.WriteUInt(dat.Lines[2], 14) + end + net.Send(ply) + end + + if table.Count(chunks) == 0 then + net.Start("GProfiler_Timers_SendData") + net.WriteBool(true) + net.WriteBool(true) + net.WriteUInt(0, 32) + net.Send(ply) + end + end +end + +function Timers.CollectTimerData(type, name, delay, func, funcTime) + if not Timers.IsDetoured then return end + + if not Timers[type][name] then + local dbgInfo = debug.getinfo(func, "S") + Timers[type][name] = { + Count = 0, + TotalTime = 0, + LongestTime = 0, + AverageTime = 0, + Func = func, + Delay = delay, + Source = dbgInfo.short_src, + Lines = {dbgInfo.linedefined, dbgInfo.lastlinedefined}, + Type = type + } + end + + local tbl = Timers[type][name] + tbl.Count = tbl.Count + 1 + tbl.TotalTime = tbl.TotalTime + funcTime + tbl.AverageTime = tbl.TotalTime / tbl.Count + tbl.LongestTime = math.max(tbl.LongestTime, funcTime) +end + +local function assertType(value, check, num, expect) + assert(check(value), string.format("bad argument #%d (%s expected, got %s)", num, expect, type(value))) + return true +end + +timer.Simple = function(delay, func, ...) + if not assertType(delay, isnumber, 1, "number") or not assertType(func, isfunction, 2, "function") then return end + + local Index = delay != 0 and table.insert(ActiveTimers.Simple, {NextRun = SysTime() + delay, Source = debug.getinfo(2)}) + + local dbgInfo = debug.getinfo(func, "S") + local sourceKey = string.format("%s:%d", dbgInfo.short_src, dbgInfo.linedefined) + + local args = {...} + Timers.OldSimpleTimer(delay, function() + local start = SysTime() + func(unpack(args)) + Timers.CollectTimerData("Simple", sourceKey, delay, func, SysTime() - start) + if Index then table.remove(ActiveTimers.Simple, Index) end + end) +end --- timer.Create = function(name, delay, reps, func) --- if not ( --- assertType(name, isstring, 1, "string") and --- assertType(delay, isnumber, 2, "number") and --- assertType(reps, isnumber, 3, "number") and --- assertType(func, isfunction, 4, "function") --- ) then return end - --- name = tostring(name) - --- for k, v in ipairs(GProfiler.ActiveTimers.Create) do --- if v.Name == name then --- table.remove(GProfiler.ActiveTimers.Create, k) --- break --- end --- end --- table.insert(GProfiler.ActiveTimers.Create, { Name = name, Reps = reps, Source = debug.getinfo(2) }) - --- GProfiler.Timers.OldCreateTimer(name, delay, reps, function() --- local start = SysTime() --- func() --- local endtime = SysTime() - start --- GProfiler.Timers.CollectTimerData("Create", name, delay, func, endtime) --- if timer.RepsLeft(name) == 0 then --- for i, data in ipairs(GProfiler.ActiveTimers.Create) do --- if data.Name == name then --- table.remove(GProfiler.ActiveTimers.Create, i) --- break --- end --- end --- end +timer.Create = function(name, delay, reps, func) + if not ( + assertType(name, isstring, 1, "string") and + assertType(delay, isnumber, 2, "number") and + assertType(reps, isnumber, 3, "number") and + assertType(func, isfunction, 4, "function") + ) then return end + + name = tostring(name) + + for k, v in ipairs(ActiveTimers.Create) do + if v.Name == name then + table.remove(ActiveTimers.Create, k) + break + end + end + table.insert(ActiveTimers.Create, { Name = name, Reps = reps, Source = debug.getinfo(2) }) + + Timers.OldCreateTimer(name, delay, reps, function() + local start = SysTime() + func() + local endtime = SysTime() - start + Timers.CollectTimerData("Create", name, delay, func, endtime) + if timer.RepsLeft(name) == 0 then + for i, data in ipairs(ActiveTimers.Create) do + if data.Name == name then + table.remove(ActiveTimers.Create, i) + break + end + end + end + end) +end + +timer.Create("GProfiler_ClearTimerList", 2, 0, function() -- because apparently table.remove does NOT want to work sometimes?? fixme + for i = #ActiveTimers.Create, 1, -1 do + local data = ActiveTimers.Create[i] + if not timer.Exists(data.Name) then + table.remove(ActiveTimers.Create, i) + end + end + + for i = #ActiveTimers.Simple, 1, -1 do + local data = ActiveTimers.Simple[i] + if SysTime() >= data.NextRun then + table.remove(ActiveTimers.Simple, i) + end + end +end) + +GProfiler.Profilers.Register("Timers", { + Realms = { "Client", "Server" }, + OnStart = function(realm, ply) + Timers:StartProfiler(ply) + end, + OnStop = function(realm, ply) + Timers:Stop(ply) + if CLIENT then + local Store = GProfiler.Profilers.GetStore("Timers") + if Store then + Store:SetData(realm, { + Simple = Timers.Simple, + Create = Timers.Create + }) + end + end + if SERVER and ply then + SendData(ply) + end + end, + WriteData = function(realm, ply) + SendData(ply) + end +}) + + +if SERVER then + util.AddNetworkString("GProfiler_Timers_ToggleServerProfile") + util.AddNetworkString("GProfiler_Timers_ServerProfileStatus") + util.AddNetworkString("GProfiler_Timers_SendData") + util.AddNetworkString("GProfiler_Timers_ActiveList") + + net.Receive("GProfiler_Timers_ActiveList", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + net.Start("GProfiler_Timers_ActiveList") + net.WriteUInt(#ActiveTimers.Create, 16) + for _, v in ipairs(ActiveTimers.Create) do + net.WriteString(v.Name) + end + net.Send(ply) + end) + + net.Receive("GProfiler_Timers_ToggleServerProfile", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + if net.ReadBool() then + Timers:StartProfiler(ply) + net.Start("GProfiler_Timers_ServerProfileStatus") + net.WriteBool(true) + net.WriteEntity(ply) + net.Broadcast() + else + Timers:Stop(ply) + net.Start("GProfiler_Timers_ServerProfileStatus") + net.WriteBool(false) + net.WriteEntity(ply) + net.Broadcast() + end + end) +end + +-- local function simplySimple() +-- timer.Simple(math.Rand(0.1, 1), function() +-- simplySimple() -- end) -- end +-- simplySimple() + +-- timer.Create("GProfiler_TestTimer", math.Rand(0.1, 1), 0, function() end) --- timer.Create("GProfiler_ClearTimerList", 2, 0, function() -- because apparently table.remove does NOT want to work sometimes?? fixme --- for i = #GProfiler.ActiveTimers.Create, 1, -1 do --- local data = GProfiler.ActiveTimers.Create[i] --- if not timer.Exists(data.Name) then --- table.remove(GProfiler.ActiveTimers.Create, i) --- end --- end - --- for i = #GProfiler.ActiveTimers.Simple, 1, -1 do --- local data = GProfiler.ActiveTimers.Simple[i] --- if SysTime() >= data.NextRun then --- table.remove(GProfiler.ActiveTimers.Simple, i) --- end --- end --- end) - --- if SERVER then --- util.AddNetworkString("GProfiler_Timers_ToggleServerProfile") --- util.AddNetworkString("GProfiler_Timers_ServerProfileStatus") --- util.AddNetworkString("GProfiler_Timers_SendData") - --- net.Receive("GProfiler_Timers_ToggleServerProfile", function(len, ply) --- if not GProfiler.Access.HasAccess(ply) then return end - --- if net.ReadBool() then --- GProfiler.Timers:StartProfiler(ply) --- net.Start("GProfiler_Timers_ServerProfileStatus") --- net.WriteBool(true) --- net.WriteEntity(ply) --- net.Broadcast() --- else --- GProfiler.Timers:Stop(ply) --- net.Start("GProfiler_Timers_ServerProfileStatus") --- net.WriteBool(false) --- net.WriteEntity(ply) --- net.Broadcast() --- end +-- local function simplySimple2() +-- timer.Simple(math.Rand(0.1, 1), function() +-- simplySimple2() -- end) --- end \ No newline at end of file +-- end +-- simplySimple2() \ No newline at end of file From 06731904f8c1749ce9e599e6f4679fa349df957b Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Wed, 6 May 2026 18:42:29 +0100 Subject: [PATCH 15/16] database profiler --- lua/autorun/gprofiler_load.lua | 3 +- .../modules/auto_profile/cl_autoprofile.lua | 12 +- .../modules/auto_profile/sv_autoprofile.lua | 139 +++-- lua/gprofiler/modules/overview/cl_init.lua | 56 ++ .../profilers/database/cl_database.lua | 540 +++++++++++++++++- .../providers/base/sv_base_provider.lua | 2 +- .../profilers/database/providers/mysqloo.lua | 24 +- .../profilers/database/providers/sqlite.lua | 6 +- .../profilers/database/sv_database.lua | 8 +- 9 files changed, 695 insertions(+), 95 deletions(-) diff --git a/lua/autorun/gprofiler_load.lua b/lua/autorun/gprofiler_load.lua index 193e881..84777ec 100644 --- a/lua/autorun/gprofiler_load.lua +++ b/lua/autorun/gprofiler_load.lua @@ -20,7 +20,8 @@ end local incFuncs = { sv = SERVER and include or function() end, cl = SERVER and AddCSLuaFile or include, - sh = function(f) include(f) AddCSLuaFile(f) end + sh = function(f) include(f) AddCSLuaFile(f) end, + nl = function() end } local function incFile(f) diff --git a/lua/gprofiler/modules/auto_profile/cl_autoprofile.lua b/lua/gprofiler/modules/auto_profile/cl_autoprofile.lua index 40a1c48..d7c7de5 100644 --- a/lua/gprofiler/modules/auto_profile/cl_autoprofile.lua +++ b/lua/gprofiler/modules/auto_profile/cl_autoprofile.lua @@ -1,5 +1,9 @@ -function GProfiler.AutoProfileTab(Content) +GProfiler.AutoProfile = GProfiler.AutoProfile or {} +GProfiler.AutoProfile.States = GProfiler.AutoProfile.States or {} -end - --- GProfiler.Menu.RegisterTab("Auto Profile", "icon16/map_go.png", 999, GProfiler.AutoProfileTab) \ No newline at end of file +net.Receive("GProfiler.AutoProfile.SendState", function() + local count = net.ReadUInt(8) + for i = 1, count do + GProfiler.AutoProfile.States[net.ReadString()] = net.ReadUInt(2) + end +end) diff --git a/lua/gprofiler/modules/auto_profile/sv_autoprofile.lua b/lua/gprofiler/modules/auto_profile/sv_autoprofile.lua index d1987d5..f977a88 100644 --- a/lua/gprofiler/modules/auto_profile/sv_autoprofile.lua +++ b/lua/gprofiler/modules/auto_profile/sv_autoprofile.lua @@ -1,85 +1,80 @@ --- util.AddNetworkString("GProfiler.AutoProfile.Configure") --- util.AddNetworkString("GProfiler.AutoProfile.SendState") +util.AddNetworkString("GProfiler.AutoProfile.Configure") +util.AddNetworkString("GProfiler.AutoProfile.SendState") --- local Profilers = { --- ["Hooks"] = "Hooks", --- ["Networking"] = "Net", --- ["Functions"] = "Functions", --- ["Commands"] = "ConCommands", --- ["Timers"] = "Timers", --- -- ["Entity Variables"] = "EntVars", --- ["Network Variables"] = "NetVars", --- ["Database"] = "Database" --- } +local Profilers = GProfiler.Profilers --- local ValidStates = { --- 0, -- Disabled --- 1, -- ASAP --- 2, -- When the gamemode has fully loaded --- 3 -- When the first player connects --- } +local function AutoStart(name, ply) + local profiler = Profilers.Get(name) + if not profiler then return end + if not profiler.Store:Start("Server", ply) then return end + if profiler.OnStart then profiler.OnStart("Server", ply) end + net.Start("GProfiler.Profiler.Status") + net.WriteString(name) + net.WriteString("Server") + net.WriteBool(true) + net.WriteEntity(ply) + net.WriteFloat(0) + net.Broadcast() +end --- hook.Add("GProfiler.Loaded", "GProfiler.AutoProfilers", function() --- sql.Query("CREATE TABLE IF NOT EXISTS gprofiler_autoprofile (profiler TEXT, state INTEGER, PRIMARY KEY(profiler))") +hook.Add("GProfiler.Loaded", "GProfiler.AutoProfile", function() + sql.Query("CREATE TABLE IF NOT EXISTS gprofiler_autoprofile (profiler TEXT PRIMARY KEY, state INTEGER)") --- local Data = sql.Query("SELECT * FROM gprofiler_autoprofile") --- if not table.IsEmpty(Data or {}) then --- for _, row in ipairs(Data) do --- row.state = tonumber(row.state) or 0 --- if row.state == 0 then continue end + local data = sql.Query("SELECT * FROM gprofiler_autoprofile") + for _, row in ipairs(data or {}) do + local state = tonumber(row.state) or 0 + if state == 0 then continue end --- local Profiler = GProfiler[Profilers[row.profiler]] --- if not Profiler then continue end + local name = row.profiler + if state == 1 then + GProfiler.Log("[Auto Profiler] Starting " .. name .. " profiler!", 2) + AutoStart(name, Entity(0)) + elseif state == 2 then + hook.Add("PostGamemodeLoaded", "GProfiler.AutoProfile." .. name, function() + hook.Remove("PostGamemodeLoaded", "GProfiler.AutoProfile." .. name) + GProfiler.Log("[Auto Profiler] Starting " .. name .. " profiler (gamemode loaded)!", 2) + AutoStart(name, Entity(0)) + end) + elseif state == 3 then + hook.Add("PlayerInitialSpawn", "GProfiler.AutoProfile." .. name, function(ply) + hook.Remove("PlayerInitialSpawn", "GProfiler.AutoProfile." .. name) + GProfiler.Log("[Auto Profiler] Starting " .. name .. " profiler (player joined)!", 2) + AutoStart(name, ply) + end) + end + end --- if row.state == 1 then --- GProfiler.Log("[Auto Profiler] Starting " .. row.profiler .. " profiler!", 2) --- Profiler:StartProfiler(Entity(0)) --- elseif row.state == 2 then --- GProfiler.Log("[Auto Profiler] Delaying " .. row.profiler .. " profiler until the gamemode has fully loaded!", 2) --- hook.Add("PostGamemodeLoaded", "GProfiler.AutoProfile." .. row.profiler, function() --- hook.Remove("PostGamemodeLoaded", "GProfiler.AutoProfile." .. row.profiler) --- GProfiler.Log("[Auto Profiler] Starting " .. row.profiler .. " profiler (gamemode loaded)!", 2) --- Profiler:StartProfiler(Entity(0)) --- end) --- elseif row.state == 3 then --- GProfiler.Log("[Auto Profiler] Delaying " .. row.profiler .. " profiler until the first player connects!", 2) --- hook.Add("PlayerConnect", "GProfiler.AutoProfile." .. row.profiler, function(ply) --- hook.Remove("PlayerConnect", "GProfiler.AutoProfile." .. row.profiler) --- GProfiler.Log("[Auto Profiler] Starting " .. row.profiler .. " profiler (player connected)!", 2) --- Profiler:StartProfiler(ply) --- end) --- end --- end --- end + sql.Query("DELETE FROM gprofiler_autoprofile") +end) --- sql.Query("DELETE FROM gprofiler_autoprofile") --- end) +net.Receive("GProfiler.AutoProfile.Configure", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end --- net.Receive("GProfiler.AutoProfile.Configure", function(_, ply) --- if not GProfiler.Access.HasAccess(ply) then return end + local name = net.ReadString() + local state = net.ReadUInt(2) --- local Profiler = net.ReadString() --- local State = net.ReadUInt(2) + if not Profilers.Get(name) then return end --- if not Profilers[Profiler] or not table.HasValue(ValidStates, State) then return end + if state == 0 then + sql.Query("DELETE FROM gprofiler_autoprofile WHERE profiler = " .. sql.SQLStr(name)) + else + sql.Query("REPLACE INTO gprofiler_autoprofile (profiler, state) VALUES (" .. sql.SQLStr(name) .. ", " .. state .. ")") + end +end) --- if State == 0 then --- sql.Query("DELETE FROM gprofiler_autoprofile WHERE profiler = " .. sql.SQLStr(Profiler)) --- else --- sql.Query("REPLACE INTO gprofiler_autoprofile (profiler, state) VALUES (" .. sql.SQLStr(Profiler) .. ", " .. State .. ")") --- end --- end) +hook.Add("PlayerInitialSpawn", "GProfiler.AutoProfile.SendState", function(ply) + timer.Simple(1, function() + if not IsValid(ply) or not GProfiler.Access.HasAccess(ply) then return end + local data = sql.Query("SELECT * FROM gprofiler_autoprofile") + if table.IsEmpty(data or {}) then return end --- hook.Add("PlayerInitialSpawn", "GProfiler.AutoProfiler.SendState", function(ply) --- local Data = sql.Query("SELECT * FROM gprofiler_autoprofile") --- if table.IsEmpty(Data or {}) then return end - --- net.Start("GProfiler.AutoProfile.SendState") --- net.WriteUInt(table.Count(Data), 4) --- for _, row in ipairs(Data) do --- net.WriteString(row.profiler) --- net.WriteUInt(row.state, 2) --- end --- net.Send(ply) --- end) \ No newline at end of file + net.Start("GProfiler.AutoProfile.SendState") + net.WriteUInt(#data, 8) + for _, row in ipairs(data) do + net.WriteString(row.profiler) + net.WriteUInt(tonumber(row.state) or 0, 2) + end + net.Send(ply) + end) +end) diff --git a/lua/gprofiler/modules/overview/cl_init.lua b/lua/gprofiler/modules/overview/cl_init.lua index 1c1bd99..cc5da13 100644 --- a/lua/gprofiler/modules/overview/cl_init.lua +++ b/lua/gprofiler/modules/overview/cl_init.lua @@ -65,6 +65,62 @@ function GProfiler.Overview.DoTab(Base) g = AddGraph("Client FPS") g:AddSegment(GProfiler.Overview.Data.CL.FPS, Color(48, 220, 160), "FPS", function(v) return string.format("%.0f", v) end, "icon16/monitor.png") + + local pad = GProfiler.GetScaledSize(8) + local rowH = GProfiler.GetScaledSize(40) + + local AutoProfileLabel = vgui.Create("DLabel", Scroll) + AutoProfileLabel:Dock(TOP) + AutoProfileLabel:DockMargin(8, 16, 8, 4) + AutoProfileLabel:SetFont("GProfiler.Inter28") + AutoProfileLabel:SetText("Auto Profile") + AutoProfileLabel:SetTextColor(GProfiler.SyntaxColors.text) + AutoProfileLabel:SetTall(GProfiler.GetScaledSize(32)) + + local DropdownOptions = { + { label = "Disabled", value = 0 }, + { label = "As soon as possible", value = 1 }, + { label = "When gamemode is loaded", value = 2 }, + { label = "When a player joins", value = 3 }, + } + + for name, profiler in SortedPairs(GProfiler.Profilers.GetAll()) do + if not table.HasValue(profiler.Realms, "Server") then continue end + + local Row = vgui.Create("DPanel", Scroll) + Row:Dock(TOP) + Row:DockMargin(8, 4, 8, 0) + Row:SetTall(rowH) + Row.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 8)) + end + + local NameLabel = vgui.Create("DLabel", Row) + NameLabel:Dock(LEFT) + NameLabel:DockMargin(pad, 0, 0, 0) + NameLabel:SetFont("GProfiler.Inter24") + NameLabel:SetText(name) + NameLabel:SetTextColor(GProfiler.SyntaxColors.text) + NameLabel:SizeToContents() + + local Dropdown = vgui.Create("DComboBox", Row) + Dropdown:Dock(RIGHT) + Dropdown:DockMargin(0, pad, pad, pad) + Dropdown:SetWide(GProfiler.GetScaledSize(220)) + Dropdown:SetFont("GProfiler.Inter24") + Dropdown:SetSortItems(false) + for _, opt in ipairs(DropdownOptions) do + Dropdown:AddChoice(opt.label, opt.value) + end + Dropdown:ChooseOptionID((GProfiler.AutoProfile.States[name] or 0) + 1) + Dropdown.OnSelect = function(s, index, value, data) + GProfiler.AutoProfile.States[name] = data + net.Start("GProfiler.AutoProfile.Configure") + net.WriteString(name) + net.WriteUInt(data, 2) + net.SendToServer() + end + end end GProfiler.Menu.RegisterTab("Overview", "gprofiler/home.png", 0, GProfiler.Overview.DoTab, function() diff --git a/lua/gprofiler/profilers/database/cl_database.lua b/lua/gprofiler/profilers/database/cl_database.lua index 390711e..c2b804e 100644 --- a/lua/gprofiler/profilers/database/cl_database.lua +++ b/lua/gprofiler/profilers/database/cl_database.lua @@ -1,11 +1,549 @@ GProfiler.Database = GProfiler.Database or {} +local Database = GProfiler.Database + +GProfiler.Profilers.Register("Database", {}) local DatabaseStore = GProfiler.Profilers.GetStore("Database") -function GProfiler.Database.DoTab(Content) +local function QueryTimeColor(time) + if time > 0.5 then return Color(238, 95, 91) end + if time > 0.1 then return Color(250, 167, 50) end + return Color(94, 185, 94) +end + +local function CreateTimelinePanel(parent, w, h, data, currentQuery) + local minWidth = 10 + local TimelinePanel = vgui.Create("DPanel", parent) + TimelinePanel:SetSize(w, h) + TimelinePanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, GProfiler.SyntaxColors.background) + if s.ShowData then s:ShowData() end + end + + function TimelinePanel:ShowData() + TimelinePanel.ShowData = nil + + local totalTime = 0 + for _, entry in ipairs(data) do + totalTime = totalTime + entry.Time + end + + local startX = 0 + local gap = 1 + local widths = {} + local allocatedWidth = 0 + + for _, entry in ipairs(data) do + local entryWidth = w * entry.Time / totalTime + if entryWidth < minWidth then entryWidth = minWidth end + table.insert(widths, entryWidth) + allocatedWidth = allocatedWidth + entryWidth + end + + local scale = w / allocatedWidth + for k, entry in ipairs(data) do + local entryWidth = widths[k] * scale - gap + if entryWidth < 1 then entryWidth = 1 end + local entryColor = QueryTimeColor(entry.Time) + if entry.Query != currentQuery then entryColor.a = 25 end + + local HoverColor = table.Copy(entryColor) + local entryPanel = vgui.Create("DPanel", TimelinePanel) + entryPanel:SetSize(k == #data and w - startX or entryWidth, h) + entryPanel:SetPos(startX, 0) + entryPanel.Lerp = 0 + entryPanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(2, 0, 1, w - 2, h - 2, entryColor) + if s.Lerp > 0 then + HoverColor.a = 230 * s.Lerp + GProfiler.RNDX.Draw(2, 0, 1, w - 2, h - 2, HoverColor) + end + end + entryPanel.Think = function(s) + s.Lerp = Lerp(FrameTime() * 10, s.Lerp, s:IsHovered() and 1 or 0) + end + entryPanel:SetCursor("hand") + entryPanel.OnMousePressed = function() + Database.SelectedQuery = entry.QueryId + end + + startX = startX + entryPanel:GetWide() + gap + end + end + + return TimelinePanel +end + +function GProfiler.Database.DoTab(Base, Outer) + local Header = GProfiler.Utils.SetupHeader(Outer, "Database", "gprofiler/database.png") + local initialActive = DatabaseStore:IsActive("Server") + local StartStop = Header:SetupStartStop(initialActive) + local Timer = Header:SetupTimer(function() + return DatabaseStore:GetTimerData("Server") + end) + + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + if QueryList then QueryList:SetSize(Base:GetWide(), Base:GetTall()) end + Header.OnHandleMoved() + end + + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Database", "Server", Running) + end + + local QueryList = vgui.Create("DPanelList", Base) + QueryList:SetSize(Base:GetWide(), Base:GetTall()) + QueryList:SetSpacing(GProfiler.GetScaledSize(10)) + QueryList:EnableVerticalScrollbar() + + local ScrollBar = QueryList.VBar + ScrollBar:SetWide(GProfiler.GetScaledSize(12)) + ScrollBar:SetHideButtons(true) + ScrollBar.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + end + ScrollBar.btnGrip.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + local rowH = GProfiler.GetScaledSize(30) + local pad = GProfiler.GetScaledSize(10) + + local function CreateRow(id, queryData, explains) + local Row = vgui.Create("DPanel", QueryList) + Row:SetSize(QueryList:GetWide() - pad, rowH) + Row.Paint = function(s, w, h) + GProfiler.RNDX.Draw(6, 0, 0, w, h, Color(24, 58, 94, 197)) + -- GProfiler.RNDX.Draw(6, 3, 3, w - 6, h - 6, Color(26, 55, 85, 150)) + end + Row.Think = function(s) + if Database.SelectedQuery == s.QueryId then + Database.SelectedQuery = nil + QueryList:ScrollToChild(s) + end + end + Row.QueryId = id + + local ScoreCol = QueryTimeColor(queryData.AverageTime) + local timeStr = string.format("%.2fms", queryData.AverageTime * 1000) + + local IdLabel = vgui.Create("DLabel", Row) + IdLabel:SetText(string.format("%d.", id)) + IdLabel:SetFont("GProfiler.Inter24") + IdLabel:SetTextColor(Color(255, 255, 255, 200)) + IdLabel:SizeToContents() + IdLabel:SetPos(pad, pad + rowH / 2 - IdLabel:GetTall() / 2) + + surface.SetFont("GProfiler.Inter24") + local bw, bh = surface.GetTextSize(timeStr) + local TimeBadge = vgui.Create("DPanel", Row) + TimeBadge:SetSize(bw + pad, bh + GProfiler.GetScaledSize(4)) + TimeBadge:SetPos(IdLabel:GetX() + IdLabel:GetWide() + GProfiler.GetScaledSize(5), pad + rowH / 2 - (bh + GProfiler.GetScaledSize(4)) / 2) + TimeBadge.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(ScoreCol.r, ScoreCol.g, ScoreCol.b, 40)) + GProfiler.RNDX.Draw(4, 1, 1, w - 2, h - 2, Color(ScoreCol.r, ScoreCol.g, ScoreCol.b, 20)) + draw.SimpleText(timeStr, "GProfiler.Inter24", w / 2, h / 2, ScoreCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local TypeBadge = vgui.Create("DLabel", Row) + TypeBadge:SetText(queryData.Type or "Unknown") + TypeBadge:SetFont("GProfiler.Inter24") + TypeBadge:SetTextColor(Color(210, 210, 210)) + TypeBadge:SizeToContents() + TypeBadge:SetSize(TypeBadge:GetWide() + pad, TypeBadge:GetTall() + GProfiler.GetScaledSize(4)) + TypeBadge:SetContentAlignment(5) + + local CountBadge = vgui.Create("DLabel", Row) + CountBadge:SetText(string.format("x%d", queryData.Count)) + CountBadge:SetFont("GProfiler.Inter24") + CountBadge:SetTextColor(GProfiler.SyntaxColors.text) + CountBadge:SizeToContents() + CountBadge:SetSize(CountBadge:GetWide() + pad, CountBadge:GetTall() + GProfiler.GetScaledSize(4)) + CountBadge:SetContentAlignment(5) + + local CopyBtn = vgui.Create("DButton", Row) + CopyBtn:SetText("Copy Query") + CopyBtn:SetFont("GProfiler.Inter24") + CopyBtn:SizeToContentsX() + CopyBtn:SetTextColor(Color(0, 0, 0, 0)) + CopyBtn:SetSize(CopyBtn:GetWide() + GProfiler.GetScaledSize(10), CountBadge:GetTall()) + CopyBtn.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) + if s:IsHovered() then GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) end + draw.SimpleText("Copy Query", "GProfiler.Inter24", w / 2, h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + CopyBtn.DoClick = function() + SetClipboardText(queryData.Query) + end + + CountBadge:SetPos(Row:GetWide() - CountBadge:GetWide() - pad, pad + rowH / 2 - CountBadge:GetTall() / 2) + TypeBadge:SetPos(CountBadge:GetX() - TypeBadge:GetWide() - GProfiler.GetScaledSize(5), pad + rowH / 2 - TypeBadge:GetTall() / 2) + CopyBtn:SetPos(TypeBadge:GetX() - CopyBtn:GetWide() - GProfiler.GetScaledSize(5), pad + rowH / 2 - CopyBtn:GetTall() / 2) + + Row:SetTall(rowH + pad * 2) + + local QueryParser = GProfiler.Database.QueryParser.New() + local template = QueryParser:ParseTemplate(queryData.Query) + + surface.SetFont("GProfiler.Inter24") + local _, lineH = surface.GetTextSize("A") + local maxW = Row:GetWide() - pad * 2 - GProfiler.GetScaledSize(10) + local xoff, yoff = 0, 0 + local queryH = lineH + + for _, v in ipairs(template) do + if v[1] == "\n" then + xoff = 0 + queryH = queryH + lineH + else + local tw = surface.GetTextSize(v[1]) + if xoff + tw > maxW then + xoff = 0 + queryH = queryH + lineH + end + xoff = xoff + tw + end + end + + local TextBg = vgui.Create("DPanel", Row) + TextBg:SetPos(pad, Row:GetTall()) + TextBg:SetSize(Row:GetWide() - pad * 2, queryH + pad) + TextBg.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(18, 28, 39, 255)) + end + + xoff, yoff = GProfiler.GetScaledSize(5), GProfiler.GetScaledSize(5) + for _, v in ipairs(template) do + if v[1] == "\n" then + xoff = GProfiler.GetScaledSize(5) + yoff = yoff + lineH + continue + end + + local lbl = vgui.Create("DLabel", TextBg) + local displayText = v[1] + if v[2] == 1 and Database.MySQLKeywords and not Database.MySQLKeywords[displayText] then + displayText = displayText:upper() + end + lbl:SetText(displayText) + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.text) + lbl:SizeToContents() + lbl:SetPos(xoff, yoff) + + local cat = Database.TokenCategories and Database.TokenCategories[v[2]] + if cat then + lbl:SetToolTip(cat.name .. ": " .. cat.description) + lbl:SetMouseInputEnabled(true) + lbl.hoverlerp = 0 + lbl.Paint = function(self, w, h) + self.hoverlerp = Lerp(FrameTime() * 10, self.hoverlerp, self:IsHovered() and 1 or 0) + if self.hoverlerp > 0 then + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 50 * self.hoverlerp)) + end + end + end + + xoff = xoff + lbl:GetWide() + if xoff > maxW then + xoff = GProfiler.GetScaledSize(5) + yoff = yoff + lineH + end + end + + Row:SetTall(Row:GetTall() + TextBg:GetTall() + GProfiler.GetScaledSize(5)) + + local TimelineData = {} + for k2, qd in pairs(Database.ProfileData or {}) do + table.insert(TimelineData, {Query = qd.Query, Time = qd.AverageTime or 0, QueryId = k2}) + end + + local Timeline = CreateTimelinePanel(Row, Row:GetWide() - pad * 2, GProfiler.GetScaledSize(15), TimelineData, queryData.Query) + Timeline:SetPos(pad, Row:GetTall()) + Row:SetTall(Row:GetTall() + Timeline:GetTall() + GProfiler.GetScaledSize(5)) + + local Collapses = {} + local function CreateCollapse(name, id, rightText) + local collapseH = GProfiler.GetScaledSize(40) + local Collapse = vgui.Create("DCollapsibleCategory", Row) + Collapse:SetSize(Row:GetWide() - pad * 2, collapseH) + Collapse:SetPos(pad, Row:GetTall()) + Collapse:SetLabel(name) + Collapse:SetExpanded(false) + Collapse:SetAnimTime(0) + Collapse.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(39, 75, 113)) + if rightText then + draw.SimpleText(rightText, "GProfiler.Inter24", w - pad, collapseH / 2, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end + Collapse.Header:SetFont("GProfiler.Inter24") + Collapse.Header:SetTall(collapseH) + + Row:SetTall(Row:GetTall() + collapseH + GProfiler.GetScaledSize(2)) + + local ContentPanel = vgui.Create("DPanel", Collapse) + ContentPanel:SetSize(Collapse:GetWide(), 0) + ContentPanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(15, 22, 30, 255), GProfiler.RNDX.NO_TL + GProfiler.RNDX.NO_TR) + end + Collapse:SetContents(ContentPanel) + + Collapse.OnToggle = function(self, expanded) + local delta = ContentPanel:GetTall() + Row:SetTall(Row:GetTall() + (expanded and delta or -delta)) + if id == 1 then + Collapses[2]:SetPos(Collapses[2]:GetX(), Collapses[2]:GetY() + (expanded and delta or -delta)) + Collapses[3]:SetPos(Collapses[3]:GetX(), Collapses[3]:GetY() + (expanded and delta or -delta)) + elseif id == 2 then + Collapses[3]:SetPos(Collapses[3]:GetX(), Collapses[3]:GetY() + (expanded and delta or -delta)) + end + if ContentPanel.OnToggle then ContentPanel:OnToggle(expanded) end + end + + table.insert(Collapses, Collapse) + return Collapse, ContentPanel + end + + local sourceRightText = string.format("%s (%d - %d)", queryData.Source.File, queryData.Source.Line1, queryData.Source.Line2) + local ExplainCollapse, ExplainPanel = CreateCollapse("Explain", 1) + local InternalCollapse, InternalPanel = CreateCollapse("Profile", 2) + local SourceCollapse, SourcePanel = CreateCollapse("Source", 3, sourceRightText) + + if not explains[id] or explains[id].noExplain then + local lbl = vgui.Create("DLabel", ExplainPanel) + lbl:SetText("No explain data available.") + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.comment) + lbl:SizeToContents() + ExplainPanel:SetTall(lbl:GetTall()) + else + local Columns = {"Select Type", "Table", "Type", "Possible Keys", "Key", "Key Len", "Ref", "Rows", "Extra"} + local innerW = ExplainPanel:GetWide() + local colW = innerW / #Columns + + local HeaderRow = vgui.Create("DPanel", ExplainPanel) + HeaderRow:SetSize(innerW, rowH) + HeaderRow.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) + end + for i, col in ipairs(Columns) do + local lbl = vgui.Create("DLabel", HeaderRow) + lbl:SetText(col) + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.text) + lbl:SizeToContents() + lbl:SetPos((i - 1) * colW + GProfiler.GetScaledSize(5), rowH / 2 - lbl:GetTall() / 2) + end + + local totalH = rowH + for i, explain in ipairs(explains[id]) do + local ExplainRow = vgui.Create("DPanel", ExplainPanel) + ExplainRow:SetSize(innerW, rowH) + ExplainRow:SetPos(0, totalH) + ExplainRow.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, i % 2 == 0 and Color(255, 255, 255, 5) or Color(255, 255, 255, 2)) + end + for j, col in ipairs(Columns) do + local val = explain[col:lower():gsub(" ", "_")] + local valStr = tostring(val ~= nil and val or "N/A") + local lbl = vgui.Create("DLabel", ExplainRow) + lbl:SetText(valStr) + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.text) + lbl:SizeToContents() + lbl:SetPos((j - 1) * colW + GProfiler.GetScaledSize(5), rowH / 2 - lbl:GetTall() / 2) + lbl:SetMouseInputEnabled(true) + lbl:SetToolTip(valStr) + end + totalH = totalH + rowH + end + ExplainPanel:SetTall(totalH) + end + + local ProfColumns = {"Status", "Duration", "Percentage"} + local profInnerW = InternalPanel:GetWide() + local profColW = profInnerW / #ProfColumns + + local ProfHeader = vgui.Create("DPanel", InternalPanel) + ProfHeader:SetSize(profInnerW, rowH) + ProfHeader.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) + end + for i, col in ipairs(ProfColumns) do + local lbl = vgui.Create("DLabel", ProfHeader) + lbl:SetText(col) + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.text) + lbl:SizeToContents() + lbl:SetPos((i - 1) * profColW + GProfiler.GetScaledSize(5), rowH / 2 - lbl:GetTall() / 2) + end + + local internalData = queryData.InternalData + if internalData then + local totalH = rowH + for i, entry in ipairs(internalData) do + local duration = (entry.Duration or 0) * 1000 + local pct = queryData.AverageTime > 0 and (entry.Duration or 0) / queryData.AverageTime * 100 or 0 + local statusColor = duration > 1000 and Color(255, 0, 0) or (duration > 500 and Color(255, 165, 0) or Color(0, 255, 0)) + local values = {entry.Status or "N/A", string.format("%.3fms", duration), string.format("%.2f%%", pct)} + + local ProfRow = vgui.Create("DPanel", InternalPanel) + ProfRow:SetSize(profInnerW, rowH) + ProfRow:SetPos(0, totalH) + ProfRow.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, i % 2 == 0 and Color(255, 255, 255, 5) or Color(255, 255, 255, 2)) + GProfiler.RNDX.Draw(100, w - rowH - GProfiler.GetScaledSize(8), rowH / 4, rowH / 2, rowH / 2, statusColor) + end + for j, val in ipairs(values) do + local lbl = vgui.Create("DLabel", ProfRow) + lbl:SetText(val) + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.text) + lbl:SizeToContents() + lbl:SetPos((j - 1) * profColW + GProfiler.GetScaledSize(5), rowH / 2 - lbl:GetTall() / 2) + end + totalH = totalH + rowH + end + InternalPanel:SetTall(totalH) + else + ProfHeader:SetVisible(false) + local lbl = vgui.Create("DLabel", InternalPanel) + lbl:SetText("No profile data available.") + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.comment) + lbl:SizeToContents() + InternalPanel:SetTall(lbl:GetTall()) + end + + local RichText = vgui.Create("RichText", SourcePanel) + RichText:SetText("Expand to load source.") + RichText:SetSize(SourcePanel:GetWide(), GProfiler.GetScaledSize(200)) + RichText:SetVerticalScrollbarEnabled(true) + function RichText:PerformLayout() + self:SetFontInternal("GProfiler.Code") + end + SourcePanel:SetTall(RichText:GetTall()) + + local requestedSource = false + function SourcePanel:OnToggle(expanded) + if not expanded or requestedSource then return end + requestedSource = true + GProfiler.RequestFunctionSource(queryData.Source.File, queryData.Source.Line1, queryData.Source.Line2, function(src) + if not IsValid(RichText) then return end + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), queryData.Source.Line1) + else + RichText:SetText("Failed to load source.") + end + end) + end + + Row:SetTall(Row:GetTall() + pad) + + return Row + end + + local function PopulateQueries() + QueryList:Clear() + local data = Database.ProfileData + local explains = Database.Explains or {} + if not data or table.Count(data) == 0 then return end + for id, queryData in ipairs(data) do + local Row = CreateRow(id, queryData, explains) + QueryList:AddItem(Row) + end + end + Database.RefreshUI = PopulateQueries + PopulateQueries() end GProfiler.Menu.RegisterTab("Database", "gprofiler/database.png", 8, GProfiler.Database.DoTab, function() + local timer = DatabaseStore:GetTimerData("Server") + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive +end) + +local TypeLookup = { + [1] = "mysqloo", + [2] = "tmysql4", + [3] = "goobie_mysql", + [4] = "sqlite" +} + +net.Receive("GProfiler_Database_SendData", function() + local isFirstChunk = net.ReadBool() + local isLastChunk = net.ReadBool() + + if isFirstChunk then + Database.ProfileData = {} + Database.Explains = {} + end + + local count = net.ReadUInt(14) + for i = 1, count do + local id = net.ReadUInt(14) + local typeId = net.ReadUInt(3) + Database.ProfileData[id] = { + Type = TypeLookup[typeId] or "unknown", + Count = net.ReadUInt(14), + Time = net.ReadFloat(), + AverageTime = net.ReadFloat(), + LongestTime = net.ReadFloat(), + Query = net.ReadString(), + Source = { + File = net.ReadString(), + Line1 = net.ReadUInt(16), + Line2 = net.ReadUInt(16) + } + } + + if net.ReadBool() then + local internalData = {} + local internalCount = net.ReadUInt(7) + for j = 1, internalCount do + internalData[j] = { + Duration = net.ReadFloat(), + Status = net.ReadString() + } + end + Database.ProfileData[id].InternalData = internalData + else + Database.ProfileData[id].InternalData = false + end + end + + count = net.ReadUInt(14) + for i = 1, count do + local id = net.ReadUInt(14) + if net.ReadBool() then + Database.Explains[id] = {noExplain = true} + else + local explainCount = net.ReadUInt(6) + Database.Explains[id] = {} + for j = 1, explainCount do + Database.Explains[id][j] = { + select_type = net.ReadString(), + table = net.ReadString(), + type = net.ReadString(), + possible_keys = net.ReadString(), + key = net.ReadString(), + key_len = net.ReadString(), + ref = net.ReadString(), + rows = net.ReadUInt(32), + extra = net.ReadString() + } + end + end + end + if isLastChunk then + DatabaseStore:SetData("Server", Database.ProfileData) + if Database.RefreshUI then Database.RefreshUI() end + end end) diff --git a/lua/gprofiler/profilers/database/providers/base/sv_base_provider.lua b/lua/gprofiler/profilers/database/providers/base/sv_base_provider.lua index b7f48fb..13738d0 100644 --- a/lua/gprofiler/profilers/database/providers/base/sv_base_provider.lua +++ b/lua/gprofiler/profilers/database/providers/base/sv_base_provider.lua @@ -72,8 +72,8 @@ function BaseProvider:StartProfiling() continue end - self:EnableSQLProfiling(v) self:DetourQueryFunction(v) + self:EnableSQLProfiling(v) end end diff --git a/lua/gprofiler/profilers/database/providers/mysqloo.lua b/lua/gprofiler/profilers/database/providers/mysqloo.lua index 3b8d005..3457c89 100644 --- a/lua/gprofiler/profilers/database/providers/mysqloo.lua +++ b/lua/gprofiler/profilers/database/providers/mysqloo.lua @@ -28,7 +28,9 @@ function MySQLOOProvider:SetupConnectionHook() end function MySQLOOProvider:DetourQueryFunction(objectData) - objectData.oldQueryFunc = objectData.object[self.QueryFunction] + local originalFunc = objectData.object[self.QueryFunction] + objectData.oldQueryFunc = originalFunc + objectData.profilingFunc = originalFunc objectData.object[self.QueryFunction] = function(obj, queryText, ...) local source = debug.getinfo(3) or debug.getinfo(2) @@ -62,14 +64,14 @@ function MySQLOOProvider:DetourQueryFunction(objectData) end function MySQLOOProvider:EnableSQLProfiling(objectData) - if not objectData.oldQueryFunc then return end + if not objectData.profilingFunc then return end - local hasProfilingQuery = objectData.oldQueryFunc(objectData.object, "SHOW VARIABLES LIKE 'have_profiling'") + local hasProfilingQuery = objectData.profilingFunc(objectData.object, "SHOW VARIABLES LIKE 'have_profiling'") hasProfilingQuery.onSuccess = function(Q, D) if table.Count(D or {}) == 0 then return end if D[1].Value == "YES" then objectData.hasProfiling = true - local enable = objectData.oldQueryFunc(objectData.object, "SET profiling_history_size = 250, profiling = 1;") + local enable = objectData.profilingFunc(objectData.object, "SET profiling_history_size = 250, profiling = 1;") enable:start() end end @@ -77,19 +79,19 @@ function MySQLOOProvider:EnableSQLProfiling(objectData) end function MySQLOOProvider:DisableSQLProfiling(objectData) - if objectData.hasProfiling and objectData.oldQueryFunc then - local disable = objectData.oldQueryFunc(objectData.object, "SET profiling_history_size = 0, profiling = 0;") + if objectData.hasProfiling and objectData.profilingFunc then + local disable = objectData.profilingFunc(objectData.object, "SET profiling_history_size = 0, profiling = 0;") disable:start() end end function MySQLOOProvider:ExecuteExplainQuery(explainQuery, objectData, queryId) - if not objectData.oldQueryFunc then + if not objectData.profilingFunc then GProfiler.Database.Explains[queryId] = { noExplain = true } return end - local query = objectData.oldQueryFunc(objectData.object, explainQuery) + local query = objectData.profilingFunc(objectData.object, explainQuery) local data = {} query.onData = function(Q, D) @@ -108,12 +110,12 @@ function MySQLOOProvider:ExecuteExplainQuery(explainQuery, objectData, queryId) end function MySQLOOProvider:GetObjectProfilingData(objectData, callback) - if not objectData.hasProfiling or not objectData.oldQueryFunc then + if not objectData.hasProfiling or not objectData.profilingFunc then callback(nil) return end - local query = objectData.oldQueryFunc(objectData.object, "SHOW PROFILES") + local query = objectData.profilingFunc(objectData.object, "SHOW PROFILES") local data = {} query.onData = function(Q, D) @@ -130,7 +132,7 @@ function MySQLOOProvider:GetObjectProfilingData(objectData, callback) local waitingFor = 0 for _, profile in pairs(data) do - local profileQuery = objectData.oldQueryFunc(objectData.object, "SHOW PROFILE FOR QUERY " .. profile.Query_ID) + local profileQuery = objectData.profilingFunc(objectData.object, "SHOW PROFILE FOR QUERY " .. profile.Query_ID) local pdata = {} profileQuery.onData = function(Q, D) diff --git a/lua/gprofiler/profilers/database/providers/sqlite.lua b/lua/gprofiler/profilers/database/providers/sqlite.lua index 7b423eb..a13487a 100644 --- a/lua/gprofiler/profilers/database/providers/sqlite.lua +++ b/lua/gprofiler/profilers/database/providers/sqlite.lua @@ -25,14 +25,14 @@ function SQLiteProvider:DetourQueryFunction(objectData) for _, method in ipairs(self.QueryMethods) do if not objectData.object[method] then continue end - objectData.oldQueryFuncs[method] = objectData.object[method] + local originalFunc = objectData.object[method] + objectData.oldQueryFuncs[method] = originalFunc objectData.object[method] = function(queryText, ...) - -- print("running query through detour", method, queryText) local source = debug.getinfo(2) local queryId = self:GetQueryId(queryText .. source.short_src .. tostring(source.linedefined) .. tostring(source.lastlinedefined)) local startTime = SysTime() - local result = objectData.oldQueryFuncs[method](queryText, ...) + local result = originalFunc(queryText, ...) self:ProcessQueryResult(queryText, startTime, queryId, source) diff --git a/lua/gprofiler/profilers/database/sv_database.lua b/lua/gprofiler/profilers/database/sv_database.lua index 6279b5f..7dc7106 100644 --- a/lua/gprofiler/profilers/database/sv_database.lua +++ b/lua/gprofiler/profilers/database/sv_database.lua @@ -18,6 +18,8 @@ local IDLookup = { ["sqlite"] = 4 } +local SendData + local function StartDetour(realm, ply) GProfiler.Log("Server database profiler started!", 2) @@ -57,16 +59,18 @@ local function StopDetour(realm, ply) if providersToWaitFor == 0 then GProfiler.Database.InternalProfileData = allProfilingData + SendData(realm, ply) end end) end if providersToWaitFor == 0 then GProfiler.Database.InternalProfileData = allProfilingData + SendData(realm, ply) end end -local function SendData(realm, ply) +SendData = function(realm, ply) if not IsValid(ply) then return end if table.Count(GProfiler.Database.ProfileData) == 0 then @@ -183,7 +187,7 @@ end util.AddNetworkString("GProfiler_Database_SendData") -Profilers.Register("Database", { -- move to shared when UI for this is done again +Profilers.Register("Database", { Realms = { "Server" }, ServerOnly = true, OnStart = StartDetour, From cd1374f07621da47ac0036c50dd4a8c377f39ab1 Mon Sep 17 00:00:00 2001 From: Callum <35779365+callumok2004@users.noreply.github.com> Date: Fri, 8 May 2026 23:00:53 +0100 Subject: [PATCH 16/16] Update cl_graphs.lua --- lua/gprofiler/modules/utils/ui/cl_graphs.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/gprofiler/modules/utils/ui/cl_graphs.lua b/lua/gprofiler/modules/utils/ui/cl_graphs.lua index bbdba4d..d43d598 100644 --- a/lua/gprofiler/modules/utils/ui/cl_graphs.lua +++ b/lua/gprofiler/modules/utils/ui/cl_graphs.lua @@ -253,6 +253,7 @@ function PANEL:Think() if self.Data[1] and not self.InitialSizeLoaded then self.InitialSizeLoaded = true local savedSize = cookie.GetNumber("gprofiler_graph_" .. self:GetTitle() .. "_size", self.Data[1].queue:Length()) + savedSize = math.Max(savedSize, 32) self.HistorySlider:SetValue(savedSize) self:ResizeQueues(savedSize) end @@ -374,4 +375,4 @@ function PANEL:Paint(w, h) end end -vgui.Register("GP.Graph", PANEL, "DPanel") \ No newline at end of file +vgui.Register("GP.Graph", PANEL, "DPanel")