From 548ccfac9fc10f01d082184f26187d93ca8b492a Mon Sep 17 00:00:00 2001 From: callumok2004 Date: Tue, 10 Mar 2026 18:30:03 +0000 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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