diff --git a/README.md b/README.md index ea123c5..0db724f 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,4 @@ ![758e7672ed29e3acd633a4826ed5578f](https://github.com/user-attachments/assets/489d4171-4c06-49c2-a481-0330f58bc322) ![fc934fdc3a3f8116a187c6bebae03060](https://github.com/user-attachments/assets/0cfaeead-c9f1-4249-ae9b-30c679b7f42d) ![a690e160c76c9a36b61f675e232cac57](https://github.com/user-attachments/assets/77b19c50-b726-443f-925b-2ff5310f3606) -![b60f6548f7c8c11297aeffa32699886a](https://github.com/user-attachments/assets/62fb0c2d-3224-44dc-9fad-4d60b96ec1dc) - +![b60f6548f7c8c11297aeffa32699886a](https://github.com/user-attachments/assets/62fb0c2d-3224-44dc-9fad-4d60b96ec1dc) \ No newline at end of file diff --git a/lua/autorun/gprofiler_load.lua b/lua/autorun/gprofiler_load.lua index 763ff28..84777ec 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)}, @@ -20,29 +20,30 @@ end local incFuncs = { sv = SERVER and include or function() end, cl = SERVER and AddCSLuaFile or include, - sh = function(f) include(f) AddCSLuaFile(f) end + sh = function(f) include(f) AddCSLuaFile(f) end, + nl = function() end } local function incFile(f) (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 + for _, f in SortedPairs(files, CLIENT) do incFile(string.format("%s/%s", folder, f)) end + if fileOnly then return end - for _, f in ipairs(folders) do incFolder(folder.."/"..f, true) end + for _, f in ipairs(folders) do incFolder(folder.."/"..f, nil, subFileOnly) end end incFile("gprofiler/sv_init.lua") incFile("gprofiler/sh_config.lua") -incFile("gprofiler/sh_utils.lua") -incFile("gprofiler/cl_language.lua") incFile("gprofiler/cl_menu.lua") -incFile("gprofiler/sh_access.lua") -incFolder("gprofiler/profilers") +incFolder("gprofiler/modules") +incFolder("gprofiler/profilers", true) hook.Run("GProfiler.Loaded") +GProfiler.Ready = true diff --git a/lua/gprofiler/cl_menu.lua b/lua/gprofiler/cl_menu.lua index ac9acbf..373b035 100644 --- a/lua/gprofiler/cl_menu.lua +++ b/lua/gprofiler/cl_menu.lua @@ -1,34 +1,27 @@ -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 +31,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, MenuColors.Black100) -- 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 +44,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) + + 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 - draw.RoundedBox(0, 0, 0, s.Lerped, h, MenuColors.TopBarSeparator) + 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 +198,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,36 +239,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) - -net.Receive("GProfiler.SendState", function() - local count = net.ReadUInt(4) - for i = 1, count do - local Profiler = net.ReadString() - local StartedAt = net.ReadFloat() - - local Tbl = GProfiler[Profiler] - if not Tbl then continue end - - Tbl.ProfileActive = true - Tbl.Realm = "Server" - Tbl.StartTime = SysTime() - StartedAt - end -end) \ No newline at end of file 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..d7c7de5 --- /dev/null +++ b/lua/gprofiler/modules/auto_profile/cl_autoprofile.lua @@ -0,0 +1,9 @@ +GProfiler.AutoProfile = GProfiler.AutoProfile or {} +GProfiler.AutoProfile.States = GProfiler.AutoProfile.States or {} + +net.Receive("GProfiler.AutoProfile.SendState", function() + local count = net.ReadUInt(8) + for i = 1, count do + GProfiler.AutoProfile.States[net.ReadString()] = net.ReadUInt(2) + end +end) diff --git a/lua/gprofiler/modules/auto_profile/sv_autoprofile.lua b/lua/gprofiler/modules/auto_profile/sv_autoprofile.lua new file mode 100644 index 0000000..f977a88 --- /dev/null +++ b/lua/gprofiler/modules/auto_profile/sv_autoprofile.lua @@ -0,0 +1,80 @@ +util.AddNetworkString("GProfiler.AutoProfile.Configure") +util.AddNetworkString("GProfiler.AutoProfile.SendState") + +local Profilers = GProfiler.Profilers + +local function AutoStart(name, ply) + local profiler = Profilers.Get(name) + if not profiler then return end + if not profiler.Store:Start("Server", ply) then return end + if profiler.OnStart then profiler.OnStart("Server", ply) end + net.Start("GProfiler.Profiler.Status") + net.WriteString(name) + net.WriteString("Server") + net.WriteBool(true) + net.WriteEntity(ply) + net.WriteFloat(0) + net.Broadcast() +end + +hook.Add("GProfiler.Loaded", "GProfiler.AutoProfile", function() + sql.Query("CREATE TABLE IF NOT EXISTS gprofiler_autoprofile (profiler TEXT PRIMARY KEY, state INTEGER)") + + local data = sql.Query("SELECT * FROM gprofiler_autoprofile") + for _, row in ipairs(data or {}) do + local state = tonumber(row.state) or 0 + if state == 0 then continue end + + local name = row.profiler + if state == 1 then + GProfiler.Log("[Auto Profiler] Starting " .. name .. " profiler!", 2) + AutoStart(name, Entity(0)) + elseif state == 2 then + hook.Add("PostGamemodeLoaded", "GProfiler.AutoProfile." .. name, function() + hook.Remove("PostGamemodeLoaded", "GProfiler.AutoProfile." .. name) + GProfiler.Log("[Auto Profiler] Starting " .. name .. " profiler (gamemode loaded)!", 2) + AutoStart(name, Entity(0)) + end) + elseif state == 3 then + hook.Add("PlayerInitialSpawn", "GProfiler.AutoProfile." .. name, function(ply) + hook.Remove("PlayerInitialSpawn", "GProfiler.AutoProfile." .. name) + GProfiler.Log("[Auto Profiler] Starting " .. name .. " profiler (player joined)!", 2) + AutoStart(name, ply) + end) + end + end + + sql.Query("DELETE FROM gprofiler_autoprofile") +end) + +net.Receive("GProfiler.AutoProfile.Configure", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + local name = net.ReadString() + local state = net.ReadUInt(2) + + if not Profilers.Get(name) then return end + + if state == 0 then + sql.Query("DELETE FROM gprofiler_autoprofile WHERE profiler = " .. sql.SQLStr(name)) + else + sql.Query("REPLACE INTO gprofiler_autoprofile (profiler, state) VALUES (" .. sql.SQLStr(name) .. ", " .. state .. ")") + end +end) + +hook.Add("PlayerInitialSpawn", "GProfiler.AutoProfile.SendState", function(ply) + timer.Simple(1, function() + if not IsValid(ply) or not GProfiler.Access.HasAccess(ply) then return end + + local data = sql.Query("SELECT * FROM gprofiler_autoprofile") + if table.IsEmpty(data or {}) then return end + + net.Start("GProfiler.AutoProfile.SendState") + net.WriteUInt(#data, 8) + for _, row in ipairs(data) do + net.WriteString(row.profiler) + net.WriteUInt(tonumber(row.state) or 0, 2) + end + net.Send(ply) + end) +end) diff --git a/lua/gprofiler/cl_language.lua b/lua/gprofiler/modules/cl_language.lua similarity index 99% rename from lua/gprofiler/cl_language.lua rename to lua/gprofiler/modules/cl_language.lua index 18ef130..7c2dd91 100644 --- a/lua/gprofiler/cl_language.lua +++ b/lua/gprofiler/modules/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/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..cc5da13 --- /dev/null +++ b/lua/gprofiler/modules/overview/cl_init.lua @@ -0,0 +1,160 @@ +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) + if next(GProfiler.Utils.Graphs.Pinned) then return end + net.Start("GProfiler.OverviewSubscribe") + net.WriteBool(false) + net.SendToServer() + end + + local Container = vgui.Create("DPanel", Base) + Container:Dock(FILL) + Container.Paint = nil + + local Scroll = vgui.Create("DScrollPanel", Container) + Scroll:Dock(FILL) + + 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) + return g + end + + 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") + + 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") + + 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") + + g = AddGraph("Client FPS") + g:AddSegment(GProfiler.Overview.Data.CL.FPS, Color(48, 220, 160), "FPS", function(v) return string.format("%.0f", v) end, "icon16/monitor.png") + + local pad = GProfiler.GetScaledSize(8) + local rowH = GProfiler.GetScaledSize(40) + + local AutoProfileLabel = vgui.Create("DLabel", Scroll) + AutoProfileLabel:Dock(TOP) + AutoProfileLabel:DockMargin(8, 16, 8, 4) + AutoProfileLabel:SetFont("GProfiler.Inter28") + AutoProfileLabel:SetText("Auto Profile") + AutoProfileLabel:SetTextColor(GProfiler.SyntaxColors.text) + AutoProfileLabel:SetTall(GProfiler.GetScaledSize(32)) + + local DropdownOptions = { + { label = "Disabled", value = 0 }, + { label = "As soon as possible", value = 1 }, + { label = "When gamemode is loaded", value = 2 }, + { label = "When a player joins", value = 3 }, + } + + for name, profiler in SortedPairs(GProfiler.Profilers.GetAll()) do + if not table.HasValue(profiler.Realms, "Server") then continue end + + local Row = vgui.Create("DPanel", Scroll) + Row:Dock(TOP) + Row:DockMargin(8, 4, 8, 0) + Row:SetTall(rowH) + Row.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 8)) + end + + local NameLabel = vgui.Create("DLabel", Row) + NameLabel:Dock(LEFT) + NameLabel:DockMargin(pad, 0, 0, 0) + NameLabel:SetFont("GProfiler.Inter24") + NameLabel:SetText(name) + NameLabel:SetTextColor(GProfiler.SyntaxColors.text) + NameLabel:SizeToContents() + + local Dropdown = vgui.Create("DComboBox", Row) + Dropdown:Dock(RIGHT) + Dropdown:DockMargin(0, pad, pad, pad) + Dropdown:SetWide(GProfiler.GetScaledSize(220)) + Dropdown:SetFont("GProfiler.Inter24") + Dropdown:SetSortItems(false) + for _, opt in ipairs(DropdownOptions) do + Dropdown:AddChoice(opt.label, opt.value) + end + Dropdown:ChooseOptionID((GProfiler.AutoProfile.States[name] or 0) + 1) + Dropdown.OnSelect = function(s, index, value, data) + GProfiler.AutoProfile.States[name] = data + net.Start("GProfiler.AutoProfile.Configure") + net.WriteString(name) + net.WriteUInt(data, 2) + net.SendToServer() + end + end +end + +GProfiler.Menu.RegisterTab("Overview", "gprofiler/home.png", 0, GProfiler.Overview.DoTab, function() + +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/sh_access.lua b/lua/gprofiler/modules/sh_access.lua similarity index 81% rename from lua/gprofiler/sh_access.lua rename to lua/gprofiler/modules/sh_access.lua index 5f0d194..eef8621 100644 --- a/lua/gprofiler/sh_access.lua +++ b/lua/gprofiler/modules/sh_access.lua @@ -1,10 +1,6 @@ GProfiler.Access.AdminSystem = GProfiler.Access.AdminSystem or false -local function GlobalExists(name) - local exists = false - pcall(function() exists = _G[name] and true or false end) - return exists -end +local function GlobalExists(name) return _G[name] != nil end local AdminSystems = { ["FAdmin"] = { @@ -77,7 +73,6 @@ function GProfiler.Access.FindAdminSystem() for name, system in SortedPairsByMemberValue(AdminSystems, "Priority") do if system.IsAvailable() then GProfiler.Access.AdminSystem = system - GProfiler.Log("Found admin system: " .. name, 2) return end end @@ -85,25 +80,25 @@ function GProfiler.Access.FindAdminSystem() GProfiler.Access.AdminSystem = false end -function GProfiler.Access.RegisterPrivilege(name) - if not GProfiler.Access.AdminSystem then - GProfiler.Log("No admin system found, cannot register privilege: " .. name, 3) - return - end - - if GProfiler.Access.AdminSystem.RegisterPrivilege then - GProfiler.Access.AdminSystem.RegisterPrivilege(name) - end -end - hook.Add("Initialize", "GProfiler.Access.Register", function() GProfiler.Access.FindAdminSystem() - GProfiler.Access.RegisterPrivilege("gprofiler") + + if GProfiler.Access.AdminSystem then + GProfiler.Access.AdminSystem.RegisterPrivilege("gprofiler") + end end) function GProfiler.Access.HasAccess(ply) - if ply:EntIndex() == 0 then return true end -- Console + if GetGlobalBool("gprofiler_lan", false) then return true end + + if CLIENT and ply == nil then ply = LocalPlayer() end + + if SERVER and ply:EntIndex() == 0 then return true end -- Console + + if GProfiler.Config.AllowSuperAdmin and ply:IsSuperAdmin() then return true end if GProfiler.Config.AllowedSteamIDs[ply:SteamID64()] or GProfiler.Config.AllowedSteamIDs[ply:SteamID()] then return true end + if not GProfiler.Access.AdminSystem then return false end + return GProfiler.Access.AdminSystem.CheckAccess(ply, "gprofiler") end diff --git a/lua/gprofiler/modules/sh_profilers.lua b/lua/gprofiler/modules/sh_profilers.lua new file mode 100644 index 0000000..465e2f3 --- /dev/null +++ b/lua/gprofiler/modules/sh_profilers.lua @@ -0,0 +1,290 @@ +GProfiler.Profilers = GProfiler.Profilers or {} +GProfiler.Profilers.Registered = GProfiler.Profilers.Registered or {} + +local Profilers = GProfiler.Profilers +local SysTime = SysTime + +if SERVER then + util.AddNetworkString("GProfiler.Profiler.Status") + util.AddNetworkString("GProfiler.Profiler.Toggle") + util.AddNetworkString("GProfiler.Profiler.RequestData") + util.AddNetworkString("GProfiler.Profiler.SyncState") +end + +local function CreateStore(name, opts) + local Store = {} + + Store.Name = name + Store.Realms = opts.Realms or { "Client", "Server" } + Store.ServerOnly = opts.ServerOnly or false + + Store.Active = {} + Store.StartTime = {} + Store.EndTime = {} + Store.Data = {} + Store.Version = {} + Store.StartedBy = {} + + for _, realm in ipairs(Store.Realms) do + Store.Active[realm] = false + Store.StartTime[realm] = 0 + Store.EndTime[realm] = 0 + Store.Data[realm] = {} + Store.Version[realm] = 0 + end + + function Store:IsActive(realm) + return self.Active[realm] or false + end + + function Store:GetData(realm) + return self.Data[realm] + end + + function Store:SetData(realm, data) + self.Data[realm] = data + self.Version[realm] = (self.Version[realm] or 0) + 1 + end + + function Store:Start(realm, ply) + if self.Active[realm] then return false end + self.Active[realm] = true + self.StartTime[realm] = SysTime() + self.EndTime[realm] = 0 + self.Data[realm] = {} + self.StartedBy[realm] = ply + return true + end + + function Store:Stop(realm, ply) + if not self.Active[realm] then return false end + self.Active[realm] = false + self.EndTime[realm] = SysTime() + return true + end + + function Store:GetTimerData(realm) + if not realm then realm = "Client" end + return { + StartTime = self.StartTime[realm] or 0, + EndTime = self.EndTime[realm] or 0, + ProfileActive = self.Active[realm] or false + } + end + + return Store +end + +function Profilers.Register(name, opts) + opts = opts or {} + local profiler = { + Name = name, + Store = CreateStore(name, opts), + Realms = opts.Realms or { "Client", "Server" }, + ServerOnly = opts.ServerOnly or false, + OnStart = opts.OnStart, + OnStop = opts.OnStop, + WriteData = opts.WriteData, + ReadData = opts.ReadData, + OnDataReceived = opts.OnDataReceived, + } + + Profilers.Registered[name] = profiler + return profiler.Store +end + +function Profilers.Get(name) + return Profilers.Registered[name] +end + +function Profilers.GetStore(name) + local p = Profilers.Registered[name] + return p and p.Store +end + +function Profilers.GetAll() + return Profilers.Registered +end + +if SERVER then + local function BroadcastStatus(name, realm, active, ply) + local store = Profilers.Get(name).Store + net.Start("GProfiler.Profiler.Status") + net.WriteString(name) + net.WriteString(realm) + net.WriteBool(active) + net.WriteEntity(ply) + net.WriteFloat(active and (SysTime() - store.StartTime[realm]) or 0) + net.Broadcast() + end + + net.Receive("GProfiler.Profiler.Toggle", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + local name = net.ReadString() + local realm = net.ReadString() + local start = net.ReadBool() + + local profiler = Profilers.Get(name) + if not profiler then return end + if realm ~= "Server" then return end + + if start then + if not profiler.Store:Start(realm, ply) then return end + if profiler.OnStart then profiler.OnStart(realm, ply) end + BroadcastStatus(name, realm, true, ply) + else + if profiler.OnStop then profiler.OnStop(realm, ply) end + if not profiler.Store:Stop(realm, ply) then return end + BroadcastStatus(name, realm, false, ply) + end + end) + + net.Receive("GProfiler.Profiler.RequestData", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + local name = net.ReadString() + local profiler = Profilers.Get(name) + if not profiler then return end + + if profiler.WriteData then + profiler.WriteData("Server", ply) + end + end) + + net.Receive("GProfiler.Profiler.SyncState", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + local count = 0 + local active = {} + for name, profiler in pairs(Profilers.Registered) do + if profiler.Store:IsActive("Server") then + count = count + 1 + active[name] = profiler + end + end + + net.Start("GProfiler.Profiler.SyncState") + net.WriteUInt(count, 8) + for name, profiler in pairs(active) do + net.WriteString(name) + net.WriteFloat(SysTime() - profiler.Store.StartTime["Server"]) + net.WriteEntity(profiler.Store.StartedBy["Server"] or Entity(0)) + end + net.Send(ply) + end) + + hook.Add("PlayerInitialSpawn", "GProfiler.SyncState", function(ply) + timer.Simple(1, function() + if not IsValid(ply) then return end + if not GProfiler.Access.HasAccess(ply) then return end + + local count = 0 + local active = {} + for name, profiler in pairs(Profilers.Registered) do + if profiler.Store:IsActive("Server") then + count = count + 1 + active[name] = profiler + end + end + + if count == 0 then return end + + net.Start("GProfiler.Profiler.SyncState") + net.WriteUInt(count, 8) + for name, profiler in pairs(active) do + net.WriteString(name) + net.WriteFloat(SysTime() - profiler.Store.StartTime["Server"]) + net.WriteEntity(profiler.Store.StartedBy["Server"] or Entity(0)) + end + net.Send(ply) + end) + end) + + return +end + +net.Receive("GProfiler.Profiler.Status", function() + local name = net.ReadString() + local realm = net.ReadString() + local active = net.ReadBool() + local ply = net.ReadEntity() + local startTime = net.ReadFloat() + + local profiler = Profilers.Get(name) + if not profiler then return end + + if active then + profiler.Store.Active[realm] = true + profiler.Store.StartTime[realm] = SysTime() - startTime + profiler.Store.EndTime[realm] = 0 + profiler.Store.StartedBy[realm] = ply + else + profiler.Store.Active[realm] = false + profiler.Store.EndTime[realm] = SysTime() + end + + hook.Run("GProfiler.Profiler.StatusChanged", name, realm, active, ply) +end) + +net.Receive("GProfiler.Profiler.SyncState", function() + local count = net.ReadUInt(8) + for i = 1, count do + local name = net.ReadString() + local elapsed = net.ReadFloat() + local ply = net.ReadEntity() + + local profiler = Profilers.Get(name) + if not profiler then continue end + + profiler.Store.Active["Server"] = true + profiler.Store.StartTime["Server"] = SysTime() - elapsed + profiler.Store.EndTime["Server"] = 0 + profiler.Store.StartedBy["Server"] = ply + + hook.Run("GProfiler.Profiler.StatusChanged", name, "Server", true, ply) + end +end) + +function Profilers.Toggle(name, realm, start) + if not GProfiler.Access.HasAccess(LocalPlayer()) then return end + + local profiler = Profilers.Get(name) + if not profiler then return end + + if realm == "Both" then + Profilers.Toggle(name, "Client", start) + Profilers.Toggle(name, "Server", start) + return + end + + if realm == "Client" then + if start then + if not profiler.Store:Start(realm) then return end + if profiler.OnStart then profiler.OnStart(realm) end + else + if profiler.OnStop then profiler.OnStop(realm) end + profiler.Store:Stop(realm) + end + hook.Run("GProfiler.Profiler.StatusChanged", name, realm, start) + else + net.Start("GProfiler.Profiler.Toggle") + net.WriteString(name) + net.WriteString(realm) + net.WriteBool(start) + net.SendToServer() + end +end + +function Profilers.RequestData(name, callback) + local profiler = Profilers.Get(name) + if not profiler then return end + + if callback then + profiler._DataCallback = callback + end + + net.Start("GProfiler.Profiler.RequestData") + net.WriteString(name) + net.SendToServer() +end diff --git a/lua/gprofiler/modules/utils/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..3b64bf4 --- /dev/null +++ b/lua/gprofiler/modules/utils/cl_vgui.lua @@ -0,0 +1,99 @@ +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") + +function GProfiler.Utils.CreateList(Parent, Header, Columns) + local ResultsList = vgui.Create("GP.ListView", Parent) + ResultsList:SetSize(Parent:GetWide(), Parent:GetTall() - Header:GetTall()) + ResultsList:SetPos(0, Header:GetTall()) + ResultsList:SetMultiSelect(false) + for _, col in ipairs(Columns) do + ResultsList:AddColumn(col) + end + ResultsList:SetHeaderHeight(GProfiler.GetScaledSize(30)) + ResultsList:SetDataHeight(GProfiler.GetScaledSize(draw.GetFontHeight("GProfiler.Inter24") + GProfiler.GetScaledSize(10))) + ResultsList.Paint = nil + + local sbar = ResultsList.VBar + sbar:SetWide(GProfiler.GetScaledSize(12)) + sbar:SetHideButtons(true) + sbar.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) + end + sbar.btnGrip.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + for k, v in ipairs(ResultsList.Columns) do + v.Header:SetFont("GProfiler.Inter28") + v.Header:SetTextColor(color_white) + local isLast = v == ResultsList.Columns[#ResultsList.Columns] + v.Header.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(64, 105, 146), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + if not isLast then + surface.SetDrawColor(Color(255, 255, 255, 20)) + surface.DrawRect(w - 1, 0, 1, h) + end + + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + end + + if ResultsList.SortedBy == k then + draw.SimpleText(ResultsList.SortedDescending and "▼" or "▲", "GProfiler.Inter24", w - GProfiler.GetScaledSize(20), h / 2, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + + local oldAddLine = ResultsList.AddLine + ResultsList.AddLine = function(self, ...) + local line = oldAddLine(self, ...) + line.Paint = function(s, w, h) + local isEven = false + for i, v in ipairs(self.Sorted) do + if v == line then + isEven = i % 2 == 0 + break + end + end + GProfiler.RNDX.Draw(0, 0, 0, w, h, isEven and Color(255, 255, 255, 10) or Color(255, 255, 255, 2)) + + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + if s:IsLineSelected() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 30)) + end + end + for _, col in pairs(line.Columns) do + col:SetFont("GProfiler.Inter24") + col:SetTextColor(Color(255, 255, 255, 200)) + end + return line + end + + local sbar = ResultsList.VBar + sbar:SetWide(GProfiler.GetScaledSize(12)) + sbar:SetHideButtons(true) + sbar.Paint = function(s, w, h) GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) end + sbar.btnGrip.Paint = function(s, w, h) GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) end + + return ResultsList +end \ No newline at end of file diff --git a/lua/gprofiler/modules/utils/sh_utils.lua b/lua/gprofiler/modules/utils/sh_utils.lua new file mode 100644 index 0000000..579e06e --- /dev/null +++ b/lua/gprofiler/modules/utils/sh_utils.lua @@ -0,0 +1,289 @@ +GProfiler.Utils = GProfiler.Utils or {} + +function GProfiler.ExpressAvailable() return !!((express and express.shSend) and GProfiler.Config.UseExpressNetworking) end + +if SERVER then + util.AddNetworkString("GProfiler_RequestFunctionSource") + + local chunkSizeLimit = 65535 + net.Receive("GProfiler_RequestFunctionSource", function(l, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + local f = net.ReadString() + local start = net.ReadUInt(32) + local endd = net.ReadUInt(32) + + local res = GProfiler.ReadFunctionSource(f, start, endd) + local chunkCount = 1 + local currentChunkSize = 0 + local chunks = {} + + if isstring(res) then res = {res} end + + for k, v in ipairs(res) do + local str = string.Replace(v, "\t", " ") + if currentChunkSize + string.len(str) > (chunkSizeLimit - 1300) then + chunkCount = chunkCount + 1 + currentChunkSize = 0 + end + + if not chunks[chunkCount] then chunks[chunkCount] = {} end + table.insert(chunks[chunkCount], str) + currentChunkSize = currentChunkSize + string.len(str) + end + + for k, v in ipairs(chunks) do + net.Start("GProfiler_RequestFunctionSource") + net.WriteBool(k == 1) + net.WriteBool(k == table.Count(chunks)) + net.WriteUInt(table.Count(v), 32) + for k, v1 in ipairs(v) do + net.WriteString(v1) + end + net.Send(ply) + end + end) + + function GProfiler.ReadFunctionSource(f, start, endd) + if f == "[C]" then return "Cannot get source for C functions!" end + if not file.Exists(f, "GAME") then return "File not found" end + if start < 0 or endd < 0 or endd < start then return "" end + + local f = file.Open(f, "r", "GAME") + + for i = 1, start - 1 do f:ReadLine() end + + local lines = {} + for i = start, endd do table.insert(lines, f:ReadLine() or "") end + + f:Close() + + return lines + end + + return +end + +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(currentState, includeBoth) + if isbool(currentState) then + currentState = currentState and "Client" or "Server" + end + currentState = currentState or "Client" + + local Items = {"Client", "Server"} + if includeBoth then table.insert(Items, "Both") end + local numItems = #Items + + local Width = GProfiler.GetScaledSize(183) * numItems + 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) + + local initialIndex = 1 + for i, item in ipairs(Items) do + if item == currentState then initialIndex = i; break end + end + + Selector.State = currentState + Selector.LerpTo = (initialIndex - 1) / math.max(1, numItems - 1) + Selector.LerpPos = Selector.LerpTo + Selector.IsClient = currentState == "Client" + 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 - Padding * 2) / numItems + local SelectorX = Padding + lerp * (w - SelectorW - Padding * 2) + RNDX.Draw(6, SelectorX, Padding, SelectorW, h - Padding * 2, Color(31, 79, 128, 255)) + end + + local ItemW = Selector:GetWide() / numItems + + for i, item in ipairs(Items) do + local Button = vgui.Create("DButton", Selector) + Button:SetSize(ItemW, Selector:GetTall()) + Button:SetPos((i - 1) * ItemW, 0) + Button:SetText(item) + 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) / math.max(1, numItems - 1) + Selector.State = item + Selector.IsClient = item == "Client" + if Selector.OnStateChanged then Selector:OnStateChanged(Selector.State) end + end + end + + return Selector + end + + function Header:SetupTimer(getter) + local profiler = getter() + 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() + local p = getter() + self:SetText(GProfiler.TimeRunning(p.StartTime or 0, p.EndTime or 0, p.ProfileActive) .. "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.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 + 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..d43d598 --- /dev/null +++ b/lua/gprofiler/modules/utils/ui/cl_graphs.lua @@ -0,0 +1,378 @@ +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 + +function GProfiler.Utils.Graphs.Render(x, y, w, h, title, visualMax, mainIcon, segments, isPinned) + draw.RoundedBox(8, x, y, w, h, Color(16, 16, 24, isPinned and 200 or 255)) + + local titleX = x + 16 + if mainIcon then + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(mainIcon) + surface.DrawTexturedRect(x + 16, y + 16, 20, 20) + titleX = titleX + 20 + 8 + end + + draw.SimpleText(title, "GProfiler.Graph.Title", titleX, y + 16, Color(220, 220, 220), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + local iconX = x + w - 16 + if isPinned then iconX = x + w - 16 end + + for i = #segments, 1, -1 do + local seg = segments[i] + if seg.icon then + surface.SetDrawColor(seg.color) + surface.SetMaterial(seg.icon) + surface.DrawTexturedRect(iconX - 20, y + 16, 20, 20) + iconX = iconX - 20 - 8 + end + end + + local currentMax = 0 + for _, seg in ipairs(segments) do + local m = seg.queue:Max() + if m > currentMax then currentMax = m end + end + + visualMax = Lerp(FrameTime() * 5, visualMax or 1, currentMax) + local max = visualMax + if max == 0 or max ~= max then max = 1 end + + local poly = GProfiler.Utils.Graphs.PolyBuffer or { + { x = 0, y = 0 }, + { x = 0, y = 0 }, + { x = 0, y = 0 }, + { x = 0, y = 0 }, + } + GProfiler.Utils.Graphs.PolyBuffer = poly + + for _, seg in ipairs(segments) do + local queue = seg.queue + local count = queue:Length() + if count < 2 then continue end + + local col = seg.color + surface.SetDrawColor(col.r, col.g, col.b, 150) + draw.NoTexture() + + for i = 0, count - 2 do + local val1 = queue:Get(i) + local x1 = x + (i / (count - 1)) * w + local y1 = y + h - (val1 / max) * (h * 0.60) + local val2 = queue:Get(i + 1) + local x2 = x + ((i + 1) / (count - 1)) * w + local y2 = y + h - (val2 / max) * (h * 0.60) + + poly[1].x = x1 + poly[1].y = y + h + poly[2].x = x1 + poly[2].y = y1 + poly[3].x = x2 + poly[3].y = y2 + poly[4].x = x2 + poly[4].y = y + h + + surface.DrawPoly(poly) + end + end + + local rightX = x + w - 72 + + for i, seg in ipairs(segments) do + local val = seg.queue:Get(seg.queue:Length() - 1) + if not val then val = 0 end + + local txt = string.format("%.2f", val) + if seg.formatter then txt = seg.formatter(val) end + + local fullText = txt .. " " .. (seg.suffix or "") + + draw.SimpleText(fullText, "GProfiler.Graph.Title", rightX, y + 14, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + rightX = rightX - surface.GetTextSize(fullText) - 8 + end + + return visualMax +end + +GProfiler.Utils.Graphs.Pinned = GProfiler.Utils.Graphs.Pinned or {} + +hook.Add("HUDPaint", "GProfiler.Graphs.Pinned", function() -- TODO: Scaling/Only hood when necessary + local width = 512 + local x = ScrW() - width - 20 + local y = 200 + local h = 150 + + for id, data in pairs(GProfiler.Utils.Graphs.Pinned) do + data.visualMax = GProfiler.Utils.Graphs.Render(x, y, width, h, data.title, data.visualMax, data.icon, data.segments, true) + y = y + h + 10 + end +end) + +surface.CreateFont("GProfiler.Graph.Title", { font = "Roboto", size = 20, weight = 500, antialias = true }) +surface.CreateFont("GProfiler.Graph.ValueLarge", { font = "Roboto", size = 32, weight = 800, antialias = true }) + +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") + +function PANEL:Init() + self.Data = {} + self:SetTitle("Graph") + self:SetVisualMax(1) + + self.HistorySlider = vgui.Create("DNumSlider", self) + self.HistorySlider:SetText("History") + self.HistorySlider:SetMin(32) + self.HistorySlider:SetMax(1024) + self.HistorySlider:SetDecimals(0) + self.HistorySlider:SizeToContents() + self.HistorySlider:SetPos(16, 40) + self.HistorySlider:SetSize(250, 40) + self.HistorySlider.Label:SetFont("GProfiler.Graph.Title") + self.HistorySlider.Label:SetTextColor(Color(220, 220, 220)) + self.HistorySlider.TextArea:SetFont("GProfiler.Graph.Title") + self.HistorySlider.TextArea:SetTextColor(Color(220, 220, 220)) + self.HistorySlider.OnValueChanged = function(s, val) + self:ResizeQueues(math.Round(val)) + end + self.HistorySlider.Slider.Paint = function(s, w, h) + draw.RoundedBox(4, 0, h / 2 - 2, w, 4, Color(60, 60, 70)) + end + self.HistorySlider.Slider.Knob.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(220, 220, 220)) + end + self.HistorySlider:SetVisible(false) + + self.PinBtn = vgui.Create("DImageButton", self) + self.PinBtn:SetImage("icon16/pin.png") -- TODO + self.PinBtn:SetSize(24, 24) + self.PinBtn:SetPos(16, 16) + self.PinBtn.DoClick = function(s) + self:TogglePin() + end + self.PinBtn:SetVisible(false) +end + +function PANEL:ResizeQueues(size) + for _, seg in ipairs(self.Data) do + seg.queue:Resize(size) + end + cookie.Set("gprofiler_graph_" .. self:GetTitle() .. "_size", size) +end + +function PANEL:TogglePin() + local id = self:GetTitle() + if GProfiler.Utils.Graphs.Pinned[id] then + GProfiler.Utils.Graphs.Pinned[id] = nil + else + GProfiler.Utils.Graphs.Pinned[id] = { + title = self:GetTitle(), + segments = self.Data, + icon = self.MainIcon, + visualMax = self:GetVisualMax() + } + end +end + +function PANEL:Think() + if self.Data[1] and not self.InitialSizeLoaded then + self.InitialSizeLoaded = true + local savedSize = cookie.GetNumber("gprofiler_graph_" .. self:GetTitle() .. "_size", self.Data[1].queue:Length()) + savedSize = math.Max(savedSize, 32) + self.HistorySlider:SetValue(savedSize) + self:ResizeQueues(savedSize) + end + + local hover = self:IsHovered() or self:IsChildHovered() + if hover != self.LastHover then + self.LastHover = hover + self.HistorySlider:SetVisible(hover) + self.PinBtn:SetVisible(hover) + end +end + +function PANEL:AddSegment(queue, color, suffix, formatter, icon) + 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, graphBg) + + 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, graphTitle, 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(col.r, col.g, col.b, 150) + draw.NoTexture() + + poly[1].y = h + poly[4].y = h + + for i = 0, count - 2 do + local val1 = queue:Get(i) + local x1 = (i / (count - 1)) * w + 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) + + poly[1].x = x1 + poly[2].x = x1 + poly[2].y = y1 + poly[3].x = x2 + poly[3].y = y2 + poly[4].x = x2 + + surface.DrawPoly(poly) + end + end + + local rightX = w - 72 + + 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 "") + + local labelX = rightX + if i == 1 then labelX = rightX end + + draw.SimpleText(fullText, "GProfiler.Graph.Title", labelX, 14, seg.color, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + rightX = rightX - surface.GetTextSize(fullText) - 8 + end + + if self.PinBtn and self.PinBtn:IsVisible() then + self.PinBtn:SetPos(w - 32, h - 32) + self.HistorySlider:SetPos(16, h - 36) + end +end + +vgui.Register("GP.Graph", PANEL, "DPanel") 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..9df4a73 100644 --- a/lua/gprofiler/profilers/concommands/cl_concommands.lua +++ b/lua/gprofiler/profilers/concommands/cl_concommands.lua @@ -1,256 +1,250 @@ 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 +local ConCommands = GProfiler.ConCommands + +local CommandsStore = GProfiler.Profilers.GetStore("Commands") + +function GProfiler.ConCommands.DoTab(Base, Outer) +local Header = GProfiler.Utils.SetupHeader(Outer, "Commands", "gprofiler/commands.png") + local initialActive = ConCommands.Realm == "Both" and (CommandsStore:IsActive("Client") or CommandsStore:IsActive("Server")) or CommandsStore:IsActive(ConCommands.Realm) + local StartStop = Header:SetupStartStop(initialActive) + local RealmSelector = Header:SetupRealmSelector(ConCommands.Realm, true) + local Timer = Header:SetupTimer(function() + local realm = ConCommands.Realm == "Both" and "Client" or ConCommands.Realm + return CommandsStore:GetTimerData(realm) + end) - callback(commandList) - end) + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Header.OnHandleMoved() end -end -function GProfiler.ConCommands.DoTab(Content) - local Header = vgui.Create("DPanel", Content) - Header:SetSize(Content:GetWide(), 40) - Header:SetPos(0, 10) - Header.Paint = nil + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Commands", ConCommands.Realm, Running) - 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) + if ConCommands.RefreshUI then + ConCommands.RefreshUI() 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 + function RealmSelector:OnStateChanged(state) + ConCommands.Realm = state + if state == "Both" then + StartStop.State = CommandsStore:IsActive("Client") or CommandsStore:IsActive("Server") 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() + StartStop.State = CommandsStore:IsActive(state) + end + StartStop:SetText(StartStop.State and "Stop" or "Start") + if ConCommands.RefreshUI then + ConCommands.RefreshUI() + end + end + + local left, Source = GProfiler.Utils.VSplitPanel(Base, GProfiler.GetScaledSize(10), "hook_lr", 0.65) + local Results, Receivers = GProfiler.Utils.HSplitPanel(left, GProfiler.GetScaledSize(10), "hook_l_bt", 0.65) + local ClientHooks, ServerHooks = GProfiler.Utils.VSplitPanel(Receivers, GProfiler.GetScaledSize(10), "hook_lb_lr", 0.5) + + local SourceHeader = vgui.Create("DLabel", Source) + SourceHeader:SetFont("GProfiler.Inter24") + SourceHeader:SetTextColor(GProfiler.SyntaxColors.comment) + SourceHeader:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(5)) + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + SourceHeader:SetText("Select a command to view source.") + + Source.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) + end + + local RichText = vgui.Create("RichText", Source) + RichText:SetText("") + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:SetVerticalScrollbarEnabled(true) + function RichText:PerformLayout() + self:SetFontInternal("GProfiler.Code") + end + + Source.OnHandleMoved = function() + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:InvalidateLayout() + end + + local Header = GProfiler.Utils.SetupHeader(Results, "Profiler Results", nil, true) + local ResultsList = GProfiler.Utils.CreateList(Results, Header, {"Command", "Calls", "Total Time", "Average Time", "Longest Time"}) + + local function PopulateResults() + if not IsValid(ResultsList) then return end + + local displayRealm = ConCommands.Realm == "Both" and "Client" or ConCommands.Realm + local realmData = CommandsStore:GetData(displayRealm) or {} + + ResultsList:Clear() + + for k, v in pairs(realmData) do + local line = ResultsList:AddLine(k, v.Count, v.Time, v.AverageTime, v.LongestTime) + line.CmdData = v + end + end + GProfiler.ConCommands.RefreshUI = PopulateResults + PopulateResults() + + function ResultsList:OnRowSelected(rowIndex, row) + local data = row.CmdData + if data then + RichText:SetText("Loading source...") + SourceHeader:SetText("") + + local file = data.Source or data[4] + local lineDefined = data.Lines and data.Lines[1] or data[5] or 0 + local lastLineDefined = data.Lines and data.Lines[2] or data[6] or 0 + + if file and file ~= "" then + file = string.match(file, "@?(.+)") + if not file then file = data.Source or data[4] end + + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) + + GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), lineDefined) + else + RichText:SetText("Failed to load source") + end + end) else - GProfiler.ConCommands:StartProfiler() - GProfiler.ConCommands.ProfileActive = true - StartButton:SetText(GProfiler.Language.GetPhrase("profiler_stop")) + RichText:SetText("Failed to load source (2)") 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) + local function CreateCmdList(Parent, Receivers, Title) + Parent:Clear() + + local HeaderPanel = vgui.Create("DPanel", Parent) + HeaderPanel:SetSize(Parent:GetWide(), GProfiler.GetScaledSize(50)) + HeaderPanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, Color(34, 77, 122), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + draw.SimpleText(Title, "GProfiler.Inter28", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end - 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() + local RefreshButton = vgui.Create("DButton", HeaderPanel) + RefreshButton:SetSize(GProfiler.GetScaledSize(80), GProfiler.GetScaledSize(30)) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + RefreshButton:SetText("Refresh") + RefreshButton:SetFont("GProfiler.Inter24") + RefreshButton:SetTextColor(Color(0,0,0,0)) + RefreshButton.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + if s:IsHovered() then + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) end + draw.SimpleText("Refresh", "GProfiler.Inter24", w / 2, h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + RefreshButton.DoClick = function() + net.Start("GProfiler_ConCommands_CommandList") + net.SendToServer() + end - for k, v in pairs(ProfilerResults.Lines) do v:SetSelected(false) end - Line:SetSelected(true) + local List = vgui.Create("DPanelList", Parent) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + List:EnableVerticalScrollbar() - 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) + 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 - 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() + 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 - for k, v in pairs(CommandList.Lines) do v:SetSelected(false) end - Line:SetSelected(true) + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end - 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) + 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 - GProfiler.StyleDListView(CommandList) - end) + Parent.OnHandleMoved = function() + HeaderPanel:SetSize(Parent:GetWide(), HeaderPanel:GetTall()) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + for k, v in pairs(List:GetItems()) do + v:SetSize(List:GetWide(), v:GetTall()) + end + end + end - GProfiler.StyleDListView(ProfilerResults) - GProfiler.StyleDListView(CommandList) -end + local CLCmds = {} + local SVCmds = {} -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 + for name, func in pairs(concommand.GetTable()) do + table.insert(CLCmds, { Name = name }) end -end) -net.Receive("GProfiler_ConCommands_ServerProfileStatus", function() - local status = net.ReadBool() - local ply = net.ReadEntity() - GProfiler.ConCommands.ProfileActive = status - if ply == LocalPlayer() then - GProfiler.Menu.OpenTab("Commands", GProfiler.ConCommands.DoTab) + local clr = CreateCmdList(ClientHooks, CLCmds, string.format("Client Commands (%d)", #CLCmds)) + + net.Receive("GProfiler_ConCommands_CommandList", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + SVCmds = {} + for i = 1, net.ReadUInt(32) do + table.insert(SVCmds, { Name = net.ReadString(), Source = net.ReadString(), Lines = {net.ReadUInt(16), net.ReadUInt(16)} }) + end + + local svr = CreateCmdList(ServerHooks, SVCmds, string.format("Server Commands (%d)", #SVCmds)) + end) + + net.Start("GProfiler_ConCommands_CommandList") + net.SendToServer() + + Results.OnHandleMoved = function() + ResultsList:SetSize(Results:GetWide(), Results:GetTall() - Header:GetTall()) + ResultsList:SetPos(0, Header:GetTall()) + Header:SetWide(Results:GetWide()) end +end + +GProfiler.Menu.RegisterTab("Commands", "gprofiler/commands.png", 4, GProfiler.ConCommands.DoTab, function() + local timer = CommandsStore:GetTimerData(GProfiler.ConCommands.Realm) + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) net.Receive("GProfiler_ConCommands_SendData", function() + local count = net.ReadUInt(32) local data = {} - for i = 1, net.ReadUInt(32) do - local cmd = net.ReadString() - data[cmd] = { + for i = 1, count do + local name = net.ReadString() + data[name] = { Count = net.ReadUInt(32), Time = net.ReadFloat(), AverageTime = net.ReadFloat(), @@ -260,6 +254,9 @@ net.Receive("GProfiler_ConCommands_SendData", function() } end - GProfiler.ConCommands.ProfileData = data - GProfiler.Menu.OpenTab("Commands", GProfiler.ConCommands.DoTab) + CommandsStore:SetData("Server", data) + + if GProfiler.ConCommands.RefreshUI then + GProfiler.ConCommands.RefreshUI() + end end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/concommands/sh_concommands.lua b/lua/gprofiler/profilers/concommands/sh_concommands.lua index 587d151..5df4023 100644 --- a/lua/gprofiler/profilers/concommands/sh_concommands.lua +++ b/lua/gprofiler/profilers/concommands/sh_concommands.lua @@ -15,14 +15,13 @@ function GProfiler.ConCommands.GetFunction(cmd, tbl) return dbgInfo.short_src, dbgInfo.linedefined, dbgInfo.lastlinedefined end -function GProfiler.ConCommands:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or GProfiler.ConCommands.IsDetoured then return end +local function StartDetour() + if GProfiler.ConCommands.IsDetoured then return end GProfiler.Log((SERVER and "Server" or "Client") .. " commands profiler started!", 2) GProfiler.ConCommands.OldRun = GProfiler.ConCommands.OldRun or concommand.Run GProfiler.ConCommands.ProfileData = {} GProfiler.ConCommands.IsDetoured = true - GProfiler.ConCommands.ProfileStarted = SysTime() concommand.Run = function(ply, cmd, ...) local start = SysTime() @@ -51,56 +50,56 @@ function GProfiler.ConCommands:StartProfiler(ply) end end -function GProfiler.ConCommands:RestoreCommands(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or not GProfiler.ConCommands.IsDetoured then return end +local function StopDetour() + if not GProfiler.ConCommands.IsDetoured then return end - GProfiler.Log((SERVER and "Server" or "Client") .. " commands profile stopped, sending data!", 2) + GProfiler.Log((SERVER and "Server" or "Client") .. " commands profile stopped!", 2) GProfiler.ConCommands.IsDetoured = false - GProfiler.ConCommands.ProfileStarted = nil - concommand.Run = GProfiler.ConCommands.OldRun +end - if SERVER then - net.Start("GProfiler_ConCommands_SendData") - net.WriteUInt(table.Count(GProfiler.ConCommands.ProfileData), 32) - for k, v in pairs(GProfiler.ConCommands.ProfileData) do - net.WriteString(k) - net.WriteUInt(v.Count, 32) - net.WriteFloat(v.Time) - net.WriteFloat(v.AverageTime) - net.WriteFloat(v.LongestTime) - net.WriteString(v.Source) - net.WriteUInt(v.Lines[1], 16) - net.WriteUInt(v.Lines[2], 16) +local function SendData(ply) + net.Start("GProfiler_ConCommands_SendData") + net.WriteUInt(table.Count(GProfiler.ConCommands.ProfileData), 32) + for k, v in pairs(GProfiler.ConCommands.ProfileData) do + net.WriteString(k) + net.WriteUInt(v.Count, 32) + net.WriteFloat(v.Time) + net.WriteFloat(v.AverageTime) + net.WriteFloat(v.LongestTime) + net.WriteString(v.Source) + net.WriteUInt(v.Lines[1], 16) + net.WriteUInt(v.Lines[2], 16) + end + net.Send(ply) +end + +GProfiler.Profilers.Register("Commands", { + Realms = { "Client", "Server" }, + OnStart = function(realm, ply) + StartDetour() + end, + OnStop = function(realm, ply) + StopDetour() + if CLIENT then + local CmdStore = GProfiler.Profilers.GetStore("Commands") + if CmdStore then + CmdStore:SetData(realm, GProfiler.ConCommands.ProfileData) end - net.Send(ply) + end + if SERVER and ply then + SendData(ply) + end + end, + WriteData = function(realm, ply) + SendData(ply) end -end +}) if SERVER then - util.AddNetworkString("GProfiler_ConCommands_ToggleServerProfile") - util.AddNetworkString("GProfiler_ConCommands_ServerProfileStatus") util.AddNetworkString("GProfiler_ConCommands_CommandList") util.AddNetworkString("GProfiler_ConCommands_SendData") - net.Receive("GProfiler_ConCommands_ToggleServerProfile", function(len, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - if net.ReadBool() then - GProfiler.ConCommands:StartProfiler(ply) - net.Start("GProfiler_ConCommands_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() - else - GProfiler.ConCommands:RestoreCommands(ply) - net.Start("GProfiler_ConCommands_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() - end - end) - net.Receive("GProfiler_ConCommands_CommandList", function(_, ply) if not GProfiler.Access.HasAccess(ply) then return end diff --git a/lua/gprofiler/profilers/database/cl_database.lua b/lua/gprofiler/profilers/database/cl_database.lua index 439c473..c2b804e 100644 --- a/lua/gprofiler/profilers/database/cl_database.lua +++ b/lua/gprofiler/profilers/database/cl_database.lua @@ -1,35 +1,26 @@ 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 Database = GProfiler.Database + +GProfiler.Profilers.Register("Database", {}) + +local DatabaseStore = GProfiler.Profilers.GetStore("Database") +local function QueryTimeColor(time) + if time > 0.5 then return Color(238, 95, 91) end + if time > 0.1 then return Color(250, 167, 50) end + return Color(94, 185, 94) +end local function CreateTimelinePanel(parent, w, h, data, currentQuery) - local minWidth = 10 - local TimelinePanel = vgui.Create("DPanel", parent) - TimelinePanel:SetSize(w, h) - TimelinePanel.Paint = function(s, w, h) - 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 + local minWidth = 10 + local TimelinePanel = vgui.Create("DPanel", parent) + TimelinePanel:SetSize(w, h) + TimelinePanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, GProfiler.SyntaxColors.background) + if s.ShowData then s:ShowData() end + end - function TimelinePanel:ShowData() + function TimelinePanel:ShowData() TimelinePanel.ShowData = nil local totalTime = 0 @@ -38,12 +29,12 @@ local function CreateTimelinePanel(parent, w, h, data, currentQuery) end local startX = 0 - local SpaceBetweenEntries = 1 - local allocatedWidth = 0 + local gap = 1 local widths = {} + local allocatedWidth = 0 for _, entry in ipairs(data) do - local entryWidth = (w * entry.Time / totalTime) + local entryWidth = w * entry.Time / totalTime if entryWidth < minWidth then entryWidth = minWidth end table.insert(widths, entryWidth) allocatedWidth = allocatedWidth + entryWidth @@ -52,619 +43,430 @@ local function CreateTimelinePanel(parent, w, h, data, currentQuery) local scale = w / allocatedWidth for k, entry in ipairs(data) do - local entryWidth = widths[k] * scale - SpaceBetweenEntries + local entryWidth = widths[k] * scale - gap if entryWidth < 1 then entryWidth = 1 end - local entryColor = QueryTimeScore(entry.Query, entry.Time) + local entryColor = QueryTimeColor(entry.Time) if entry.Query != currentQuery then entryColor.a = 25 end + local HoverColor = table.Copy(entryColor) local entryPanel = vgui.Create("DPanel", TimelinePanel) - if k == #data then entryPanel:SetSize(w - startX, h) - else entryPanel:SetSize(entryWidth, h) end + entryPanel:SetSize(k == #data and w - startX or entryWidth, h) 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 - + GProfiler.RNDX.Draw(2, 0, 1, w - 2, h - 2, entryColor) if s.Lerp > 0 then - HoverColor.a = (230 * s.Lerp) - draw.RoundedBox(2, 0, 1, w - 2, h - 2, HoverColor) + HoverColor.a = 230 * s.Lerp + GProfiler.RNDX.Draw(2, 0, 1, w - 2, h - 2, HoverColor) end end + entryPanel.Think = function(s) + s.Lerp = Lerp(FrameTime() * 10, s.Lerp, s:IsHovered() and 1 or 0) + end entryPanel:SetCursor("hand") entryPanel.OnMousePressed = function() - print("Clicked on entry:", entry.QueryId) - GProfiler.Database.SelectedQuery = entry.QueryId + Database.SelectedQuery = entry.QueryId end - startX = startX + entryPanel:GetWide() + SpaceBetweenEntries + startX = startX + entryPanel:GetWide() + gap end end - return TimelinePanel + 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 +function GProfiler.Database.DoTab(Base, Outer) + local Header = GProfiler.Utils.SetupHeader(Outer, "Database", "gprofiler/database.png") + local initialActive = DatabaseStore:IsActive("Server") + local StartStop = Header:SetupStartStop(initialActive) + local Timer = Header:SetupTimer(function() + return DatabaseStore:GetTimerData("Server") + end) + + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + if QueryList then QueryList:SetSize(Base:GetWide(), Base:GetTall()) end + Header.OnHandleMoved() end - 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) + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Database", "Server", Running) 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 QueryList = vgui.Create("DPanelList", Base) + QueryList:SetSize(Base:GetWide(), Base:GetTall()) + QueryList:SetSpacing(GProfiler.GetScaledSize(10)) + QueryList:EnableVerticalScrollbar() - local ScrollBar = List.VBar - ScrollBar:SetWide(10) + local ScrollBar = QueryList.VBar + ScrollBar:SetWide(GProfiler.GetScaledSize(12)) 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 + ScrollBar.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) 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}) + ScrollBar.btnGrip.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) end - local function CreateRow(List, id, queryData) - local Row = vgui.Create("DPanel", List) - Row:SetSize(List:GetWide() --[[/ 2]] - 10, 30) + local rowH = GProfiler.GetScaledSize(30) + local pad = GProfiler.GetScaledSize(10) + + local function CreateRow(id, queryData, explains) + local Row = vgui.Create("DPanel", QueryList) + Row:SetSize(QueryList:GetWide() - pad, rowH) Row.Paint = function(s, w, h) - draw.RoundedBox(4, 0, 0, w, h, MenuColors.DListRowBackground) + GProfiler.RNDX.Draw(6, 0, 0, w, h, Color(24, 58, 94, 197)) + -- GProfiler.RNDX.Draw(6, 3, 3, w - 6, h - 6, Color(26, 55, 85, 150)) end Row.Think = function(s) - if GProfiler.Database.SelectedQuery == s.QueryId then - GProfiler.Database.SelectedQuery = nil - List:ScrollToChild(s) + if Database.SelectedQuery == s.QueryId then + Database.SelectedQuery = nil + QueryList: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 ScoreCol = QueryTimeColor(queryData.AverageTime) + local timeStr = string.format("%.2fms", queryData.AverageTime * 1000) + + local IdLabel = vgui.Create("DLabel", Row) + IdLabel:SetText(string.format("%d.", id)) + IdLabel:SetFont("GProfiler.Inter24") + IdLabel:SetTextColor(Color(255, 255, 255, 200)) + IdLabel:SizeToContents() + IdLabel:SetPos(pad, pad + rowH / 2 - IdLabel:GetTall() / 2) - local time = string.format("%.2fms", queryData.AverageTime * 1000) - local badgeW, badgeH = surface.GetTextSize(time) + surface.SetFont("GProfiler.Inter24") + local bw, bh = surface.GetTextSize(timeStr) 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:SetSize(bw + pad, bh + GProfiler.GetScaledSize(4)) + TimeBadge:SetPos(IdLabel:GetX() + IdLabel:GetWide() + GProfiler.GetScaledSize(5), pad + rowH / 2 - (bh + GProfiler.GetScaledSize(4)) / 2) TimeBadge.Paint = function(s, w, h) - 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) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(ScoreCol.r, ScoreCol.g, ScoreCol.b, 40)) + GProfiler.RNDX.Draw(4, 1, 1, w - 2, h - 2, Color(ScoreCol.r, ScoreCol.g, ScoreCol.b, 20)) + draw.SimpleText(timeStr, "GProfiler.Inter24", w / 2, h / 2, ScoreCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end local TypeBadge = vgui.Create("DLabel", Row) TypeBadge:SetText(queryData.Type or "Unknown") - TypeBadge:SetFont("GProfiler.Menu.RowText") + TypeBadge:SetFont("GProfiler.Inter24") + TypeBadge:SetTextColor(Color(210, 210, 210)) 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:SetSize(TypeBadge:GetWide() + pad, TypeBadge:GetTall() + GProfiler.GetScaledSize(4)) 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 + local CountBadge = vgui.Create("DLabel", Row) + CountBadge:SetText(string.format("x%d", queryData.Count)) + CountBadge:SetFont("GProfiler.Inter24") + CountBadge:SetTextColor(GProfiler.SyntaxColors.text) + CountBadge:SizeToContents() + CountBadge:SetSize(CountBadge:GetWide() + pad, CountBadge:GetTall() + GProfiler.GetScaledSize(4)) + CountBadge:SetContentAlignment(5) + + local CopyBtn = vgui.Create("DButton", Row) + CopyBtn:SetText("Copy Query") + CopyBtn:SetFont("GProfiler.Inter24") + CopyBtn:SizeToContentsX() + CopyBtn:SetTextColor(Color(0, 0, 0, 0)) + CopyBtn:SetSize(CopyBtn:GetWide() + GProfiler.GetScaledSize(10), CountBadge:GetTall()) + CopyBtn.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) + if s:IsHovered() then GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) end + draw.SimpleText("Copy Query", "GProfiler.Inter24", w / 2, h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end - CopyQuery.DoClick = function() + CopyBtn.DoClick = function() SetClipboardText(queryData.Query) end - Row:SetTall(TimeBadge:GetTall() + 20) + CountBadge:SetPos(Row:GetWide() - CountBadge:GetWide() - pad, pad + rowH / 2 - CountBadge:GetTall() / 2) + TypeBadge:SetPos(CountBadge:GetX() - TypeBadge:GetWide() - GProfiler.GetScaledSize(5), pad + rowH / 2 - TypeBadge:GetTall() / 2) + CopyBtn:SetPos(TypeBadge:GetX() - CopyBtn:GetWide() - GProfiler.GetScaledSize(5), pad + rowH / 2 - CopyBtn:GetTall() / 2) + + Row:SetTall(rowH + pad * 2) local QueryParser = GProfiler.Database.QueryParser.New() local template = QueryParser:ParseTemplate(queryData.Query) - local templateString = "" + + surface.SetFont("GProfiler.Inter24") + local _, lineH = surface.GetTextSize("A") + local maxW = Row:GetWide() - pad * 2 - GProfiler.GetScaledSize(10) + local xoff, yoff = 0, 0 + local queryH = lineH + for _, v in ipairs(template) do if v[1] == "\n" then - templateString = templateString .. "\n" + xoff = 0 + queryH = queryH + lineH 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 + local tw = surface.GetTextSize(v[1]) + if xoff + tw > maxW then + xoff = 0 + queryH = queryH + lineH end + xoff = xoff + tw 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) + local TextBg = vgui.Create("DPanel", Row) + TextBg:SetPos(pad, Row:GetTall()) + TextBg:SetSize(Row:GetWide() - pad * 2, queryH + pad) + TextBg.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(18, 28, 39, 255)) end - 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) + xoff, yoff = GProfiler.GetScaledSize(5), GProfiler.GetScaledSize(5) + for _, v in ipairs(template) do + if v[1] == "\n" then + xoff = GProfiler.GetScaledSize(5) + yoff = yoff + lineH + continue end - local 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 - + local lbl = vgui.Create("DLabel", TextBg) + local displayText = v[1] + if v[2] == 1 and Database.MySQLKeywords and not Database.MySQLKeywords[displayText] then + displayText = displayText:upper() + end + lbl:SetText(displayText) + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.text) + lbl:SizeToContents() + lbl:SetPos(xoff, yoff) + + local cat = Database.TokenCategories and Database.TokenCategories[v[2]] + if cat then + lbl:SetToolTip(cat.name .. ": " .. cat.description) + lbl:SetMouseInputEnabled(true) + lbl.hoverlerp = 0 + lbl.Paint = function(self, w, h) + self.hoverlerp = Lerp(FrameTime() * 10, self.hoverlerp, self:IsHovered() and 1 or 0) if self.hoverlerp > 0 then - draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 10 * self.hoverlerp)) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 50 * self.hoverlerp)) end end + end - local lw = label:GetWide() - xoff = xoff + lw - if xoff + lw > maxw then - xoff = 5 - yoff = yoff + fHeight - end + xoff = xoff + lbl:GetWide() + if xoff > maxW then + xoff = GProfiler.GetScaledSize(5) + yoff = yoff + lineH 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) + Row:SetTall(Row:GetTall() + TextBg:GetTall() + GProfiler.GetScaledSize(5)) - local TimelinePanel = CreateTimelinePanel(Row, Row:GetWide() - 20, 15, TimelineData, queryData.Query) - TimelinePanel:SetPos(10, 15 + TimeBadge:GetTall() + TextArea:GetTall() + 5) + local TimelineData = {} + for k2, qd in pairs(Database.ProfileData or {}) do + table.insert(TimelineData, {Query = qd.Query, Time = qd.AverageTime or 0, QueryId = k2}) + end - Row:SetTall(Row:GetTall() + TimelinePanel:GetTall() + 5) + local Timeline = CreateTimelinePanel(Row, Row:GetWide() - pad * 2, GProfiler.GetScaledSize(15), TimelineData, queryData.Query) + Timeline:SetPos(pad, Row:GetTall()) + Row:SetTall(Row:GetTall() + Timeline:GetTall() + GProfiler.GetScaledSize(5)) local Collapses = {} - - local function CreateCollapse(Name, Id, RighText) + local function CreateCollapse(name, id, rightText) + local collapseH = GProfiler.GetScaledSize(40) 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:SetSize(Row:GetWide() - pad * 2, collapseH) + Collapse:SetPos(pad, Row:GetTall()) + Collapse:SetLabel(name) Collapse:SetExpanded(false) Collapse:SetAnimTime(0) Collapse.Paint = function(s, w, h) - 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) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(39, 75, 113)) + if rightText then + draw.SimpleText(rightText, "GProfiler.Inter24", w - pad, collapseH / 2, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end end - Collapse.Header:SetFont("GProfiler.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) + Collapse.Header:SetFont("GProfiler.Inter24") + Collapse.Header:SetTall(collapseH) + + Row:SetTall(Row:GetTall() + collapseH + GProfiler.GetScaledSize(2)) + + local ContentPanel = vgui.Create("DPanel", Collapse) + ContentPanel:SetSize(Collapse:GetWide(), 0) + ContentPanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(15, 22, 30, 255), GProfiler.RNDX.NO_TL + GProfiler.RNDX.NO_TR) end - 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 + Collapse:SetContents(ContentPanel) - if CollapsePanel.OnToggle then - CollapsePanel:OnToggle(expanded) + Collapse.OnToggle = function(self, expanded) + local delta = ContentPanel:GetTall() + Row:SetTall(Row:GetTall() + (expanded and delta or -delta)) + if id == 1 then + Collapses[2]:SetPos(Collapses[2]:GetX(), Collapses[2]:GetY() + (expanded and delta or -delta)) + Collapses[3]:SetPos(Collapses[3]:GetX(), Collapses[3]:GetY() + (expanded and delta or -delta)) + elseif id == 2 then + Collapses[3]:SetPos(Collapses[3]:GetX(), Collapses[3]:GetY() + (expanded and delta or -delta)) end + if ContentPanel.OnToggle then ContentPanel:OnToggle(expanded) end end table.insert(Collapses, Collapse) - - return Collapse, CollapsePanel + return Collapse, ContentPanel end + local sourceRightText = string.format("%s (%d - %d)", queryData.Source.File, queryData.Source.Line1, queryData.Source.Line2) local ExplainCollapse, ExplainPanel = CreateCollapse("Explain", 1) local InternalCollapse, InternalPanel = CreateCollapse("Profile", 2) - local SourceCollapse, SourcePanel = CreateCollapse("Source", 3, 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)) + local SourceCollapse, SourcePanel = CreateCollapse("Source", 3, sourceRightText) + + if not explains[id] or explains[id].noExplain then + local lbl = vgui.Create("DLabel", ExplainPanel) + lbl:SetText("No explain data available.") + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.comment) + lbl:SizeToContents() + ExplainPanel:SetTall(lbl:GetTall()) else local Columns = {"Select Type", "Table", "Type", "Possible Keys", "Key", "Key Len", "Ref", "Rows", "Extra"} - local 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 + local innerW = ExplainPanel:GetWide() + local colW = innerW / #Columns + local HeaderRow = vgui.Create("DPanel", ExplainPanel) + HeaderRow:SetSize(innerW, rowH) + HeaderRow.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) + end for i, col in ipairs(Columns) do - local 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) + local lbl = vgui.Create("DLabel", HeaderRow) + lbl:SetText(col) + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.text) + lbl:SizeToContents() + lbl:SetPos((i - 1) * colW + GProfiler.GetScaledSize(5), rowH / 2 - lbl:GetTall() / 2) end - for i, explain in ipairs(Explains[id] or {}) do + local totalH = rowH + for i, explain in ipairs(explains[id]) do local ExplainRow = vgui.Create("DPanel", ExplainPanel) - ExplainRow:SetSize(ExplainPanel:GetWide(), fHeight * 2) - ExplainRow:SetPos(0, (i) * (fHeight * 2)) + ExplainRow:SetSize(innerW, rowH) + ExplainRow:SetPos(0, totalH) 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) + GProfiler.RNDX.Draw(0, 0, 0, w, h, i % 2 == 0 and Color(255, 255, 255, 5) or Color(255, 255, 255, 2)) end - for j, col in ipairs(Columns) do - local 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) + local val = explain[col:lower():gsub(" ", "_")] + local valStr = tostring(val ~= nil and val or "N/A") + local lbl = vgui.Create("DLabel", ExplainRow) + lbl:SetText(valStr) + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.text) + lbl:SizeToContents() + lbl:SetPos((j - 1) * colW + GProfiler.GetScaledSize(5), rowH / 2 - lbl:GetTall() / 2) + lbl:SetMouseInputEnabled(true) + lbl:SetToolTip(valStr) end - - ExplainPanel:Add(ExplainRow) + totalH = totalH + rowH end + ExplainPanel:SetTall(totalH) 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 ProfColumns = {"Status", "Duration", "Percentage"} + local profInnerW = InternalPanel:GetWide() + local profColW = profInnerW / #ProfColumns - 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) + local ProfHeader = vgui.Create("DPanel", InternalPanel) + ProfHeader:SetSize(profInnerW, rowH) + ProfHeader.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 10)) + end + for i, col in ipairs(ProfColumns) do + local lbl = vgui.Create("DLabel", ProfHeader) + lbl:SetText(col) + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.text) + lbl:SizeToContents() + lbl:SetPos((i - 1) * profColW + GProfiler.GetScaledSize(5), rowH / 2 - lbl:GetTall() / 2) end local internalData = queryData.InternalData if internalData then - 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 + local totalH = rowH + for i, entry in ipairs(internalData) do + local duration = (entry.Duration or 0) * 1000 + local pct = queryData.AverageTime > 0 and (entry.Duration or 0) / queryData.AverageTime * 100 or 0 + local statusColor = duration > 1000 and Color(255, 0, 0) or (duration > 500 and Color(255, 165, 0) or Color(0, 255, 0)) + local values = {entry.Status or "N/A", string.format("%.3fms", duration), string.format("%.2f%%", pct)} + + local ProfRow = vgui.Create("DPanel", InternalPanel) + ProfRow:SetSize(profInnerW, rowH) + ProfRow:SetPos(0, totalH) + ProfRow.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, i % 2 == 0 and Color(255, 255, 255, 5) or Color(255, 255, 255, 2)) + GProfiler.RNDX.Draw(100, w - rowH - GProfiler.GetScaledSize(8), rowH / 4, rowH / 2, rowH / 2, statusColor) end - - for j, 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 + for j, val in ipairs(values) do + local lbl = vgui.Create("DLabel", ProfRow) + lbl:SetText(val) + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.text) + lbl:SizeToContents() + lbl:SetPos((j - 1) * profColW + GProfiler.GetScaledSize(5), rowH / 2 - lbl:GetTall() / 2) end - InternalPanel:Add(InternalRow) + totalH = totalH + rowH end + InternalPanel:SetTall(totalH) 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) + ProfHeader:SetVisible(false) + local lbl = vgui.Create("DLabel", InternalPanel) + lbl:SetText("No profile data available.") + lbl:SetFont("GProfiler.Inter24") + lbl:SetTextColor(GProfiler.SyntaxColors.comment) + lbl:SizeToContents() + InternalPanel:SetTall(lbl:GetTall()) end - local 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 + local RichText = vgui.Create("RichText", SourcePanel) + RichText:SetText("Expand to load source.") + RichText:SetSize(SourcePanel:GetWide(), GProfiler.GetScaledSize(200)) + RichText:SetVerticalScrollbarEnabled(true) + function RichText:PerformLayout() + self:SetFontInternal("GProfiler.Code") + end + SourcePanel:SetTall(RichText:GetTall()) + + local requestedSource = false function SourcePanel:OnToggle(expanded) - if 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() + if not expanded or requestedSource then return end + requestedSource = true + GProfiler.RequestFunctionSource(queryData.Source.File, queryData.Source.Line1, queryData.Source.Line2, function(src) + if not IsValid(RichText) then return end + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), queryData.Source.Line1) + else + RichText:SetText("Failed to load source.") + end end) end + Row:SetTall(Row:GetTall() + pad) + return Row end - for id, queryData in ipairs(Data) do - local Row = CreateRow(List, id, queryData) - List:AddItem(Row) + local function PopulateQueries() + QueryList:Clear() + local data = Database.ProfileData + local explains = Database.Explains or {} + if not data or table.Count(data) == 0 then return end + for id, queryData in ipairs(data) do + local Row = CreateRow(id, queryData, explains) + QueryList:AddItem(Row) + end end + Database.RefreshUI = PopulateQueries + PopulateQueries() end -GProfiler.Menu.RegisterTab("Database", "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 +GProfiler.Menu.RegisterTab("Database", "gprofiler/database.png", 8, GProfiler.Database.DoTab, function() + local timer = DatabaseStore:GetTimerData("Server") + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) local TypeLookup = { @@ -679,16 +481,15 @@ net.Receive("GProfiler_Database_SendData", function() local isLastChunk = net.ReadBool() if isFirstChunk then - GProfiler.Database.ReceivingData = true - GProfiler.Database.ProfileData = {} - GProfiler.Database.Explains = {} + Database.ProfileData = {} + Database.Explains = {} end local count = net.ReadUInt(14) for i = 1, count do local id = net.ReadUInt(14) local typeId = net.ReadUInt(3) - GProfiler.Database.ProfileData[id] = { + Database.ProfileData[id] = { Type = TypeLookup[typeId] or "unknown", Count = net.ReadUInt(14), Time = net.ReadFloat(), @@ -702,19 +503,18 @@ net.Receive("GProfiler_Database_SendData", function() } } - local hasInt = net.ReadBool() - if hasInt then - local data = {} + if net.ReadBool() then + local internalData = {} local internalCount = net.ReadUInt(7) for j = 1, internalCount do - data[j] = { + internalData[j] = { Duration = net.ReadFloat(), Status = net.ReadString() } end - GProfiler.Database.ProfileData[id].InternalData = data + Database.ProfileData[id].InternalData = internalData else - GProfiler.Database.ProfileData[id].InternalData = false + Database.ProfileData[id].InternalData = false end end @@ -722,12 +522,12 @@ net.Receive("GProfiler_Database_SendData", function() for i = 1, count do local id = net.ReadUInt(14) if net.ReadBool() then - GProfiler.Database.Explains[id] = {noExplain = true} + Database.Explains[id] = {noExplain = true} else local explainCount = net.ReadUInt(6) - GProfiler.Database.Explains[id] = {} + Database.Explains[id] = {} for j = 1, explainCount do - GProfiler.Database.Explains[id][j] = { + Database.Explains[id][j] = { select_type = net.ReadString(), table = net.ReadString(), type = net.ReadString(), @@ -743,7 +543,7 @@ net.Receive("GProfiler_Database_SendData", function() end if isLastChunk then - GProfiler.Database.ReceivingData = false - GProfiler.Menu.OpenTab("Database", GProfiler.Database.DoTab) + DatabaseStore:SetData("Server", Database.ProfileData) + if Database.RefreshUI then Database.RefreshUI() end end end) diff --git a/lua/gprofiler/profilers/database/cl_queryparser.lua b/lua/gprofiler/profilers/database/cl_queryparser.lua index dd65492..bd8a03b 100644 --- a/lua/gprofiler/profilers/database/cl_queryparser.lua +++ b/lua/gprofiler/profilers/database/cl_queryparser.lua @@ -1,3 +1,5 @@ +GProfiler.Database = GProfiler.Database or {} + local MySQLKeywords = { ["ACCESSIBLE"] = "Ensures table alterations are compatible with storage engines.", ["ACCOUNT"] = "Used in security statements to manage user accounts (e.g., LOCK/UNLOCK).", diff --git a/lua/gprofiler/profilers/database/providers/base/sv_base_provider.lua b/lua/gprofiler/profilers/database/providers/base/sv_base_provider.lua index b7f48fb..13738d0 100644 --- a/lua/gprofiler/profilers/database/providers/base/sv_base_provider.lua +++ b/lua/gprofiler/profilers/database/providers/base/sv_base_provider.lua @@ -72,8 +72,8 @@ function BaseProvider:StartProfiling() continue end - self:EnableSQLProfiling(v) self:DetourQueryFunction(v) + self:EnableSQLProfiling(v) end end diff --git a/lua/gprofiler/profilers/database/providers/mysqloo.lua b/lua/gprofiler/profilers/database/providers/mysqloo.lua index 3b8d005..3457c89 100644 --- a/lua/gprofiler/profilers/database/providers/mysqloo.lua +++ b/lua/gprofiler/profilers/database/providers/mysqloo.lua @@ -28,7 +28,9 @@ function MySQLOOProvider:SetupConnectionHook() end function MySQLOOProvider:DetourQueryFunction(objectData) - objectData.oldQueryFunc = objectData.object[self.QueryFunction] + local originalFunc = objectData.object[self.QueryFunction] + objectData.oldQueryFunc = originalFunc + objectData.profilingFunc = originalFunc objectData.object[self.QueryFunction] = function(obj, queryText, ...) local source = debug.getinfo(3) or debug.getinfo(2) @@ -62,14 +64,14 @@ function MySQLOOProvider:DetourQueryFunction(objectData) end function MySQLOOProvider:EnableSQLProfiling(objectData) - if not objectData.oldQueryFunc then return end + if not objectData.profilingFunc then return end - local hasProfilingQuery = objectData.oldQueryFunc(objectData.object, "SHOW VARIABLES LIKE 'have_profiling'") + local hasProfilingQuery = objectData.profilingFunc(objectData.object, "SHOW VARIABLES LIKE 'have_profiling'") hasProfilingQuery.onSuccess = function(Q, D) if table.Count(D or {}) == 0 then return end if D[1].Value == "YES" then objectData.hasProfiling = true - local enable = objectData.oldQueryFunc(objectData.object, "SET profiling_history_size = 250, profiling = 1;") + local enable = objectData.profilingFunc(objectData.object, "SET profiling_history_size = 250, profiling = 1;") enable:start() end end @@ -77,19 +79,19 @@ function MySQLOOProvider:EnableSQLProfiling(objectData) end function MySQLOOProvider:DisableSQLProfiling(objectData) - if objectData.hasProfiling and objectData.oldQueryFunc then - local disable = objectData.oldQueryFunc(objectData.object, "SET profiling_history_size = 0, profiling = 0;") + if objectData.hasProfiling and objectData.profilingFunc then + local disable = objectData.profilingFunc(objectData.object, "SET profiling_history_size = 0, profiling = 0;") disable:start() end end function MySQLOOProvider:ExecuteExplainQuery(explainQuery, objectData, queryId) - if not objectData.oldQueryFunc then + if not objectData.profilingFunc then GProfiler.Database.Explains[queryId] = { noExplain = true } return end - local query = objectData.oldQueryFunc(objectData.object, explainQuery) + local query = objectData.profilingFunc(objectData.object, explainQuery) local data = {} query.onData = function(Q, D) @@ -108,12 +110,12 @@ function MySQLOOProvider:ExecuteExplainQuery(explainQuery, objectData, queryId) end function MySQLOOProvider:GetObjectProfilingData(objectData, callback) - if not objectData.hasProfiling or not objectData.oldQueryFunc then + if not objectData.hasProfiling or not objectData.profilingFunc then callback(nil) return end - local query = objectData.oldQueryFunc(objectData.object, "SHOW PROFILES") + local query = objectData.profilingFunc(objectData.object, "SHOW PROFILES") local data = {} query.onData = function(Q, D) @@ -130,7 +132,7 @@ function MySQLOOProvider:GetObjectProfilingData(objectData, callback) local waitingFor = 0 for _, profile in pairs(data) do - local profileQuery = objectData.oldQueryFunc(objectData.object, "SHOW PROFILE FOR QUERY " .. profile.Query_ID) + local profileQuery = objectData.profilingFunc(objectData.object, "SHOW PROFILE FOR QUERY " .. profile.Query_ID) local pdata = {} profileQuery.onData = function(Q, D) diff --git a/lua/gprofiler/profilers/database/providers/sqlite.lua b/lua/gprofiler/profilers/database/providers/sqlite.lua index 7b423eb..a13487a 100644 --- a/lua/gprofiler/profilers/database/providers/sqlite.lua +++ b/lua/gprofiler/profilers/database/providers/sqlite.lua @@ -25,14 +25,14 @@ function SQLiteProvider:DetourQueryFunction(objectData) for _, method in ipairs(self.QueryMethods) do if not objectData.object[method] then continue end - objectData.oldQueryFuncs[method] = objectData.object[method] + local originalFunc = objectData.object[method] + objectData.oldQueryFuncs[method] = originalFunc objectData.object[method] = function(queryText, ...) - -- print("running query through detour", method, queryText) local source = debug.getinfo(2) local queryId = self:GetQueryId(queryText .. source.short_src .. tostring(source.linedefined) .. tostring(source.lastlinedefined)) local startTime = SysTime() - local result = objectData.oldQueryFuncs[method](queryText, ...) + local result = originalFunc(queryText, ...) self:ProcessQueryResult(queryText, startTime, queryId, source) diff --git a/lua/gprofiler/profilers/database/sv_database.lua b/lua/gprofiler/profilers/database/sv_database.lua index 571e45b..7dc7106 100644 --- a/lua/gprofiler/profilers/database/sv_database.lua +++ b/lua/gprofiler/profilers/database/sv_database.lua @@ -6,6 +6,8 @@ GProfiler.Database.SeenQuery = GProfiler.Database.SeenQuery or {} GProfiler.Database.InternalProfileData = GProfiler.Database.InternalProfileData or {} GProfiler.Database.QueryIdCounter = GProfiler.Database.QueryIdCounter or 0 +local Profilers = GProfiler.Profilers + local ProviderLoader = include("providers/base/sv_provider_loader.lua") local availableProviders = ProviderLoader.Initialize() @@ -16,11 +18,9 @@ local IDLookup = { ["sqlite"] = 4 } --- TODO: Express Networking Support! - -function GProfiler.Database:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or GProfiler.Database.IsDetoured then return end +local SendData +local function StartDetour(realm, ply) GProfiler.Log("Server database profiler started!", 2) GProfiler.Database.ProfileData = {} @@ -29,24 +29,50 @@ function GProfiler.Database:StartProfiler(ply) GProfiler.Database.QueryIdCounter = 0 GProfiler.Database.QueryMapping = {} GProfiler.Database.IsDetoured = true - GProfiler.Database.ProfileStarted = SysTime() for name, provider in pairs(availableProviders) do provider:StartProfiling() end end -function GProfiler.Database:StopProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or not GProfiler.Database.IsDetoured then return end - - GProfiler.Log((SERVER and "Server" or "Client") .. " database profiler stopped, sending data!", 2) +local function StopDetour(realm, ply) + GProfiler.Log("Server database profiler stopped!", 2) GProfiler.Database.IsDetoured = false - GProfiler.Database.ProfileStarted = nil for name, provider in pairs(availableProviders) do provider:StopProfiling() end + local providersToWaitFor = 0 + local allProfilingData = {} + + for name, provider in pairs(availableProviders) do + providersToWaitFor = providersToWaitFor + 1 + provider:GetProfilingData(function(data) + providersToWaitFor = providersToWaitFor - 1 + + if data then + for query, profile in pairs(data) do + allProfilingData[query] = profile + end + end + + if providersToWaitFor == 0 then + GProfiler.Database.InternalProfileData = allProfilingData + SendData(realm, ply) + end + end) + end + + if providersToWaitFor == 0 then + GProfiler.Database.InternalProfileData = allProfilingData + SendData(realm, ply) + end +end + +SendData = function(realm, ply) + if not IsValid(ply) then return end + if table.Count(GProfiler.Database.ProfileData) == 0 then net.Start("GProfiler_Database_SendData") net.WriteBool(true) @@ -57,119 +83,88 @@ function GProfiler.Database:StopProfiler(ply) return end - local sent = false - local function SendData() - if sent then return end - sent = true - - local function SendChunk(ply, profileData, explains, startIndex, chunkSize) - if not IsValid(ply) then return end - net.Start("GProfiler_Database_SendData") - net.WriteBool(startIndex == 1) - local endIndex = math.min(startIndex + chunkSize - 1, table.Count(profileData)) - net.WriteBool(endIndex == table.Count(profileData)) - net.WriteUInt(endIndex - startIndex + 1, 14) - local index = 0 - for k, v in pairs(profileData) do - index = index + 1 - if index >= startIndex and index <= endIndex then - local FlattenQuery = v.Query:gsub("%s+", " "):Trim():lower() - if FlattenQuery:EndsWith(";") then FlattenQuery = FlattenQuery:sub(1, -2) end - local internalData = GProfiler.Database.InternalProfileData[FlattenQuery] - if not internalData then - for k2, v2 in pairs(GProfiler.Database.InternalProfileData) do - local k2Flat = k2:gsub("%s+", " "):Trim():lower() - if k2Flat:EndsWith(";") then k2Flat = k2Flat:sub(1, -2) end - if FlattenQuery:StartsWith(k2Flat) then - internalData = v2 - break - end + local function SendChunk(ply, profileData, explains, startIndex, chunkSize) + if not IsValid(ply) then return end + net.Start("GProfiler_Database_SendData") + net.WriteBool(startIndex == 1) + local endIndex = math.min(startIndex + chunkSize - 1, table.Count(profileData)) + net.WriteBool(endIndex == table.Count(profileData)) + net.WriteUInt(endIndex - startIndex + 1, 14) + local index = 0 + for k, v in pairs(profileData) do + index = index + 1 + if index >= startIndex and index <= endIndex then + local FlattenQuery = v.Query:gsub("%s+", " "):Trim():lower() + if FlattenQuery:EndsWith(";") then FlattenQuery = FlattenQuery:sub(1, -2) end + local internalData = GProfiler.Database.InternalProfileData[FlattenQuery] + if not internalData then + for k2, v2 in pairs(GProfiler.Database.InternalProfileData) do + local k2Flat = k2:gsub("%s+", " "):Trim():lower() + if k2Flat:EndsWith(";") then k2Flat = k2Flat:sub(1, -2) end + if FlattenQuery:StartsWith(k2Flat) then + internalData = v2 + break end end - net.WriteUInt(k, 14) - net.WriteUInt(IDLookup[v.Type] or 0, 3) - net.WriteUInt(v.Count, 14) - net.WriteFloat(v.Time) - net.WriteFloat(internalData and internalData.Duration or v.AverageTime) - net.WriteFloat(v.LongestTime) - net.WriteString(v.Query or "") - net.WriteString(v.Source[1] or "Unknown") - net.WriteUInt(v.Source[2] or 0, 16) - net.WriteUInt(v.Source[3] or 0, 16) - if internalData then - net.WriteBool(true) - local count = table.Count(internalData.ProfileData) - net.WriteUInt(count, 7) - for k2, v2 in ipairs(internalData.ProfileData) do - net.WriteFloat(v2.Duration or 0) - net.WriteString(v2.Status or "Unknown") - end - else - net.WriteBool(false) + end + net.WriteUInt(k, 14) + net.WriteUInt(IDLookup[v.Type] or 0, 3) + net.WriteUInt(v.Count, 14) + net.WriteFloat(v.Time) + net.WriteFloat(internalData and internalData.Duration or v.AverageTime) + net.WriteFloat(v.LongestTime) + net.WriteString(v.Query or "") + net.WriteString(v.Source[1] or "Unknown") + net.WriteUInt(v.Source[2] or 0, 16) + net.WriteUInt(v.Source[3] or 0, 16) + if internalData then + net.WriteBool(true) + local count = table.Count(internalData.ProfileData) + net.WriteUInt(count, 7) + for k2, v2 in ipairs(internalData.ProfileData) do + net.WriteFloat(v2.Duration or 0) + net.WriteString(v2.Status or "Unknown") end + else + net.WriteBool(false) end end - if endIndex == table.Count(profileData) then - net.WriteUInt(table.Count(explains), 14) - for k, v in pairs(explains) do - net.WriteUInt(k, 14) - if v.noExplain then - net.WriteBool(true) - else - net.WriteBool(false) - net.WriteUInt(table.Count(v), 6) - for i, explain in ipairs(v) do - net.WriteString(explain.select_type or "") - net.WriteString(explain.table or "") - net.WriteString(explain.type or "") - net.WriteString(explain.possible_keys or "") - net.WriteString(explain.key or "") - net.WriteString(explain.key_len or "") - net.WriteString(explain.ref or "") - net.WriteUInt(explain.rows or 0, 32) - net.WriteString(explain.Extra or "") - end + end + if endIndex == table.Count(profileData) then + net.WriteUInt(table.Count(explains), 14) + for k, v in pairs(explains) do + net.WriteUInt(k, 14) + if v.noExplain then + net.WriteBool(true) + else + net.WriteBool(false) + net.WriteUInt(table.Count(v), 6) + for i, explain in ipairs(v) do + net.WriteString(explain.select_type or "") + net.WriteString(explain.table or "") + net.WriteString(explain.type or "") + net.WriteString(explain.possible_keys or "") + net.WriteString(explain.key or "") + net.WriteString(explain.key_len or "") + net.WriteString(explain.ref or "") + net.WriteUInt(explain.rows or 0, 32) + net.WriteString(explain.Extra or "") end end - else - net.WriteUInt(0, 32) end - net.Send(ply) - end - - local chunkSize = 20 - local totalProfiles = table.Count(GProfiler.Database.ProfileData) - for startIndex = 1, totalProfiles, chunkSize do - timer.Simple(0.1, function() - SendChunk(ply, GProfiler.Database.ProfileData, GProfiler.Database.Explains, startIndex, chunkSize) - end) + else + net.WriteUInt(0, 32) end + net.Send(ply) end - local providersToWaitFor = 0 - local allProfilingData = {} - - for name, provider in pairs(availableProviders) do - providersToWaitFor = providersToWaitFor + 1 - provider:GetProfilingData(function(data) - providersToWaitFor = providersToWaitFor - 1 - - if data then - for query, profile in pairs(data) do - allProfilingData[query] = profile - end - end - - if providersToWaitFor == 0 then - GProfiler.Database.InternalProfileData = allProfilingData - SendData() - end + local chunkSize = 20 + local totalProfiles = table.Count(GProfiler.Database.ProfileData) + for startIndex = 1, totalProfiles, chunkSize do + timer.Simple(0.1, function() + SendChunk(ply, GProfiler.Database.ProfileData, GProfiler.Database.Explains, startIndex, chunkSize) end) end - - if providersToWaitFor == 0 then - SendData() - end end function GProfiler.Database:GetAllDatabaseObjects() @@ -190,24 +185,12 @@ function GProfiler.Database:GetAvailableProviders() return availableProviders end -util.AddNetworkString("GProfiler_Database_ToggleServerProfile") -util.AddNetworkString("GProfiler_Database_ServerProfileStatus") util.AddNetworkString("GProfiler_Database_SendData") -net.Receive("GProfiler_Database_ToggleServerProfile", function(len, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - if net.ReadBool() then - GProfiler.Database:StartProfiler(ply) - net.Start("GProfiler_Database_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() - else - GProfiler.Database:StopProfiler(ply) - net.Start("GProfiler_Database_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() - end -end) \ No newline at end of file +Profilers.Register("Database", { + Realms = { "Server" }, + ServerOnly = true, + OnStart = StartDetour, + OnStop = StopDetour, + WriteData = SendData +}) \ No newline at end of file diff --git a/lua/gprofiler/profilers/entvars/cl_entvars.lua b/lua/gprofiler/profilers/entvars/cl_entvars.lua index 7c1ec46..fa0d27f 100644 --- a/lua/gprofiler/profilers/entvars/cl_entvars.lua +++ b/lua/gprofiler/profilers/entvars/cl_entvars.lua @@ -1,111 +1,75 @@ 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) + +local EntVars = GProfiler.EntVars +EntVars.ProfileData = EntVars.ProfileData or {} + +GProfiler.Profilers.Register("Entity Variables", { + Realms = {"Client"}, + OnStart = function(realm, ply) + EntVars.ProfileActive = true + EntVars.ProfileData = {} + end, + OnStop = function(realm, ply) + EntVars.ProfileActive = false + local EntVarsStore = GProfiler.Profilers.GetStore("Entity Variables") + if EntVarsStore then + EntVarsStore:SetData(realm, EntVars.ProfileData) end + if EntVars.RefreshUI then EntVars.RefreshUI() end + end +}) +local EntVarsStore = GProfiler.Profilers.GetStore("Entity Variables") + +function GProfiler.EntVars.DoTab(Base, Outer) + local Header = GProfiler.Utils.SetupHeader(Outer, "Entity Variables", "gprofiler/entvars.png") + local StartStop = Header:SetupStartStop(EntVarsStore:IsActive("Client")) + local Timer = Header:SetupTimer(function() + return EntVarsStore:GetTimerData("Client") + end) + + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Header.OnHandleMoved() end - function 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 + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Entity Variables", "Client", Running) + if EntVars.RefreshUI then EntVars.RefreshUI() 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 + local Header = GProfiler.Utils.SetupHeader(Base, "Profiler Results", nil, true) + local ResultsList = GProfiler.Utils.CreateList(Base, Header, {"Entity", "Variable", "Times Changed", "Current Value"}) + ResultsList:SetTall(Base:GetTall() - Header:GetTall()) + ResultsList.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(38, 63, 89, 255)) end - local 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() + local function PopulateResults() + if not IsValid(ResultsList) then return end + + local realmData = EntVarsStore:GetData("Client") or {} + + ResultsList:Clear() + + for k, v in pairs(realmData) do + for var, count in pairs(v) do + if var == "GProfiler_SavedEnt" or var == "GProfiler_CurrentValues" then continue end + local currentVal = v.GProfiler_CurrentValues and v.GProfiler_CurrentValues[var] or "N/A" + ResultsList:AddLine(v.GProfiler_SavedEnt or "N/A", var, count, currentVal) end end end - - ProfilerResults:SortByColumn(3, true) - - GProfiler.StyleDListView(ProfilerResults) + EntVars.RefreshUI = PopulateResults + PopulateResults() 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 +GProfiler.Menu.RegisterTab("Entity Variables", "gprofiler/entvars.png", 6, GProfiler.EntVars.DoTab, function() + local timer = EntVarsStore:GetTimerData("Client") + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) function GProfiler.EntVars.CollectData(ent, var, _, val) @@ -141,4 +105,4 @@ end hook.Add("OnEntityCreated", "GProfiler.EntVars.CaptureEnt", function(ent) timer.Simple(0, function() CaptureEnt(ent) end) end) hook.Add("InitPostEntity", "GProfiler.EntVars.CaptureEnts", function() for k, v in ipairs(ents.GetAll()) do CaptureEnt(v) end -end) +end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/functions/cl_functions.lua b/lua/gprofiler/profilers/functions/cl_functions.lua index fafcf2f..5e6bd06 100644 --- a/lua/gprofiler/profilers/functions/cl_functions.lua +++ b/lua/gprofiler/profilers/functions/cl_functions.lua @@ -1,460 +1,13 @@ 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 +local FunctionsStore = GProfiler.Profilers.GetStore("Functions") -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) -} +function GProfiler.Functions.Tab(Content) -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() + local timer = FunctionsStore:GetTimerData(GProfiler.Functions.Realm) + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) diff --git a/lua/gprofiler/profilers/functions/sh_functions.lua b/lua/gprofiler/profilers/functions/sh_functions.lua index 4cb2892..3a38bb7 100644 --- a/lua/gprofiler/profilers/functions/sh_functions.lua +++ b/lua/gprofiler/profilers/functions/sh_functions.lua @@ -24,7 +24,6 @@ local function garbage() -- need to experiment more, which is more accurate? bra return collectgarbage("count") * 1024 end --- Hacky solution for functions from the same location but have different signatures local function CombineDuplicates() local combined = {} for k, v in pairs(FunctionsProfiler.ProfileData) do @@ -118,13 +117,12 @@ local function onEvent(event) end end -function GProfiler.Functions:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or FunctionsProfiler.IsDetoured then return end +local function StartDetour() + if FunctionsProfiler.IsDetoured then return end GProfiler.Log((SERVER and "Server" or "Client") .. " function profiler started!", 2) FunctionsProfiler.ProfileData = {} FunctionsProfiler.IsDetoured = true - FunctionsProfiler.ProfileStarted = SysTime() recurse = {} startTimes = {} @@ -132,18 +130,17 @@ function GProfiler.Functions:StartProfiler(ply) debug.sethook(onEvent, "cr") end -function GProfiler.Functions:RestoreFunctions(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or not FunctionsProfiler.IsDetoured then return end +local function StopDetour() + if not FunctionsProfiler.IsDetoured then return end - GProfiler.Log((SERVER and "Server" or "Client") .. " function profile stopped, sending data!", 2) + GProfiler.Log((SERVER and "Server" or "Client") .. " function profile stopped!", 2) FunctionsProfiler.IsDetoured = false - FunctionsProfiler.ProfileStarted = nil debug.sethook() CombineDuplicates() +end - if CLIENT then return end - +local function SendData(ply) local Count = table.Count(FunctionsProfiler.ProfileData) if GProfiler.ExpressAvailable() and Count > (GProfiler.Config.ExpressMinimumResults or 25) - 1 and Count > 0 then local Data = {} @@ -210,38 +207,44 @@ function GProfiler.Functions:RestoreFunctions(ply) end end +GProfiler.Profilers.Register("Functions", { + Realms = { "Client", "Server" }, + OnStart = function(realm, ply) + StartDetour() + end, + OnStop = function(realm, ply) + StopDetour() + if CLIENT then + local FuncStore = GProfiler.Profilers.GetStore("Functions") + if FuncStore then + FuncStore:SetData(realm, FunctionsProfiler.ProfileData) + end + end + if SERVER and ply then + SendData(ply) + end + end, + WriteData = function(realm, ply) + SendData(ply) + end +}) + if SERVER then - util.AddNetworkString("GProfiler_Functions_ToggleServerProfile") - util.AddNetworkString("GProfiler_Functions_ServerProfileStatus") util.AddNetworkString("GProfiler_Functions_SendData") + util.AddNetworkString("GProfiler_Functions_SetFocus") - net.Receive("GProfiler_Functions_ToggleServerProfile", function(len, ply) + net.Receive("GProfiler_Functions_SetFocus", function(len, ply) if not GProfiler.Access.HasAccess(ply) then return end - local startStop = net.ReadBool() - if startStop then - local hasFocus = net.ReadBool() - if hasFocus then - local count = net.ReadUInt(5) - FunctionsProfiler.Focus = {} - for i = 1, count do - FunctionsProfiler.Focus[net.ReadString()] = true - end - else - FunctionsProfiler.Focus = false + local hasFocus = net.ReadBool() + if hasFocus then + local count = net.ReadUInt(5) + FunctionsProfiler.Focus = {} + for i = 1, count do + FunctionsProfiler.Focus[net.ReadString()] = true end - - GProfiler.Functions:StartProfiler(ply) - net.Start("GProfiler_Functions_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() else - GProfiler.Functions:RestoreFunctions(ply) - net.Start("GProfiler_Functions_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() + FunctionsProfiler.Focus = false end end) end diff --git a/lua/gprofiler/profilers/hooks/cl_hooks.lua b/lua/gprofiler/profilers/hooks/cl_hooks.lua index 3c125ca..8d4cd62 100644 --- a/lua/gprofiler/profilers/hooks/cl_hooks.lua +++ b/lua/gprofiler/profilers/hooks/cl_hooks.lua @@ -1,341 +1,270 @@ 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 +local Hooks = GProfiler.Hooks +Hooks.Realm = Hooks.Realm or "Client" + +local HooksStore = GProfiler.Profilers.GetStore("Hooks") + +function GProfiler.Hooks.DoTab(Base, Outer) + local Header = GProfiler.Utils.SetupHeader(Outer, "Hooks", "gprofiler/hooks.png") + local initialActive = Hooks.Realm == "Both" and (HooksStore:IsActive("Client") or HooksStore:IsActive("Server")) or HooksStore:IsActive(Hooks.Realm) + local StartStop = Header:SetupStartStop(initialActive) + local RealmSelector = Header:SetupRealmSelector(Hooks.Realm, true) + local Timer = Header:SetupTimer(function() + local realm = Hooks.Realm == "Both" and "Client" or Hooks.Realm + return HooksStore:GetTimerData(realm) + end) - callback(hookTbl) + 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 -function GProfiler.Hooks.DoTab(Content) - local Header = vgui.Create("DPanel", Content) - Header:SetSize(Content:GetWide(), 40) - Header:SetPos(0, 10) - Header.Paint = nil + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Hooks", Hooks.Realm, Running) - 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) + if Hooks.RefreshUI then + Hooks.RefreshUI() 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) + function RealmSelector:OnStateChanged(state) + Hooks.Realm = state + if state == "Both" then + StartStop.State = HooksStore:IsActive("Client") or HooksStore:IsActive("Server") + else + StartStop.State = HooksStore:IsActive(state) + end + StartStop:SetText(StartStop.State and "Stop" or "Start") + if Hooks.RefreshUI then + Hooks.RefreshUI() end end - local 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 + local left, Source = GProfiler.Utils.VSplitPanel(Base, GProfiler.GetScaledSize(10), "hook_lr", 0.65) + local Results, Receivers = GProfiler.Utils.HSplitPanel(left, GProfiler.GetScaledSize(10), "hook_l_bt", 0.65) + local ClientHooks, ServerHooks = GProfiler.Utils.VSplitPanel(Receivers, GProfiler.GetScaledSize(10), "hook_lb_lr", 0.5) + + local SourceHeader = vgui.Create("DLabel", Source) + SourceHeader:SetFont("GProfiler.Inter24") + SourceHeader:SetTextColor(GProfiler.SyntaxColors.comment) + SourceHeader:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(5)) + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + SourceHeader:SetText("Select a hook to view source.") + + Source.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) end + local RichText = vgui.Create("RichText", Source) + RichText:SetText("") + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:SetVerticalScrollbarEnabled(true) + function RichText:PerformLayout() + self:SetFontInternal("GProfiler.Code") + end - 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() + Source.OnHandleMoved = function() + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:InvalidateLayout() + end + + local Header = GProfiler.Utils.SetupHeader(Results, "Profiler Results", nil, true) + local ResultsList = GProfiler.Utils.CreateList(Results, Header, {"Hook", "Count", "Time (Total)", "Time (Avg)", "Time (Longest)"}) + + local function PopulateResults() + if not IsValid(ResultsList) then return end + + local displayRealm = Hooks.Realm == "Both" and "Client" or Hooks.Realm + local realmData = HooksStore:GetData(displayRealm) or {} + + ResultsList:Clear() + + for k, v in pairs(realmData) do + local row = ResultsList:AddLine(string.format("%s (%s)", v.r, v.h), v.c, v.t, v.c > 0 and v.t / v.c or 0, v.t) + row.HookData = v + end + end + GProfiler.Hooks.RefreshUI = PopulateResults + PopulateResults() + + function ResultsList:OnRowSelected(rowIndex, row) + local data = row.HookData + if data then + RichText:SetText("Loading source...") + SourceHeader:SetText("") + + local file = data.Source or data[4] + local lineDefined = data.Lines and data.Lines[1] or data[5] or 0 + local lastLineDefined = data.Lines and data.Lines[2] or data[6] or 0 + + if file and file ~= "" then + file = string.match(file, "@?(.+)") + if not file then file = data.Source or data[4] end + + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) + + GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), lineDefined) + else + RichText:SetText("Failed to load source") + end + end) else - GProfiler.Hooks:StartProfiler() - GProfiler.Hooks.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 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 + local function CreateHookList(Parent, Receivers, Title) + Parent:Clear() - Line.OnSelect = function() - if LastSelected == v.h..v.r then return end - LastSelected = v.h..v.r + 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 - 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) + local RefreshButton = vgui.Create("DButton", HeaderPanel) + RefreshButton:SetSize(GProfiler.GetScaledSize(80), GProfiler.GetScaledSize(30)) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + RefreshButton:SetText("Refresh") + RefreshButton:SetFont("GProfiler.Inter24") + RefreshButton:SetTextColor(Color(0,0,0,0)) + RefreshButton.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + if s:IsHovered() then + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + end + draw.SimpleText("Refresh", "GProfiler.Inter24", w / 2, h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + RefreshButton.DoClick = function() + net.Start("GProfiler_Hooks_HookTbl") + net.SendToServer() end - end - HookProfiler:SortByColumn(3, true) + local List = vgui.Create("DPanelList", Parent) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + List:EnableVerticalScrollbar() - 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 + 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 - UpdateLists() - 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 - 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) + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + draw.SimpleText(Receiver.Name, "GProfiler.Inter24", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(Receiver.Hook, "GProfiler.Inter24", w - GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.comment, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end + + List:AddItem(Item) end - 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) + 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 - HookProfiler:DataLayout() - HookProfiler:InvalidateLayout() end - timer.Simple(0, function() - if GProfiler.Hooks.ListSearchText then - ListSearch:SetText(GProfiler.Hooks.ListSearchText) - ListSearch:OnTextChanged() + local CLHooks = {} + local SVHooks = {} + + for hookName, hookReceivers in pairs(hook.GetTable()) do + for receiverName, _ in pairs(hookReceivers) do + table.insert(CLHooks, { + Name = tostring(receiverName or ""), + Hook = hookName + }) end + end + + local clr = CreateHookList(ClientHooks, CLHooks, string.format("Client Hooks (%d)", #CLHooks)) + + net.Receive("GProfiler_Hooks_HookTbl", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end - if GProfiler.Hooks.ResultsFilterText then - ResultsFilter:SetText(GProfiler.Hooks.ResultsFilterText) - ResultsFilter:OnTextChanged() + SVHooks = {} + for i = 1, net.ReadUInt(15) do + local hookName = net.ReadString() + for j = 1, net.ReadUInt(10) do + local receiverName = net.ReadString() + table.insert(SVHooks, { + Name = tostring(receiverName or ""), + Hook = hookName + }) + end end - 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) + local svr = CreateHookList(ServerHooks, SVHooks, string.format("Server Hooks (%d)", #SVHooks)) + end) -net.Receive("GProfiler_Hooks_ServerProfileStatus", function() - local status = net.ReadBool() - local ply = net.ReadEntity() - GProfiler.Hooks.ProfileActive = status + net.Start("GProfiler_Hooks_HookTbl") + net.SendToServer() - if ply == LocalPlayer() and not status then - GProfiler.Menu.OpenTab("Hooks", GProfiler.Hooks.DoTab) + Results.OnHandleMoved = function() + ResultsList:SetSize(Results:GetWide(), Results:GetTall() - Header:GetTall()) + ResultsList:SetPos(0, Header:GetTall()) + Header:SetWide(Results:GetWide()) end +end + +GProfiler.Menu.RegisterTab("Hooks", "gprofiler/hooks.png", 1, Hooks.DoTab, function() + local timer = HooksStore:GetTimerData(Hooks.Realm) + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) net.Receive("GProfiler_Hooks_SendData", function() - local data = {} + local Data = {} for i = 1, net.ReadUInt(20) do - local hookName = net.ReadString() - data[hookName] = { + table.insert(Data, { + r = net.ReadString(), 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) + HooksStore:SetData("Server", Data) + if Hooks.RefreshUI then Hooks.RefreshUI() end end) \ No newline at end of file diff --git a/lua/gprofiler/profilers/hooks/sh_hooks.lua b/lua/gprofiler/profilers/hooks/sh_hooks.lua index 14e97fb..5790044 100644 --- a/lua/gprofiler/profilers/hooks/sh_hooks.lua +++ b/lua/gprofiler/profilers/hooks/sh_hooks.lua @@ -3,19 +3,17 @@ GProfiler.Hooks = GProfiler.Hooks or {} local HooksProfiler = GProfiler.Hooks HooksProfiler.IsDetoured = HooksProfiler.IsDetoured or false HooksProfiler.ProfileData = HooksProfiler.ProfileData or {} -HooksProfiler.RestoreHookTable = HooksProfiler.RestoreHookTable or {} local SysTime = SysTime local unpack = unpack local debug = debug -function GProfiler.Hooks:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or HooksProfiler.IsDetoured then return end +local function StartDetour() + if HooksProfiler.IsDetoured then return end GProfiler.Log((SERVER and "Server" or "Client") .. " hook profiler started!", 2) HooksProfiler.ProfileData = {} HooksProfiler.IsDetoured = true - HooksProfiler.ProfileStarted = SysTime() HooksProfiler.AddHook = HooksProfiler.AddHook or hook.Add local function profileHook(hookName, receiverName, receiverFunc, ...) @@ -58,23 +56,20 @@ function GProfiler.Hooks:StartProfiler(ply) end end - hook.Add = function(hookName, receiverName, receiverFunc, ...) - profileHook(hookName, receiverName, receiverFunc, ...) - end + hook.Add = profileHook end -function GProfiler.Hooks:RestoreHooks(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or not HooksProfiler.IsDetoured then return end +local function StopDetour() + if not HooksProfiler.IsDetoured then return end - GProfiler.Log((SERVER and "Server" or "Client") .. " hook profile stopped, sending data!", 2) + GProfiler.Log((SERVER and "Server" or "Client") .. " hook profile stopped!", 2) HooksProfiler.IsDetoured = false - HooksProfiler.ProfileStarted = nil hook.Add = HooksProfiler.AddHook for hookName, hookReceivers in pairs(hook.GetTable()) do for receiverName, receiverFunc in pairs(hookReceivers) do - if type(receiverName) ~= "string" or type(receiverFunc) ~= "function" then continue end + if not isstring(receiverName) or not isfunction(receiverFunc) then continue end local data = HooksProfiler.ProfileData[string.format("%s_%s", hookName, receiverName)] if data then hook.Add(hookName, receiverName, data.f, unpack(data.extra or {})) @@ -82,16 +77,16 @@ function GProfiler.Hooks:RestoreHooks(ply) end end - if CLIENT then return end - for k, v in pairs(HooksProfiler.ProfileData) do if v.c == 0 then HooksProfiler.ProfileData[k] = nil end end +end +local function SendData(ply) local Count = table.Count(HooksProfiler.ProfileData) - if GProfiler.ExpressAvailable() and Count > GProfiler.Config.ExpressMinimumResults-1 and Count > 0 then + if GProfiler.ExpressAvailable() and Count > GProfiler.Config.ExpressMinimumResults - 1 and Count > 0 then local Data = {} for k, v in pairs(HooksProfiler.ProfileData) do Data[k] = v @@ -118,31 +113,33 @@ function GProfiler.Hooks:RestoreHooks(ply) end end +GProfiler.Profilers.Register("Hooks", { + Realms = { "Client", "Server" }, + OnStart = function(realm, ply) + StartDetour() + end, + OnStop = function(realm, ply) + StopDetour() + if CLIENT then + local HookStore = GProfiler.Profilers.GetStore("Hooks") + if HookStore then + HookStore:SetData(realm, HooksProfiler.ProfileData) + end + end + if SERVER and ply then + SendData(ply) + end + end, + WriteData = function(realm, ply) + SendData(ply) + end +}) + if SERVER then - util.AddNetworkString("GProfiler_Hooks_ToggleServerProfile") - util.AddNetworkString("GProfiler_Hooks_ServerProfileStatus") util.AddNetworkString("GProfiler_Hooks_SendData") util.AddNetworkString("GProfiler_Hooks_HookTbl") util.AddNetworkString("GProfiler_Hooks_RemoveHook") - net.Receive("GProfiler_Hooks_ToggleServerProfile", function(len, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - if net.ReadBool() then - GProfiler.Hooks:StartProfiler(ply) - net.Start("GProfiler_Hooks_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() - else - GProfiler.Hooks:RestoreHooks(ply) - net.Start("GProfiler_Hooks_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() - end - end) - net.Receive("GProfiler_Hooks_HookTbl", function(len, ply) if not GProfiler.Access.HasAccess(ply) then return end @@ -152,6 +149,9 @@ if SERVER then for hookName, hookReceivers in pairs(hooks) do net.WriteString(hookName) net.WriteUInt(table.Count(hookReceivers), 10) + for receiverName, _ in pairs(hookReceivers) do + net.WriteString(tostring(receiverName or "")) + end end net.Send(ply) end) diff --git a/lua/gprofiler/profilers/net/cl_net.lua b/lua/gprofiler/profilers/net/cl_net.lua index 3d2b1ae..3ce09b3 100644 --- a/lua/gprofiler/profilers/net/cl_net.lua +++ b/lua/gprofiler/profilers/net/cl_net.lua @@ -1,301 +1,707 @@ 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" - end +local Net = GProfiler.Net + +Net.Realm = Net.Realm or "Client" + +local NetStore = GProfiler.Profilers.GetStore("Networking") + +local function FormatBits(bits) + if not bits or bits < 0.01 then return "0 Bytes" end + if not isnumber(bits) then return "oh fiddlesticks, what now?" end + + 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 -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) - } - 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 - } +function GProfiler.Net.DoTab(Base, Outer) + local Header = GProfiler.Utils.SetupHeader(Outer, "Networking", "gprofiler/network.png") + local initialActive = Net.Realm == "Both" and (NetStore:IsActive("Client") or NetStore:IsActive("Server")) or NetStore:IsActive(Net.Realm) + local StartStop = Header:SetupStartStop(initialActive) + local RealmSelector = Header:SetupRealmSelector(Net.Realm, true) + local Timer = Header:SetupTimer(function() + local realm = Net.Realm == "Both" and "Client" or Net.Realm + return NetStore:GetTimerData(realm) + end) + + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Header.OnHandleMoved() + end + + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Networking", Net.Realm, Running) + + if Net.RefreshUI then + Net.RefreshUI() 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 + if state == "Both" then + StartStop.State = NetStore:IsActive("Client") or NetStore:IsActive("Server") + else + StartStop.State = NetStore:IsActive(state) + end + StartStop:SetText(StartStop.State and "Stop" or "Start") + if Net.RefreshUI then + Net.RefreshUI() + end + 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) + + 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:SetText("") + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:SetVerticalScrollbarEnabled(true) + function RichText:PerformLayout() + self:SetFontInternal("GProfiler.Code") + end + + Source.OnHandleMoved = function() + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:InvalidateLayout() + end + + local 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 SentHeader = GProfiler.Utils.SetupHeader(ResultsSent, "Messages Sent", nil, true) + local ReceivedHeader = GProfiler.Utils.SetupHeader(ResultsReceived, "Messages Received", nil, true) + + local ResultsList = GProfiler.Utils.CreateList(ResultsSent, SentHeader, {"Name", "Count", "Size (Largest)", "Size (Total)", "Time (Total)", "Time (Avg)", "Time (Longest)"}) + local ReceivedList = GProfiler.Utils.CreateList(ResultsReceived, ReceivedHeader, {"Name", "Count", "Size (Largest)", "Size (Total)", "Time (Total)", "Time (Avg)", "Time (Longest)"}) + + Results.OnHandleMoved = function() + ResultsList:SetSize(Results:GetWide(), Results:GetTall() - SentHeader:GetTall()) + 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(math.max(h + GProfiler.GetScaledSize(10), BreakdownPanel:GetTall())) + 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) + Canvas.OnMousePressed = function(s, code) + if code == MOUSE_LEFT then + s.MousePressed = true + end end + + Canvas:InvalidateLayout() 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) + GProfiler.Net.UpdateBreakdownUI = function(name) + local data = GProfiler.Net.Breakdowns[name] + if data then + PopulateBreakdown(name, data.Nodes, data.Size) end 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 + function ResultsList:OnRowSelected(rowIndex, row) + ReceivedList:ClearSelection() + local name = row:GetColumnText(1) + local displayRealm = Net.Realm == "Both" and "Client" or Net.Realm + local realmData = NetStore:GetData(displayRealm) or {} + local data = realmData.Out and realmData.Out[name] + + if data then + BreakdownPanel:Clear() + + 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 + 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 + + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) + + GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), lineDefined) + else + RichText:SetText("Failed to load source") + end + end) else - 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) - net.SendToServer() + + if displayRealm == "Client" then + local breakdownData = GProfiler.Net.Breakdowns and GProfiler.Net.Breakdowns[name] + if breakdownData then + PopulateBreakdown(name, breakdownData.Children or {}, data.Size or data[7] or 0) + end else - GProfiler.Net:StartProfiler() - GProfiler.Net.ProfileActive = true - StartButton:SetText(GProfiler.Language.GetPhrase("profiler_stop")) + 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() 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() - end + function ReceivedList:OnRowSelected(rowIndex, row) + ResultsList:ClearSelection() + local name = row:GetColumnText(1) + local displayRealm = Net.Realm == "Both" and "Client" or Net.Realm + local realmData = NetStore:GetData(displayRealm) or {} + local data = realmData.Inc and realmData.Inc[name] - Line.OnSelect = function() - if not v[4] or LastSelected == v then return end - LastSelected = v + if data then + BreakdownPanel:Clear() - 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) + 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 + + if file and file ~= "" then + file = string.match(file, "@?(.+)") + if not file then file = data.Source or data[4] end + + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) + + GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), lineDefined) + else + RichText:SetText("Failed to load source") + end + end) + else + RichText:SetText("Failed to load source (2)") + end end end - ProfilerResults:SortByColumn(2, true) + local function PopulateResults() + if not IsValid(ResultsList) or not IsValid(ReceivedList) then return end + + local displayRealm = Net.Realm == "Both" and "Client" or Net.Realm + local realmData = NetStore:GetData(displayRealm) or {} - local function UpdateLists() - GProfiler.StyleDListView(ProfilerResults) - GProfiler.StyleDListView(ReceiversList) + ResultsList:Clear() + if realmData.Out then + for name, data in pairs(realmData.Out) do + 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 realmData.Inc then + for name, data in pairs(realmData.Inc) do + 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 - 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() + GProfiler.Net.RefreshUI = PopulateResults + PopulateResults() + + 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() + local timer = NetStore:GetTimerData(Net.Realm) + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) net.Receive("GProfiler_Net_SendData", function() - 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) + + local serverData = NetStore:GetData("Server") or {} + if not serverData.Inc then serverData.Inc = {} end + if not serverData.Out then serverData.Out = {} end + + local target = isIncoming and serverData.Inc or serverData.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 + + NetStore:SetData("Server", serverData) + + 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..3e084a4 100644 --- a/lua/gprofiler/profilers/net/sh_net.lua +++ b/lua/gprofiler/profilers/net/sh_net.lua @@ -1,18 +1,179 @@ 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 -function GProfiler.Net:StartProfiler(ply) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or GProfiler.Net.IsDetoured then return end +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 = {} }, + StartTime = SysTime() + } + 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) + + 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 + 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 + +local function StartDetour() + if 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() GProfiler.Net.OriginalIncoming = GProfiler.Net.OriginalIncoming or net.Incoming @@ -21,19 +182,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,78 +210,98 @@ 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) - if not GProfiler.Access.HasAccess(ply or LocalPlayer()) or not GProfiler.Net.IsDetoured then return end +local function StopDetour() + if not GProfiler.Net.IsDetoured then return end - GProfiler.Log((SERVER and "Server" or "Client") .. " net profile stopped, sending data!", 2) + GProfiler.Log((SERVER and "Server" or "Client") .. " net profile stopped!", 2) GProfiler.Net.IsDetoured = false - GProfiler.Net.ProfileStarted = nil net.Incoming = GProfiler.Net.OriginalIncoming + RestoreOutgoing() - 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 + if GProfiler.Net.RefreshUI then + GProfiler.Net.RefreshUI() + end +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]) +local function SendNetData(ply) + local function SendData(dataMap, isIncoming) + local count = table.Count(dataMap) + if GProfiler.ExpressAvailable() and count > GProfiler.Config.ExpressMinimumResults - 1 and count > 0 then + 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", { 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 +GProfiler.Profilers.Register("Networking", { + Realms = { "Client", "Server" }, + OnStart = function(realm, ply) + StartDetour() + end, + OnStop = function(realm, ply) + StopDetour() + if CLIENT then + local NetStore = GProfiler.Profilers.GetStore("Networking") + if NetStore then + NetStore:SetData(realm, { + Inc = GProfiler.Net.ProfileData.Inc, + Out = GProfiler.Net.ProfileData.Out + }) + end + end + if SERVER and ply then + SendNetData(ply) + end + end, + WriteData = function(realm, ply) + SendNetData(ply) + end +}) + if SERVER then - util.AddNetworkString("GProfiler_Net_ToggleServerProfile") - util.AddNetworkString("GProfiler_Net_ServerProfileStatus") util.AddNetworkString("GProfiler_Net_SendData") + util.AddNetworkString("GProfiler_Net_RequestBreakdown") + util.AddNetworkString("GProfiler_Net_SendBreakdown") util.AddNetworkString("GProfiler_Net_ReceiverTbl") - net.Receive("GProfiler_Net_ToggleServerProfile", function(len, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - if net.ReadBool() then - GProfiler.Net:StartProfiler(ply) - net.Start("GProfiler_Net_ServerProfileStatus") - net.WriteBool(true) - net.WriteEntity(ply) - net.Broadcast() - else - GProfiler.Net:RestoreNet(ply) - net.Start("GProfiler_Net_ServerProfileStatus") - net.WriteBool(false) - net.WriteEntity(ply) - net.Broadcast() - end - end) - net.Receive("GProfiler_Net_ReceiverTbl", function(len, ply) if not GProfiler.Access.HasAccess(ply) then return end @@ -128,11 +310,42 @@ 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) + + 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) end diff --git a/lua/gprofiler/profilers/netvars/cl_netvars.lua b/lua/gprofiler/profilers/netvars/cl_netvars.lua index 5691bd7..b9f1eb2 100644 --- a/lua/gprofiler/profilers/netvars/cl_netvars.lua +++ b/lua/gprofiler/profilers/netvars/cl_netvars.lua @@ -1,149 +1,12 @@ 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 +GProfiler.Profilers.Register("Network Variables", {}) +local NetVarsStore = GProfiler.Profilers.GetStore("Network Variables") 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..40a9949 100644 --- a/lua/gprofiler/profilers/timers/cl_timers.lua +++ b/lua/gprofiler/profilers/timers/cl_timers.lua @@ -1,215 +1,269 @@ 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) +local Timers = GProfiler.Timers + +Timers.Realm = Timers.Realm or "Client" + +local TimersStore = GProfiler.Profilers.GetStore("Timers") + +function GProfiler.Timers.DoTab(Base, Outer) + local Header = GProfiler.Utils.SetupHeader(Outer, "Timers", "gprofiler/timers.png") + local initialActive = Timers.Realm == "Both" and (TimersStore:IsActive("Client") or TimersStore:IsActive("Server")) or TimersStore:IsActive(Timers.Realm) + local StartStop = Header:SetupStartStop(initialActive) + local RealmSelector = Header:SetupRealmSelector(Timers.Realm, true) + local Timer = Header:SetupTimer(function() + local realm = Timers.Realm == "Both" and "Client" or Timers.Realm + return TimersStore:GetTimerData(realm) end) - 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) + + Base:SetPos(GProfiler.GetScaledSize(10), Header:GetTall() + GProfiler.GetScaledSize(12)) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Base.OnHandleMoved = function() + Header:SetWide(Outer:GetWide()) + Base:SetSize(Outer:GetWide() - GProfiler.GetScaledSize(20), Outer:GetTall() - Header:GetTall() - GProfiler.GetScaledSize(22)) + Header.OnHandleMoved() + end + + function StartStop:OnStateChanged(Running) + GProfiler.Profilers.Toggle("Timers", Timers.Realm, Running) + + if Timers.RefreshUI then + Timers.RefreshUI() end end - 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) + function RealmSelector:OnStateChanged(state) + Timers.Realm = state + if state == "Both" then + StartStop.State = TimersStore:IsActive("Client") or TimersStore:IsActive("Server") + else + StartStop.State = TimersStore:IsActive(state) + end + StartStop:SetText(StartStop.State and "Stop" or "Start") + if Timers.RefreshUI then + Timers.RefreshUI() end end - 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 + local left, Source = GProfiler.Utils.VSplitPanel(Base, GProfiler.GetScaledSize(10), "timers_lr", 0.65) + local Results, ActivePanel = GProfiler.Utils.HSplitPanel(left, GProfiler.GetScaledSize(10), "timers_l_bt", 0.65) + local ClientTimers, ServerTimers = GProfiler.Utils.VSplitPanel(ActivePanel, GProfiler.GetScaledSize(10), "timers_lb_lr", 0.5) - 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() + local SourceHeader = vgui.Create("DLabel", Source) + SourceHeader:SetFont("GProfiler.Inter24") + SourceHeader:SetTextColor(GProfiler.SyntaxColors.comment) + SourceHeader:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(5)) + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + SourceHeader:SetText("Select a timer to view source.") + + Source.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, GProfiler.SyntaxColors.background, GProfiler.RNDX.NO_BR + GProfiler.RNDX.NO_BL) + end + + local RichText = vgui.Create("RichText", Source) + RichText:SetText("") + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:SetVerticalScrollbarEnabled(true) + function RichText:PerformLayout() + self:SetFontInternal("GProfiler.Code") + end + + Source.OnHandleMoved = function() + SourceHeader:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), GProfiler.GetScaledSize(24)) + RichText:SetSize(Source:GetWide() - GProfiler.GetScaledSize(20), Source:GetTall() - GProfiler.GetScaledSize(20) - GProfiler.GetScaledSize(30)) + RichText:SetPos(GProfiler.GetScaledSize(10), GProfiler.GetScaledSize(30)) + RichText:InvalidateLayout() + end + + local Header = GProfiler.Utils.SetupHeader(Results, "Profiler Results", nil, true) + local ResultsList = GProfiler.Utils.CreateList(Results, Header, {"Name", "Type", "Count", "Delay", "Total Time", "Avg Time", "Longest Time"}) + + local function PopulateResults() + if not IsValid(ResultsList) then return end + + local displayRealm = Timers.Realm == "Both" and "Client" or Timers.Realm + local realmData = TimersStore:GetData(displayRealm) or {} + + ResultsList:Clear() + + local merged = table.Merge(realmData.Simple or {}, realmData.Create or {}) + for k, v in pairs(merged) do + local line = ResultsList:AddLine(tostring(k), v.Type, v.Count, v.Delay, v.TotalTime, v.AverageTime, v.LongestTime) + line.TimerData = v + end + end + GProfiler.Timers.RefreshUI = PopulateResults + PopulateResults() + + function ResultsList:OnRowSelected(rowIndex, row) + local data = row.TimerData + if data then + RichText:SetText("Loading source...") + SourceHeader:SetText("") + + local file = data.Source + local lineDefined = data.Lines and data.Lines[1] or 0 + local lastLineDefined = data.Lines and data.Lines[2] or 0 + + if file and file ~= "" then + file = string.match(file, "@?(.+)") + if not file then file = data.Source end + + SourceHeader:SetText(string.format("%s (%d - %d)", file, lineDefined, lastLineDefined)) + + GProfiler.RequestFunctionSource(file, lineDefined, lastLineDefined, function(src) + if src then + GProfiler.SyntaxHighlight(RichText, table.concat(src, ""), lineDefined) + else + RichText:SetText("Failed to load source") + end + end) else - GProfiler.Timers:StartProfiler() - GProfiler.Timers.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, 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() + local function CreateTimerList(Parent, ActiveList, Title) + Parent:Clear() + + local HeaderPanel = vgui.Create("DPanel", Parent) + HeaderPanel:SetSize(Parent:GetWide(), GProfiler.GetScaledSize(50)) + HeaderPanel.Paint = function(s, w, h) + GProfiler.RNDX.Draw(8, 0, 0, w, h, Color(34, 77, 122), GProfiler.RNDX.NO_BL + GProfiler.RNDX.NO_BR) + draw.SimpleText(Title, "GProfiler.Inter28", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local RefreshButton = vgui.Create("DButton", HeaderPanel) + RefreshButton:SetSize(GProfiler.GetScaledSize(80), GProfiler.GetScaledSize(30)) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + RefreshButton:SetText("Refresh") + RefreshButton:SetFont("GProfiler.Inter24") + RefreshButton:SetTextColor(Color(0,0,0,0)) + RefreshButton.Paint = function(s, w, h) + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) + if s:IsHovered() then + GProfiler.RNDX.Draw(4, 0, 0, w, h, Color(255, 255, 255, 20)) end + draw.SimpleText("Refresh", "GProfiler.Inter24", w / 2, h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + RefreshButton.DoClick = function() + net.Start("GProfiler_Timers_ActiveList") + net.SendToServer() + end + + local List = vgui.Create("DPanelList", Parent) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + List:EnableVerticalScrollbar() + + local ScrollBar = List.VBar + ScrollBar:SetWide(GProfiler.GetScaledSize(12)) + ScrollBar:SetHideButtons(true) + ScrollBar.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + end + ScrollBar.btnGrip.Paint = function(s, w, h) + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + for k, v in ipairs(ActiveList) do + local i = k + local Item = vgui.Create("DButton", List) + Item:SetSize(List:GetWide(), GProfiler.GetScaledSize(30)) + Item:SetText(v.Name) + Item:SetFont("GProfiler.Inter24") + Item:SizeToContentsY() + Item:SetTall(Item:GetTall() + GProfiler.GetScaledSize(10)) + Item:SetContentAlignment(1) + Item:SetTextColor(Color(0,0,0,0)) + Item.Paint = function(s, w, h) + if i % 2 == 0 then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 10)) + else + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 2)) + end - for k, v in pairs(ProfilerResults.Lines) do - v:SetSelected(false) + if s:IsHovered() then + GProfiler.RNDX.Draw(0, 0, 0, w, h, Color(255, 255, 255, 20)) + end + + draw.SimpleText(v.Name, "GProfiler.Inter24", GProfiler.GetScaledSize(10), h / 2, GProfiler.SyntaxColors.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end - 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) + List:AddItem(Item) 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) + Parent.OnHandleMoved = function() + HeaderPanel:SetSize(Parent:GetWide(), HeaderPanel:GetTall()) + RefreshButton:SetPos(HeaderPanel:GetWide() - RefreshButton:GetWide() - GProfiler.GetScaledSize(10), (HeaderPanel:GetTall() - RefreshButton:GetTall()) / 2) + List:SetSize(Parent:GetWide(), Parent:GetTall() - HeaderPanel:GetTall()) + List:SetPos(0, HeaderPanel:GetTall()) + for k, v in pairs(List:GetItems()) do + v:SetSize(List:GetWide(), v:GetTall()) + end + end end - 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 + + local CLTimers = {} + local SVTimers = {} + + for _, v in ipairs(GProfiler.Timers.ActiveTimers.Create) do + table.insert(CLTimers, { Name = v.Name }) end -end) -net.Receive("GProfiler_Timers_ServerProfileStatus", function() - local status = net.ReadBool() - local ply = net.ReadEntity() - GProfiler.Timers.ProfileActive = status + CreateTimerList(ClientTimers, CLTimers, string.format("Client Timers (%d)", #CLTimers)) + + net.Receive("GProfiler_Timers_ActiveList", function() + SVTimers = {} + for i = 1, net.ReadUInt(16) do + table.insert(SVTimers, { Name = net.ReadString() }) + end + + CreateTimerList(ServerTimers, SVTimers, string.format("Server Timers (%d)", #SVTimers)) + end) + + net.Start("GProfiler_Timers_ActiveList") + net.SendToServer() - if ply == LocalPlayer() and not GProfiler.Timers.ProfileActive then - GProfiler.Menu.OpenTab("Timers", GProfiler.Timers.DoTab) + Results.OnHandleMoved = function() + ResultsList:SetSize(Results:GetWide(), Results:GetTall() - Header:GetTall()) + ResultsList:SetPos(0, Header:GetTall()) + Header:SetWide(Results:GetWide()) end +end + +GProfiler.Menu.RegisterTab("Timers", "gprofiler/timers.png", 5, GProfiler.Timers.DoTab, function() + local timer = TimersStore:GetTimerData(Timers.Realm) + if timer.StartTime == 0 then return end + return GProfiler.TimeRunning(timer.StartTime, timer.EndTime, timer.ProfileActive), timer.ProfileActive end) -net.Receive("GProfiler_Timers_SendData", function(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() +net.Receive("GProfiler_Timers_SendData", function() + local isFirst = net.ReadBool() + local isLast = net.ReadBool() + local count = net.ReadUInt(32) + + local serverData = isFirst and { Simple = {}, Create = {} } or (TimersStore:GetData("Server") or { Simple = {}, Create = {} }) + + for i = 1, count do + local timerType = net.ReadString() local name = net.ReadString() - GProfiler.Timers[type][name] = { + serverData[timerType][name] = { + Type = timerType, Count = net.ReadUInt(15), Delay = net.ReadFloat(), TotalTime = net.ReadFloat(), LongestTime = net.ReadFloat(), AverageTime = net.ReadFloat(), Source = net.ReadString(), - Lines = {net.ReadUInt(14), net.ReadUInt(14)}, - Type = type + Lines = {net.ReadUInt(14), net.ReadUInt(14)} } end - if lastChunk then - GProfiler.Menu.OpenTab("Timers", GProfiler.Timers.DoTab) + + TimersStore:SetData("Server", serverData) + + if isLast and Timers.RefreshUI then + Timers.RefreshUI() end -end) \ No newline at end of file +end) diff --git a/lua/gprofiler/profilers/timers/sh_timers.lua b/lua/gprofiler/profilers/timers/sh_timers.lua index 0e898e5..30cf8c6 100644 --- a/lua/gprofiler/profilers/timers/sh_timers.lua +++ b/lua/gprofiler/profilers/timers/sh_timers.lua @@ -1,40 +1,39 @@ --- 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.ActiveTimers = GProfiler.Timers.ActiveTimers or { Simple = {}, Create = {} } GProfiler.Timers.OldSimpleTimer = GProfiler.Timers.OldSimpleTimer or timer.Simple GProfiler.Timers.OldCreateTimer = GProfiler.Timers.OldCreateTimer or timer.Create -GProfiler.ActiveTimers = GProfiler.ActiveTimers or { Simple = {}, Create = {} } + +local Timers = GProfiler.Timers +local ActiveTimers = Timers.ActiveTimers + +GProfiler.Profilers.Register("Timers", {}) local chunkSizeLimit = 65535 -function GProfiler.Timers:StartProfiler(ply) +function Timers:StartProfiler(ply) if not GProfiler.Access.HasAccess(ply or LocalPlayer()) then return end - if GProfiler.Timers.IsDetoured then return end + if Timers.IsDetoured then return end GProfiler.Log((SERVER and "Server" or "Client") .. " timer profiler started!", 2) - GProfiler.Timers.IsDetoured = true - GProfiler.Timers.ProfileStarted = SysTime() + Timers.IsDetoured = true - GProfiler.Timers.Simple = {} - GProfiler.Timers.Create = {} + Timers.Simple = {} + Timers.Create = {} end -function GProfiler.Timers:Stop(ply) +function Timers:Stop(ply) if not GProfiler.Access.HasAccess(ply or LocalPlayer()) then return end - if not GProfiler.Timers.IsDetoured then return end + if not 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 + Timers.IsDetoured = false +end +local function SendData(ply) if SERVER then - local ProfileData = table.Merge(GProfiler.Timers.Simple, GProfiler.Timers.Create) + local ProfileData = table.Merge(Timers.Simple, Timers.Create) local chunkCount = 1 local currentChunkSize = 0 local chunks = {} @@ -81,12 +80,12 @@ function GProfiler.Timers:Stop(ply) end end -function GProfiler.Timers.CollectTimerData(type, name, delay, func, funcTime) - if not GProfiler.Timers.IsDetoured then return end +function Timers.CollectTimerData(type, name, delay, func, funcTime) + if not Timers.IsDetoured then return end - if not GProfiler.Timers[type][name] then + if not Timers[type][name] then local dbgInfo = debug.getinfo(func, "S") - GProfiler.Timers[type][name] = { + Timers[type][name] = { Count = 0, TotalTime = 0, LongestTime = 0, @@ -99,7 +98,7 @@ function GProfiler.Timers.CollectTimerData(type, name, delay, func, funcTime) } end - local tbl = GProfiler.Timers[type][name] + local tbl = Timers[type][name] tbl.Count = tbl.Count + 1 tbl.TotalTime = tbl.TotalTime + funcTime tbl.AverageTime = tbl.TotalTime / tbl.Count @@ -114,14 +113,17 @@ 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 Index = delay != 0 and table.insert(ActiveTimers.Simple, {NextRun = SysTime() + delay, Source = debug.getinfo(2)}) + + local dbgInfo = debug.getinfo(func, "S") + local sourceKey = string.format("%s:%d", dbgInfo.short_src, dbgInfo.linedefined) local args = {...} - GProfiler.Timers.OldSimpleTimer(delay, function() + 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 + Timers.CollectTimerData("Simple", sourceKey, delay, func, SysTime() - start) + if Index then table.remove(ActiveTimers.Simple, Index) end end) end @@ -135,23 +137,23 @@ timer.Create = function(name, delay, reps, func) name = tostring(name) - for k, v in ipairs(GProfiler.ActiveTimers.Create) do + for k, v in ipairs(ActiveTimers.Create) do if v.Name == name then - table.remove(GProfiler.ActiveTimers.Create, k) + table.remove(ActiveTimers.Create, k) break end end - table.insert(GProfiler.ActiveTimers.Create, { Name = name, Reps = reps, Source = debug.getinfo(2) }) + table.insert(ActiveTimers.Create, { Name = name, Reps = reps, Source = debug.getinfo(2) }) - GProfiler.Timers.OldCreateTimer(name, delay, reps, function() + Timers.OldCreateTimer(name, delay, reps, function() local start = SysTime() func() local endtime = SysTime() - start - GProfiler.Timers.CollectTimerData("Create", name, delay, func, endtime) + Timers.CollectTimerData("Create", name, delay, func, endtime) if timer.RepsLeft(name) == 0 then - for i, data in ipairs(GProfiler.ActiveTimers.Create) do + for i, data in ipairs(ActiveTimers.Create) do if data.Name == name then - table.remove(GProfiler.ActiveTimers.Create, i) + table.remove(ActiveTimers.Create, i) break end end @@ -160,41 +162,95 @@ timer.Create = function(name, delay, reps, func) 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] + for i = #ActiveTimers.Create, 1, -1 do + local data = ActiveTimers.Create[i] if not timer.Exists(data.Name) then - table.remove(GProfiler.ActiveTimers.Create, i) + table.remove(ActiveTimers.Create, i) end end - for i = #GProfiler.ActiveTimers.Simple, 1, -1 do - local data = GProfiler.ActiveTimers.Simple[i] + for i = #ActiveTimers.Simple, 1, -1 do + local data = ActiveTimers.Simple[i] if SysTime() >= data.NextRun then - table.remove(GProfiler.ActiveTimers.Simple, i) + table.remove(ActiveTimers.Simple, i) end end end) +GProfiler.Profilers.Register("Timers", { + Realms = { "Client", "Server" }, + OnStart = function(realm, ply) + Timers:StartProfiler(ply) + end, + OnStop = function(realm, ply) + Timers:Stop(ply) + if CLIENT then + local Store = GProfiler.Profilers.GetStore("Timers") + if Store then + Store:SetData(realm, { + Simple = Timers.Simple, + Create = Timers.Create + }) + end + end + if SERVER and ply then + SendData(ply) + end + end, + WriteData = function(realm, ply) + SendData(ply) + end +}) + + if SERVER then util.AddNetworkString("GProfiler_Timers_ToggleServerProfile") util.AddNetworkString("GProfiler_Timers_ServerProfileStatus") util.AddNetworkString("GProfiler_Timers_SendData") + util.AddNetworkString("GProfiler_Timers_ActiveList") + + net.Receive("GProfiler_Timers_ActiveList", function(len, ply) + if not GProfiler.Access.HasAccess(ply) then return end + + net.Start("GProfiler_Timers_ActiveList") + net.WriteUInt(#ActiveTimers.Create, 16) + for _, v in ipairs(ActiveTimers.Create) do + net.WriteString(v.Name) + end + net.Send(ply) + end) net.Receive("GProfiler_Timers_ToggleServerProfile", function(len, ply) if not GProfiler.Access.HasAccess(ply) then return end if net.ReadBool() then - GProfiler.Timers:StartProfiler(ply) + Timers:StartProfiler(ply) net.Start("GProfiler_Timers_ServerProfileStatus") net.WriteBool(true) net.WriteEntity(ply) net.Broadcast() else - GProfiler.Timers:Stop(ply) + 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 +end + +-- local function simplySimple() +-- timer.Simple(math.Rand(0.1, 1), function() +-- simplySimple() +-- end) +-- end +-- simplySimple() + +-- timer.Create("GProfiler_TestTimer", math.Rand(0.1, 1), 0, function() end) + +-- local function simplySimple2() +-- timer.Simple(math.Rand(0.1, 1), function() +-- simplySimple2() +-- end) +-- end +-- simplySimple2() \ No newline at end of file diff --git a/lua/gprofiler/sh_config.lua b/lua/gprofiler/sh_config.lua index edaf64e..9178c28 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. @@ -11,10 +11,16 @@ GProfiler.Config.LOG_WARNING = true GProfiler.Config.LOG_ERROR = true GProfiler.Config.LOG_LOAD = false +-- Access GProfiler.Config.AllowedSteamIDs = { -- SteamIDs that can access GProfiler ["76561198XXXXXXXXX"] = true } +GProfiler.Config.AllowSuperAdmin = false -- Allow players with superadmin (Player:IsSuperAdmin()) to access GProfiler regardless of other checks. + +-- todo, requires a lot of changes probably +-- GProfiler.Config.AlwaysAllowClient = false -- Allow clients to use GProfiler (limited to client realm/cannot view sources, etc) + --[[ If express is available, we use it over gmod's net library This can handle larger data sizes, and we shouldn't need to worry about overflowing the buffer @@ -27,47 +33,22 @@ GProfiler.Config.UseExpressNetworking = true -- Express is not worth using for small amounts, as it will be slower for small data GProfiler.Config.ExpressMinimumResults = 25 -if CLIENT then - GProfiler.MenuColors = { - White = Color(255, 255, 255), - Blue = Color(91, 118, 255), - - -- Menu - Background = Color(8, 27, 48, 220), - OpaqueBlack = Color(0, 0, 0, 200), - OpaqueBlack2 = Color(0, 0, 0, 150), - TopBarSeparator = Color(91, 118, 255, 10), - HeaderSeparator = Color(91, 118, 255, 50), - RealmSelectorBackground = Color(38, 57, 78), - RealmSelectorOutline = Color(88, 107, 138), - ActiveProfile = Color(10, 155, 10), - InactiveProfile = Color(200, 50, 50), +if SERVER then return end - -- Lists - DListBackground = Color(18, 37, 58), - DListColumnBackground = Color(68, 87, 108), - DListColumnOutline = Color(88, 107, 138), - DListRowBackground = Color(48, 67, 88), - DListRowHover = Color(68, 87, 108), - DListRowTextColor = Color(235, 235, 235), - DListRowSelected = Color(91, 118, 255, 50), +GProfiler.MenuColors = { + -- Misc + White = Color(255, 255, 255), + Blue = Color(91, 118, 255), + Black100 = Color(0, 0, 0, 100), - -- Scrollbars - ScrollBar = Color(38, 57, 78), - ScrollBarGrip = Color(68, 87, 108), - ScrollBarGripOutline = Color(88, 107, 138), - - -- Buttons - ButtonOutline = Color(88, 107, 138), - ButtonBackground = Color(38, 57, 78), - ButtonHover = Color(58, 77, 98), - - CodeBackground = Color(45, 45, 45) - } + -- Scrollbars + ScrollBar = Color(38, 57, 78), + ScrollBarGrip = Color(68, 87, 108), + ScrollBarGripOutline = Color(88, 107, 138), +} - GProfiler.Config.MenuCommands = { - Chat = '!gprofiler', -- False to disable - Console = 'gprofiler', -- False to disable - Closekey = KEY_F4 -- False to disable - } -end +GProfiler.Config.MenuCommands = { + Chat = '!gprofiler', -- False to disable + Console = 'gprofiler', -- False to disable + Closekey = KEY_F4 -- False to disable +} \ No newline at end of file diff --git a/lua/gprofiler/sh_utils.lua b/lua/gprofiler/sh_utils.lua deleted file mode 100644 index c042343..0000000 --- a/lua/gprofiler/sh_utils.lua +++ /dev/null @@ -1,334 +0,0 @@ -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 - - 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.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 -else - util.AddNetworkString("GProfiler_RequestFunctionSource") - - local chunkSizeLimit = 65535 - net.Receive("GProfiler_RequestFunctionSource", function(l, ply) - if not GProfiler.Access.HasAccess(ply) then return end - - local f = net.ReadString() - local start = net.ReadUInt(32) - local endd = net.ReadUInt(32) - - local res = GProfiler.ReadFunctionSource(f, start, endd) - local chunkCount = 1 - local currentChunkSize = 0 - local chunks = {} - - if type(res) == "string" then res = {res} end - - for k, v in ipairs(res) do - local str = string.Replace(v, "\t", " ") - if currentChunkSize + string.len(str) > (chunkSizeLimit - 1300) then - chunkCount = chunkCount + 1 - currentChunkSize = 0 - end - - if not chunks[chunkCount] then chunks[chunkCount] = {} end - table.insert(chunks[chunkCount], str) - currentChunkSize = currentChunkSize + string.len(str) - end - - for k, v in ipairs(chunks) do - net.Start("GProfiler_RequestFunctionSource") - net.WriteBool(k == 1) - net.WriteBool(k == table.Count(chunks)) - net.WriteUInt(table.Count(v), 32) - for k, v1 in ipairs(v) do - net.WriteString(v1) - end - net.Send(ply) - end - end) - - function GProfiler.ReadFunctionSource(f, start, endd) - if f == "[C]" then return "Cannot get source for C functions!" end - if not file.Exists(f, "GAME") then return "File not found" end - if start < 0 or endd < 0 or endd < start then return "" end - - local f = file.Open(f, "r", "GAME") - - for i = 1, start - 1 do f:ReadLine() end - - local lines = {} - for i = start, endd do table.insert(lines, f:ReadLine() or "") end - - f:Close() - - return lines - end -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.ExpressAvailable() return !!((express and express.shSend) and GProfiler.Config.UseExpressNetworking) end diff --git a/lua/gprofiler/sv_init.lua b/lua/gprofiler/sv_init.lua index 8677970..b6ed32b 100644 --- a/lua/gprofiler/sv_init.lua +++ b/lua/gprofiler/sv_init.lua @@ -1,25 +1,2 @@ -util.AddNetworkString("GProfiler.SendState") - -local Profilers = { - "ConCommands", --[["EntVars",]] "Functions", - "Hooks", "Net", "NetVars", "Timers", "Database" -} - -hook.Add("PlayerInitialSpawn", "GProfiler.SendState", function(ply) - local Active = {} - for _, profiler in ipairs(Profilers) do - if GProfiler[profiler].ProfileStarted then - Active[profiler] = GProfiler[profiler].ProfileStarted - end - end - - if table.IsEmpty(Active) then return end - - net.Start("GProfiler.SendState") - net.WriteUInt(table.Count(Active), 4) - for profiler, time in pairs(Active) do - net.WriteString(profiler) - net.WriteFloat(SysTime() - time) - end - net.Send(ply) -end) +local lan = GetConVar("sv_lan") +if lan:GetBool() then SetGlobalBool("gprofiler_lan", true) end \ No newline at end of file