├── .gitignore ├── LICENSE ├── README.md └── lua ├── autorun └── blobsprofiler_autorun.lua └── blobsprofiler ├── client ├── cl_blobsprofiler.lua ├── js │ ├── ace1.js.lua │ ├── ace2.js.lua │ ├── ace3.js.lua │ ├── ace4.js.lua │ ├── ace5.js.lua │ ├── ace6.js.lua │ ├── ace7.js.lua │ ├── ace8.js.lua │ ├── mode-glua1.js.lua │ ├── mode-glua2.js.lua │ ├── mode-glua3.js.lua │ ├── mode-glua4.js.lua │ └── mode-sql.js.lua └── vgui │ ├── vgui_bpdtree.lua │ ├── vgui_bpdtree_node.lua │ └── vgui_bpdtree_node_button.lua ├── server └── sv_blobsprofiler.lua └── shared ├── modules ├── bp_concommands.lua ├── bp_errors.lua ├── bp_files.lua ├── bp_hooks.lua ├── bp_lua.lua ├── bp_network.lua ├── bp_profiling.lua ├── bp_sqlite.lua └── bp_timers.lua ├── sh_blobsprofiler.lua ├── sh_modules.lua ├── sh_netstream.lua └── sh_pon.lua /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blobles-dev/blobsProfiler/60903e4caf307a752cbd02a4677e7629091ca265/.gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 blobles-dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blobsProfiler 2 | A powerful task-manager style & profiling addon for garrysmod 3 | 4 | ![Preview](https://i.imgur.com/FmsdAVB.png) 5 | 6 | More screenshots can be found [here](https://github.com/blobles-dev/blobsProfiler/wiki/Media "here") 7 | 8 | ## Very early development (lacking a lot of features) 9 | Found an issue/bug or have an idea/suggestion? Use the [Issues](https://github.com/blobles-dev/blobsProfiler/issues "Issues") tab 10 | ### Overview 11 | This addon will be the one-stop shop for your development & test server needs. 12 | Early development access, feel free to contribute. 13 | I suck at repo management, so bare with me. 14 | Open the menu with `blobsprofiler` 15 | 16 | ### :exclamation: WARNING :exclamation: 17 | I would highly recommend against using this on a production/live server, especially in its current state. 18 | For now, the all modules are currently locked away behind only a usergroup == superadmin check (CL for client side area of modules, and SV checks for server side area of modules) 19 | 20 | 21 | ### Features & To-Do 22 | ##### Modules 23 | | Module | Client | Server | 24 | | ------------ | ------------ | ------------ | 25 | | [Globals](https://github.com/blobles-dev/blobsProfiler/wiki/Media#profiling-module-wip) | :white_check_mark: | :white_check_mark: | 26 | | [Lua execute](https://github.com/blobles-dev/blobsProfiler/wiki/Media#execute-lua-submodule) | :white_check_mark: | :white_check_mark: | 27 | | [Hooks](https://github.com/blobles-dev/blobsProfiler/wiki/Media#hooks-module) | :white_check_mark: | :white_check_mark: | 28 | | [ConCommands](https://github.com/blobles-dev/blobsProfiler/wiki/Media#concommands-module) | :white_check_mark: | :white_check_mark: | 29 | | Convar | :x: | :x: | 30 | | [Files](https://github.com/blobles-dev/blobsProfiler/wiki/Media#files-module) | :white_check_mark: | :white_check_mark: | 31 | | [Network (Receivers)](https://github.com/blobles-dev/blobsProfiler/wiki/Media#network-module) | :white_check_mark: | :white_check_mark: | 32 | | [Timers](https://github.com/blobles-dev/blobsProfiler/wiki/Media#timers-module) | :white_check_mark: | :white_check_mark: | 33 | | [Profiling](https://github.com/blobles-dev/blobsProfiler/wiki/Media#profiling-module-wip) | :x: | :x: | 34 | | [SQLite Schema](https://github.com/blobles-dev/blobsProfiler/wiki/Media#schema-sqlite-submodule) | :white_check_mark: | :white_check_mark: | 35 | | [SQLite Data](https://github.com/blobles-dev/blobsProfiler/wiki/Media#data-sqlite-submodule) | :white_check_mark: | :white_check_mark: | 36 | | [SQLite Execute](https://github.com/blobles-dev/blobsProfiler/wiki/Media#execute-sqlite-submodule) | :white_check_mark: | :white_check_mark: | 37 | | [Errors](https://github.com/blobles-dev/blobsProfiler/wiki/Media#errors-module) | :white_check_mark: | :white_check_mark: | 38 | | Remote SQL Schema | :x: | :x: | 39 | | Remote SQL Data | :x: | :x: | 40 | | Remote SQL Execute | :x: | :x: | 41 | 42 | ##### Other 43 | - Settings 44 | - :x: Theme 45 | - :x: Module enable/disable 46 | - :x: Module usergroup permission 47 | - Cleanup 48 | - :wavy_dash: Module-system (Code refactor) 49 | 50 | ### Known issues 51 | - Other addons detouring timer.Create can cause issues with obtaining correct source 52 | 53 | ### Credits 54 | - [Ace Editor](https://ace.c9.io/ "Ace Editor") 55 | - Ingame Lua (and soon SQL) editors 56 | - [Yogpod](https://github.com/Yogpod "Yogpod") 57 | - Posted a DTree script which sparked the whole idea behind this journey 58 | - [Meta Construct](https://github.com/Metastruct "Meta Construct") 59 | - GLua mode for Ace Editor 60 | - [Phoenixf](https://github.com/phoen1xf/ "Phoenixf") 61 | - Being an awesome friend <3 62 | -------------------------------------------------------------------------------- /lua/autorun/blobsprofiler_autorun.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler = blobsProfiler or {} 2 | blobsProfiler.Modules = blobsProfiler.Modules or {} 3 | 4 | blobsProfiler.Client = blobsProfiler.Client or {} 5 | blobsProfiler.Server = blobsProfiler.Server or {} 6 | 7 | blobsProfiler.Client.Profile = blobsProfiler.Client.Profile or {} 8 | blobsProfiler.Server.Profile = blobsProfiler.Server.Profile or {} 9 | 10 | blobsProfiler.svDataChunkSize = 15000 11 | 12 | local realmDataTable = {} 13 | 14 | if SERVER then 15 | realmDataTable = blobsProfiler.Server 16 | else 17 | realmDataTable = blobsProfiler.Client 18 | end 19 | 20 | 21 | MsgN("- blobsProfiler initializing files -") 22 | 23 | local function incFile(fileData) 24 | local CL = fileData.CL or false 25 | local SV = fileData.SV or false 26 | local filePath = fileData.File or error("blobsProfiler: Failed to load file no .File provided!") 27 | 28 | if SV && SERVER && !CL then 29 | print("[blobsProfiler] SV Load: " .. filePath) 30 | include("blobsprofiler/" .. filePath) 31 | else 32 | if SERVER then 33 | print("[blobsProfiler] " .. (SV && "SH" || "CL") .. " DL: " .. filePath) 34 | AddCSLuaFile("blobsprofiler/" .. filePath) 35 | end 36 | 37 | if (SV && CL) || (CL && CLIENT) then 38 | print("[blobsProfiler] " .. (SV && "SH" || "CL") .. " Load: " .. filePath) 39 | if CLIENT and fileData.CL_NoInclude then print("[blobsProfiler] " .. (SV && "SH" || "CL") .. " CL_NoInclude: " .. filePath) return end 40 | include("blobsprofiler/" .. filePath) 41 | end 42 | end 43 | end 44 | 45 | blobsProfiler.FileList = { 46 | { 47 | File = "shared/sh_pon.lua", 48 | CL = true, 49 | SV = true, 50 | }, 51 | { 52 | File = "shared/sh_netstream.lua", 53 | CL = true, 54 | SV = true, 55 | }, 56 | { 57 | File = "shared/sh_blobsprofiler.lua", 58 | CL = true, 59 | SV = true, 60 | }, 61 | { 62 | File = "shared/sh_modules.lua", 63 | CL = true, 64 | SV = true, 65 | }, 66 | { 67 | File = "client/cl_blobsprofiler.lua", 68 | CL = true 69 | }, 70 | { 71 | File = "client/vgui/vgui_bpdtree.lua", 72 | CL = true 73 | }, 74 | { 75 | File = "client/vgui/vgui_bpdtree_node.lua", 76 | CL = true 77 | }, 78 | { 79 | File = "client/vgui/vgui_bpdtree_node_button.lua", 80 | CL = true 81 | }, 82 | { 83 | File = "server/sv_blobsprofiler.lua", 84 | SV = true, 85 | } 86 | } 87 | 88 | blobsProfiler.LoadFiles = function() 89 | for _, fileData in ipairs(blobsProfiler.FileList) do 90 | incFile(fileData) 91 | end 92 | end 93 | 94 | blobsProfiler.LoadFiles() 95 | 96 | blobsProfiler.LoadModules = function() 97 | local foundModuleFiles = file.Find("blobsprofiler/shared/modules/bp_*.lua", "LUA") 98 | 99 | for _, moduleLuaFile in ipairs(foundModuleFiles) do 100 | if SERVER then 101 | AddCSLuaFile("blobsprofiler/shared/modules/"..moduleLuaFile) 102 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Module AddCSLuaFile: ".. moduleLuaFile) 103 | end 104 | include("blobsprofiler/shared/modules/"..moduleLuaFile) 105 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Module include(): ".. moduleLuaFile) 106 | end 107 | end 108 | 109 | blobsProfiler.LoadModules() 110 | 111 | local JSFiles = { 112 | { FileName = "ace", Parts = 8 }, 113 | { FileName = "mode-sql" }, 114 | { FileName = "mode-glua", Parts = 4} 115 | } 116 | 117 | blobsProfiler.JSFileData = blobsProfiler.JSFileData or {} 118 | 119 | blobsProfiler.LoadJSFiles = function() 120 | for _, JSFile in ipairs(JSFiles) do 121 | local basePath = "blobsProfiler/client/js/" .. JSFile.FileName 122 | local partCount = JSFile.Parts or 1 123 | local parts = {} 124 | 125 | for i = 1, partCount do 126 | local filePath = basePath .. (partCount > 1 and i or "") .. ".js.lua" 127 | if SERVER then 128 | AddCSLuaFile(filePath) 129 | else 130 | parts[i] = include(filePath) 131 | end 132 | end 133 | 134 | if not SERVER then 135 | blobsProfiler.JSFileData[JSFile.FileName] = table.concat(parts) 136 | end 137 | end 138 | end 139 | 140 | blobsProfiler.LoadJSFiles() -------------------------------------------------------------------------------- /lua/blobsprofiler/client/cl_blobsprofiler.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler = blobsProfiler or {} 2 | -- <3 yogpod for the initial file tree viewer base && sparking the idea 3 | -- https://gist.github.com/Yogpod/f3be860207bc71607f14225cd7a0c948 4 | blobsProfiler.Menu = blobsProfiler.Menu or {} 5 | 6 | --[[ 7 | TODO: 8 | Double click for first RC option 9 | Hooks: Suspend/Resume 10 | More RC stuff 11 | profiling 12 | profiler results 13 | SV data 14 | Theming / dark theme (settings) 15 | security 16 | errors - sv/cl (player focus?) 17 | convars 18 | settings - disable individual modules 19 | Refactoring - proper modularisation 20 | sqlite - data pagination 21 | sqlite - execute (ace editor, sql mode) 22 | ]] 23 | 24 | 25 | --[[ 26 | string, number, boolean, 27 | function, table, Angle, 28 | Vector, Player, Entity, 29 | Panel, IMaterial, ITexture, 30 | IMesh, CUserCmd, ConVar, 31 | nil, userdata, Vehicle 32 | Weapon, NPC, NextBot 33 | PhysObj, SaveRestore, EffectData 34 | Sound, Texture, NavArea 35 | Path, PhysicsCollide, Trace 36 | WeaponProficiency, ScriptedVehicle 37 | ]] 38 | 39 | blobsProfiler.TypesToIcon = { 40 | ["table"] = "folder", 41 | ["function"] = "page_white_code", 42 | ["IMaterial"] = "page_white_picture", 43 | ["Panel"] = "page_white_paint", 44 | ["Player"] = "page_white_world", 45 | ["Entity"] = "page_white_world" 46 | } 47 | 48 | blobsProfiler.Menu.GlobalTypesToCondense = { 49 | { 50 | type = "string", 51 | prettyPlural = "Strings" 52 | }, 53 | { 54 | type = "number", 55 | prettyPlural = "Numbers" 56 | }, 57 | { 58 | type = "boolean", 59 | prettyPlural = "Booleans" 60 | }, 61 | { 62 | type = "Panel", 63 | prettyPlural = "Panels" 64 | }, 65 | { 66 | type = "function", 67 | prettyPlural = "Functions" 68 | }, 69 | { 70 | type = "Entity", 71 | prettyPlural = "Entities" 72 | }, 73 | { 74 | type = "Vector", 75 | prettyPlural = "Vectors" 76 | }, 77 | { 78 | type = "Angle", 79 | prettyPlural = "Angles" 80 | }, 81 | { 82 | type = "table", 83 | prettyPlural = "Tables" 84 | } 85 | } 86 | 87 | blobsProfiler.viewPropertiesPopup = function(title, data, width, height) 88 | local propertiesFrame = vgui.Create("DFrame") 89 | width = width or 500 90 | height = height or 500 91 | 92 | propertiesFrame:SetSize(width, height) 93 | propertiesFrame:SetTitle(title) 94 | propertiesFrame:Center() 95 | propertiesFrame:MakePopup() 96 | 97 | local propertiesList = vgui.Create("DProperties", propertiesFrame) 98 | propertiesList:Dock(FILL) 99 | 100 | for propertiesGroup, propertiesData in pairs(data) do 101 | for propertyKey, propertyValue in pairs(propertiesData) do 102 | local propertyRow = propertiesList:CreateRow(propertiesGroup, propertyKey) 103 | propertyRow:Setup("Generic") 104 | propertyRow:SetValue(tostring(propertyValue)) 105 | end 106 | end 107 | 108 | return propertiesFrame 109 | end 110 | 111 | blobsProfiler.generateAceEditorPanel = function(parentPanel, content, editorMode, readOnly, startLine, highlightLine) 112 | local dhtmlPanel = vgui.Create("DHTML", parentPanel) 113 | content = content or [[print("Hello world!")]] 114 | editorMode = editorMode or "Lua" 115 | local useMode = "ace/mode/glua" 116 | local useModeFile = "mode-glua" 117 | 118 | if editorMode == "SQL" then 119 | useMode = "ace/mode/sql" 120 | useModeFile = "mode-sql" 121 | end 122 | 123 | local highlightJS = "" 124 | if highlightLine and highlightLine ~= 0 then 125 | highlightJS = [[ 126 | var lineNumber = ]].. highlightLine - 1 ..[[; 127 | 128 | var Range = ace.require("ace/range").Range; 129 | editor.session.addMarker(new Range(lineNumber, 0, lineNumber, 1), "errorHighlight", "fullLine"); 130 | 131 | editor.session.setAnnotations([{ 132 | row: lineNumber, 133 | column: 0, 134 | //text: "Error: ", // TODO: Pass through error message? 135 | type: "error" 136 | }]); 137 | 138 | setTimeout(function() { 139 | editor.scrollToLine(lineNumber, true, true, function() {}); 140 | editor.gotoLine(lineNumber); 141 | }, 100); // murder me in my sleep 142 | ]] 143 | end 144 | 145 | dhtmlPanel:SetHTML([[ 146 | 147 | 148 | 149 | 150 | blobsProfiler: Lua Execution 151 | 162 | 163 | 164 |
]].. content ..[[
165 | 166 | 167 | 192 | 193 | 194 | ]]) 195 | 196 | return dhtmlPanel 197 | end 198 | 199 | blobsProfiler.sourceFrames = {} 200 | 201 | local function popupSourceView(sourceContent, frameTitle, highlightLine) 202 | print("highlightLine", highlightLine) 203 | local sourceFrame = vgui.Create("DFrame") 204 | sourceFrame:SetSize(500,500) 205 | sourceFrame:SetTitle(frameTitle or "View source") 206 | sourceFrame:Center() 207 | sourceFrame:MakePopup() 208 | 209 | local startLine, endLine = frameTitle:match("Lines%: (%d+)%-(%d+)") 210 | startLine = tonumber(startLine) 211 | endLine = tonumber(endLine) 212 | 213 | local sourcePanel = blobsProfiler.generateAceEditorPanel(sourceFrame, sourceContent, "Lua", true, startLine, highlightLine) 214 | sourcePanel:Dock(FILL) 215 | 216 | sourcePanel.OnRemove = function() 217 | blobsProfiler.sourceFrames[frameTitle] = nil 218 | end 219 | 220 | blobsProfiler.sourceFrames[frameTitle] = sourceFrame 221 | end 222 | 223 | local function killAllSourcePopups() 224 | for k,v in pairs(blobsProfiler.sourceFrames) do 225 | if IsValid(v) then 226 | v:Remove() 227 | end 228 | 229 | blobsProfiler.sourceFrames = {} 230 | end 231 | end 232 | killAllSourcePopups() 233 | 234 | local receivedSource = {} 235 | 236 | net.Receive("blobsProfiler:sendSourceChunk", function() 237 | local requestId = net.ReadString() 238 | local startPos = net.ReadUInt(32) 239 | local chunk = net.ReadString() 240 | local highlightLine = net.ReadUInt(16) 241 | 242 | if not receivedSource[requestId] then 243 | receivedSource[requestId] = { 244 | receivedSource = {}, 245 | chunksReceived = 0 246 | } 247 | end 248 | 249 | local request = receivedSource[requestId] 250 | request.receivedSource[startPos] = chunk 251 | request.chunksReceived = request.chunksReceived + 1 252 | 253 | local combinedSource = "" 254 | local allChunksReceived = true 255 | local chunkSize = 30000 256 | 257 | for i = 1, request.chunksReceived * chunkSize, chunkSize do 258 | if request.receivedSource[i] then 259 | combinedSource = combinedSource .. request.receivedSource[i] 260 | else 261 | allChunksReceived = false 262 | break 263 | end 264 | end 265 | 266 | if allChunksReceived then 267 | --local splitRequest = string.Explode(":", requestId) 268 | popupSourceView(combinedSource, requestId, highlightLine) 269 | 270 | receivedSource[requestId] = nil -- Clean up the request data 271 | end 272 | end) 273 | 274 | blobsProfiler.Menu.RCFunctions = {} 275 | blobsProfiler.Menu.RCFunctions_DEFAULT = { 276 | ["string"] = { 277 | { 278 | name = "Print", 279 | func = function(ref, node) 280 | print(ref.value) 281 | print(node.GlobalPath) 282 | end, 283 | icon = "icon16/application_osx_terminal.png" 284 | } 285 | }, 286 | ["number"] = { 287 | { 288 | name = "Print", 289 | func = function(ref, node) 290 | print(ref.value) 291 | print(node.GlobalPath) 292 | end, 293 | icon = "icon16/application_osx_terminal.png" 294 | } 295 | }, 296 | ["boolean"] = { 297 | { 298 | name = "Print", 299 | func = function(ref, node) 300 | print(ref.value) 301 | print(node.GlobalPath) 302 | end, 303 | icon = "icon16/application_osx_terminal.png" 304 | } 305 | }, 306 | ["table"] = { 307 | { 308 | name = "Expand/Collapse", 309 | func = function(ref, node) 310 | local curState 311 | if node and IsValid(node) and node.SetExpanded then 312 | local curState = node:GetExpanded() 313 | node:SetExpanded(not curState) 314 | 315 | if ref.special then -- Don't go deeper for Lua.Globals 'type' root nodes 316 | return 317 | end 318 | 319 | for _, childNode in ipairs(node:GetChildNodes()) do 320 | if childNode and IsValid(childNode) and childNode.SetExpanded then 321 | childNode:SetExpanded(not curState) 322 | end 323 | end 324 | end 325 | end, 326 | icon = "icon16/folder_explore.png" 327 | }, 328 | { 329 | name = "Print", 330 | func = function(ref, node) 331 | PrintTable(ref.value) 332 | print("Global Path:", node.GlobalPath) 333 | end, 334 | icon = "icon16/application_osx_terminal.png" 335 | } 336 | }, 337 | ["function"] = { 338 | { 339 | name = "Toggle Profiling", 340 | func = function(ref, node) 341 | if node.Expander and IsValid(node.Expander) and node.Expander.SetChecked then 342 | local curChecked = node.Expander:GetChecked() 343 | node.Expander:SetChecked(not curChecked) 344 | node.Expander:OnChange(not curChecked) 345 | end 346 | end, 347 | condition = function(ref, node) 348 | if not node.Expander or not IsValid(node.Expander) or not node.Expander.SetChecked or not node.Expander:IsVisible() then 349 | return false 350 | end 351 | 352 | return true 353 | end, 354 | icon = "icon16/chart_bar.png" 355 | }, 356 | { 357 | name = "Stop Profiling", -- this is literally only for the profiling module. i could use the modular function, but then i'd lose all the defaults. TODO: default AND custom combined RC options 358 | func = function(ref, node) 359 | if istable(ref.value) and ref.value.node and IsValid(ref.value.node) then 360 | if ref.value.node.Expander and IsValid(ref.value.node.Expander) and ref.value.node.Expander.SetChecked then 361 | ref.value.node.Expander:SetChecked(false) 362 | ref.value.node.Expander:OnChange(false) 363 | 364 | if node:GetParentNode() and node:GetParentNode():GetChildNodeCount() == 1 then 365 | node:GetParentNode():Remove() 366 | else 367 | node:Remove() 368 | end 369 | end 370 | end 371 | end, 372 | condition = function(ref, node, luaState) 373 | if node.Expander and not node.Expander:IsVisible() then 374 | return true 375 | end 376 | 377 | return false 378 | end, 379 | icon = "icon16/chart_bar.png" 380 | }, 381 | { 382 | name = "View source", 383 | func = function(ref, node, luaState) 384 | local useValue = isfunction(ref.value) and ref.value or ref.value.func 385 | if luaState == "Client" then 386 | local debugInfo = debug.getinfo(useValue, "S") 387 | if not string.EndsWith(debugInfo.short_src, ".lua") then 388 | Derma_Message("Invalid function source: ".. debugInfo.short_src.."\nOnly functions defined in Lua can be read!", "Function view source", "OK") 389 | return 390 | end 391 | 392 | net.Start("blobsProfiler:requestSource") 393 | net.WriteString(debugInfo.short_src) 394 | net.WriteUInt(debugInfo.linedefined, 16) 395 | net.WriteUInt(debugInfo.lastlinedefined, 16) 396 | net.SendToServer() 397 | elseif luaState == "Server" then 398 | if not string.EndsWith(ref.value.short_src, ".lua") then 399 | Derma_Message("Invalid function source: ".. ref.value.short_src.."\nOnly functions defined in Lua can be read!", "Function view source", "OK") 400 | return 401 | end 402 | 403 | net.Start("blobsProfiler:requestSource") 404 | net.WriteString(ref.value.short_src) 405 | net.WriteUInt(ref.value.linedefined, 16) 406 | net.WriteUInt(ref.value.lastlinedefined, 16) 407 | net.SendToServer() 408 | end 409 | end, 410 | icon = "icon16/magnifier.png" 411 | }, 412 | { 413 | name = "View properties", 414 | func = function(ref, node, luaState) 415 | local propertiesData = {} 416 | 417 | if luaState == "Client" then 418 | local debugInfo = debug.getinfo(isfunction(ref.value) and ref.value or ref.value.func) 419 | propertiesData["debug.getinfo()"] = debugInfo 420 | elseif luaState == "Server" then 421 | local propertiesTbl = table.Copy(ref.value) 422 | propertiesTbl.fakeVarType = nil 423 | propertiesData["debug.getinfo()"] = propertiesTbl 424 | end 425 | 426 | local popupView = blobsProfiler.viewPropertiesPopup("View Function: " .. ref.key, propertiesData) 427 | end, 428 | icon = "icon16/magnifier.png" 429 | } 430 | }, 431 | ["file"] = { 432 | { 433 | name = "View source", 434 | func = function(ref, node, luaState) 435 | if not string.EndsWith(ref.value.Source, ".lua") then 436 | Derma_Message("Invalid file source: ".. ref.value.Source .."\nOnly Lua files can be read!", "Function view source", "OK") 437 | return 438 | end 439 | 440 | net.Start("blobsProfiler:requestSource") 441 | net.WriteString(ref.value.Source) 442 | net.WriteUInt(ref.value.Line, 16) 443 | net.WriteUInt(0, 16) 444 | net.SendToServer() 445 | end, 446 | icon = "icon16/magnifier.png" 447 | } 448 | } 449 | } 450 | 451 | blobsProfiler.Menu.TypeFolders = {} 452 | blobsProfiler.Menu.TypeFolders.Client = {} 453 | blobsProfiler.Menu.TypeFolders.Server = {} 454 | 455 | for k,v in ipairs(blobsProfiler.Menu.GlobalTypesToCondense) do 456 | blobsProfiler.Menu.TypeFolders.Client[v.type] = true 457 | blobsProfiler.Menu.TypeFolders.Server[v.type] = true 458 | end 459 | 460 | local function nodeEntriesTableKeySort(a, b) 461 | local aIsTable = type(a.value) == 'table' 462 | local bIsTable = type(b.value) == 'table' 463 | if aIsTable && !bIsTable then 464 | return true 465 | elseif !aIsTable && bIsTable then 466 | return false 467 | else 468 | return tostring(a.key) < tostring(b.key) -- Just in case.. (It's here for a reason :)) 469 | end 470 | end 471 | 472 | local function rootNodeEntriesTableKeySort(a, b) 473 | local aIsTable = type(a.value) == 'table' 474 | local bIsTable = type(b.value) == 'table' 475 | if !aIsTable && bIsTable then 476 | return true 477 | elseif aIsTable && !bIsTable then 478 | return false 479 | else 480 | return a.key < b.key 481 | end 482 | end 483 | 484 | local selectedNode = nil 485 | local function addDTreeNode(parentNode, nodeData, specialType, isRoot, varType, luaState) 486 | local nodeKey = tostring(nodeData.key) 487 | local nodeValue = nodeData.value 488 | local dataType = type(nodeValue) 489 | local visualDataType = dataType 490 | local iconOverride = nil 491 | 492 | local childNode 493 | 494 | local useParent = parentNode 495 | 496 | if istable(nodeValue) and nodeValue.fakeVarType then 497 | visualDataType = nodeValue.fakeVarType 498 | end 499 | 500 | if isRoot && varType == "Lua.Globals" then 501 | local dataType = type(nodeData.value) 502 | local specialFolderPanel = blobsProfiler.Menu.TypeFolders[luaState][visualDataType] 503 | if specialFolderPanel && type(specialFolderPanel) == "Panel" then 504 | useParent = specialFolderPanel 505 | end 506 | end 507 | if visualDataType == "table" then 508 | local useNodeName = nodeKey 509 | local getModule = blobsProfiler.GetModule(varType) 510 | if not specialType and getModule.FormatNodeName and getModule.FormatNodeName(luaState, nodeKey, nodeValue) then 511 | useNodeName = getModule.FormatNodeName(luaState, nodeKey, nodeValue) 512 | end 513 | 514 | childNode = useParent:AddNode(useNodeName) 515 | 516 | childNode.Icon:SetImage("icon16/folder.png") 517 | 518 | childNode.oldExpand = childNode.SetExpanded 519 | 520 | --childNode.NeedsLazyLoad = true -- TODO: add check to make sure there even is children? 521 | 522 | if istable(nodeValue) and table.Count(nodeValue) > 0 then 523 | childNode.NeedsLazyLoad = true 524 | end 525 | 526 | childNode.SetExpanded = function(...) 527 | if !childNode.LazyLoaded then 528 | -- Lazy loading! 529 | 530 | local grandchildNode = {} 531 | 532 | for key, value in pairs(nodeValue) do 533 | table.insert(grandchildNode, { 534 | key = key, 535 | value = value 536 | }) 537 | end 538 | 539 | table.sort(grandchildNode, nodeEntriesTableKeySort) 540 | 541 | for index, gcNodeData in ipairs(grandchildNode) do 542 | addDTreeNode(childNode, gcNodeData, false, false, varType, luaState) 543 | end 544 | 545 | 546 | childNode.LazyLoaded = true 547 | end 548 | 549 | local RCTable = blobsProfiler.GetRCFunctionsTable(varType) 550 | if RCTable and RCTable[dataType] then 551 | for k,v in ipairs(RCTable[dataType]) do 552 | if v.condition and not v.condition(nodeData, childNode, luaState) then 553 | continue 554 | end 555 | 556 | if v.onLoad then v.onLoad(nodeData, childNode, luaState) end 557 | end 558 | end 559 | 560 | childNode.oldExpand(...) 561 | end 562 | 563 | local getModule = blobsProfiler.GetModule(varType) 564 | if getModule.FormatNodeIcon and getModule.FormatNodeIcon(luaState, nodeKey, nodeValue) then 565 | childNode.Icon:SetImage(getModule.FormatNodeIcon(luaState, nodeKey, nodeValue)) 566 | end 567 | else 568 | local nodeText = nodeKey 569 | 570 | local getModule = blobsProfiler.GetModule(varType) 571 | if getModule.FormatNodeName then 572 | nodeText = getModule.FormatNodeName(luaState, nodeKey, nodeValue) 573 | end 574 | 575 | childNode = useParent:AddNode(nodeText) 576 | childNode.Icon:SetImage("icon16/".. (blobsProfiler.TypesToIcon[visualDataType] || "page_white_text") ..".png") 577 | 578 | if getModule.FormatNodeIcon and getModule.FormatNodeIcon(luaState, nodeKey, nodeValue) then 579 | childNode.Icon:SetImage(getModule.FormatNodeIcon(luaState, nodeKey, nodeValue)) 580 | end 581 | 582 | childNode.DoClick = function() 583 | if isRoot && useParent == parentNode && varType == "Globals" then 584 | print("blobsProfiler: Non-foldered root for type: ".. type(nodeValue)) 585 | end 586 | 587 | return true 588 | end 589 | 590 | end 591 | 592 | childNode.GlobalPath = childNode.GlobalPath || "" 593 | 594 | if not nodeData.special then 595 | if isRoot then 596 | childNode.GlobalPath = nodeKey 597 | else 598 | childNode.GlobalPath = parentNode.GlobalPath .. "." .. nodeKey 599 | end 600 | end 601 | 602 | varType = varType or "Lua.Globals" 603 | 604 | childNode.DoRightClick = function() 605 | childNode:InternalDoClick() 606 | 607 | local RCTable = blobsProfiler.GetRCFunctionsTable(varType) 608 | if RCTable and RCTable[visualDataType] then 609 | blobsProfiler.Menu.RCMenu = DermaMenu() 610 | local RCMenu = blobsProfiler.Menu.RCMenu 611 | 612 | for _, rcM in ipairs(RCTable[visualDataType]) do 613 | if rcM.condition and not rcM.condition(nodeData, childNode, luaState) then 614 | continue 615 | end 616 | 617 | local useName = rcM.name 618 | if type(rcM.name) == "function" then 619 | useName = rcM.name(nodeData, childNode, luaState) 620 | end 621 | if useName then 622 | if rcM.submenu then 623 | local rcChild, rcParent = RCMenu:AddSubMenu(useName) 624 | 625 | local useIcon = rcM.icon 626 | if type(rcM.icon) == "function" then 627 | useIcon = rcM.icon(nodeData, childNode, luaState) 628 | end 629 | if useIcon then rcParent:SetIcon(useIcon) end 630 | 631 | for _, rcMS in ipairs(rcM.submenu) do 632 | if rcMS.condition and not rcMS.condition(nodeData, childNode, luaState) then 633 | continue 634 | end 635 | 636 | local useNameSM = rcMS.name 637 | if type(rcMS.name) == "function" then 638 | useNameSM = rcMS.name(nodeData, childNode, luaState) 639 | end 640 | if useNameSM then 641 | local rcChildP = rcChild:AddOption(useNameSM, function() 642 | rcMS.func(nodeData, childNode, luaState) 643 | if rcMS.onLoad then 644 | rcMS.onLoad(nodeData, childNode, luaState) 645 | end 646 | end) 647 | 648 | local useIconSM = rcMS.icon 649 | if type(rcMS.icon) == "function" then 650 | useIconSM = rcMS.icon(nodeData, childNode, luaState) 651 | end 652 | if useIconSM then rcChildP:SetIcon(useIconSM) end 653 | end 654 | end 655 | else 656 | local rcOption = RCMenu:AddOption(useName, function() 657 | rcM.func(nodeData, childNode, luaState) 658 | if rcM.onLoad then 659 | rcM.onLoad(nodeData, childNode, luaState) 660 | end 661 | end) 662 | 663 | local useIcon = rcM.icon 664 | if type(rcM.icon) == "function" then 665 | useIcon = rcM.icon(nodeData, childNode, luaState) 666 | end 667 | if useIcon then rcOption:SetIcon(useIcon) end 668 | end 669 | end 670 | end 671 | 672 | RCMenu:Open() 673 | end 674 | end 675 | 676 | local RCTable = blobsProfiler.GetRCFunctionsTable(varType) 677 | if RCTable and RCTable[visualDataType] then 678 | for k,v in ipairs(RCTable[visualDataType]) do 679 | if v.condition and not v.condition(nodeData, childNode, luaState) then 680 | continue 681 | end 682 | 683 | if v.onLoad then v.onLoad(nodeData, childNode, luaState) end 684 | end 685 | end 686 | 687 | if not isRoot then 688 | childNode.parentNode = parentNode 689 | end 690 | 691 | if visualDataType and visualDataType == "function" then 692 | childNode.FunctionRef = {name=nodeKey, func=nodeValue, path = childNode.GlobalPath, fakeVarType = "function", node=childNode} 693 | childNode:SetForceShowExpander(true) 694 | 695 | if varType ~= "Profiling.Targets" then 696 | childNode:IsFunc() -- This is what swaps the expander for a dcheckbox if it's a function 697 | else 698 | childNode:SetForceShowExpander(false) -- No need to select already selected functions for profiling.. 699 | end 700 | 701 | blobsProfiler[luaState].Profile = blobsProfiler[luaState].Profile or {} 702 | 703 | blobsProfiler[luaState].Profile.Raw = blobsProfiler[luaState].Profile.Raw or {} 704 | blobsProfiler[luaState].Profile.Called = blobsProfiler[luaState].Profile.Called or {} 705 | blobsProfiler[luaState].Profile.Results = blobsProfiler[luaState].Profile.Results or {} 706 | childNode.Expander.OnChange = function(s, isChecked) 707 | blobsProfiler[luaState].Profile[varType] = blobsProfiler[luaState].Profile[varType] or {} 708 | 709 | if isChecked then 710 | blobsProfiler[luaState].Profile[varType][tostring(nodeValue)] = childNode.FunctionRef 711 | blobsProfiler[luaState].Profile.Raw[tostring(nodeValue)] = true 712 | blobsProfiler[luaState].Profile.Called[tostring(nodeValue)] = blobsProfiler[luaState].Profile.Called[tostring(nodeValue)] or {} 713 | blobsProfiler[luaState].Profile.Results[tostring(nodeValue)] = blobsProfiler[luaState].Profile.Results[tostring(nodeValue)] or {} 714 | else 715 | blobsProfiler[luaState].Profile[varType][tostring(nodeValue)] = nil 716 | blobsProfiler[luaState].Profile.Raw[tostring(nodeValue)] = falses 717 | blobsProfiler[luaState].Profile.Called[tostring(nodeValue)] = blobsProfiler[luaState].Profile.Called[tostring(nodeValue)] or {} 718 | blobsProfiler[luaState].Profile.Results[tostring(nodeValue)] = blobsProfiler[luaState].Profile.Results[tostring(nodeValue)] or {} 719 | end 720 | end 721 | end 722 | 723 | childNode.Label.DoDoubleClick = function() 724 | local dataType = type(nodeValue) 725 | local visualDataType = dataType 726 | 727 | if istable(nodeValue) and nodeValue.fakeVarType then 728 | visualDataType = nodeValue.fakeVarType -- every day we stray further away from god 729 | end 730 | 731 | local RCTable = blobsProfiler.GetRCFunctionsTable(varType) 732 | 733 | if RCTable and RCTable[visualDataType] then 734 | for _, rcM in ipairs(RCTable[visualDataType]) do 735 | if rcM.condition and not rcM.condition(nodeData, childNode, luaState) then 736 | continue 737 | end 738 | 739 | rcM.func(nodeData, childNode, luaState) -- this should be first one they have access to 740 | break 741 | end 742 | end 743 | end 744 | 745 | childNode.varType = varType 746 | 747 | 748 | return childNode 749 | end 750 | 751 | local function buildDTree(luaState, parentPanel, rvarType, dataTableOverride) 752 | local dTree = vgui.Create("BP_DTree", parentPanel) 753 | dTree:Dock(FILL) 754 | --dTree:SetVisible(false) 755 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "buildDTree " .. luaState .. " " .. rvarType) 756 | local rootNodes = {} 757 | 758 | local subModuleSplit = string.Explode(".", rvarType) 759 | local varType = subModuleSplit[1] 760 | 761 | if #subModuleSplit > 1 then 762 | varType = subModuleSplit[2] -- ew 763 | end 764 | 765 | local dataTable 766 | 767 | if dataTableOverride then 768 | dataTable = dataTableOverride 769 | else 770 | dataTable = blobsProfiler.GetDataTableForRealm(luaState, rvarType) or {} 771 | end 772 | 773 | if rvarType == "Lua.Globals" then -- TODO: make this shit modular 774 | for key, value in pairs(dataTable) do 775 | table.insert(rootNodes, { 776 | key = key, 777 | value = value 778 | }) 779 | end 780 | 781 | local specialNodes = {} 782 | 783 | for k,v in ipairs(blobsProfiler.Menu.GlobalTypesToCondense) do 784 | table.insert(specialNodes, { 785 | key = v.prettyPlural, 786 | value = {}, 787 | special = v.type 788 | }) 789 | end 790 | 791 | for index, nodeData in ipairs(specialNodes) do 792 | if blobsProfiler.Menu.TypeFolders[luaState][nodeData.special] == true then 793 | blobsProfiler.Menu.TypeFolders[luaState][nodeData.special] = addDTreeNode(dTree, nodeData, true, true, rvarType, luaState) 794 | blobsProfiler.Menu.TypeFolders[luaState][nodeData.special].nodeData = nodeData 795 | end 796 | end 797 | elseif rvarType == "SQLite.Schema" then 798 | if dataTable.Tables and dataTable.Indices then 799 | table.insert(rootNodes, { 800 | key = "Tables", 801 | value = blobsProfiler.TableSort.SQLTableColSort(dataTable.Tables) 802 | }) 803 | 804 | table.insert(rootNodes, { 805 | key = "Indices", 806 | value = blobsProfiler.TableSort.KeyAlphabetical(dataTable.Indices) 807 | }) 808 | end 809 | else 810 | for k, v in pairs(dataTable) do 811 | table.insert(rootNodes, { 812 | key = k, 813 | value = v 814 | }) 815 | end 816 | end 817 | 818 | if rvarType ~= "SQLite.Schema" then -- ewww 819 | table.sort(rootNodes, nodeEntriesTableKeySort) 820 | end 821 | 822 | local rootNodesLen = #rootNodes 823 | 824 | for index, nodeData in ipairs(rootNodes) do 825 | addDTreeNode(dTree, nodeData, false, true, rvarType, luaState) 826 | 827 | if index == rootNodesLen then 828 | dTree:SetVisible(true) 829 | end 830 | end 831 | end 832 | 833 | if blobsProfiler.Menu.MenuFrame && IsValid(blobsProfiler.Menu.MenuFrame) then 834 | blobsProfiler.Menu.MenuFrame:Remove() -- kill on lua refresh 835 | end 836 | 837 | blobsProfiler.Tabs = {} 838 | blobsProfiler.Tabs.Client = {} 839 | blobsProfiler.Tabs.Server = {} 840 | 841 | concommand.Add("blobsprofiler", function(ply, cmd, args, argStr) 842 | if not blobsProfiler.CanAccess(LocalPlayer(), "OpenMenu") then return end -- TODO: better more modular permissions via settings 843 | 844 | if argStr == "reloadclient" then 845 | if blobsProfiler.Menu.MenuFrame or IsValid(blobsProfiler.Menu.MenuFrame) then 846 | blobsProfiler.Menu.MenuFrame:Remove() 847 | blobsProfiler.Menu.MenuFrame = nil 848 | end 849 | 850 | blobsProfiler.Client = {} 851 | blobsProfiler.DataTablesSetup = false 852 | 853 | blobsProfiler.LoadFiles() 854 | 855 | print("blobsProfiler: Reloaded client data & files") 856 | return 857 | end 858 | 859 | if not blobsProfiler.DataTablesSetup or argStr == "refresh" then 860 | blobsProfiler.Client = {} 861 | blobsProfiler.Server = {} 862 | 863 | if blobsProfiler.Menu.MenuFrame or IsValid(blobsProfiler.Menu.MenuFrame) then 864 | blobsProfiler.Menu.MenuFrame:Remove() 865 | blobsProfiler.Menu.MenuFrame = nil 866 | end 867 | end 868 | 869 | if blobsProfiler.Menu.MenuFrame && IsValid(blobsProfiler.Menu.MenuFrame) then 870 | if blobsProfiler.Menu.MenuFrame:IsVisible() then 871 | blobsProfiler.Menu.MenuFrame:Hide() 872 | else 873 | blobsProfiler.Menu.MenuFrame:Show() 874 | end 875 | 876 | return 877 | end 878 | 879 | blobsProfiler.Menu.TypeFolders = {} 880 | blobsProfiler.Menu.TypeFolders.Client = {} 881 | blobsProfiler.Menu.TypeFolders.Server = {} 882 | for k,v in ipairs(blobsProfiler.Menu.GlobalTypesToCondense) do 883 | blobsProfiler.Menu.TypeFolders.Client[v.type] = true 884 | blobsProfiler.Menu.TypeFolders.Server[v.type] = true 885 | end 886 | 887 | blobsProfiler.Menu.selectedRealm = "Client" 888 | blobsProfiler.Menu.MenuFrame = vgui.Create("DFrame") 889 | blobsProfiler.Menu.MenuFrame:SetSize(900, 550) 890 | blobsProfiler.Menu.MenuFrame:Center() 891 | blobsProfiler.Menu.MenuFrame:SetTitle("blobsProfiler - " .. blobsProfiler.Menu.selectedRealm) 892 | blobsProfiler.Menu.MenuFrame:MakePopup() 893 | blobsProfiler.Menu.MenuFrame:SetSizable(true) 894 | blobsProfiler.Menu.MenuFrame:SetMinWidth( blobsProfiler.Menu.MenuFrame:GetWide() ) 895 | blobsProfiler.Menu.MenuFrame:SetMinHeight( blobsProfiler.Menu.MenuFrame:GetTall() ) 896 | 897 | blobsProfiler.Menu.MenuFrame.OnRemove = function() 898 | killAllSourcePopups() 899 | end 900 | 901 | local tabMenu = vgui.Create( "DPropertySheet", blobsProfiler.Menu.MenuFrame) 902 | tabMenu:Dock( FILL ) 903 | 904 | local tabClient = vgui.Create("DPropertySheet", tabMenu) 905 | tabMenu:AddSheet("Client", tabClient, "icon16/application.png") 906 | 907 | local tabServer = vgui.Create("DPropertySheet", tabMenu) 908 | tabMenu:AddSheet("Server", tabServer, "icon16/application_xp_terminal.png") 909 | 910 | local tabSettings = vgui.Create("DPropertySheet", tabMenu) 911 | tabMenu:AddSheet("Settings", tabSettings, "icon16/cog.png") 912 | 913 | local luaStates = { 914 | Client = tabClient, 915 | Server = tabServer 916 | } 917 | 918 | local orderedModules = {} 919 | for moduleName, moduleData in pairs(blobsProfiler.Modules) do 920 | table.insert(orderedModules, {name=moduleName, data=moduleData}) 921 | end 922 | 923 | local function sortByLoadPriority(a, b) 924 | return (a.data.OrderPriority or 9999) < (b.data.OrderPriority or 9999) 925 | end 926 | 927 | table.sort(orderedModules, sortByLoadPriority) 928 | local firstSubModule = {} 929 | for luaState, statePanel in pairs(luaStates) do 930 | for loadOrderID, loadData in ipairs(orderedModules) do 931 | local moduleName = loadData.name 932 | local moduleData = loadData.data 933 | local usePanelType = moduleData.SubModules and "DPropertySheet" or "DPanel" 934 | 935 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Module panel setup for module: " .. moduleName .. " (".. luaState ..")") 936 | 937 | local moduleTab = vgui.Create(usePanelType, statePanel) 938 | local moduleSheet = statePanel:AddSheet( moduleName, moduleTab, moduleData.Icon ) 939 | 940 | if luaState == "Client" and moduleData.UpdateRealmData and moduleData.PreloadClient then 941 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Preloading client data for module: ".. moduleName) 942 | moduleData.UpdateRealmData("Client") 943 | end 944 | if luaState == "Server" and moduleData.UpdateRealmData and moduleData.PreloadServer then 945 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Preloading server data for module: ".. moduleName) 946 | moduleData.UpdateRealmData("Server") 947 | moduleData.retrievingData = true 948 | end 949 | 950 | if moduleData.BuildPanel then 951 | moduleData.BuildPanel(luaState, moduleTab) 952 | end 953 | 954 | if moduleData.SubModules then 955 | local orderedSubModules = {} 956 | for subModuleName, subModuleData in pairs(moduleData.SubModules) do 957 | table.insert(orderedSubModules, {name=subModuleName, data=subModuleData}) 958 | end 959 | 960 | table.sort(orderedSubModules, sortByLoadPriority) 961 | 962 | 963 | for subLoadOrderID, subLoadData in pairs(orderedSubModules) do 964 | local subModuleName = subLoadData.name 965 | local subModuleData = subLoadData.data 966 | 967 | if not firstSubModule[moduleName] then firstSubModule[moduleName] = {name=subModuleName, data=subModuleData} end 968 | 969 | local subModuleTab = vgui.Create("DPanel", moduleTab) 970 | local subModuleSheet = moduleTab:AddSheet( subModuleName, subModuleTab, subModuleData.Icon ) 971 | 972 | if luaState == "Client" and subModuleData.PreloadClient and subModuleData.UpdateRealmData then 973 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Preloading client data for ".. moduleName .." submodule: ".. subModuleName) 974 | subModuleData.UpdateRealmData("Client") 975 | end 976 | if luaState == "Server" and subModuleData.PreloadServer and subModuleData.UpdateRealmData then 977 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Preloading server data for ".. moduleName .." submodule: ".. subModuleName) 978 | subModuleData.UpdateRealmData("Server") 979 | subModuleData.retrievingData = true 980 | end 981 | 982 | if subModuleData.CustomPanel then 983 | subModuleData.CustomPanel(luaState, subModuleTab) 984 | end 985 | 986 | if subModuleData.BuildPanel then 987 | subModuleData.BuildPanel(luaState, subModuleTab) 988 | end 989 | if subModuleData.RefreshButton then 990 | local refreshButton = vgui.Create("DButton", subModuleTab) 991 | refreshButton:Dock(BOTTOM) 992 | refreshButton:SetText(subModuleData.RefreshButton) 993 | refreshButton.DoClick = function() 994 | subModuleData.UpdateRealmData(luaState) 995 | if luaState == "Server" then subModuleData.retrievingData = true end 996 | if luaState == "Client" and subModuleData.BuildPanel then 997 | subModuleData.BuildPanel(luaState, subModuleTab) 998 | end 999 | end 1000 | end 1001 | subModuleTab.PaintOver = function(s,w,h) 1002 | if luaState == "Server" and subModuleData.retrievingData then 1003 | surface.SetDrawColor(50,50,50,100) 1004 | surface.DrawRect(0,0,w,h) 1005 | draw.SimpleTextOutlined("Retrieving data..", "HudDefault", w/2, h/2, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, Color(0,0,0)) 1006 | 1007 | local subModuleFullName = moduleName .. "." .. subModuleName 1008 | if blobsProfiler.chunkModuleData[subModuleFullName] and blobsProfiler.chunkModuleData[subModuleFullName].receivedChunks then 1009 | local recvChunks = #blobsProfiler.chunkModuleData[subModuleFullName].receivedChunks 1010 | local totChunks = blobsProfiler.chunkModuleData[subModuleFullName].totalChunks 1011 | 1012 | if recChunks ~= totChunks then 1013 | draw.SimpleTextOutlined(recvChunks.. "/" .. totChunks, "HudDefault", w/2, h/2+15, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, Color(0,0,0)) 1014 | end 1015 | end 1016 | end 1017 | end 1018 | 1019 | if luaState == "Client" then 1020 | subModuleData.ClientTab = subModuleTab 1021 | elseif luaState == "Server" then 1022 | subModuleData.ServerTab = subModuleTab 1023 | local rawModuleName = moduleName .. "." .. subModuleName 1024 | local subModuleTable, parentModuleTable = blobsProfiler.GetModule(rawModuleName) 1025 | 1026 | subModuleSheet.Tab.PaintOver = function(s,w,h) 1027 | if parentModuleTable.childrenReceiving and parentModuleTable.childrenReceiving[rawModuleName] then 1028 | local recvTbl = parentModuleTable.childrenReceiving[rawModuleName] 1029 | local recvChunks = recvTbl.receivedChunks and #recvTbl.receivedChunks or 0 1030 | local totChunks = recvTbl.totalChunks or 1 1031 | local perc = recvChunks / totChunks 1032 | 1033 | local dynamicH = (moduleTab.GetActiveTab and moduleTab:GetActiveTab() == s) and h-7 or h -- this is SO dumb 1034 | local startY = (moduleTab.GetActiveTab and moduleTab:GetActiveTab() == s) and 0 or 1 1035 | 1036 | draw.RoundedBoxEx(4, 0, startY, perc * w, dynamicH, Color(255,255,0,50), true, true) 1037 | elseif subModuleTable.flashyUpdate then 1038 | if (moduleTab.GetActiveTab and moduleTab:GetActiveTab() == s) then -- TODO: DPanel subModuleTabs will never stop flashing 1039 | subModuleTable.flashyUpdate = nil 1040 | return 1041 | end 1042 | 1043 | local flashState = math.floor(CurTime() / 0.35) % 2 == 0 1044 | 1045 | if flashState then 1046 | local dynamicH = (moduleTab.GetActiveTab and moduleTab:GetActiveTab() == s) and h-7 or h -- this is SO dumb 1047 | local startY = (moduleTab.GetActiveTab and moduleTab:GetActiveTab() == s) and 0 or 1 1048 | 1049 | draw.RoundedBoxEx(4, 0, startY, w, dynamicH, Color(0,255,0,50), true, true) 1050 | end 1051 | elseif subModuleTable.retrievingData then 1052 | local marqueeSpeed = 50 -- Speed of the marquee movement in pixels per second 1053 | local marqueeWidth = 50 -- Width of the marquee rectangle 1054 | local currentTime = CurTime() 1055 | 1056 | local marqueeX = (currentTime * marqueeSpeed) % (w + marqueeWidth) - marqueeWidth 1057 | 1058 | local dynamicH = (moduleTab.GetActiveTab and moduleTab:GetActiveTab() == s) and h-7 or h -- this is SO dumb 1059 | local startY = (moduleTab.GetActiveTab and moduleTab:GetActiveTab() == s) and 0 or 1 1060 | 1061 | local visibleWidth = marqueeWidth 1062 | local drawLeftRound = false 1063 | local drawRightRound = false 1064 | 1065 | if marqueeX < 0 then -- off screen - left side 1066 | visibleWidth = marqueeWidth + marqueeX 1067 | marqueeX = 0 1068 | drawLeftRound = true 1069 | elseif marqueeX + marqueeWidth > w then -- off screen - right side 1070 | visibleWidth = w - marqueeX 1071 | drawRightRound = true 1072 | end 1073 | 1074 | draw.RoundedBoxEx( 1075 | 4, 1076 | marqueeX, 1077 | startY, 1078 | visibleWidth, 1079 | dynamicH, 1080 | Color(255, 255, 0, 50), 1081 | drawLeftRound, -- Top-left rounding if the left edge is offscreen 1082 | drawRightRound -- Top-right rounding if the right edge is offscreen 1083 | ) 1084 | end 1085 | end 1086 | end 1087 | moduleTab:OnActiveTabChanged(nil, moduleTab:GetActiveTab()) 1088 | end 1089 | 1090 | moduleTab.OnActiveTabChanged = function(s, pnlOld, pnlNew) 1091 | local rawModuleName = moduleName .. "." .. pnlNew:GetText() 1092 | local subModuleTable, parentModuleTable = blobsProfiler.GetModule(rawModuleName) 1093 | 1094 | if not blobsProfiler[luaState][moduleName] or not blobsProfiler[luaState][moduleName][pnlNew:GetText()] then 1095 | if subModuleTable.UpdateRealmData then 1096 | subModuleTable.UpdateRealmData(luaState) 1097 | if luaState == "Server" then 1098 | subModuleTable.retrievingData = true 1099 | end 1100 | end 1101 | end 1102 | 1103 | if subModuleTable.OnOpen then 1104 | local prntPanel 1105 | if luaState == "Client" then 1106 | prntPanel = subModuleTable.ClientTab 1107 | elseif luaState == "Server" then 1108 | prntPanel = subModuleTable.ServerTab 1109 | end 1110 | subModuleTable.OnOpen(luaState, prntPanel) 1111 | end 1112 | end 1113 | end 1114 | 1115 | moduleTab.PaintOver = function(s,w,h) 1116 | if luaState == "Server" and moduleData.retrievingData then 1117 | surface.SetDrawColor(50,50,50,100) 1118 | surface.DrawRect(0,0,w,h) 1119 | draw.SimpleTextOutlined("Retrieving data..", "HudDefault", w/2, h/2-15, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, Color(0,0,0)) 1120 | 1121 | if blobsProfiler.chunkModuleData[moduleName] and blobsProfiler.chunkModuleData[moduleName].receivedChunks then 1122 | local recvChunks = #blobsProfiler.chunkModuleData[moduleName].receivedChunks 1123 | local totChunks = blobsProfiler.chunkModuleData[moduleName].totalChunks 1124 | 1125 | if recChunks ~= totChunks then 1126 | draw.SimpleTextOutlined(recvChunks.. "/" .. totChunks, "HudDefault", w/2, h/2+15, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, Color(0,0,0)) 1127 | end 1128 | end 1129 | end 1130 | end 1131 | 1132 | if moduleData.RefreshButton then 1133 | local refreshButton = vgui.Create("DButton", moduleTab) 1134 | refreshButton:Dock(BOTTOM) 1135 | refreshButton:SetText(moduleData.RefreshButton) 1136 | refreshButton.DoClick = function() 1137 | moduleData.UpdateRealmData(luaState) 1138 | if luaState == "Server" then moduleData.retrievingData = true end 1139 | if luaState == "Client" and moduleData.BuildPanel then 1140 | moduleData.BuildPanel(luaState, moduleTab) 1141 | end 1142 | end 1143 | end 1144 | 1145 | if luaState == "Client" then 1146 | moduleData.ClientTab = moduleTab 1147 | elseif luaState == "Server" then 1148 | moduleData.ServerTab = moduleTab 1149 | 1150 | local moduleTable = blobsProfiler.GetModule(moduleName) 1151 | moduleSheet.Tab.PaintOver = function(s,w,h) 1152 | -- 'Total' indicator progress bar of all submodules progress 1153 | if moduleTable.childrenReceiving then 1154 | local totRecv = 0 1155 | local totChunks = 1 1156 | 1157 | for moduleN, moduleRD in pairs(moduleTable.childrenReceiving) do 1158 | totRecv = totRecv + (moduleRD.receivedChunks and #moduleRD.receivedChunks or 0) 1159 | totChunks = totChunks + (moduleRD.totalChunks or 1) 1160 | end 1161 | 1162 | local totalPerc = totRecv / totChunks 1163 | 1164 | local dynamicH = (statePanel.GetActiveTab and statePanel:GetActiveTab() == s) and h-7 or h -- this is SO dumb 1165 | local startY = (statePanel.GetActiveTab and statePanel:GetActiveTab() == s) and 0 or 1 1166 | draw.RoundedBoxEx(4, 0, startY, totalPerc * w, dynamicH, Color(255,255,0,50), true, true) 1167 | elseif moduleTable.flashyUpdate then 1168 | if (statePanel.GetActiveTab and statePanel:GetActiveTab() == s) then -- TODO: DPanel subModuleTabs will never stop flashing 1169 | moduleTable.flashyUpdate = nil 1170 | return 1171 | end 1172 | 1173 | local flashState = math.floor(CurTime() / 0.35) % 2 == 0 1174 | 1175 | if flashState then 1176 | local dynamicH = (statePanel.GetActiveTab and statePanel:GetActiveTab() == s) and h-7 or h -- this is SO dumb 1177 | local startY = (statePanel.GetActiveTab and statePanel:GetActiveTab() == s) and 0 or 1 1178 | draw.RoundedBoxEx(4, 0, startY, w, dynamicH, Color(0,255,0,50), true, true) 1179 | end 1180 | elseif moduleTable.retrievingData then 1181 | local marqueeSpeed = 50 -- Speed of the marquee movement in pixels per second 1182 | local marqueeWidth = 50 -- Width of the marquee rectangle 1183 | local currentTime = CurTime() 1184 | 1185 | local marqueeX = (currentTime * marqueeSpeed) % (w + marqueeWidth) - marqueeWidth 1186 | 1187 | local dynamicH = (statePanel.GetActiveTab and statePanel:GetActiveTab() == s) and h-7 or h -- this is SO dumb 1188 | local startY = (statePanel.GetActiveTab and statePanel:GetActiveTab() == s) and 0 or 1 1189 | 1190 | local visibleWidth = marqueeWidth 1191 | local drawLeftRound = false 1192 | local drawRightRound = false 1193 | 1194 | if marqueeX < 0 then 1195 | visibleWidth = marqueeWidth + marqueeX 1196 | marqueeX = 0 1197 | drawLeftRound = true 1198 | elseif marqueeX + marqueeWidth > w then 1199 | visibleWidth = w - marqueeX 1200 | drawRightRound = true 1201 | end 1202 | 1203 | draw.RoundedBoxEx( 1204 | 4, 1205 | marqueeX, 1206 | startY, 1207 | visibleWidth, 1208 | dynamicH, 1209 | Color(255, 255, 0, 50), 1210 | drawLeftRound, -- Top-left rounding if the left edge is offscreen 1211 | drawRightRound -- Top-right rounding if the right edge is offscreen 1212 | ) 1213 | end 1214 | end 1215 | end 1216 | end 1217 | 1218 | --[[ 1219 | local tabSQL = vgui.Create( "DPropertySheet", statePanel ) 1220 | statePanel:AddSheet( "SQLite", tabSQL, "icon16/database.png" ) 1221 | buildSQLTab(luaState, tabSQL) 1222 | blobsProfiler.Tabs[luaState].SQLite = tabSQL]] 1223 | end 1224 | 1225 | tabMenu.OnActiveTabChanged = function(s, pnlOld, pnlNew) 1226 | blobsProfiler.Menu.selectedRealm = pnlNew:GetText() 1227 | blobsProfiler.Menu.MenuFrame:SetTitle("blobsProfiler - " .. blobsProfiler.Menu.selectedRealm) 1228 | 1229 | local subActiveTab = pnlNew:GetPanel():GetActiveTab() 1230 | local subPropertySheetText = "" 1231 | if subActiveTab and subActiveTab:GetText() then 1232 | subPropertySheetText = " - " .. subActiveTab:GetText() 1233 | end 1234 | blobsProfiler.Menu.MenuFrame:SetTitle("blobsProfiler - " .. blobsProfiler.Menu.selectedRealm .. subPropertySheetText) 1235 | if not subActiveTab then return end 1236 | 1237 | if blobsProfiler.Modules[subActiveTab:GetText()] and firstSubModule[subActiveTab:GetText()] then 1238 | if pnlNew:GetText() == "Server" and firstSubModule[subActiveTab:GetText()].data.UpdateRealmData then 1239 | if blobsProfiler.Server[subActiveTab:GetText()][firstSubModule[subActiveTab:GetText()].name] then return end 1240 | firstSubModule[subActiveTab:GetText()].data.retrievingData = true 1241 | firstSubModule[subActiveTab:GetText()].data.UpdateRealmData(pnlNew:GetText()) 1242 | end 1243 | end 1244 | 1245 | -- get selected module tab, call on ActiveTabChanged 1246 | if pnlNew:GetText() == "Client" then 1247 | tabClient:OnActiveTabChanged(nil, tabClient:GetActiveTab()) 1248 | else 1249 | tabServer:OnActiveTabChanged(nil, tabServer:GetActiveTab()) 1250 | end 1251 | end 1252 | 1253 | tabClient.OnActiveTabChanged = function(s, pnlOld, pnlNew) 1254 | blobsProfiler.Menu.MenuFrame:SetTitle("blobsProfiler - " .. blobsProfiler.Menu.selectedRealm .. " - " .. pnlNew:GetText()) 1255 | local moduleTable = blobsProfiler.GetModule(pnlNew:GetText()) 1256 | 1257 | if not blobsProfiler.Client[pnlNew:GetText()] then 1258 | if moduleTable.UpdateRealmData then 1259 | moduleTable.UpdateRealmData("Client") 1260 | end 1261 | end 1262 | 1263 | local getSheet = pnlNew:GetPanel() 1264 | if getSheet.OnActiveTabChanged then 1265 | getSheet:OnActiveTabChanged(nil, getSheet:GetActiveTab()) 1266 | end 1267 | 1268 | if moduleTable.OnOpen then 1269 | moduleTable.OnOpen("Client", moduleTable.ClientTab) 1270 | end 1271 | end 1272 | 1273 | tabServer.OnActiveTabChanged = function(s, pnlOld, pnlNew) 1274 | blobsProfiler.Menu.MenuFrame:SetTitle("blobsProfiler - " .. blobsProfiler.Menu.selectedRealm .. " - " .. pnlNew:GetText()) 1275 | local moduleTable = blobsProfiler.GetModule(pnlNew:GetText()) 1276 | 1277 | if not blobsProfiler.Server[pnlNew:GetText()] then 1278 | if moduleTable.UpdateRealmData then 1279 | moduleTable.retrievingData = true 1280 | moduleTable.UpdateRealmData("Server") 1281 | end 1282 | end 1283 | 1284 | if firstSubModule[pnlNew:GetText()] then 1285 | if firstSubModule[pnlNew:GetText()].data.UpdateRealmData then 1286 | if blobsProfiler.Server[pnlNew:GetText()][firstSubModule[pnlNew:GetText()].name] then return end 1287 | firstSubModule[pnlNew:GetText()].data.retrievingData = true 1288 | firstSubModule[pnlNew:GetText()].data.UpdateRealmData("Server") 1289 | end 1290 | end 1291 | 1292 | local getSheet = pnlNew:GetPanel() 1293 | if getSheet.OnActiveTabChanged then 1294 | getSheet:OnActiveTabChanged(nil, getSheet:GetActiveTab()) 1295 | end 1296 | 1297 | if moduleTable.OnOpen then 1298 | moduleTable.OnOpen("Server", moduleTable.ServerTab) 1299 | end 1300 | end 1301 | 1302 | tabMenu:OnActiveTabChanged(nil, tabMenu:GetActiveTab()) -- lol 1303 | end) 1304 | 1305 | local function handleSVDataUpdate(rawModuleName, dataTable) 1306 | local moduleTable, parentModule = blobsProfiler.GetModule(rawModuleName) 1307 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "requestData module: ".. rawModuleName) 1308 | 1309 | if parentModule then 1310 | if parentModule.childrenReceiving[rawModuleName] then 1311 | parentModule.childrenReceiving[rawModuleName] = nil 1312 | end 1313 | 1314 | parentModule.flashyUpdate = true 1315 | else 1316 | if moduleTable.childrenReceiving[rawModuleName] then 1317 | moduleTable.childrenReceiving[rawModuleName] = nil 1318 | end 1319 | end 1320 | 1321 | blobsProfiler.SetRealmData("Server", rawModuleName, dataTable) 1322 | moduleTable.retrievingData = false 1323 | moduleTable.flashyUpdate = true 1324 | 1325 | if moduleTable.BuildPanel then 1326 | moduleTable.BuildPanel("Server", moduleTable.ServerTab) 1327 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Module.BuildPanel completed for ".. rawModuleName .. " (Server)") 1328 | end 1329 | end 1330 | 1331 | blobsProfiler.chunkModuleData = {} 1332 | 1333 | net.Receive("blobsProfiler:requestData", function() 1334 | local moduleName = net.ReadString() 1335 | local totalChunks = net.ReadUInt(16) 1336 | local currentChunk = net.ReadUInt(16) 1337 | local chunkData = net.ReadData(blobsProfiler.svDataChunkSize) 1338 | 1339 | local moduleSplit = string.Explode(".", moduleName) 1340 | local moduleParent = moduleSplit[1] 1341 | 1342 | if not blobsProfiler.chunkModuleData[moduleName] then 1343 | blobsProfiler.chunkModuleData[moduleName] = { 1344 | totalChunks = totalChunks, 1345 | receivedChunks = {}, 1346 | } 1347 | end 1348 | 1349 | if not blobsProfiler.chunkModuleData[moduleName].receivedChunks then 1350 | blobsProfiler.chunkModuleData[moduleName].receivedChunks = {} 1351 | end 1352 | 1353 | table.insert(blobsProfiler.chunkModuleData[moduleName].receivedChunks, chunkData) 1354 | 1355 | -- using blobsProfiler.Modules is acceptable here because it would look shit using .GetModule (i tried) 1356 | blobsProfiler.Modules[moduleParent].childrenReceiving = blobsProfiler.Modules[moduleParent].childrenReceiving or {} 1357 | blobsProfiler.Modules[moduleParent].childrenReceiving[moduleName] = blobsProfiler.chunkModuleData[moduleName] 1358 | 1359 | if #blobsProfiler.chunkModuleData[moduleName].receivedChunks == totalChunks then 1360 | local fullData = table.concat(blobsProfiler.chunkModuleData[moduleName].receivedChunks) 1361 | blobsProfiler.chunkModuleData[moduleName] = util.JSONToTable(util.Decompress(fullData), false, true) 1362 | 1363 | handleSVDataUpdate(moduleName, blobsProfiler.chunkModuleData[moduleName]) 1364 | 1365 | blobsProfiler.chunkModuleData[moduleName] = nil 1366 | blobsProfiler.Modules[moduleParent].childrenReceiving[moduleName] = nil 1367 | 1368 | if table.Count(blobsProfiler.Modules[moduleParent].childrenReceiving) == 0 then 1369 | blobsProfiler.Modules[moduleParent].childrenReceiving = nil 1370 | end 1371 | end 1372 | end) 1373 | 1374 | blobsProfiler.buildDTree = buildDTree -------------------------------------------------------------------------------- /lua/blobsprofiler/client/js/ace8.js.lua: -------------------------------------------------------------------------------- 1 | return [==[tion:"Add cursor below",exec:function(e){e.selectMoreLines(1)},bindKey:{win:"Ctrl-Alt-Down",mac:"Ctrl-Alt-Down"},scrollIntoView:"cursor",readOnly:!0},{name:"addCursorAboveSkipCurrent",description:"Add cursor above (skip current)",exec:function(e){e.selectMoreLines(-1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Up",mac:"Ctrl-Alt-Shift-Up"},scrollIntoView:"cursor",readOnly:!0},{name:"addCursorBelowSkipCurrent",description:"Add cursor below (skip current)",exec:function(e){e.selectMoreLines(1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Down",mac:"Ctrl-Alt-Shift-Down"},scrollIntoView:"cursor",readOnly:!0},{name:"selectMoreBefore",description:"Select more before",exec:function(e){e.selectMore(-1)},bindKey:{win:"Ctrl-Alt-Left",mac:"Ctrl-Alt-Left"},scrollIntoView:"cursor",readOnly:!0},{name:"selectMoreAfter",description:"Select more after",exec:function(e){e.selectMore(1)},bindKey:{win:"Ctrl-Alt-Right",mac:"Ctrl-Alt-Right"},scrollIntoView:"cursor",readOnly:!0},{name:"selectNextBefore",description:"Select next before",exec:function(e){e.selectMore(-1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Left",mac:"Ctrl-Alt-Shift-Left"},scrollIntoView:"cursor",readOnly:!0},{name:"selectNextAfter",description:"Select next after",exec:function(e){e.selectMore(1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Right",mac:"Ctrl-Alt-Shift-Right"},scrollIntoView:"cursor",readOnly:!0},{name:"toggleSplitSelectionIntoLines",description:"Split selection into lines",exec:function(e){e.multiSelect.rangeCount>1?e.multiSelect.joinSelections():e.multiSelect.splitIntoLines()},bindKey:{win:"Ctrl-Alt-L",mac:"Ctrl-Alt-L"},readOnly:!0},{name:"splitSelectionIntoLines",description:"Split into lines",exec:function(e){e.multiSelect.splitIntoLines()},readOnly:!0},{name:"alignCursors",description:"Align cursors",exec:function(e){e.alignCursors()},bindKey:{win:"Ctrl-Alt-A",mac:"Ctrl-Alt-A"},scrollIntoView:"cursor"},{name:"findAll",description:"Find all",exec:function(e){e.findAll()},bindKey:{win:"Ctrl-Alt-K",mac:"Ctrl-Alt-G"},scrollIntoView:"cursor",readOnly:!0}],t.multiSelectCommands=[{name:"singleSelection",description:"Single selection",bindKey:"esc",exec:function(e){e.exitMultiSelectMode()},scrollIntoView:"cursor",readOnly:!0,isAvailable:function(e){return e&&e.inMultiSelectMode}}];var i=e("../keyboard/hash_handler").HashHandler;t.keyboardHandler=new i(t.multiSelectCommands)}),define("ace/multi_select",["require","exports","module","ace/range_list","ace/range","ace/selection","ace/mouse/multi_select_handler","ace/lib/event","ace/lib/lang","ace/commands/multi_select_commands","ace/search","ace/edit_session","ace/editor","ace/config"],function(e,t,n){var i=e("./range_list").RangeList,o=e("./range").Range,r=e("./selection").Selection,s=e("./mouse/multi_select_handler").onMouseDown,a=e("./lib/event"),l=e("./lib/lang"),c=e("./commands/multi_select_commands");t.commands=c.defaultCommands.concat(c.multiSelectCommands);var h=new(e("./search")).Search;(function(){this.getSelectionMarkers=function(){return this.$selectionMarkers}}).call(e("./edit_session").EditSession.prototype),(function(){this.ranges=null,this.rangeList=null,this.addRange=function(e,t){if(e){if(!this.inMultiSelectMode&&0===this.rangeCount){var n=this.toOrientedRange();if(this.rangeList.add(n),this.rangeList.add(e),2!=this.rangeList.ranges.length)return this.rangeList.removeAll(),t||this.fromOrientedRange(e);this.rangeList.removeAll(),this.rangeList.add(n),this.$onAddRange(n)}e.cursor||(e.cursor=e.end);var i=this.rangeList.add(e);return this.$onAddRange(e),i.length&&this.$onRemoveRange(i),this.rangeCount>1&&!this.inMultiSelectMode&&(this._signal("multiSelect"),this.inMultiSelectMode=!0,this.session.$undoSelect=!1,this.rangeList.attach(this.session)),t||this.fromOrientedRange(e)}},this.toSingleRange=function(e){e=e||this.ranges[0];var t=this.rangeList.removeAll();t.length&&this.$onRemoveRange(t),e&&this.fromOrientedRange(e)},this.substractPoint=function(e){var t=this.rangeList.substractPoint(e);if(t)return this.$onRemoveRange(t),t[0]},this.mergeOverlappingRanges=function(){var e=this.rangeList.merge();e.length&&this.$onRemoveRange(e)},this.$onAddRange=function(e){this.rangeCount=this.rangeList.ranges.length,this.ranges.unshift(e),this._signal("addRange",{range:e})},this.$onRemoveRange=function(e){if(this.rangeCount=this.rangeList.ranges.length,1==this.rangeCount&&this.inMultiSelectMode){var t=this.rangeList.ranges.pop();e.push(t),this.rangeCount=0}for(var n=e.length;n--;){var i=this.ranges.indexOf(e[n]);this.ranges.splice(i,1)}this._signal("removeRange",{ranges:e}),0===this.rangeCount&&this.inMultiSelectMode&&(this.inMultiSelectMode=!1,this._signal("singleSelect"),this.session.$undoSelect=!0,this.rangeList.detach(this.session)),(t=t||this.ranges[0])&&!t.isEqual(this.getRange())&&this.fromOrientedRange(t)},this.$initRangeList=function(){this.rangeList||(this.rangeList=new i,this.ranges=[],this.rangeCount=0)},this.getAllRanges=function(){return this.rangeCount?this.rangeList.ranges.concat():[this.getRange()]},this.splitIntoLines=function(){for(var e=this.ranges.length?this.ranges:[this.getRange()],t=[],n=0;n1){var e=this.rangeList.ranges,t=e[e.length-1],n=o.fromPoints(e[0].start,t.end);this.toSingleRange(),this.setSelectionRange(n,t.cursor==t.start)}else{var i=this.session.documentToScreenPosition(this.cursor),r=this.session.documentToScreenPosition(this.anchor);this.rectangularRangeBlock(i,r).forEach(this.addRange,this)}},this.rectangularRangeBlock=function(e,t,n){var i,r=[],s=e.column0;)v--;if(v>0)for(var y=0;r[y].isEmpty();)y++;for(var w=v;w>=y;w--)r[w].isEmpty()&&r.splice(w,1)}return r}}).call(r.prototype);var u=e("./editor").Editor;function d(e,t){return e.row==t.row&&e.column==t.column}function p(e){e.$multiselectOnSessionChange||(e.$onAddRange=e.$onAddRange.bind(e),e.$onRemoveRange=e.$onRemoveRange.bind(e),e.$onMultiSelect=e.$onMultiSelect.bind(e),e.$onSingleSelect=e.$onSingleSelect.bind(e),e.$multiselectOnSessionChange=t.onSessionChange.bind(e),e.$checkMultiselectChange=e.$checkMultiselectChange.bind(e),e.$multiselectOnSessionChange(e),e.on("changeSession",e.$multiselectOnSessionChange),e.on("mousedown",s),e.commands.addCommands(c.defaultCommands),function e(t){if(t.textInput){var n=t.textInput.getElement(),i=!1;a.addListener(n,"keydown",function(e){var n=18==e.keyCode&&!(e.ctrlKey||e.shiftKey||e.metaKey);t.$blockSelectEnabled&&n?i||(t.renderer.setMouseCursor("crosshair"),i=!0):i&&o()},t),a.addListener(n,"keyup",o,t),a.addListener(n,"blur",o,t)}function o(e){i&&(t.renderer.setMouseCursor(""),i=!1)}}(e))}(function(){this.updateSelectionMarkers=function(){this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.addSelectionMarker=function(e){e.cursor||(e.cursor=e.end);var t=this.getSelectionStyle();return e.marker=this.session.addMarker(e,"ace_selection",t),this.session.$selectionMarkers.push(e),this.session.selectionMarkerCount=this.session.$selectionMarkers.length,e},this.removeSelectionMarker=function(e){if(e.marker){this.session.removeMarker(e.marker);var t=this.session.$selectionMarkers.indexOf(e);-1!=t&&this.session.$selectionMarkers.splice(t,1),this.session.selectionMarkerCount=this.session.$selectionMarkers.length}},this.removeSelectionMarkers=function(e){for(var t=this.session.$selectionMarkers,n=e.length;n--;){var i=e[n];if(i.marker){this.session.removeMarker(i.marker);var o=t.indexOf(i);-1!=o&&t.splice(o,1)}}this.session.selectionMarkerCount=t.length},this.$onAddRange=function(e){this.addSelectionMarker(e.range),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onRemoveRange=function(e){this.removeSelectionMarkers(e.ranges),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onMultiSelect=function(e){this.inMultiSelectMode||(this.inMultiSelectMode=!0,this.setStyle("ace_multiselect"),this.keyBinding.addKeyboardHandler(c.keyboardHandler),this.commands.setDefaultHandler("exec",this.$onMultiSelectExec),this.renderer.updateCursor(),this.renderer.updateBackMarkers())},this.$onSingleSelect=function(e){this.session.multiSelect.inVirtualMode||(this.inMultiSelectMode=!1,this.unsetStyle("ace_multiselect"),this.keyBinding.removeKeyboardHandler(c.keyboardHandler),this.commands.removeDefaultHandler("exec",this.$onMultiSelectExec),this.renderer.updateCursor(),this.renderer.updateBackMarkers(),this._emit("changeSelection"))},this.$onMultiSelectExec=function(e){var t=e.command,n=e.editor;if(n.multiSelect){if(t.multiSelectAction)"forEach"==t.multiSelectAction?i=n.forEachSelection(t,e.args):"forEachLine"==t.multiSelectAction?i=n.forEachSelection(t,e.args,!0):"single"==t.multiSelectAction?(n.exitMultiSelectMode(),i=t.exec(n,e.args||{})):i=t.multiSelectAction(n,e.args||{});else{var i=t.exec(n,e.args||{});n.multiSelect.addRange(n.multiSelect.toOrientedRange()),n.multiSelect.mergeOverlappingRanges()}return i}},this.forEachSelection=function(e,t,n){if(!this.inVirtualSelectionMode){var i,o=n&&n.keepOrder,s=!0==n||n&&n.$byLines,a=this.session,l=this.selection,c=l.rangeList,h=(o?l:c).ranges;if(!h.length)return e.exec?e.exec(this,t||{}):e(this,t||{});var u=l._eventRegistry;l._eventRegistry={};var d=new r(a);this.inVirtualSelectionMode=!0;for(var p=h.length;p--;){if(s)for(;p>0&&h[p].start.row==h[p-1].end.row;)p--;d.fromOrientedRange(h[p]),d.index=p,this.selection=a.selection=d;var g=e.exec?e.exec(this,t||{}):e(this,t||{});i||void 0===g||(i=g),d.toOrientedRange(h[p])}d.detach(),this.selection=a.selection=l,this.inVirtualSelectionMode=!1,l._eventRegistry=u,l.mergeOverlappingRanges(),l.ranges[0]&&l.fromOrientedRange(l.ranges[0]);var f=this.renderer.$scrollAnimation;return this.onCursorChange(),this.onSelectionChange(),f&&f.from==f.to&&this.renderer.animateScrolling(f.from),i}},this.exitMultiSelectMode=function(){this.inMultiSelectMode&&!this.inVirtualSelectionMode&&this.multiSelect.toSingleRange()},this.getSelectedText=function(){var e="";if(this.inMultiSelectMode&&!this.inVirtualSelectionMode){for(var t=this.multiSelect.rangeList.ranges,n=[],i=0;is&&(s=n.column),ih?e.insert(i,l.stringRepeat(" ",r-h)):e.remove(new o(i.row,i.column,i.row,i.column-r+h)),t.start.column=t.end.column=s,t.start.row=t.end.row=i.row,t.cursor=t.end}),t.fromOrientedRange(n[0]),this.renderer.updateCursor(),this.renderer.updateBackMarkers()}else{var h=this.selection.getRange(),u=h.start.row,d=h.end.row,p=u==d;if(p){var g,f=this.session.getLength();do g=this.session.getLine(d);while(/[=:]/.test(g)&&++d0);u<0&&(u=0),d>=f&&(d=f-1)}var m=this.session.removeFullLines(u,d);m=this.$reAlignText(m,p),this.session.insert({row:u,column:0},m.join("\n")+"\n"),p||(h.start.column=0,h.end.column=m[m.length-1].length),this.selection.setRange(h)}},this.$reAlignText=function(e,t){var n,i,o,r=!0,s=!0;return e.map(function(e){var t=e.match(/(\s*)(.*?)(\s*)([=:].*)/);return t?null==n?(n=t[1].length,i=t[2].length,o=t[3].length,t):(n+i+o!=t[1].length+t[2].length+t[3].length&&(s=!1),n!=t[1].length&&(r=!1),n>t[1].length&&(n=t[1].length),it[3].length&&(o=t[3].length),t):[e]}).map(t?c:r?s?function e(t){return t[2]?a(n+i-t[2].length)+t[2]+a(o)+t[4].replace(/^([=:])\s+/,"$1 "):t[0]}:c:function e(t){return t[2]?a(n)+t[2]+a(o)+t[4].replace(/^([=:])\s+/,"$1 "):t[0]});function a(e){return l.stringRepeat(" ",e)}function c(e){return e[2]?a(n)+e[2]+a(i-e[2].length+o)+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}}}).call(u.prototype),t.onSessionChange=function(e){var t=e.session;t&&!t.multiSelect&&(t.$selectionMarkers=[],t.selection.$initRangeList(),t.multiSelect=t.selection),this.multiSelect=t&&t.multiSelect;var n=e.oldSession;n&&(n.multiSelect.off("addRange",this.$onAddRange),n.multiSelect.off("removeRange",this.$onRemoveRange),n.multiSelect.off("multiSelect",this.$onMultiSelect),n.multiSelect.off("singleSelect",this.$onSingleSelect),n.multiSelect.lead.off("change",this.$checkMultiselectChange),n.multiSelect.anchor.off("change",this.$checkMultiselectChange)),t&&(t.multiSelect.on("addRange",this.$onAddRange),t.multiSelect.on("removeRange",this.$onRemoveRange),t.multiSelect.on("multiSelect",this.$onMultiSelect),t.multiSelect.on("singleSelect",this.$onSingleSelect),t.multiSelect.lead.on("change",this.$checkMultiselectChange),t.multiSelect.anchor.on("change",this.$checkMultiselectChange)),t&&this.inMultiSelectMode!=t.selection.inMultiSelectMode&&(t.selection.inMultiSelectMode?this.$onMultiSelect():this.$onSingleSelect())},t.MultiSelect=p,e("./config").defineOptions(u.prototype,"editor",{enableMultiselect:{set:function(e){p(this),e?this.on("mousedown",s):this.off("mousedown",s)},value:!0},enableBlockSelect:{set:function(e){this.$blockSelectEnabled=e},value:!0}})}),define("ace/mode/folding/fold_mode",["require","exports","module","ace/range"],function(e,t,n){"use strict";var i=e("../../range").Range;(function(){this.foldingStartMarker=null,this.foldingStopMarker=null,this.getFoldWidget=function(e,t,n){var i=e.getLine(n);return this.foldingStartMarker.test(i)?"start":"markbeginend"==t&&this.foldingStopMarker&&this.foldingStopMarker.test(i)?"end":""},this.getFoldWidgetRange=function(e,t,n){return null},this.indentationBlock=function(e,t,n){var o=/\S/,r=e.getLine(t),s=r.search(o);if(-1!=s){for(var a=n||r.length,l=e.getLength(),c=t,h=t;++tc){var p=e.getLine(h).length;return new i(c,a,h,p)}}},this.openingBracketBlock=function(e,t,n,o,r){var s={row:n,column:o+1},a=e.$findClosingBracket(t,s,r);if(a){var l=e.foldWidgets[a.row];return null==l&&(l=e.getFoldWidget(a.row)),"start"==l&&a.row>s.row&&(a.row--,a.column=e.getLine(a.row).length),i.fromPoints(s,a)}},this.closingBracketBlock=function(e,t,n,o,r){var s={row:n,column:o},a=e.$findOpeningBracket(t,s);if(a)return a.column++,s.column--,i.fromPoints(a,s)}}).call((t.FoldMode=function(){}).prototype)}),define("ace/ext/error_marker",["require","exports","module","ace/line_widgets","ace/lib/dom","ace/range","ace/config"],function(e,t,n){"use strict";var i=e("../line_widgets").LineWidgets,o=e("../lib/dom"),r=e("../range").Range,s=e("../config").nls;t.showErrorMarker=function(e,t){var n,a=e.session;a.widgetManager||(a.widgetManager=new i(a),a.widgetManager.attach(e));var l=e.getCursorPosition(),c=l.row,h=a.widgetManager.getWidgetsAtRow(c).filter(function(e){return"errorMarker"==e.type})[0];h?h.destroy():c-=t;var u=function e(t,n,i){var o=t.getAnnotations().sort(r.comparePoints);if(o.length){var s=function e(t,n,i){for(var o=0,r=t.length-1;o<=r;){var s=o+r>>1,a=i(n,t[s]);if(a>0)o=s+1;else{if(!(a<0))return s;r=s-1}}return-(o+1)}(o,{row:n,column:-1},r.comparePoints);s<0&&(s=-s-1),s>=o.length?s=i>0?0:o.length-1:0===s&&i<0&&(s=o.length-1);var a=o[s];if(a&&i){if(a.row===n){do a=o[s+=i];while(a&&a.row===n);if(!a)return o.slice()}var l=[];n=a.row;do l[i<0?"unshift":"push"](a),a=o[s+=i];while(a&&a.row==n);return l.length&&l}}}(a,c,t);if(u){var d=u[0];l.column=(d.pos&&"number"!=typeof d.column?d.pos.sc:d.column)||0,l.row=d.row,n=e.renderer.$gutterLayer.$annotations[l.row]}else{if(h)return;n={displayText:[s("error-marker.good-state","Looks good!")],className:"ace_ok"}}e.session.unfold(l.row),e.selection.moveToPosition(l);var p={row:l.row,fixedWidth:!0,coverGutter:!0,el:o.createElement("div"),type:"errorMarker"},g=p.el.appendChild(o.createElement("div")),f=p.el.appendChild(o.createElement("div"));f.className="error_widget_arrow "+n.className;var m=e.renderer.$cursorLayer.getPixelPosition(l).left;f.style.left=m+e.renderer.gutterWidth-5+"px",p.el.className="error_widget_wrapper",g.className="error_widget "+n.className,n.displayText.forEach(function(e,t){g.appendChild(o.createTextNode(e)),t|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|=" 52 | }, { 53 | token: "paren.lparen", 54 | regex: "[\\(]" 55 | }, { 56 | token: "paren.rparen", 57 | regex: "[\\)]" 58 | }, { 59 | token: "text", 60 | regex: "\\s+" 61 | }] 62 | }; 63 | this.normalizeRules(); 64 | }; 65 | oop.inherits(SqlHighlightRules, TextHighlightRules); 66 | exports.SqlHighlightRules = SqlHighlightRules; 67 | 68 | }); 69 | 70 | define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"], function(require, exports, module){"use strict"; 71 | var oop = require("../../lib/oop"); 72 | var Range = require("../../range").Range; 73 | var BaseFoldMode = require("./fold_mode").FoldMode; 74 | var FoldMode = exports.FoldMode = function (commentRegex) { 75 | if (commentRegex) { 76 | this.foldingStartMarker = new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.start)); 77 | this.foldingStopMarker = new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.end)); 78 | } 79 | }; 80 | oop.inherits(FoldMode, BaseFoldMode); 81 | (function () { 82 | this.foldingStartMarker = /([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/; 83 | this.foldingStopMarker = /^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/; 84 | this.singleLineBlockCommentRe = /^\s*(\/\*).*\*\/\s*$/; 85 | this.tripleStarBlockCommentRe = /^\s*(\/\*\*\*).*\*\/\s*$/; 86 | this.startRegionRe = /^\s*(\/\*|\/\/)#?region\b/; 87 | this._getFoldWidgetBase = this.getFoldWidget; 88 | this.getFoldWidget = function (session, foldStyle, row) { 89 | var line = session.getLine(row); 90 | if (this.singleLineBlockCommentRe.test(line)) { 91 | if (!this.startRegionRe.test(line) && !this.tripleStarBlockCommentRe.test(line)) 92 | return ""; 93 | } 94 | var fw = this._getFoldWidgetBase(session, foldStyle, row); 95 | if (!fw && this.startRegionRe.test(line)) 96 | return "start"; // lineCommentRegionStart 97 | return fw; 98 | }; 99 | this.getFoldWidgetRange = function (session, foldStyle, row, forceMultiline) { 100 | var line = session.getLine(row); 101 | if (this.startRegionRe.test(line)) 102 | return this.getCommentRegionBlock(session, line, row); 103 | var match = line.match(this.foldingStartMarker); 104 | if (match) { 105 | var i = match.index; 106 | if (match[1]) 107 | return this.openingBracketBlock(session, match[1], row, i); 108 | var range = session.getCommentFoldRange(row, i + match[0].length, 1); 109 | if (range && !range.isMultiLine()) { 110 | if (forceMultiline) { 111 | range = this.getSectionRange(session, row); 112 | } 113 | else if (foldStyle != "all") 114 | range = null; 115 | } 116 | return range; 117 | } 118 | if (foldStyle === "markbegin") 119 | return; 120 | var match = line.match(this.foldingStopMarker); 121 | if (match) { 122 | var i = match.index + match[0].length; 123 | if (match[1]) 124 | return this.closingBracketBlock(session, match[1], row, i); 125 | return session.getCommentFoldRange(row, i, -1); 126 | } 127 | }; 128 | this.getSectionRange = function (session, row) { 129 | var line = session.getLine(row); 130 | var startIndent = line.search(/\S/); 131 | var startRow = row; 132 | var startColumn = line.length; 133 | row = row + 1; 134 | var endRow = row; 135 | var maxRow = session.getLength(); 136 | while (++row < maxRow) { 137 | line = session.getLine(row); 138 | var indent = line.search(/\S/); 139 | if (indent === -1) 140 | continue; 141 | if (startIndent > indent) 142 | break; 143 | var subRange = this.getFoldWidgetRange(session, "all", row); 144 | if (subRange) { 145 | if (subRange.start.row <= startRow) { 146 | break; 147 | } 148 | else if (subRange.isMultiLine()) { 149 | row = subRange.end.row; 150 | } 151 | else if (startIndent == indent) { 152 | break; 153 | } 154 | } 155 | endRow = row; 156 | } 157 | return new Range(startRow, startColumn, endRow, session.getLine(endRow).length); 158 | }; 159 | this.getCommentRegionBlock = function (session, line, row) { 160 | var startColumn = line.search(/\s*$/); 161 | var maxRow = session.getLength(); 162 | var startRow = row; 163 | var re = /^\s*(?:\/\*|\/\/|--)#?(end)?region\b/; 164 | var depth = 1; 165 | while (++row < maxRow) { 166 | line = session.getLine(row); 167 | var m = re.exec(line); 168 | if (!m) 169 | continue; 170 | if (m[1]) 171 | depth--; 172 | else 173 | depth++; 174 | if (!depth) 175 | break; 176 | } 177 | var endRow = row; 178 | if (endRow > startRow) { 179 | return new Range(startRow, startColumn, endRow, line.length); 180 | } 181 | }; 182 | }).call(FoldMode.prototype); 183 | 184 | }); 185 | 186 | define("ace/mode/folding/sql",["require","exports","module","ace/lib/oop","ace/mode/folding/cstyle"], function(require, exports, module){"use strict"; 187 | var oop = require("../../lib/oop"); 188 | var BaseFoldMode = require("./cstyle").FoldMode; 189 | var FoldMode = exports.FoldMode = function () { }; 190 | oop.inherits(FoldMode, BaseFoldMode); 191 | (function () { 192 | }).call(FoldMode.prototype); 193 | 194 | }); 195 | 196 | define("ace/mode/sql",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/sql_highlight_rules","ace/mode/folding/sql"], function(require, exports, module){"use strict"; 197 | var oop = require("../lib/oop"); 198 | var TextMode = require("./text").Mode; 199 | var SqlHighlightRules = require("./sql_highlight_rules").SqlHighlightRules; 200 | var SqlFoldMode = require("./folding/sql").FoldMode; 201 | var Mode = function () { 202 | this.HighlightRules = SqlHighlightRules; 203 | this.foldingRules = new SqlFoldMode(); 204 | this.$behaviour = this.$defaultBehaviour; 205 | }; 206 | oop.inherits(Mode, TextMode); 207 | (function () { 208 | this.lineCommentStart = "--"; 209 | this.blockComment = { start: "/*", end: "*/" }; 210 | this.$id = "ace/mode/sql"; 211 | this.snippetFileId = "ace/snippets/sql"; 212 | }).call(Mode.prototype); 213 | exports.Mode = Mode; 214 | 215 | }); (function() { 216 | window.require(["ace/mode/sql"], function(m) { 217 | if (typeof module == "object" && typeof exports == "object" && module) { 218 | module.exports = m; 219 | } 220 | }); 221 | })(); 222 | 223 | ]==] -------------------------------------------------------------------------------- /lua/blobsprofiler/client/vgui/vgui_bpdtree.lua: -------------------------------------------------------------------------------- 1 | 2 | local PANEL = {} 3 | 4 | AccessorFunc( PANEL, "m_bShowIcons", "ShowIcons" ) 5 | AccessorFunc( PANEL, "m_iIndentSize", "IndentSize" ) 6 | AccessorFunc( PANEL, "m_iLineHeight", "LineHeight" ) 7 | AccessorFunc( PANEL, "m_pSelectedItem", "SelectedItem" ) 8 | AccessorFunc( PANEL, "m_bClickOnDragHover", "ClickOnDragHover" ) 9 | 10 | function PANEL:Init() 11 | 12 | --self:SetMouseInputEnabled( true ) 13 | --self:SetClickOnDragHover( false ) 14 | 15 | self:SetShowIcons( true ) 16 | self:SetIndentSize( 14 ) 17 | self:SetLineHeight( 17 ) 18 | --self:SetPadding( 2 ) 19 | 20 | self.RootNode = self:GetCanvas():Add( "BP_DTree_Node" ) 21 | self.RootNode:SetRoot( self ) 22 | self.RootNode:SetParentNode( self ) 23 | self.RootNode:Dock( TOP ) 24 | self.RootNode:SetText( "" ) 25 | self.RootNode:SetExpanded( true, true ) 26 | self.RootNode:DockMargin( 0, 4, 0, 0 ) 27 | 28 | self:SetPaintBackground( true ) 29 | 30 | end 31 | 32 | -- 33 | -- Get the root node 34 | -- 35 | function PANEL:Root() 36 | return self.RootNode 37 | end 38 | 39 | function PANEL:AddNode( strName, strIcon ) 40 | 41 | return self.RootNode:AddNode( strName, strIcon ) 42 | 43 | end 44 | 45 | function PANEL:ChildExpanded( bExpand ) 46 | 47 | self:InvalidateLayout() 48 | 49 | end 50 | 51 | function PANEL:ShowIcons() 52 | 53 | return self.m_bShowIcons 54 | 55 | end 56 | 57 | function PANEL:ExpandTo( bExpand ) 58 | end 59 | 60 | function PANEL:SetExpanded( bExpand ) 61 | 62 | -- The top most node shouldn't react to this. 63 | 64 | end 65 | 66 | function PANEL:Clear() 67 | self:Root():Clear() 68 | end 69 | 70 | function PANEL:Paint( w, h ) 71 | 72 | derma.SkinHook( "Paint", "Tree", self, w, h ) 73 | return true 74 | 75 | end 76 | 77 | function PANEL:DoClick( node ) 78 | return false 79 | end 80 | 81 | function PANEL:DoRightClick( node ) 82 | return false 83 | end 84 | 85 | function PANEL:SetSelectedItem( node ) 86 | 87 | if ( IsValid( self.m_pSelectedItem ) ) then 88 | self.m_pSelectedItem:SetSelected( false ) 89 | end 90 | 91 | self.m_pSelectedItem = node 92 | 93 | if ( node ) then 94 | node:SetSelected( true ) 95 | node:OnNodeSelected( node ) 96 | end 97 | 98 | end 99 | 100 | function PANEL:OnNodeSelected( node ) 101 | end 102 | 103 | function PANEL:MoveChildTo( child, pos ) 104 | 105 | self:InsertAtTop( child ) 106 | 107 | end 108 | 109 | function PANEL:LayoutTree() 110 | 111 | self:InvalidateChildren( true ) 112 | 113 | end 114 | 115 | function PANEL:GenerateExample( ClassName, PropertySheet, Width, Height ) 116 | 117 | local ctrl = vgui.Create( ClassName ) 118 | --ctrl:SetPadding( 5 ) 119 | ctrl:SetSize( 300, 300 ) 120 | 121 | local node = ctrl:AddNode( "Node One" ) 122 | local node = ctrl:AddNode( "Node Two" ) 123 | 124 | local cnode = node:AddNode( "Node 2.1" ) 125 | local cnode = node:AddNode( "Node 2.2" ) 126 | local cnode = node:AddNode( "Node 2.3" ) 127 | local cnode = node:AddNode( "Node 2.4" ) 128 | local cnode = node:AddNode( "Node 2.5" ) 129 | for i = 1, 64 do 130 | local gcnode = cnode:AddNode( "Node 2.5." .. i ) 131 | end 132 | local cnode = node:AddNode( "Node 2.6" ) 133 | 134 | local node = ctrl:AddNode( "Node Three ( Maps Folder )" ) 135 | node:MakeFolder( "maps", "GAME" ) 136 | 137 | local node = ctrl:AddNode( "Node Four" ) 138 | 139 | PropertySheet:AddSheet( ClassName, ctrl, nil, true, true ) 140 | 141 | end 142 | 143 | derma.DefineControl( "BP_DTree", "Tree View", PANEL, "DScrollPanel" ) -------------------------------------------------------------------------------- /lua/blobsprofiler/client/vgui/vgui_bpdtree_node.lua: -------------------------------------------------------------------------------- 1 | 2 | local PANEL = {} 3 | 4 | AccessorFunc( PANEL, "m_pRoot", "Root" ) 5 | 6 | AccessorFunc( PANEL, "m_pParentNode", "ParentNode" ) 7 | 8 | AccessorFunc( PANEL, "m_strFolder", "Folder" ) 9 | AccessorFunc( PANEL, "m_strFileName", "FileName" ) 10 | AccessorFunc( PANEL, "m_strPathID", "PathID" ) 11 | AccessorFunc( PANEL, "m_strWildCard", "WildCard" ) 12 | AccessorFunc( PANEL, "m_bNeedsPopulating", "NeedsPopulating" ) 13 | AccessorFunc( PANEL, "m_bShowFiles", "ShowFiles", FORCE_BOOL ) 14 | 15 | AccessorFunc( PANEL, "m_bDirty", "Dirty", FORCE_BOOL ) 16 | AccessorFunc( PANEL, "m_bHideExpander", "HideExpander", FORCE_BOOL ) 17 | AccessorFunc( PANEL, "m_bNeedsChildSearch", "NeedsChildSearch", FORCE_BOOL ) 18 | 19 | AccessorFunc( PANEL, "m_bForceShowExpander", "ForceShowExpander", FORCE_BOOL ) 20 | AccessorFunc( PANEL, "m_bDoubleClickToOpen", "DoubleClickToOpen", FORCE_BOOL ) 21 | 22 | AccessorFunc( PANEL, "m_bLastChild", "LastChild", FORCE_BOOL ) 23 | AccessorFunc( PANEL, "m_bDrawLines", "DrawLines", FORCE_BOOL ) 24 | AccessorFunc( PANEL, "m_bExpanded", "Expanded", FORCE_BOOL ) 25 | AccessorFunc( PANEL, "m_strDraggableName", "DraggableName" ) 26 | 27 | function PANEL:Init() 28 | 29 | self:SetDoubleClickToOpen( true ) 30 | 31 | self.Label = vgui.Create( "BP_DTree_Node_Button", self ) 32 | self.Label:SetDragParent( self ) 33 | self.Label.DoClick = function() self:InternalDoClick() end 34 | self.Label.DoDoubleClick = function() self:InternalDoClick() end 35 | self.Label.DoRightClick = function() self:InternalDoRightClick() end 36 | self.Label.DragHover = function( s, t ) self:DragHover( t ) end 37 | 38 | self.Expander = vgui.Create( "DExpandButton", self ) 39 | self.Expander.DoClick = function() self:SetExpanded( !self:GetExpanded() ) end 40 | self.Expander:SetVisible( false ) 41 | 42 | self.Icon = vgui.Create( "DImage", self ) 43 | self.Icon:SetImage( "icon16/folder.png" ) 44 | self.Icon:SizeToContents() 45 | 46 | self.animSlide = Derma_Anim( "Anim", self, self.AnimSlide ) 47 | 48 | self.fLastClick = SysTime() 49 | 50 | self:SetDrawLines( false ) 51 | self:SetLastChild( false ) 52 | 53 | end 54 | 55 | function PANEL:IsFunc() 56 | self.Expander = vgui.Create("DCheckBox", self) 57 | self.Expander:SetTooltip("Toggle Profiling") 58 | end 59 | 60 | function PANEL:IsRootNode() 61 | return self.m_pRoot == self.m_pParentNode 62 | end 63 | 64 | function PANEL:InternalDoClick() 65 | 66 | self:GetRoot():SetSelectedItem( self ) 67 | 68 | if ( self:DoClick() ) then return end 69 | if ( self:GetRoot():DoClick( self ) ) then return end 70 | 71 | if ( !self.m_bDoubleClickToOpen || ( SysTime() - self.fLastClick < 0.3 ) ) then 72 | self:SetExpanded( !self:GetExpanded() ) 73 | end 74 | 75 | self.fLastClick = SysTime() 76 | 77 | end 78 | 79 | function PANEL:OnNodeSelected( node ) 80 | 81 | local parent = self:GetParentNode() 82 | if ( IsValid( parent ) && parent.OnNodeSelected ) then 83 | parent:OnNodeSelected( node ) 84 | end 85 | 86 | end 87 | 88 | function PANEL:OnNodeAdded( node ) 89 | -- Called when Panel.AddNode is called on this node 90 | end 91 | 92 | function PANEL:InternalDoRightClick() 93 | 94 | if ( self:DoRightClick() ) then return end 95 | if ( self:GetRoot():DoRightClick( self ) ) then return end 96 | 97 | end 98 | 99 | function PANEL:DoClick() 100 | return false 101 | end 102 | 103 | function PANEL:DoRightClick() 104 | return false 105 | end 106 | 107 | function PANEL:Clear() 108 | if ( IsValid( self.ChildNodes ) ) then self.ChildNodes:Clear() end 109 | end 110 | 111 | function PANEL:AnimSlide( anim, delta, data ) 112 | 113 | if ( !IsValid( self.ChildNodes ) ) then anim:Stop() return end 114 | 115 | if ( anim.Started ) then 116 | data.To = self:GetTall() 117 | data.Visible = self.ChildNodes:IsVisible() 118 | self.canExpand = false 119 | end 120 | 121 | if ( anim.Finished ) then 122 | self:InvalidateLayout() 123 | self.ChildNodes:SetVisible( data.Visible ) 124 | self:SetTall( data.To ) 125 | self:GetParentNode():ChildExpanded() 126 | self.canExpand = true 127 | return 128 | end 129 | 130 | self.ChildNodes:SetVisible( true ) 131 | 132 | self:SetTall( Lerp( delta, data.From, data.To ) ) 133 | 134 | self:GetParentNode():ChildExpanded() 135 | 136 | end 137 | 138 | function PANEL:SetIcon( str ) 139 | if ( !str || str == "" ) then return end 140 | 141 | self.Icon:SetImage( str ) 142 | end 143 | 144 | function PANEL:ShowIcons() 145 | return self:GetParentNode():ShowIcons() 146 | end 147 | 148 | function PANEL:GetLineHeight() 149 | return self:GetParentNode():GetLineHeight() 150 | end 151 | 152 | function PANEL:GetIndentSize() 153 | return self:GetParentNode():GetIndentSize() 154 | end 155 | 156 | function PANEL:SetText( strName ) 157 | 158 | self.Label:SetText( strName ) 159 | 160 | end 161 | 162 | function PANEL:ExpandRecurse( bExpand ) 163 | 164 | self:SetExpanded( bExpand, true ) 165 | 166 | if ( !IsValid( self.ChildNodes ) ) then return end 167 | 168 | for k, Child in pairs( self.ChildNodes:GetChildren() ) do 169 | if ( Child.ExpandRecurse ) then 170 | Child:ExpandRecurse( bExpand ) 171 | end 172 | end 173 | 174 | end 175 | 176 | function PANEL:ExpandTo( bExpand ) 177 | 178 | self:SetExpanded( bExpand, true ) 179 | self:GetParentNode():ExpandTo( bExpand ) 180 | 181 | end 182 | 183 | function PANEL:SetExpanded( bExpand, bSurpressAnimation ) 184 | 185 | self:GetParentNode():ChildExpanded( bExpand ) 186 | if self.Expander.SetExpanded then self.Expander:SetExpanded( bExpand ) end -- we use checkboxes for function profiling selection, this will error hehe 187 | self.m_bExpanded = bExpand 188 | self:InvalidateLayout( true ) 189 | 190 | if ( !IsValid( self.ChildNodes ) ) then return end 191 | 192 | local StartTall = self:GetTall() 193 | self.animSlide:Stop() 194 | 195 | -- Populate the child folders.. 196 | if ( bExpand && self:PopulateChildrenAndSelf( true ) ) then 197 | -- Could really do with a 'loading' thing here 198 | return 199 | end 200 | 201 | if ( IsValid( self.ChildNodes ) ) then 202 | self.ChildNodes:SetVisible( bExpand ) 203 | if ( bExpand ) then 204 | self.ChildNodes:InvalidateLayout( true ) 205 | end 206 | end 207 | 208 | self:InvalidateLayout( true ) 209 | 210 | -- Do animation.. 211 | if ( !bSurpressAnimation ) then 212 | self.animSlide:Start( 0.3, { From = StartTall } ) 213 | self.animSlide:Run() 214 | end 215 | 216 | end 217 | 218 | function PANEL:ChildExpanded( bExpand ) 219 | 220 | self.ChildNodes:InvalidateLayout( true ) 221 | self:InvalidateLayout( true ) 222 | self:GetParentNode():ChildExpanded( bExpand ) 223 | 224 | end 225 | 226 | function PANEL:Paint() 227 | end 228 | 229 | function PANEL:HasChildren() 230 | 231 | if ( !IsValid( self.ChildNodes ) ) then return false end 232 | return self.ChildNodes:HasChildren() 233 | 234 | end 235 | 236 | function PANEL:DoChildrenOrder() 237 | 238 | if ( !IsValid( self.ChildNodes ) ) then return end 239 | 240 | local children = self.ChildNodes:GetChildren() 241 | local last = #children 242 | 243 | for i = 1, (last - 1) do 244 | children[i]:SetLastChild( false ) 245 | end 246 | children[last]:SetLastChild( true ) 247 | 248 | end 249 | 250 | function PANEL:PerformRootNodeLayout() 251 | 252 | self.Expander:SetVisible( false ) 253 | self.Label:SetVisible( false ) 254 | self.Icon:SetVisible( false ) 255 | 256 | if ( IsValid( self.ChildNodes ) ) then 257 | 258 | self.ChildNodes:Dock( TOP ) 259 | self:SetTall( self.ChildNodes:GetTall() ) 260 | 261 | end 262 | 263 | end 264 | 265 | function PANEL:PerformLayout() 266 | 267 | if ( self:IsRootNode() ) then 268 | return self:PerformRootNodeLayout() 269 | end 270 | 271 | if ( self.animSlide:Active() ) then return end 272 | 273 | local LineHeight = self:GetLineHeight() 274 | 275 | if ( self.m_bHideExpander ) then 276 | 277 | self.Expander:SetPos( -11, 0 ) 278 | self.Expander:SetSize( 15, 15 ) 279 | self.Expander:SetVisible( false ) 280 | 281 | else 282 | 283 | self.Expander:SetPos( 2, 0 ) 284 | self.Expander:SetSize( 15, 15 ) 285 | self.Expander:SetVisible( self.NeedsLazyLoad || self:HasChildren() || self:GetForceShowExpander() ) 286 | self.Expander:SetZPos( 10 ) 287 | 288 | end 289 | 290 | self.Label:StretchToParent( 0, nil, 0, nil ) 291 | self.Label:SetTall( LineHeight ) 292 | 293 | if ( self:ShowIcons() ) then 294 | self.Icon:SetVisible( true ) 295 | self.Icon:SetPos( self.Expander.x + self.Expander:GetWide() + 4, ( LineHeight - self.Icon:GetTall() ) * 0.5 ) 296 | self.Label:SetTextInset( self.Icon.x + self.Icon:GetWide() + 4, 0 ) 297 | else 298 | self.Icon:SetVisible( false ) 299 | self.Label:SetTextInset( self.Expander.x + self.Expander:GetWide() + 4, 0 ) 300 | end 301 | 302 | if ( !IsValid( self.ChildNodes ) || !self.ChildNodes:IsVisible() ) then 303 | self:SetTall( LineHeight ) 304 | return 305 | end 306 | 307 | self.ChildNodes:SizeToContents() 308 | self:SetTall( LineHeight + self.ChildNodes:GetTall() ) 309 | 310 | self.ChildNodes:StretchToParent( LineHeight, LineHeight, 0, 0 ) 311 | 312 | self:DoChildrenOrder() 313 | 314 | end 315 | 316 | function PANEL:CreateChildNodes() 317 | 318 | if ( IsValid( self.ChildNodes ) ) then return end 319 | 320 | self.ChildNodes = vgui.Create( "DListLayout", self ) 321 | self.ChildNodes:SetDropPos( "852" ) 322 | self.ChildNodes:SetVisible( self:GetExpanded() ) 323 | self.ChildNodes.OnChildRemoved = function() 324 | 325 | self.ChildNodes:InvalidateLayout() 326 | 327 | -- Root node should never be closed 328 | if ( !self.ChildNodes:HasChildren() && !self:IsRootNode() ) then 329 | self:SetExpanded( false ) 330 | end 331 | 332 | end 333 | 334 | self.ChildNodes.OnModified = function() 335 | 336 | self:OnModified() 337 | 338 | end 339 | 340 | self:InvalidateLayout() 341 | 342 | end 343 | 344 | function PANEL:AddPanel( pPanel ) 345 | 346 | self:CreateChildNodes() 347 | 348 | self.ChildNodes:Add( pPanel ) 349 | self:InvalidateLayout() 350 | 351 | end 352 | 353 | function PANEL:AddNode( strName, strIcon ) 354 | 355 | self:CreateChildNodes() 356 | 357 | local pNode = vgui.Create( "BP_DTree_Node", self ) 358 | pNode:SetText( strName ) 359 | pNode:SetParentNode( self ) 360 | pNode:SetTall( self:GetLineHeight() ) 361 | pNode:SetRoot( self:GetRoot() ) 362 | pNode:SetIcon( strIcon ) 363 | pNode:SetDrawLines( !self:IsRootNode() ) 364 | 365 | self:InstallDraggable( pNode ) 366 | 367 | self.ChildNodes:Add( pNode ) 368 | self:InvalidateLayout() 369 | 370 | -- Let addons do whatever they need 371 | self:OnNodeAdded( pNode ) 372 | 373 | return pNode 374 | 375 | end 376 | 377 | function PANEL:InsertNode( pNode ) 378 | 379 | self:CreateChildNodes() 380 | 381 | pNode:SetParentNode( self ) 382 | pNode:SetRoot( self:GetRoot() ) 383 | self:InstallDraggable( pNode ) 384 | 385 | self.ChildNodes:Add( pNode ) 386 | self:InvalidateLayout() 387 | 388 | return pNode 389 | 390 | end 391 | 392 | function PANEL:InstallDraggable( pNode ) 393 | 394 | local DragName = self:GetDraggableName() 395 | if ( !DragName ) then return end 396 | 397 | -- Make this node draggable 398 | pNode:SetDraggableName( DragName ) 399 | pNode:Droppable( DragName ) 400 | 401 | -- Allow item dropping onto us 402 | self.ChildNodes:MakeDroppable( DragName, true ) 403 | 404 | end 405 | 406 | function PANEL:DroppedOn( pnl ) 407 | 408 | self:InsertNode( pnl ) 409 | self:SetExpanded( true ) 410 | 411 | end 412 | 413 | function PANEL:AddFolder( strName, strFolder, strPath, bShowFiles, strWildCard, bDontForceExpandable ) 414 | 415 | local node = self:AddNode( strName ) 416 | node:MakeFolder( strFolder, strPath, bShowFiles, strWildCard, bDontForceExpandable ) 417 | return node 418 | 419 | end 420 | 421 | function PANEL:MakeFolder( strFolder, strPath, bShowFiles, strWildCard, bDontForceExpandable ) 422 | 423 | -- Store the data 424 | self:SetNeedsPopulating( true ) 425 | self:SetWildCard( strWildCard || "*" ) 426 | self:SetFolder( strFolder ) 427 | self:SetPathID( strPath ) 428 | self:SetShowFiles( bShowFiles || false ) 429 | 430 | self:CreateChildNodes() 431 | self:SetNeedsChildSearch( true ) 432 | 433 | if ( !bDontForceExpandable ) then 434 | self:SetForceShowExpander( true ) 435 | end 436 | 437 | -- If the parent is already open, populate myself. Do not require the user to collapse and expand for this to happen 438 | if ( self:GetParentNode():GetExpanded() ) then 439 | -- Yuck! This is basically a hack for gameprops.lua 440 | timer.Simple( 0, function() 441 | if ( !IsValid( self ) ) then return end 442 | self:PopulateChildrenAndSelf() 443 | end ) 444 | end 445 | 446 | end 447 | 448 | function PANEL:FilePopulateCallback( files, folders, foldername, path, bAndChildren, wildcard ) 449 | 450 | local showfiles = self:GetShowFiles() 451 | 452 | self.ChildNodes:InvalidateLayout( true ) 453 | 454 | local FileCount = 0 455 | 456 | if ( folders ) then 457 | 458 | for k, File in SortedPairsByValue( folders ) do 459 | 460 | local Node = self:AddNode( File ) 461 | Node:MakeFolder( string.Trim( foldername .. "/" .. File, "/" ), path, showfiles, wildcard, true ) 462 | FileCount = FileCount + 1 463 | 464 | end 465 | 466 | end 467 | 468 | if ( showfiles ) then 469 | 470 | for k, File in SortedPairs( files ) do 471 | 472 | local icon = "icon16/page_white.png" 473 | 474 | local Node = self:AddNode( File, icon ) 475 | Node:SetFileName( string.Trim( foldername .. "/" .. File, "/" ) ) 476 | FileCount = FileCount + 1 477 | 478 | end 479 | 480 | end 481 | 482 | if ( FileCount == 0 ) then 483 | self.ChildNodes:Remove() 484 | self.ChildNodes = nil 485 | 486 | self:SetNeedsPopulating( false ) 487 | self:SetShowFiles( nil ) 488 | self:SetWildCard( nil ) 489 | 490 | self:InvalidateLayout() 491 | 492 | self.Expander:SetExpanded( true ) 493 | 494 | return 495 | end 496 | 497 | self:InvalidateLayout() 498 | 499 | end 500 | 501 | function PANEL:FilePopulate( bAndChildren, bExpand ) 502 | 503 | if ( !self:GetNeedsPopulating() ) then return end 504 | 505 | local folder = self:GetFolder() 506 | local path = self:GetPathID() 507 | local wildcard = self:GetWildCard() 508 | 509 | if ( !folder || !wildcard || !path ) then return false end 510 | 511 | local files, folders = file.Find( string.Trim( folder .. "/" .. wildcard, "/" ), path ) 512 | if ( folders && folders[ 1 ] == "/" ) then table.remove( folders, 1 ) end 513 | 514 | self:SetNeedsPopulating( false ) 515 | self:SetNeedsChildSearch( false ) 516 | 517 | self:FilePopulateCallback( files, folders, folder, path, bAndChildren, wildcard ) 518 | 519 | if ( bExpand ) then 520 | self:SetExpanded( true ) 521 | end 522 | 523 | return true 524 | 525 | end 526 | 527 | function PANEL:PopulateChildren() 528 | 529 | if ( !IsValid( self.ChildNodes ) ) then return end 530 | 531 | for k, v in ipairs( self.ChildNodes:GetChildren() ) do 532 | timer.Simple( k * 0.1, function() 533 | 534 | if ( IsValid( v ) ) then 535 | v:FilePopulate( false ) 536 | end 537 | 538 | end ) 539 | 540 | end 541 | 542 | end 543 | 544 | function PANEL:PopulateChildrenAndSelf( bExpand ) 545 | 546 | -- Make sure we're populated 547 | if ( self:FilePopulate( true, bExpand ) ) then return true end 548 | 549 | self:PopulateChildren() 550 | 551 | end 552 | 553 | function PANEL:SetSelected( b ) 554 | 555 | self.Label:SetSelected( b ) 556 | self.Label:InvalidateLayout() 557 | 558 | end 559 | 560 | function PANEL:Think() 561 | 562 | self.animSlide:Run() 563 | 564 | end 565 | 566 | -- 567 | -- DragHoverClick 568 | -- 569 | function PANEL:DragHoverClick( HoverTime ) 570 | 571 | if ( !self:GetExpanded() ) then 572 | self:SetExpanded( true ) 573 | end 574 | 575 | if ( self:GetRoot():GetClickOnDragHover() ) then 576 | 577 | self:InternalDoClick() 578 | 579 | end 580 | 581 | end 582 | 583 | function PANEL:MoveToTop() 584 | 585 | local parent = self:GetParentNode() 586 | if ( !IsValid( parent ) ) then return end 587 | 588 | self:GetParentNode():MoveChildTo( self, 1 ) 589 | 590 | end 591 | 592 | function PANEL:MoveChildTo( child ) 593 | 594 | self.ChildNodes:InsertAtTop( child ) 595 | 596 | end 597 | 598 | function PANEL:GetText() 599 | return self.Label:GetText() 600 | end 601 | 602 | function PANEL:GetIcon() 603 | return self.Icon:GetImage() 604 | end 605 | 606 | function PANEL:CleanList() 607 | 608 | for k, panel in pairs( self.Items ) do 609 | 610 | if ( !IsValid( panel ) || panel:GetParent() != self.pnlCanvas ) then 611 | self.Items[k] = nil 612 | end 613 | 614 | end 615 | 616 | end 617 | 618 | function PANEL:Insert( pNode, pNodeNextTo, bBefore ) 619 | 620 | pNode:SetParentNode( self ) 621 | pNode:SetRoot( self:GetRoot() ) 622 | 623 | self:CreateChildNodes() 624 | 625 | if ( bBefore ) then 626 | self.ChildNodes:InsertBefore( pNodeNextTo, pNode ) 627 | else 628 | self.ChildNodes:InsertAfter( pNodeNextTo, pNode ) 629 | end 630 | 631 | self:InvalidateLayout() 632 | 633 | end 634 | 635 | function PANEL:LeaveTree( pnl ) 636 | 637 | self.ChildNodes:RemoveItem( pnl, true ) 638 | self:InvalidateLayout() 639 | 640 | end 641 | 642 | function PANEL:OnModified() 643 | 644 | -- Override Me 645 | 646 | end 647 | 648 | function PANEL:GetChildNode( iNum ) 649 | 650 | if ( !IsValid( self.ChildNodes ) ) then return end 651 | return self.ChildNodes:GetChild( iNum ) 652 | 653 | end 654 | 655 | function PANEL:GetChildNodes() 656 | 657 | if ( !IsValid( self.ChildNodes ) ) then return {} end 658 | return self.ChildNodes:GetChildren() 659 | 660 | end 661 | 662 | function PANEL:GetChildNodeCount() 663 | 664 | if ( !IsValid( self.ChildNodes ) ) then return 0 end 665 | return self.ChildNodes:ChildCount() 666 | 667 | end 668 | 669 | function PANEL:Paint( w, h ) 670 | 671 | derma.SkinHook( "Paint", "TreeNode", self, w, h ) 672 | 673 | end 674 | 675 | function PANEL:Copy() 676 | 677 | local copy = vgui.Create( "BP_DTree_Node", self:GetParent() ) 678 | copy:SetText( self:GetText() ) 679 | copy:SetIcon( self:GetIcon() ) 680 | copy:SetRoot( self:GetRoot() ) 681 | copy:SetParentNode( self:GetParentNode() ) 682 | 683 | if ( self.ChildNodes ) then 684 | 685 | for k, v in ipairs( self.ChildNodes:GetChildren() ) do 686 | 687 | local childcopy = v:Copy() 688 | copy:InsertNode( childcopy ) 689 | 690 | end 691 | 692 | end 693 | 694 | self:SetupCopy( copy ) 695 | 696 | return copy 697 | 698 | end 699 | 700 | function PANEL:SetupCopy( copy ) 701 | 702 | -- TODO. 703 | 704 | end 705 | 706 | derma.DefineControl( "BP_DTree_Node", "Tree Node", PANEL, "DPanel" ) -------------------------------------------------------------------------------- /lua/blobsprofiler/client/vgui/vgui_bpdtree_node_button.lua: -------------------------------------------------------------------------------- 1 | 2 | local PANEL = {} 3 | 4 | function PANEL:Init() 5 | 6 | self:SetTextInset( 32, 0 ) 7 | self:SetContentAlignment( 4 ) 8 | 9 | end 10 | 11 | function PANEL:Paint( w, h ) 12 | 13 | derma.SkinHook( "Paint", "TreeNodeButton", self, w, h ) 14 | 15 | -- 16 | -- Draw the button text 17 | -- 18 | return false 19 | 20 | end 21 | 22 | function PANEL:UpdateColours( skin ) 23 | 24 | if ( self:IsSelected() ) then return self:SetTextStyleColor( skin.Colours.Tree.Selected ) end 25 | if ( self.Hovered ) then return self:SetTextStyleColor( skin.Colours.Tree.Hover ) end 26 | 27 | return self:SetTextStyleColor( skin.Colours.Tree.Normal ) 28 | 29 | end 30 | 31 | function PANEL:GenerateExample() 32 | 33 | -- Do nothing! 34 | 35 | end 36 | 37 | derma.DefineControl( "BP_DTree_Node_Button", "Tree Node Button", PANEL, "DButton" ) -------------------------------------------------------------------------------- /lua/blobsprofiler/server/sv_blobsprofiler.lua: -------------------------------------------------------------------------------- 1 | print("blobsProfiler - SV INIT") 2 | 3 | util.AddNetworkString("blobsProfiler:requestSource") 4 | util.AddNetworkString("blobsProfiler:sendSourceChunk") 5 | 6 | util.AddNetworkString("blobsProfiler:requestData") 7 | 8 | local function SendChunkedString(ply, requestID, largeString, highlightLine) 9 | local chunkSize = 30000 -- If changed, don't forget to change on the client! 10 | local totalLength = #largeString 11 | 12 | for i = 1, totalLength, chunkSize do 13 | local chunk = largeString:sub(i, i + chunkSize - 1) 14 | net.Start("blobsProfiler:sendSourceChunk") 15 | net.WriteString(requestID) 16 | net.WriteUInt(i, 32) 17 | net.WriteString(chunk) 18 | net.WriteUInt(highlightLine or 0, 16) -- ugh 19 | net.Send(ply) 20 | end 21 | end 22 | 23 | net.Receive("blobsProfiler:requestSource", function(l, ply) 24 | if not blobsProfiler.CanAccess(ply, "requestSource") then return end 25 | 26 | local filePath = net.ReadString() 27 | local startLine = net.ReadUInt(16) 28 | local endLine = net.ReadUInt(16) 29 | 30 | local highlightLine 31 | if startLine ~= 0 and endLine == 0 then 32 | highlightLine = startLine 33 | startLine = 0 34 | endLine = 0 35 | end 36 | 37 | if startLine < 0 or endLine < 0 or startLine > endLine then return end 38 | 39 | local findFile = file.Open(filePath, "r", "GAME") 40 | if not findFile then 41 | blobsProfiler.Log(blobsProfiler.L_NH_ERROR, "Cannot find file: " .. filePath) 42 | return 43 | end 44 | 45 | local fileContent = {} 46 | local currentLine = 1 47 | local readWholeFile = (startLine == 0 and endLine == 0) 48 | 49 | if startLine == 0 then 50 | startLine = 1 51 | end 52 | 53 | while not findFile:EndOfFile() do 54 | local line = findFile:ReadLine() 55 | 56 | if readWholeFile or (currentLine >= startLine and currentLine <= endLine) then 57 | table.insert(fileContent, line) 58 | end 59 | 60 | if not readWholeFile and currentLine > endLine then 61 | break 62 | end 63 | 64 | currentLine = currentLine + 1 65 | end 66 | 67 | findFile:Close() 68 | 69 | local combinedSource = table.concat(fileContent) 70 | local requestID = string.format("View source: %s (Lines: %i-%i)", filePath, startLine, (endLine == 0 and currentLine) or endLine) -- Generate request ID 71 | SendChunkedString(ply, requestID, combinedSource, highlightLine) 72 | end) 73 | 74 | local transmissionStates = {} 75 | 76 | local function sendDataToClient(ply, moduleName, dataTbl) 77 | if transmissionStates[ply] and transmissionStates[ply][moduleName] then 78 | return 79 | end 80 | 81 | local data = util.Compress(util.TableToJSON(dataTbl)) 82 | local totalChunks = math.ceil(#data / blobsProfiler.svDataChunkSize) 83 | 84 | transmissionStates[ply] = transmissionStates[ply] or {} 85 | transmissionStates[ply][moduleName] = { 86 | data = data, 87 | totalChunks = totalChunks, 88 | currentChunk = 1 89 | } 90 | 91 | local function sendNextChunk() 92 | if not IsValid(ply) then return end 93 | local state = transmissionStates[ply][moduleName] 94 | if not state then return end 95 | 96 | local startIdx = (state.currentChunk - 1) * blobsProfiler.svDataChunkSize + 1 97 | local endIdx = math.min(startIdx + blobsProfiler.svDataChunkSize - 1, #state.data) 98 | local chunk = string.sub(state.data, startIdx, endIdx) 99 | 100 | net.Start("blobsProfiler:requestData") 101 | net.WriteString(moduleName) 102 | net.WriteUInt(state.totalChunks, 16) 103 | net.WriteUInt(state.currentChunk, 16) 104 | net.WriteData(chunk, #chunk) 105 | net.Send(ply) 106 | 107 | state.currentChunk = state.currentChunk + 1 108 | if state.currentChunk > state.totalChunks then 109 | transmissionStates[ply][moduleName] = nil 110 | else 111 | timer.Simple(0.1, sendNextChunk) 112 | end 113 | end 114 | 115 | sendNextChunk() 116 | end 117 | 118 | local function transformModuleNameForPermissionsCheck(moduleName) 119 | -- Example: Takes 'Lua.Globals' and turns it into {'Lua', 'Lua_Globals'} 120 | local part1, part2 = string.match(moduleName, "([^%.]+)%.(.+)") 121 | 122 | if part1 and part2 then 123 | return { part1, part1 .. "_" .. part2 } 124 | else 125 | return { moduleName } 126 | end 127 | end 128 | 129 | blobsProfiler.SendModuleData = function(rawModuleName, ply) 130 | if not blobsProfiler.CanAccess(ply, "serverData") then return end 131 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "requestData SV: ".. rawModuleName) 132 | 133 | local permStrs = transformModuleNameForPermissionsCheck(rawModuleName) 134 | for k, v in ipairs(permStrs) do 135 | if not blobsProfiler.CanAccess(ply, "serverData"..v) then return end -- Passing 'Lua.Globals' will check 'serverData_Lua' & 'serverData_Lua_Globals' 136 | end 137 | 138 | local moduleTable, parentModuleTable = blobsProfiler.GetModule(rawModuleName) 139 | 140 | local dataTbl 141 | 142 | if moduleTable and moduleTable.PrepServerData then 143 | dataTbl = moduleTable:PrepServerData() 144 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Module: ".. rawModuleName .. " PrepServerData() called!") 145 | end 146 | 147 | if not dataTbl then 148 | blobsProfiler.Log(blobsProfiler.L_NH_ERROR, "Module: ".. rawModuleName .." did not return data in PrepServerData()!") 149 | dataTbl = {} 150 | end 151 | 152 | sendDataToClient(ply, rawModuleName, dataTbl) 153 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Module: ".. rawModuleName .." data sent to client!") 154 | end 155 | 156 | net.Receive("blobsProfiler:requestData", function(l, ply) 157 | local rawModuleName = net.ReadString() 158 | 159 | blobsProfiler.SendModuleData(rawModuleName, ply) 160 | end) -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/modules/bp_concommands.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler.RegisterModule("ConCommands", { 2 | Icon = "icon16/application_xp_terminal.png", 3 | OrderPriority = 4, 4 | UpdateRealmData = function(luaState) 5 | if luaState == "Client" then 6 | local concmdTbl = concommand.GetTable() 7 | local concommandsData = {} 8 | 9 | for ccName, ccFunc in pairs(concmdTbl) do 10 | ccName = tostring(ccName) 11 | 12 | local debugInfo = debug.getinfo(ccFunc) 13 | concommandsData[ccName] = debugInfo 14 | concommandsData[ccName].func = ccFunc 15 | 16 | concommandsData[ccName].fakeVarType = "function" 17 | end 18 | 19 | blobsProfiler.Client.ConCommands = concommandsData 20 | else 21 | net.Start("blobsProfiler:requestData") 22 | net.WriteString("ConCommands") 23 | net.SendToServer() 24 | end 25 | end, 26 | PrepServerData = function() 27 | local concmdTbl = concommand.GetTable() 28 | local concommandsData = {} 29 | 30 | for ccName, ccFunc in pairs(concmdTbl) do 31 | ccName = tostring(ccName) 32 | 33 | local debugInfo = debug.getinfo(ccFunc) 34 | concommandsData[ccName] = debugInfo 35 | concommandsData[ccName].func = tostring(ccFunc) 36 | 37 | concommandsData[ccName].fakeVarType = "function" 38 | end 39 | 40 | return concommandsData 41 | end, 42 | PreloadClient = true, 43 | PreloadServer = false, 44 | BuildPanel = function(luaState, parentPanel) 45 | blobsProfiler.buildDTree(luaState, parentPanel, "ConCommands") 46 | end, 47 | RefreshButton = "Re-scan" 48 | }) -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/modules/bp_errors.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler.RegisterModule("Errors", { 2 | Icon = "icon16/bug.png", 3 | OrderPriority = 9, 4 | UpdateRealmData = function(luaState) 5 | if luaState == "Client" then 6 | -- we dont need to set, it's automatically set by the OnLuaError hook 7 | else 8 | net.Start("blobsProfiler:requestData") 9 | net.WriteString("Errors") 10 | net.SendToServer() 11 | end 12 | end, 13 | PrepServerData = function() 14 | return blobsProfiler.Server and blobsProfiler.Server.Errors or {} 15 | end, 16 | PreloadClient = true, 17 | PreloadServer = false, 18 | BuildPanel = function(luaState, parentPanel) 19 | blobsProfiler.buildDTree(luaState, parentPanel, "Errors") 20 | end, 21 | OnOpen = function(luaState, parentPanel) 22 | if luaState == "Client" then 23 | blobsProfiler.buildDTree(luaState, parentPanel, "Errors") 24 | end 25 | end, 26 | RefreshButton = "Reload", 27 | FormatNodeName = function(luaState, nodeKey, nodeValue) 28 | if nodeKey == "Last Errored" or nodeKey == "First Errored" then 29 | return nodeKey .. ": ".. os.date("%c", nodeValue) 30 | elseif not istable(nodeValue) then 31 | return nodeKey .. ": ".. tostring(nodeValue) 32 | elseif istable(nodeValue) and nodeValue.Error then 33 | return nodeValue.Error 34 | elseif istable(nodeValue) and nodeValue.fakeVarType == "file" then 35 | return "[" .. nodeValue.Level .. "] " .. nodeValue.Source .. ":" ..nodeValue.Line 36 | end 37 | 38 | return nodeKey 39 | end, 40 | FormatNodeIcon = function(luaState, nodeKey, nodeValue) 41 | if nodeKey == "Last Errored" or nodeKey == "First Errored" then 42 | return "icon16/clock.png" 43 | elseif nodeKey == "Count" then 44 | return "icon16/chart_bar.png" 45 | elseif nodeKey == "Stacktrace" then 46 | return "icon16/script_error.png" 47 | elseif nodeKey == "Addon ID" then 48 | return "icon16/plugin_link.png" 49 | elseif istable(nodeValue) and nodeValue.Stacktrace and nodeValue.Count then 50 | return "icon16/bug_error.png" 51 | elseif istable(nodeValue) and nodeValue.fakeVarType == "file" then 52 | return "icon16/page_white_code.png" 53 | end 54 | end 55 | }) 56 | 57 | local function capitalizeFirstLetter(str) 58 | return (str and str:sub(1, 1):upper() .. str:sub(2):lower()) or str -- are you shitting me 59 | end 60 | 61 | hook.Add("OnLuaError", "test", function( err, realm, stack, name, addon_id ) 62 | local luaState = capitalizeFirstLetter(realm) 63 | 64 | name = name or "Unknown Origin" 65 | err = err or "Unknown Error" -- should this ever happen? 66 | 67 | local errorKey = util.CRC(err .. "\n" .. (stack and util.TableToJSON(stack) or "No Stack Trace")) 68 | 69 | blobsProfiler[luaState] = blobsProfiler[luaState] or {} 70 | blobsProfiler[luaState].Errors = blobsProfiler[luaState].Errors or {} 71 | blobsProfiler[luaState].Errors[name] = blobsProfiler[luaState].Errors[name] or {} 72 | 73 | if not blobsProfiler[luaState].Errors[name][errorKey] then 74 | local genStack 75 | for k, stackDetails in ipairs(stack) do 76 | genStack = genStack or {} 77 | 78 | local stackData = {} 79 | stackData.fakeVarType = "file" 80 | stackData.Level = k 81 | stackData.Func = tostring(stackDetails.Function) 82 | stackData.Source = stackDetails.File 83 | stackData.Line = stackDetails.Line 84 | 85 | table.insert(genStack, stackData) 86 | end 87 | 88 | blobsProfiler[luaState].Errors[name][errorKey] = { 89 | Error = err, 90 | Count = 1, 91 | Stacktrace = genStack, 92 | ["Addon ID"] = (tonumber(addon_id) ~= 0) and addon_id or nil, 93 | ["First Errored"] = os.time() 94 | } 95 | else 96 | blobsProfiler[luaState].Errors[name][errorKey].Count = blobsProfiler[luaState].Errors[name][errorKey].Count + 1 97 | blobsProfiler[luaState].Errors[name][errorKey]["Last Errored"] = os.time() 98 | end 99 | end) -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/modules/bp_files.lua: -------------------------------------------------------------------------------- 1 | local localIncludedFiles = {} 2 | 3 | local function normalizePath(filePath) 4 | return string.gsub(filePath, '\\', '/') 5 | end 6 | 7 | local scanNewFiles = {} 8 | -- Function to recursively build nested tables for directories 9 | local function addFileToTable(directoryTable, filePath) 10 | local parts = string.Explode("/", filePath) 11 | local currentTable = directoryTable 12 | 13 | for i = 1, #parts - 1 do 14 | local part = parts[i] 15 | currentTable[part] = currentTable[part] or {} 16 | currentTable = currentTable[part] 17 | end 18 | 19 | if not table.HasValue(currentTable, parts[#parts]) then 20 | table.insert(currentTable, parts[#parts]) 21 | table.insert(scanNewFiles, filePath) 22 | end 23 | end 24 | 25 | local function scanTable(tbl, visited) 26 | for key, value in pairs(tbl) do 27 | if type(value) == "function" then 28 | local info = debug.getinfo(value, "S") 29 | if info and info.source and info.source:sub(1, 1) == "@" then 30 | local fullPath = info.source:sub(2) 31 | if string.EndsWith(fullPath, ".lua") then 32 | addFileToTable(localIncludedFiles, fullPath) 33 | end 34 | end 35 | elseif type(value) == "table" and not visited[value] then 36 | visited[value] = true 37 | scanTable(value, visited) 38 | end 39 | end 40 | end 41 | 42 | blobsProfiler.ScanGLoadedFiles = function() 43 | scanNewFiles = {} 44 | 45 | local visited = {} 46 | visited[_G] = true 47 | scanTable(_G, visited) 48 | 49 | return localIncludedFiles, scanNewFiles 50 | end 51 | 52 | blobsProfiler.RegisterModule("Files", { 53 | Icon = "icon16/folder_page.png", 54 | OrderPriority = 5, 55 | UpdateRealmData = function(luaState) 56 | if luaState == "Client" then 57 | local allFiles, newFiles = blobsProfiler.ScanGLoadedFiles() 58 | 59 | blobsProfiler.Client.Files = allFiles 60 | else 61 | net.Start("blobsProfiler:requestData") 62 | net.WriteString("Files") 63 | net.SendToServer() 64 | end 65 | end, 66 | PrepServerData = function() 67 | local allFiles, newFiles = blobsProfiler.ScanGLoadedFiles() 68 | 69 | return allFiles 70 | end, 71 | PreloadClient = true, 72 | PreloadServer = false, 73 | BuildPanel = function(luaState, parentPanel) 74 | blobsProfiler.buildDTree(luaState, parentPanel, "Files") 75 | end, 76 | RefreshButton = "Re-scan", 77 | RCFunctions = { 78 | ["table"] = { 79 | { 80 | name = "Expand/Collapse", 81 | func = function(ref, node) 82 | node:ExpandRecurse(not node:GetExpanded()) 83 | -- it's ok to use the ExpandRecurse here, not as bad as global tables. 84 | end, 85 | icon = "icon16/folder_explore.png" 86 | }, 87 | { 88 | name = "Print path", 89 | func = function(ref, node) 90 | local path = "" 91 | local function apparentParentDir(child) 92 | if child.parentNode then 93 | path = child.Label:GetText() .. (path ~= "" and "/" or "") .. path 94 | apparentParentDir(child.parentNode) 95 | else 96 | path = child:GetText() .. "/" .. path 97 | end 98 | end 99 | 100 | apparentParentDir(node) 101 | 102 | print(path) 103 | end, 104 | icon = "icon16/application_osx_terminal.png" 105 | }, 106 | { 107 | name = "Copy path", 108 | func = function(ref, node) 109 | local path = "" 110 | local function apparentParentDir(child) 111 | if child.parentNode then 112 | path = child.Label:GetText() .. (path ~= "" and "/" or "") .. path 113 | apparentParentDir(child.parentNode) 114 | else 115 | path = child:GetText() .. "/" .. path 116 | end 117 | end 118 | 119 | apparentParentDir(node) 120 | 121 | SetClipboardText(path) 122 | end, 123 | icon = "icon16/page_copy.png" 124 | } 125 | }, 126 | ["string"] = { 127 | { 128 | name = "View source", 129 | func = function(ref, node) 130 | local path = "" 131 | local function apparentParentDir(child) 132 | if child.parentNode then 133 | path = child.Label:GetText() .. (path ~= "" and "/" or "") .. path 134 | apparentParentDir(child.parentNode) 135 | else 136 | path = child:GetText() .. "/" .. path 137 | end 138 | end 139 | 140 | apparentParentDir(node) 141 | 142 | net.Start("blobsProfiler:requestSource") 143 | net.WriteString(path) 144 | net.SendToServer() 145 | end, 146 | icon = "icon16/script_code.png" 147 | }, 148 | { 149 | name = "Print path", 150 | func = function(ref, node) 151 | local path = "" 152 | local function apparentParentDir(child) 153 | if child.parentNode then 154 | path = child.Label:GetText() .. (path ~= "" and "/" or "") .. path 155 | apparentParentDir(child.parentNode) 156 | else 157 | path = child:GetText() .. "/" .. path 158 | end 159 | end 160 | 161 | apparentParentDir(node) 162 | 163 | print(path) 164 | end, 165 | icon = "icon16/application_osx_terminal.png" 166 | }, 167 | { 168 | name = "Copy path", 169 | func = function(ref, node) 170 | local path = "" 171 | local function apparentParentDir(child) 172 | if child.parentNode then 173 | path = child.Label:GetText() .. (path ~= "" and "/" or "") .. path 174 | apparentParentDir(child.parentNode) 175 | else 176 | path = child:GetText() .. "/" .. path 177 | end 178 | end 179 | 180 | apparentParentDir(node) 181 | 182 | SetClipboardText(path) 183 | end, 184 | icon = "icon16/page_copy.png" 185 | } 186 | } 187 | }, 188 | FormatNodeName = function(luaState, nodeKey, nodeValue) 189 | return istable(nodeValue) and nodeKey or nodeValue 190 | end 191 | }) -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/modules/bp_hooks.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler.RegisterModule("Hooks", { 2 | Icon = "icon16/brick_add.png", 3 | OrderPriority = 3, 4 | UpdateRealmData = function(luaState) 5 | if luaState == "Client" then 6 | local hooksTable = {} 7 | for hookName, hookEvents in pairs(hook.GetTable()) do 8 | hookName = tostring(hookName) 9 | 10 | hooksTable[hookName] = hooksTable[hookName] or {} 11 | for eventName, eventFunc in pairs(hookEvents) do 12 | eventName = tostring(eventName) 13 | 14 | local debugInfo = debug.getinfo(eventFunc) 15 | hooksTable[hookName][eventName] = debugInfo 16 | hooksTable[hookName][eventName].func = eventFunc 17 | 18 | hooksTable[hookName][eventName].fakeVarType = "function" 19 | end 20 | end 21 | 22 | blobsProfiler.Client.Hooks = hooksTable 23 | else 24 | net.Start("blobsProfiler:requestData") 25 | net.WriteString("Hooks") 26 | net.SendToServer() 27 | end 28 | end, 29 | PrepServerData = function() 30 | local hooksTable = {} 31 | for hookName, hookEvents in pairs(hook.GetTable()) do 32 | hookName = tostring(hookName) 33 | 34 | hooksTable[hookName] = hooksTable[hookName] or {} 35 | for eventName, eventFunc in pairs(hookEvents) do 36 | eventName = tostring(eventName) 37 | 38 | local debugInfo = debug.getinfo(eventFunc) 39 | hooksTable[hookName][eventName] = debugInfo 40 | hooksTable[hookName][eventName].func = tostring(eventFunc) 41 | 42 | hooksTable[hookName][eventName].fakeVarType = "function" 43 | end 44 | end 45 | 46 | return hooksTable 47 | end, 48 | PreloadClient = true, 49 | PreloadServer = false, 50 | BuildPanel = function(luaState, parentPanel) 51 | blobsProfiler.buildDTree(luaState, parentPanel, "Hooks") 52 | end, 53 | RefreshButton = "Re-scan", 54 | FormatNodeName = function(luaState, nodeKey, nodeValue) 55 | if nodeValue and istable(nodeValue) and not nodeValue.fakeVarType then 56 | return nodeKey .. " (".. table.Count(nodeValue) ..")" 57 | end 58 | 59 | return nodeKey 60 | end 61 | }) -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/modules/bp_lua.lua: -------------------------------------------------------------------------------- 1 | if SERVER then 2 | util.AddNetworkString("blobsProfiler:sendLua") 3 | util.AddNetworkString("blobsProfiler:sendLua_versus") 4 | end 5 | 6 | blobsProfiler.RegisterModule("Lua", { 7 | Icon = "icon16/world.png", 8 | OrderPriority = 2, 9 | }) 10 | 11 | --[[local validTypes = { 12 | ["string"] = true, 13 | ["number"] = true, 14 | ["boolean"] = true, 15 | --["Panel"] = true, -- the function is only used for serverside, which this will never exist on anyway 16 | ["function"] = true, 17 | ["Entity"] = true, 18 | ["Vector"] = true, 19 | ["Angle"] = true, 20 | ["table"] = true, 21 | ["Weapon"] = true, 22 | ["Player"] = true 23 | }]] 24 | -- https://gist.github.com/Yogpod/94b8ffa6ed9222e29961d0288f2b969c 25 | 26 | blobsProfiler.serialiseGlobals = function() 27 | local function copyTable(orig, seen) 28 | seen = seen or {} 29 | if seen[orig] then 30 | return nil 31 | --return seen[orig] 32 | end 33 | 34 | local copy = {} 35 | seen[orig] = copy 36 | 37 | for k, v in pairs(orig) do 38 | --local keyIsValid = type(k) == "string" or type(k) == "number" or type(k) == "boolean" 39 | local value 40 | 41 | --if validTypes[type(v)] and keyIsValid then 42 | if type(v) == 'table' then 43 | value = copyTable(v, seen) 44 | elseif type(v) == 'function' then 45 | local getDebugInfo = debug.getinfo(v, "S") 46 | value = { 47 | func = tostring(v), 48 | fakeVarType = "function", 49 | lastlinedefined = getDebugInfo.lastlinedefined, 50 | linedefined = getDebugInfo.linedefined, 51 | short_src = getDebugInfo.short_src 52 | --debugInfo = debugInfo -- this breaks pON?? 53 | } 54 | else 55 | value = v 56 | end 57 | copy[k] = value 58 | --else 59 | -- This line can get spammy 60 | -- pOn can only encode certain types, so we need to whitelist (for now) 61 | -- print("blobsProfiler.serialiseGlobals: Invalid type for key:", k, "value:", v, "type:", type(v)) 62 | --end 63 | end 64 | 65 | return copy 66 | end 67 | 68 | return copyTable(_G) 69 | end 70 | 71 | blobsProfiler.RegisterSubModule("Lua", "Globals", { 72 | Icon = "icon16/page_white_world.png", 73 | OrderPriority = 1, 74 | UpdateRealmData = function(luaState, t) 75 | if luaState == "Client" then 76 | blobsProfiler.Client.Lua = blobsProfiler.Client.Lua or {} 77 | local gimmyG = table.Copy(_G) 78 | blobsProfiler.Client.Lua.Globals = gimmyG 79 | else 80 | net.Start("blobsProfiler:requestData") 81 | net.WriteString("Lua.Globals") 82 | net.SendToServer() 83 | end 84 | end, 85 | PrepServerData = function() 86 | local _GSerialized = blobsProfiler.serialiseGlobals() 87 | return _GSerialized 88 | end, 89 | PreloadClient = true, 90 | PreloadServer = false, 91 | BuildPanel = function(luaState, parentPanel) 92 | blobsProfiler.Menu.TypeFolders = {} 93 | blobsProfiler.Menu.TypeFolders.Client = {} 94 | blobsProfiler.Menu.TypeFolders.Server = {} 95 | for k,v in ipairs(blobsProfiler.Menu.GlobalTypesToCondense) do 96 | blobsProfiler.Menu.TypeFolders.Client[v.type] = true 97 | blobsProfiler.Menu.TypeFolders.Server[v.type] = true 98 | end 99 | 100 | blobsProfiler.buildDTree(luaState, parentPanel, "Lua.Globals") 101 | end, 102 | RefreshButton = "Re-scan", 103 | FormatNodeName = function(luaState, nodeKey, nodeValue) 104 | if nodeValue and istable(nodeValue) and not nodeValue.fakeVarType then 105 | return nodeKey .. " (".. table.Count(nodeValue) ..")" 106 | end 107 | 108 | return nodeKey 109 | end, 110 | FormatNodeIcon = function(luaState, nodeKey, nodeValue) 111 | for _, varTypes in ipairs(blobsProfiler.Menu.GlobalTypesToCondense) do 112 | if varTypes.prettyPlural == nodeKey then 113 | return "icon16/folder_database.png" 114 | end 115 | end 116 | end 117 | }) 118 | 119 | local function luaExecuteFilesInit() 120 | if not file.Exists("blobsProfiler", "DATA") then 121 | file.CreateDir("blobsProfiler") 122 | end 123 | 124 | if not file.Exists("blobsProfiler/Client_LuaExecute.txt", "DATA") then 125 | file.Write("blobsProfiler/Client_LuaExecute.txt", [[print("Hello client!")]]) 126 | end 127 | 128 | if not file.Exists("blobsProfiler/Server_LuaExecute.txt", "DATA") then 129 | file.Write("blobsProfiler/Server_LuaExecute.txt", [[print("Hello server!")]]) 130 | end 131 | end 132 | 133 | if CLIENT then 134 | luaExecuteFilesInit() 135 | end 136 | 137 | blobsProfiler.RegisterSubModule("Lua", "Execute", { 138 | Icon = "icon16/script_code.png", 139 | OrderPriority = 2, 140 | CustomPanel = function(luaState, parentPanel) 141 | luaExecuteFilesInit() 142 | 143 | local preLoadContent = file.Read("blobsProfiler/"..luaState.."_LuaExecute.txt") 144 | local dhtmlPanel = blobsProfiler.generateAceEditorPanel(parentPanel, preLoadContent or [[print("Hello world!")]]) 145 | 146 | dhtmlPanel:Dock(FILL) 147 | dhtmlPanel.lastCode = preLoadContent 148 | 149 | dhtmlPanel:AddFunction("gmod", "saveContentsToFile", function(value) 150 | if value ~= dhtmlPanel.lastCode then -- prevent unnecessary writes 151 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Writing Lua Execute editor content to: 'blobsProfiler/"..luaState.."_LuaExecute.txt'") 152 | file.Write("blobsProfiler/"..luaState.."_LuaExecute.txt", value) 153 | dhtmlPanel.lastCode = value 154 | end 155 | end) 156 | 157 | dhtmlPanel:AddFunction("gmod", "receiveEditorContent", function(value) 158 | if value ~= dhtmlPanel.lastCode then -- prevent unnecessary writes 159 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Writing Lua Execute editor content to: 'blobsProfiler/"..luaState.."_LuaExecute.txt'") 160 | file.Write("blobsProfiler/"..luaState.."_LuaExecute.txt", value) 161 | dhtmlPanel.lastCode = value 162 | end 163 | 164 | if luaState == "Client" then 165 | RunString(value, "blobsprofiler:lua.execute") 166 | elseif luaState == "Server" then 167 | net.Start("blobsProfiler:sendLua") 168 | net.WriteString(value) -- TODO: allow bigger strings? no biggy for now, needs to be done securely! 169 | net.SendToServer() 170 | end 171 | end) 172 | 173 | 174 | local executeButton = vgui.Create("DButton", parentPanel) 175 | executeButton:Dock(BOTTOM) 176 | executeButton:SetText("Execute Lua Code") 177 | executeButton:DockMargin(0, 5, 0, 0) 178 | 179 | executeButton.DoClick = function() 180 | dhtmlPanel:RunJavascript([[ 181 | var value = getEditorValue(); 182 | gmod.receiveEditorContent(value); 183 | ]]) 184 | end 185 | 186 | dhtmlPanel.attemptSaveContentsToFile = function() 187 | dhtmlPanel:RunJavascript([[ 188 | var value = getEditorValue(); 189 | gmod.saveContentsToFile(value); 190 | ]]) 191 | end 192 | 193 | parentPanel.codePanel = dhtmlPanel 194 | end 195 | }) 196 | 197 | timer.Create("blobsProfiler:LuaExecute_SaveToFile", 15, 0, function() -- TODO: Make this configurable once I do settings - and module settings 198 | local moduleTbl = blobsProfiler.GetModule("Lua.Execute") 199 | if not moduleTbl then return end 200 | 201 | if moduleTbl.ClientTab and moduleTbl.ClientTab.codePanel and moduleTbl.ClientTab.codePanel.attemptSaveContentsToFile then 202 | moduleTbl.ClientTab.codePanel.attemptSaveContentsToFile() 203 | end 204 | if moduleTbl.ServerTab and moduleTbl.ServerTab.codePanel and moduleTbl.ServerTab.codePanel.attemptSaveContentsToFile then 205 | moduleTbl.ServerTab.codePanel.attemptSaveContentsToFile() 206 | end 207 | end) 208 | 209 | local function prettyProfilerTimeFormat(seconds) 210 | if seconds >= 0.5 then 211 | return string.format("%.2fs", seconds) 212 | else 213 | return string.format("%.6fms", seconds * 1000) 214 | end 215 | end 216 | 217 | local function SetupResultsPanel(parentPanel, numIterations, lcTotalTime, lcMin, lcMax, rcTotalTime, rcMin, rcMax) 218 | parentPanel.stopCompare = false 219 | 220 | if parentPanel.resultsPanel and IsValid(parentPanel.resultsPanel) then 221 | parentPanel.resultsPanel:Remove() 222 | parentPanel.resultsPanel = nil 223 | end 224 | 225 | parentPanel.resultsPanel = vgui.Create("DPanel", parentPanel) 226 | parentPanel.resultsPanel:Dock(BOTTOM) 227 | parentPanel.resultsPanel:SetTall(70) 228 | 229 | local colorGreen = Color(0,136,0) 230 | local colorRed = Color(255,0,0) 231 | 232 | parentPanel.resultsPanel.Paint = function(s,w,h) 233 | draw.SimpleText("Total execution time: ".. prettyProfilerTimeFormat(lcTotalTime), "DermaDefault", 5, 5, lcTotalTimercTotalTime and colorGreen or colorRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) 240 | draw.SimpleText("Slowest execution time: ".. prettyProfilerTimeFormat(rcMax), "DermaDefault", 5 + dividerPos, 20, lcMax>rcMax and colorGreen or colorRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) 241 | draw.SimpleText("Fastest execution time: ".. prettyProfilerTimeFormat(rcMin), "DermaDefault", 5 + dividerPos, 35, lcMin>rcMin and colorGreen or colorRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) 242 | draw.SimpleText("Average execution time: ".. prettyProfilerTimeFormat((rcTotalTime/numIterations)), "DermaDefault", 5 + dividerPos, 50, ((lcTotalTime/numIterations)>(rcTotalTime/numIterations)) and colorGreen or colorRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) 243 | end 244 | end 245 | 246 | blobsProfiler.RegisterSubModule("Lua", "Versus", { 247 | Icon = "icon16/application_cascade.png", 248 | CustomPanel = function(luaState, parentPanel) 249 | local leftCode = blobsProfiler.generateAceEditorPanel(parentPanel,[[local function addNumbers(a, b) 250 | return a + b 251 | end 252 | 253 | local sum = addNumbers(5, 5)]]) 254 | local rightCode = blobsProfiler.generateAceEditorPanel(parentPanel,[[local sum = 5 + 5]]) 255 | 256 | local codeContainer = vgui.Create("DHorizontalDivider", parentPanel) 257 | codeContainer:Dock(FILL) 258 | 259 | codeContainer:SetLeft(leftCode) 260 | codeContainer:SetRight(rightCode) 261 | 262 | codeContainer:SetLeftMin(0) 263 | codeContainer:SetRightMin(0) 264 | 265 | -- Custom paint function for the drag bar 266 | codeContainer.m_DragBar.Paint = function(s, w, h) 267 | surface.SetDrawColor(0, 0, 0) 268 | surface.DrawRect(0, 0, w, h) 269 | end 270 | 271 | local optionsContainer = vgui.Create("DPanel", parentPanel) 272 | optionsContainer:Dock(BOTTOM) 273 | optionsContainer:SetTall(50) 274 | 275 | local iterationNumSlider = vgui.Create("DNumSlider", optionsContainer) 276 | iterationNumSlider:SetText("## of iterations") 277 | iterationNumSlider.Label:SetTextColor(Color(0,0,0)) 278 | iterationNumSlider:SetMin(0) 279 | iterationNumSlider:SetMax(50000) 280 | iterationNumSlider:SetDecimals(0) 281 | iterationNumSlider:SetValue(1000) 282 | iterationNumSlider:Dock(TOP) 283 | iterationNumSlider:DockMargin(15,0,0,0) 284 | iterationNumSlider:DockPadding(0,0,0,5) 285 | 286 | local runComparison = vgui.Create("DButton", optionsContainer) 287 | runComparison:Dock(BOTTOM) 288 | runComparison:SetText("Compare code") 289 | 290 | runComparison.DoClick = function() 291 | if not parentPanel.stopCompare or parentPanel.stopCompare == false then -- Is this redundant? probably. I forget. my brain is mush. 292 | if parentPanel.resultsPanel and IsValid(parentPanel.resultsPanel) then 293 | parentPanel.resultsPanel:Remove() 294 | parentPanel.resultsPanel = nil 295 | end 296 | 297 | leftCode:RunJavascript([[ 298 | var value = getEditorValue(); 299 | gmod.receiveEditorContent(value); 300 | ]]) 301 | 302 | rightCode:RunJavascript([[ 303 | var value = getEditorValue(); 304 | gmod.receiveEditorContent(value); 305 | ]]) 306 | 307 | parentPanel.stopCompare = true -- This will be reset when results are displayed 308 | else 309 | if runComparison.Warning and IsValid(runComparison.Warning) then runComparison.Warning:Remove() runComparison.Warning = nil end 310 | runComparison.Warning = Derma_Message("Please wait for the previous comparison to finish", "blobsProfiler - Slow down!", "OK") 311 | end 312 | end 313 | 314 | local leftCodeContent 315 | local rightCodeContent 316 | 317 | leftCode:AddFunction("gmod", "receiveEditorContent", function(value) 318 | leftCodeContent = value 319 | end) 320 | 321 | rightCode:AddFunction("gmod", "receiveEditorContent", function(value) 322 | rightCodeContent = value 323 | 324 | if luaState == "Client" then 325 | local numIterations = math.Round(iterationNumSlider:GetValue()) 326 | if numIterations > 0 then 327 | local lcMin, lcMax 328 | local rcMin, rcMax -- Why aren't all these on a single line? because, fuck you. Thats why. 329 | 330 | -- TODO: Maybe add up the individual times, so the c checks aren't adding to performace (which is likely VERY minor) 331 | local lcStart = SysTime() 332 | for i=1, numIterations do 333 | local ciStart = SysTime() 334 | RunString(leftCodeContent, "blobsprofiler:lua.versus_LEFT") 335 | local ciTotal = SysTime() - ciStart 336 | 337 | if not lcMax or (ciTotal > lcMax) then lcMax = ciTotal end 338 | if not lcMin or (ciTotal < lcMin) then lcMin = ciTotal end 339 | end 340 | local lcTotalTime = SysTime() - lcStart 341 | 342 | local rcStart = SysTime() 343 | for i=1, numIterations do 344 | local ciStart = SysTime() 345 | RunString(rightCodeContent, "blobsprofiler:lua.versus_RIGHT") 346 | local ciTotal = SysTime() - ciStart 347 | 348 | if not rcMax or (ciTotal > rcMax) then rcMax = ciTotal end 349 | if not rcMin or (ciTotal < rcMin) then rcMin = ciTotal end 350 | end 351 | local rcTotalTime = SysTime() - rcStart 352 | 353 | SetupResultsPanel(parentPanel, numIterations, lcTotalTime, lcMin, lcMax, rcTotalTime, rcMin, rcMax) 354 | end 355 | elseif luaState == "Server" then 356 | blobsProfiler.Modules["Lua"].SubModules["Versus"].retrievingData = true 357 | net.Start("blobsProfiler:sendLua_versus") 358 | net.WriteUInt(math.Round(iterationNumSlider:GetValue()) ,20) 359 | net.WriteString(leftCodeContent) 360 | net.WriteString(rightCodeContent) 361 | net.SendToServer() 362 | end 363 | end) 364 | 365 | parentPanel.codeContainer = codeContainer 366 | end, 367 | OnOpen = function(luaState, parentPanel) 368 | timer.Simple(0, function() 369 | local width = parentPanel.codeContainer:GetWide() 370 | local dividerWidth = parentPanel.codeContainer:GetDividerWidth() or 8 371 | local halfWidth = (width - dividerWidth) * 0.5 372 | parentPanel.codeContainer:SetLeftWidth(halfWidth) 373 | end) 374 | end 375 | }) 376 | 377 | net.Receive("blobsProfiler:sendLua", function(l, ply) 378 | if not SERVER or not blobsProfiler.CanAccess(ply, "sendLua", "Server") then return end 379 | local luaRun = net.ReadString() 380 | 381 | blobsProfiler.Log(blobsProfiler.L_LOG, ply:Name() .. " (".. ply:SteamID64() ..") sent Lua to the server:") 382 | blobsProfiler.Log(blobsProfiler.L_LOG, luaRun) 383 | 384 | RunString(luaRun, "blobsprofiler:lua.execute") 385 | end) 386 | 387 | 388 | net.Receive("blobsProfiler:sendLua_versus", function(l, ply) 389 | if not blobsProfiler.CanAccess(ply or LocalPlayer(), "sendLua_versus") then return end 390 | if SERVER then 391 | local numIterations = net.ReadUInt(20) 392 | local luaRunL = net.ReadString() 393 | local luaRunR = net.ReadString() 394 | 395 | if numIterations > 0 then 396 | local lcMin, lcMax 397 | local rcMin, rcMax -- Why aren't all these on a single line? because, fuck you. Thats why. 398 | 399 | -- TODO: Maybe add up the individual times, so the c checks aren't adding to performace (which is likely VERY minor) 400 | local lcStart = SysTime() 401 | for i=1, numIterations do 402 | local ciStart = SysTime() 403 | RunString(luaRunL, "blobsprofiler:lua.versus_LEFT") 404 | local ciTotal = SysTime() - ciStart 405 | 406 | if not lcMax or (ciTotal > lcMax) then lcMax = ciTotal end 407 | if not lcMin or (ciTotal < lcMin) then lcMin = ciTotal end 408 | end 409 | local lcTotalTime = SysTime() - lcStart 410 | 411 | local rcStart = SysTime() 412 | for i=1, numIterations do 413 | local ciStart = SysTime() 414 | RunString(luaRunR, "blobsprofiler:lua.versus_RIGHT") 415 | local ciTotal = SysTime() - ciStart 416 | 417 | if not rcMax or (ciTotal > rcMax) then rcMax = ciTotal end 418 | if not rcMin or (ciTotal < rcMin) then rcMin = ciTotal end 419 | end 420 | local rcTotalTime = SysTime() - rcStart 421 | 422 | net.Start("blobsProfiler:sendLua_versus") 423 | net.WriteUInt(numIterations, 20) 424 | 425 | net.WriteDouble(lcTotalTime) 426 | net.WriteDouble(lcMin) 427 | net.WriteDouble(lcMax) 428 | 429 | net.WriteDouble(rcTotalTime) 430 | net.WriteDouble(rcMin) 431 | net.WriteDouble(rcMax) 432 | net.Send(ply) 433 | end 434 | else 435 | blobsProfiler.Modules["Lua"].SubModules["Versus"].retrievingData = false 436 | 437 | local numIterations = net.ReadUInt(20) 438 | 439 | local lcTotalTime = net.ReadDouble() 440 | local lcMin = net.ReadDouble() 441 | local lcMax = net.ReadDouble() 442 | 443 | local rcTotalTime = net.ReadDouble() 444 | local rcMin = net.ReadDouble() 445 | local rcMax = net.ReadDouble() 446 | 447 | local parentPanel = blobsProfiler.Modules["Lua"].SubModules["Versus"].ServerTab 448 | SetupResultsPanel(parentPanel, numIterations, lcTotalTime, lcMin, lcMax, rcTotalTime, rcMin, rcMax) 449 | end 450 | end) -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/modules/bp_network.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler.RegisterModule("Network", { 2 | Icon = "icon16/drive_network.png", 3 | OrderPriority = 6, 4 | UpdateRealmData = function(luaState) 5 | if luaState == "Client" then 6 | local netRecieversData = {} 7 | for recvName, recvFunc in pairs(net.Receivers) do 8 | recvName = tostring(recvName) 9 | 10 | local debugInfo = debug.getinfo(recvFunc) 11 | netRecieversData[recvName] = debugInfo 12 | netRecieversData[recvName].func = recvFunc 13 | 14 | netRecieversData[recvName].fakeVarType = "function" 15 | end 16 | 17 | blobsProfiler.Client.Network = netRecieversData 18 | else 19 | net.Start("blobsProfiler:requestData") 20 | net.WriteString("Network") 21 | net.SendToServer() 22 | end 23 | end, 24 | PrepServerData = function() 25 | local netRecieversData = {} 26 | for recvName, recvFunc in pairs(net.Receivers) do 27 | recvName = tostring(recvName) 28 | 29 | local debugInfo = debug.getinfo(recvFunc) 30 | netRecieversData[recvName] = debugInfo 31 | netRecieversData[recvName].func = tostring(recvFunc) 32 | 33 | netRecieversData[recvName].fakeVarType = "function" 34 | end 35 | 36 | return netRecieversData 37 | end, 38 | PreloadClient = true, 39 | PreloadServer = false, 40 | BuildPanel = function(luaState, parentPanel) 41 | blobsProfiler.buildDTree(luaState, parentPanel, "Network") 42 | end, 43 | RefreshButton = "Re-scan" 44 | }) -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/modules/bp_profiling.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler.RegisterModule("Profiling", { 2 | Icon = "icon16/hourglass.png", 3 | OrderPriority = 1 4 | }) 5 | 6 | local function profileLog(luaState, event) 7 | local info = debug.getinfo(3, "f") 8 | if not info then return end -- ??? 9 | 10 | local funcId = tostring(info.func) 11 | if blobsProfiler[luaState].Profile.Raw[funcId] then 12 | if event == "call" then 13 | blobsProfiler[luaState].Profile.Called[funcId] = SysTime() 14 | elseif event == "return" then 15 | if blobsProfiler[luaState].Profile.Called[funcId] then 16 | local timeToRun = SysTime() - blobsProfiler[luaState].Profile.Called[funcId] 17 | blobsProfiler[luaState].Profile.Called[funcId] = nil 18 | table.insert(blobsProfiler[luaState].Profile.Results[funcId], timeToRun) 19 | end 20 | end 21 | end 22 | end 23 | 24 | if not blobsProfiler.Modules["Profiling"].ProfilingStatus then 25 | blobsProfiler.Modules["Profiling"].ProfilingStatus = {} 26 | blobsProfiler.Modules["Profiling"].ProfilingStatus["Client"] = false 27 | blobsProfiler.Modules["Profiling"].ProfilingStatus["Server"] = false 28 | end 29 | 30 | 31 | blobsProfiler.RegisterSubModule("Profiling", "Targets", { 32 | Icon = "icon16/book_addresses.png", 33 | OrderPriority = 1, 34 | OnOpen = function(luaState, parentPanel) 35 | local profilerTable = blobsProfiler[luaState].Profile or {} 36 | local profilerData = table.Copy(profilerTable) 37 | profilerData.Raw = nil -- we dont need to display these 38 | profilerData.Called = nil 39 | profilerData.Results = nil 40 | 41 | local getKeys = table.GetKeys(profilerData) 42 | for _, tblKey in ipairs(getKeys) do 43 | if profilerData[tblKey] and table.Count(profilerData[tblKey]) == 0 then 44 | profilerData[tblKey] = nil 45 | end 46 | end 47 | 48 | blobsProfiler.buildDTree(luaState, parentPanel, "Profiling.Targets", profilerData) 49 | 50 | if parentPanel.startProfilingButton and IsValid(parentPanel.startProfilingButton) then 51 | parentPanel.startProfilingButton:Remove() 52 | parentPanel.startProfilingButton = nil 53 | end 54 | if parentPanel.stopProfilingButton and IsValid(parentPanel.stopProfilingButton) then 55 | parentPanel.stopProfilingButton:Remove() 56 | parentPanel.stopProfilingButton = nil 57 | end 58 | 59 | parentPanel.startProfilingButton = vgui.Create("DButton", parentPanel) 60 | parentPanel.stopProfilingButton = vgui.Create("DButton", parentPanel) 61 | 62 | parentPanel.startProfilingButton:SetText("Start profiling") 63 | parentPanel.stopProfilingButton:SetText("Stop profiling") 64 | 65 | parentPanel.stopProfilingButton:Dock(BOTTOM) 66 | parentPanel.startProfilingButton:Dock(BOTTOM) 67 | 68 | parentPanel.startProfilingButton:SetEnabled(not blobsProfiler.Modules["Profiling"].ProfilingStatus[luaState]) 69 | parentPanel.stopProfilingButton:SetEnabled(blobsProfiler.Modules["Profiling"].ProfilingStatus[luaState]) 70 | 71 | parentPanel.startProfilingButton.DoClick = function() 72 | if not blobsProfiler.CanAccess(LocalPlayer(), "Profiling_"..luaState) then return end 73 | if luaState == "Client" then 74 | blobsProfiler.Modules["Profiling"].ProfilingStatus[luaState] = true 75 | debug.sethook(function(e) profileLog("Client", e) end, "cr") 76 | 77 | parentPanel.startProfilingButton:SetEnabled(not blobsProfiler.Modules["Profiling"].ProfilingStatus[luaState]) 78 | parentPanel.stopProfilingButton:SetEnabled(blobsProfiler.Modules["Profiling"].ProfilingStatus[luaState]) 79 | elseif luaState == "Server" then 80 | 81 | end 82 | end 83 | 84 | parentPanel.stopProfilingButton.DoClick = function() 85 | if not blobsProfiler.CanAccess(LocalPlayer(), "Profiling_"..luaState) then return end 86 | if luaState == "Client" then 87 | blobsProfiler.Modules["Profiling"].ProfilingStatus[luaState] = false 88 | debug.sethook() 89 | 90 | parentPanel.startProfilingButton:SetEnabled(not blobsProfiler.Modules["Profiling"].ProfilingStatus[luaState]) 91 | parentPanel.stopProfilingButton:SetEnabled(blobsProfiler.Modules["Profiling"].ProfilingStatus[luaState]) 92 | elseif luaState == "Server" then 93 | 94 | end 95 | end 96 | end, 97 | FormatNodeName = function(luaState, nodeKey, nodeValue) 98 | return nodeValue.name or nodeKey 99 | end, 100 | FormatNodeIcon = function(luaState, nodeKey, nodeValue) 101 | local fullModuleName = nodeKey 102 | 103 | local getModule = blobsProfiler.GetModule(fullModuleName) 104 | if getModule and getModule.Icon then 105 | return getModule.Icon 106 | end 107 | end 108 | }) 109 | 110 | blobsProfiler.RegisterSubModule("Profiling", "Results", { 111 | Icon = "icon16/chart_bar.png", 112 | OrderPriority = 2 113 | }) -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/modules/bp_sqlite.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler.RegisterModule("SQLite", { 2 | Icon = "icon16/database.png", 3 | OrderPriority = 8 4 | }) 5 | 6 | local function buildSQLiteSchemaTable() 7 | local SQLiteSchema = {} 8 | SQLiteSchema.Tables = {} 9 | SQLiteSchema.Indices = {} 10 | 11 | local grabTableIndexData = sql.Query("SELECT * FROM sqlite_master") 12 | 13 | if grabTableIndexData then 14 | for _, tblData in ipairs(grabTableIndexData) do 15 | if tblData.type == "table" then 16 | SQLiteSchema.Tables[tblData.name] = {} 17 | 18 | local grabTableColumnData = sql.Query("PRAGMA table_info(".. sql.SQLStr(tblData.name) ..");") 19 | if grabTableColumnData then 20 | for _, tblCol in ipairs(grabTableColumnData) do 21 | SQLiteSchema.Tables[tblData.name][tblCol.cid] = {} 22 | SQLiteSchema.Tables[tblData.name][tblCol.cid]["ID"] = tblCol.cid 23 | SQLiteSchema.Tables[tblData.name][tblCol.cid]["Name"] = tblCol.name 24 | SQLiteSchema.Tables[tblData.name][tblCol.cid]["Primary Key"] = tobool(tblCol.pk) or nil 25 | SQLiteSchema.Tables[tblData.name][tblCol.cid]["Type"] = tblCol.type or nil 26 | SQLiteSchema.Tables[tblData.name][tblCol.cid]["Not NULL"] = tblCol.notnull or nil 27 | SQLiteSchema.Tables[tblData.name][tblCol.cid]["Default"] = tblCol.dflt_value or nil 28 | end 29 | end 30 | elseif tblData.type == "index" then 31 | SQLiteSchema.Indices[tblData.name] = {} 32 | --blobsProfiler.SQLite.SchemaIndices[v.name].CreateSQL = v.sql 33 | end 34 | end 35 | end 36 | 37 | return SQLiteSchema 38 | end 39 | 40 | if SERVER then 41 | util.AddNetworkString("blobsProfiler:SQLite_Schema_CreateSQL") 42 | 43 | net.Receive("blobsProfiler:SQLite_Schema_CreateSQL", function(len, ply) 44 | if not blobsProfiler.CanAccess(ply, "SQLite") then return end 45 | if not blobsProfiler.CanAccess(ply, "SQLite_Schema") then return end 46 | if not blobsProfiler.CanAccess(ply, "SQLite_Schema_CreateSQL") then return end 47 | 48 | local tableName = net.ReadString() 49 | local rcAction = net.ReadUInt(2) 50 | local grabData = sql.QueryValue("SELECT sql FROM sqlite_master WHERE name = ".. sql.SQLStr(tableName) .." LIMIT 1;") 51 | 52 | if grabData then 53 | net.Start("blobsProfiler:SQLite_Schema_CreateSQL") 54 | net.WriteString(grabData) 55 | net.WriteUInt(rcAction, 2) 56 | net.Send(ply) 57 | end 58 | end) 59 | else 60 | net.Receive("blobsProfiler:SQLite_Schema_CreateSQL", function() 61 | local createSQL = net.ReadString() 62 | local rcAction = net.ReadUInt(2) 63 | 64 | if rcAction == 1 then 65 | print(createSQL) 66 | 67 | Derma_Message("SQLite creation statement printed to console", "blobsProfiler: SQLite Schema - Create SQL", "OK") 68 | elseif rcAction == 2 then 69 | SetClipboardText(createSQL) 70 | 71 | Derma_Message("SQLite creation statement copied to clipboard", "blobsProfiler: SQLite Schema - Create SQL", "OK") 72 | end 73 | end) 74 | end 75 | 76 | blobsProfiler.RegisterSubModule("SQLite", "Schema", { 77 | Icon = "icon16/database_gear.png", 78 | OrderPriority = 1, 79 | UpdateRealmData = function(luaState) 80 | if luaState == "Client" then 81 | blobsProfiler.Client.SQLite = blobsProfiler.Client.SQLite or {} 82 | blobsProfiler.Client.SQLite.Schema = buildSQLiteSchemaTable() 83 | else 84 | net.Start("blobsProfiler:requestData") 85 | net.WriteString("SQLite.Schema") 86 | net.SendToServer() 87 | end 88 | end, 89 | PrepServerData = function() 90 | return buildSQLiteSchemaTable() 91 | end, 92 | PreloadClient = true, 93 | PreloadServer = false, 94 | BuildPanel = function(luaState, parentPanel) 95 | blobsProfiler.buildDTree(luaState, parentPanel, "SQLite.Schema") 96 | end, 97 | RefreshButton = "Refresh", 98 | RCFunctions = { 99 | ["table"] = { 100 | { 101 | name = "SQL Create statement", 102 | submenu = { 103 | { 104 | name = "Print", 105 | func = function(ref, node, luaState) 106 | if luaState == "Client" then 107 | local grabSQLCreate = sql.QueryValue("SELECT sql FROM sqlite_master WHERE name = ".. sql.SQLStr(ref.key) .." LIMIT 1;") 108 | print(grabSQLCreate) 109 | 110 | Derma_Message("SQLite creation statement printed to console", "blobsProfiler: SQLite Schema - Create SQL", "OK") 111 | else 112 | net.Start("blobsProfiler:SQLite_Schema_CreateSQL") 113 | net.WriteString(ref.key) 114 | net.WriteUInt(1, 2) 115 | net.SendToServer() 116 | end 117 | end, 118 | icon = "icon16/application_osx_terminal.png" 119 | }, 120 | { 121 | name = "Copy to clipboard", 122 | func = function(ref, node, luaState) 123 | if luaState == "Client" then 124 | local grabSQLCreate = sql.QueryValue("SELECT sql FROM sqlite_master WHERE name = ".. sql.SQLStr(ref.key) .." LIMIT 1;") 125 | SetClipboardText(grabSQLCreate) 126 | 127 | Derma_Message("SQLite creation statement copied to clipboard", "blobsProfiler: SQLite Schema - Create SQL", "OK") 128 | else 129 | net.Start("blobsProfiler:SQLite_Schema_CreateSQL") 130 | net.WriteString(ref.key) 131 | net.WriteUInt(2, 2) 132 | net.SendToServer() 133 | end 134 | end, 135 | icon = "icon16/page_copy.png" 136 | } 137 | }, 138 | icon = "icon16/table_lightning.png", 139 | condition = function(ref, node, realm) 140 | if not blobsProfiler[realm].SQLite.Schema.Tables then return false end 141 | 142 | return blobsProfiler[realm].SQLite.Schema.Tables[ref.key] 143 | end 144 | } 145 | } 146 | }, 147 | FormatNodeName = function(luaState, nodeKey, nodeValue) 148 | if istable(nodeValue) then 149 | if nodeValue["ID"] and nodeValue["Name"] then 150 | return nodeValue["Name"] 151 | else 152 | return nodeKey 153 | end 154 | else 155 | return nodeKey .. ": " .. tostring(nodeValue) 156 | end 157 | end, 158 | FormatNodeIcon = function(luaState, nodeKey, nodeValue) 159 | if istable(nodeValue) and nodeValue["Primary Key"] then 160 | return "icon16/table_key.png" 161 | elseif not istable(nodeValue) then 162 | return "icon16/page.png" 163 | else 164 | return "icon16/table.png" 165 | end 166 | end 167 | }) 168 | 169 | local function splitAndProcessQueries(query) -- why the fuck did I bother 170 | local results = {} 171 | local queries = string.Explode(";", query) 172 | 173 | for _, singleQuery in ipairs(queries) do 174 | singleQuery = string.Trim(singleQuery) 175 | 176 | if singleQuery ~= "" then 177 | local queryType = string.match(singleQuery:upper(), "^(%w+)") 178 | local result 179 | local affectedRowsStr 180 | local affectedRows 181 | 182 | if queryType == "SELECT" or queryType == "PRAGMA" or queryType == "EXPLAIN" then 183 | result = sql.Query(singleQuery) 184 | if result == false then 185 | table.insert(results, {type = "ERROR", message=sql.LastError(), query = singleQuery}) 186 | else 187 | table.insert(results, {type = queryType, data = result, query = singleQuery}) 188 | end 189 | elseif queryType == "INSERT" or queryType == "UPDATE" or queryType == "DELETE" then 190 | result = sql.Query(singleQuery) 191 | affectedRowsStr = sql.QueryValue("SELECT changes()") 192 | affectedRows = tonumber(affectedRowsStr) or 0 193 | if result == false then 194 | table.insert(results, {type = "ERROR", message = sql.LastError(), query = singleQuery}) 195 | else 196 | table.insert(results, {type = queryType, message = "Rows affected: " .. affectedRows, rowsAffected = affectedRows, query = singleQuery}) 197 | end 198 | elseif queryType == "COMMIT" or queryType == "ROLLBACK" or queryType == "CREATE" or queryType == "ALTER" or queryType == "DROP" then 199 | local success = sql.Query(singleQuery) 200 | if success == false then 201 | table.insert(results, {type = "ERROR", message = sql.LastError(), query = singleQuery}) 202 | else 203 | table.insert(results, {type = queryType, message = queryType .. " operation successful", query = singleQuery}) 204 | end 205 | elseif queryType == "SAVEPOINT" then 206 | local savepointName = string.match(singleQuery, "SAVEPOINT%s+([%w_]+)") 207 | if savepointName then 208 | local success = sql.Query("SAVEPOINT " .. savepointName) 209 | if success == false then 210 | table.insert(results, {type = "ERROR", message = sql.LastError(), query = singleQuery}) 211 | else 212 | table.insert(results, {type = "SAVEPOINT", message = "Savepoint created: " .. savepointName, query = singleQuery}) 213 | end 214 | else 215 | table.insert(results, {type = "ERROR", message = "Savepoint name not specified", query = singleQuery}) 216 | end 217 | else 218 | local tryInvalid = sql.Query(singleQuery) 219 | 220 | if tryInvalid == false then 221 | table.insert(results, {type = "ERROR", message = sql.LastError(), query = singleQuery}) 222 | else 223 | table.insert(results, {type = "ERROR", message = "UNHANDLED SQL QUERY TYPE: ".. queryType, query = singleQuery}) 224 | end 225 | end 226 | end 227 | end 228 | 229 | return results 230 | end 231 | 232 | if SERVER then 233 | util.AddNetworkString("blobsProfiler:requestSQLiteData") 234 | 235 | net.Receive("blobsProfiler:requestSQLiteData", function(len, ply) 236 | if not blobsProfiler.CanAccess(ply, "serverData") then return end 237 | if not blobsProfiler.CanAccess(ply, "serverData_SQLite") then return end 238 | if not blobsProfiler.CanAccess(ply, "serverData_SQLite_Data") then return end 239 | 240 | local tableName = net.ReadString() 241 | local pageNum = net.ReadUInt(12) 242 | if pageNum < 1 then pageNum = 1 end 243 | 244 | local getSQLData = sql.Query("SELECT * FROM ".. sql.SQLStr(tableName) .. " LIMIT 25") 245 | if getSQLData == false then 246 | -- error 247 | elseif getSQLData == nil then 248 | -- no data 249 | else 250 | net.Start("blobsProfiler:requestSQLiteData") 251 | net.WriteString(tableName) 252 | net.WriteTable(getSQLData) 253 | net.Send(ply) 254 | end 255 | end) 256 | 257 | util.AddNetworkString("blobsProfiler:runSQLite") 258 | 259 | net.Receive("blobsProfiler:runSQLite", function(len, ply) 260 | if not blobsProfiler.CanAccess(ply, "serverData") then return end 261 | if not blobsProfiler.CanAccess(ply, "serverData_SQLite") then return end 262 | if not blobsProfiler.CanAccess(ply, "serverData_SQLite_Execute") then return end 263 | 264 | local sqlQuery = net.ReadString() 265 | 266 | local proccessQuery = splitAndProcessQueries(sqlQuery) 267 | 268 | net.Start("blobsProfiler:runSQLite") 269 | net.WriteTable(proccessQuery or {}) -- todo: use chunked sending 270 | net.Send(ply) 271 | end) 272 | else 273 | net.Receive("blobsProfiler:requestSQLiteData", function() 274 | local tableName = net.ReadString() 275 | local getSQLData = net.ReadTable() 276 | 277 | blobsProfiler.Modules.SQLite.SubModules.Data.retrievingData = false 278 | local schemaDataTable = blobsProfiler.Server.SQLite.Schema 279 | local tableSelectorList = blobsProfiler.Server.SQLite.Data.tableSelectorList 280 | local tableDataListView = blobsProfiler.Server.SQLite.Data.tableDataListView 281 | 282 | for k, line in ipairs( tableDataListView:GetLines() ) do 283 | tableDataListView:RemoveLine(k) 284 | end 285 | 286 | for k,v in ipairs(tableDataListView.Columns) do 287 | if v and IsValid(v) then v:Remove() end 288 | end 289 | 290 | tableDataListView.Columns = {} 291 | 292 | local colList = schemaDataTable.Tables[tableName] 293 | if colList then 294 | local colAmnt = table.Count(colList) 295 | for i=0, colAmnt-1 do 296 | local colData = schemaDataTable.Tables[tableName][i] -- why the fuck is it a number now 297 | tableDataListView:AddColumn(colData.Name) 298 | end 299 | end 300 | tableDataListView:SetDirty( true ) 301 | 302 | tableDataListView:FixColumnsLayout() 303 | 304 | local tblOrder = {} 305 | local colList = schemaDataTable.Tables[tableName] 306 | if colList then 307 | local colAmnt = table.Count(colList) 308 | for i=0, colAmnt-1 do 309 | local colData = schemaDataTable.Tables[tableName][i] 310 | table.insert(tblOrder, colData.Name) 311 | end 312 | end 313 | 314 | for _, record in ipairs(getSQLData) do 315 | local dataBuild = {} 316 | for __, key in ipairs(tblOrder) do 317 | table.insert(dataBuild, record[key]) 318 | end 319 | tableDataListView:AddLine(unpack(dataBuild)) 320 | end 321 | end) 322 | 323 | net.Receive("blobsProfiler:runSQLite", function() 324 | local queryResults = net.ReadTable() 325 | 326 | local subModuleRef = blobsProfiler.Modules.SQLite.SubModules.Execute 327 | subModuleRef.ServerTab.handleQueries("Server", queryResults) 328 | end) 329 | end 330 | 331 | local function requestSQLiteData(luaState, tableName, pageNum) 332 | pageNum = pageNum or 1 333 | local schemaDataTable = blobsProfiler[luaState].SQLite.Schema 334 | 335 | local tableSelectorList = blobsProfiler[luaState].SQLite.Data.tableSelectorList 336 | local tableDataListView = blobsProfiler[luaState].SQLite.Data.tableDataListView 337 | 338 | if luaState == "Client" then 339 | for k, line in ipairs( tableDataListView:GetLines() ) do 340 | tableDataListView:RemoveLine(k) 341 | end 342 | 343 | for k,v in ipairs(tableDataListView.Columns) do 344 | if v and IsValid(v) then v:Remove() end 345 | end 346 | 347 | tableDataListView.Columns = {} 348 | 349 | local colList = schemaDataTable.Tables[tableName] 350 | if colList then 351 | local colAmnt = table.Count(colList) 352 | for i=0, colAmnt-1 do 353 | local colData = schemaDataTable.Tables[tableName][tostring(i)] -- TODO: make this actual number ffs 354 | tableDataListView:AddColumn(colData.Name) 355 | end 356 | end 357 | tableDataListView:SetDirty( true ) 358 | 359 | tableDataListView:FixColumnsLayout() 360 | 361 | local getSQLData = sql.Query("SELECT * FROM ".. sql.SQLStr(tableName) .. " LIMIT 25") 362 | if getSQLData == false then 363 | -- error 364 | elseif getSQLData == nil then 365 | -- no data 366 | else 367 | 368 | local tblOrder = {} 369 | local colList = schemaDataTable.Tables[tableName] 370 | if colList then 371 | local colAmnt = table.Count(colList) 372 | for i=0, colAmnt-1 do 373 | local colData = schemaDataTable.Tables[tableName][tostring(i)] -- TODO: make this actual number ffs 374 | table.insert(tblOrder, colData.Name) 375 | end 376 | end 377 | 378 | for _, record in ipairs(getSQLData) do 379 | local dataBuild = {} 380 | for __, key in ipairs(tblOrder) do 381 | table.insert(dataBuild, record[key]) 382 | end 383 | tableDataListView:AddLine(unpack(dataBuild)) 384 | end 385 | 386 | end 387 | else 388 | blobsProfiler.Modules.SQLite.SubModules.Data.retrievingData = true 389 | net.Start("blobsProfiler:requestSQLiteData") 390 | net.WriteString(tableName) 391 | net.WriteUInt(pageNum, 12) 392 | net.SendToServer() 393 | end 394 | end 395 | 396 | blobsProfiler.RegisterSubModule("SQLite", "Data", { 397 | Icon = "icon16/page_white_database.png", 398 | OrderPriority = 2, 399 | CustomPanel = function(luaState, parentPanel) 400 | blobsProfiler[luaState].SQLite = blobsProfiler[luaState].SQLite or {} 401 | 402 | blobsProfiler[luaState].SQLite.Data = blobsProfiler[luaState].SQLite.Data or {} 403 | 404 | local schemaDataTable = blobsProfiler[luaState].SQLite.Schema or {} 405 | 406 | blobsProfiler[luaState].SQLite.Data.tableSelectorList = vgui.Create("DComboBox", parentPanel) 407 | blobsProfiler[luaState].SQLite.Data.tableDataListView = vgui.Create("DListView", parentPanel) 408 | 409 | local tableSelectorList = blobsProfiler[luaState].SQLite.Data.tableSelectorList 410 | local tableDataListView = blobsProfiler[luaState].SQLite.Data.tableDataListView 411 | 412 | tableDataListView:Dock(FILL) 413 | 414 | tableSelectorList:Dock(TOP) 415 | tableSelectorList:SetSortItems(false) 416 | 417 | tableSelectorList.OnSelect = function(s, index, value) 418 | requestSQLiteData(luaState, value) 419 | end 420 | 421 | for k,v in pairs(schemaDataTable.Tables or {}) do 422 | tableSelectorList:AddChoice(k) 423 | 424 | if not tableSelectorList:GetSelected() then 425 | tableSelectorList:ChooseOption(k, 1) 426 | end 427 | end 428 | end, 429 | OnOpen = function(luaState) 430 | if blobsProfiler[luaState].SQLite.Data.tableSelectorList then 431 | 432 | blobsProfiler[luaState].SQLite.Data.tableSelectorList.Data = {} 433 | blobsProfiler[luaState].SQLite.Data.tableSelectorList.Choices = {} 434 | 435 | local schemaDataTable = blobsProfiler[luaState].SQLite.Schema or {} 436 | 437 | for k, v in pairs(schemaDataTable.Tables or {}) do 438 | blobsProfiler[luaState].SQLite.Data.tableSelectorList:AddChoice(k) 439 | 440 | if not blobsProfiler[luaState].SQLite.Data.tableSelectorList:GetSelected() then 441 | blobsProfiler[luaState].SQLite.Data.tableSelectorList:ChooseOption(k, 1) 442 | end 443 | end 444 | end 445 | end 446 | }) 447 | 448 | blobsProfiler.RegisterSubModule("SQLite", "Execute", { 449 | Icon = "icon16/database_go.png", 450 | OrderPriority = 3, 451 | CustomPanel = function(luaState, parentPanel) 452 | local dhtmlPanel = blobsProfiler.generateAceEditorPanel(parentPanel, "", "SQL") 453 | dhtmlPanel:Dock(FILL) 454 | 455 | local resultContainer = vgui.Create("DPropertySheet", parentPanel) 456 | resultContainer:SetVisible(false) 457 | 458 | local executeButton = vgui.Create("DButton", parentPanel) 459 | 460 | parentPanel.handleQueries = function(luaStateHQ, dataTable) 461 | local realmString = string.lower(luaStateHQ) 462 | if not blobsProfiler.CanAccess(LocalPlayer(), realmString .. "Data") then return end 463 | if not blobsProfiler.CanAccess(LocalPlayer(), realmString .. "Data_SQLite") then return end 464 | if not blobsProfiler.CanAccess(LocalPlayer(), realmString .. "Data_SQLite_Execute") then return end 465 | 466 | if resultContainer and IsValid(resultContainer) then 467 | resultContainer:Remove() 468 | end 469 | 470 | resultContainer = vgui.Create("DPropertySheet", parentPanel) 471 | resultContainer:SetVisible(false) 472 | 473 | for queryID, queryTable in ipairs(dataTable) do 474 | local queryType = queryTable.type or "ERROR" 475 | local isError = queryType == "ERROR" or false 476 | 477 | local panel1 = vgui.Create( "DPanel", resultContainer ) 478 | 479 | if queryType == "ERROR" then -- TODO: some of this can be condensed 480 | local queryResult = vgui.Create("DPanel", panel1) 481 | queryResult:Dock(FILL) 482 | queryResult.Paint = function(s,w,h) 483 | draw.SimpleTextOutlined(queryTable.query, "DermaDefault", 5, 2, Color(255,80,80), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, color_black) 484 | draw.SimpleText(queryTable.message or "Unknown Error", "DermaDefault", 5, 20, color_black, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) 485 | end 486 | elseif queryType == "SELECT" or queryType == "PRAGMA" or queryType == "EXPLAIN" then 487 | local queryResult = vgui.Create("DListView", panel1) 488 | queryResult:Dock(FILL) 489 | 490 | if queryTable.data and #queryTable.data >= 1 then 491 | local keys = table.GetKeys(queryTable.data[1]) -- idk if this is good enough 492 | for _, keyName in ipairs(keys) do 493 | queryResult:AddColumn(keyName) 494 | end 495 | 496 | for i, tblRecord in ipairs(queryTable.data) do 497 | local lineData = {} 498 | for _, key in ipairs(keys) do 499 | table.insert(lineData, tblRecord[key]) 500 | end 501 | queryResult:AddLine(unpack(lineData)) 502 | end 503 | end 504 | 505 | local querySummary = vgui.Create("DPanel", panel1) 506 | querySummary:SetTall(20) 507 | if queryType == "SELECT" and queryTable.data then 508 | querySummary:SetTall(32) 509 | end 510 | querySummary:Dock(TOP) 511 | querySummary.Paint = function(s,w,h) 512 | draw.SimpleTextOutlined(queryTable.query, "DermaDefault", 5, 2, Color(0,255,0), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, color_black) 513 | if queryType == "SELECT" and queryTable.data then 514 | draw.SimpleText("Rows: " .. (queryTable.data and #queryTable.data or "0"), "DermaDefault", 5, 16, color_black, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) 515 | end 516 | end 517 | elseif queryType == "INSERT" or queryType == "UPDATE" or queryType == "DELETE" 518 | or queryType == "COMMIT" or queryType == "ROLLBACK" or queryType == "CREATE" or queryType == "ALTER" or queryType == "DROP" 519 | or queryType == "SAVEPOINT" then 520 | local queryResult = vgui.Create("DPanel", panel1) 521 | queryResult:Dock(FILL) 522 | queryResult.Paint = function(s,w,h) 523 | draw.SimpleTextOutlined(queryTable.query, "DermaDefault", 5, 2, Color(0,255,0), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, color_black) 524 | draw.SimpleText(queryTable.message, "DermaDefault", 5, 16, color_black, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) 525 | end 526 | else 527 | --- ??? 528 | blobsProfiler.Log(blobsProfiler.L_NH_ERROR, "Unhandled SQL query type: ".. (queryTable.type or "UNKNOWN")) 529 | PrintTable(queryTable) 530 | end 531 | 532 | local qTab 533 | if isError then 534 | qTab = resultContainer:AddSheet( "Query "..queryID, panel1, "icon16/database_error.png") 535 | qTab.Tab.PaintOver = function(self, w, h) 536 | surface.SetDrawColor(255, 0, 0, 50) 537 | surface.DrawRect(0, 0, w, h) 538 | end 539 | else 540 | qTab = resultContainer:AddSheet( "Query "..queryID, panel1, "icon16/database.png") 541 | end 542 | 543 | qTab.Tab:SetTooltip(queryTable.query) 544 | end 545 | 546 | resultContainer:SetVisible(true) 547 | resultContainer:Dock(BOTTOM) 548 | resultContainer:SetTall(200) 549 | executeButton:Dock(BOTTOM) 550 | end 551 | 552 | dhtmlPanel:AddFunction("gmod", "receiveEditorContent", function(value) 553 | if luaState == "Client" then -- 'data' indicates a response from SV, ASSUME SV IF data IS PASSED! 554 | local results = splitAndProcessQueries(value) 555 | parentPanel.handleQueries("Client", results) 556 | elseif luaState == "Server" then 557 | net.Start("blobsProfiler:runSQLite") 558 | net.WriteString(value) 559 | net.SendToServer() 560 | end 561 | end) 562 | 563 | executeButton:Dock(BOTTOM) 564 | executeButton:SetText("Execute SQL") 565 | 566 | executeButton.DoClick = function() 567 | dhtmlPanel:RunJavascript([[ 568 | var value = getEditorValue(); 569 | gmod.receiveEditorContent(value); 570 | ]]) 571 | end 572 | end 573 | }) -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/modules/bp_timers.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler.original_timerCreate = blobsProfiler.original_timerCreate or timer.Create 2 | 3 | blobsProfiler.createdTimers = blobsProfiler.createdTimers or {} 4 | 5 | function timer.Create(identifier, delay, reps, func) 6 | local debugInfo = debug.getinfo(func) 7 | 8 | blobsProfiler.createdTimers[tostring(identifier)] = { 9 | ["Identifier: " .. tostring(identifier) ] = tostring(identifier), 10 | ["Delay: "..delay] = delay, 11 | ["Repititions: "..reps] = reps or 0, 12 | [tostring(func)] = { 13 | fakeVarType = "function", 14 | func = CLIENT and func or tostring(func), 15 | lastlinedefined = debugInfo.lastlinedefined, 16 | linedefined = debugInfo.linedefined, 17 | short_src = debugInfo.short_src 18 | } 19 | } 20 | 21 | return blobsProfiler.original_timerCreate(identifier, delay, reps, func) 22 | end 23 | 24 | if SERVER then 25 | util.AddNetworkString("blobsProfiler:Timers_Control") 26 | net.Receive("blobsProfiler:Timers_Control", function(len,ply) 27 | if not blobsProfiler.CanAccess(ply, "Timers") then return end 28 | if not blobsProfiler.CanAccess(ply, "Timers_Control") then return end 29 | 30 | local timerName = net.ReadString() 31 | local Control = net.ReadUInt(2) 32 | 33 | if Control == 1 then 34 | timer.Pause(timerName) 35 | elseif Control == 2 then 36 | timer.UnPause(timerName) 37 | elseif Control == 3 then 38 | timer.Remove(timerName) 39 | elseif Control == 0 then 40 | blobsProfiler.createdTimers[timerName] = nil 41 | end 42 | 43 | blobsProfiler.SendModuleData("Timers", ply) 44 | end) 45 | end 46 | 47 | blobsProfiler.RegisterModule("Timers", { 48 | Icon = "icon16/clock.png", 49 | OrderPriority = 7, 50 | UpdateRealmData = function(luaState) 51 | if luaState == "Client" then 52 | -- why dont we just set it straight away ffs 53 | blobsProfiler.Client.Timers = blobsProfiler.createdTimers 54 | else 55 | net.Start("blobsProfiler:requestData") 56 | net.WriteString("Timers") 57 | net.SendToServer() 58 | end 59 | end, 60 | PrepServerData = function() 61 | for k,v in pairs(blobsProfiler.createdTimers) do 62 | local timerAlive = timer.Exists(k) 63 | v.timeLeft = timerAlive and timer.TimeLeft(k) 64 | v.isAlive = timerAlive 65 | end 66 | return blobsProfiler.createdTimers 67 | end, 68 | PreloadClient = true, 69 | PreloadServer = false, 70 | BuildPanel = function(luaState, parentPanel) 71 | blobsProfiler.buildDTree(luaState, parentPanel, "Timers") 72 | end, 73 | RefreshButton = "Refresh", 74 | RCFunctions = { 75 | ["table"] = { 76 | { 77 | name = "Expand/Collapse", 78 | func = function(ref, node) 79 | if node and IsValid(node) and node.SetExpanded then 80 | node:SetExpanded(not node:GetExpanded()) 81 | end 82 | end, 83 | icon = "icon16/folder_explore.png" 84 | }, 85 | { 86 | name = "Print", 87 | func = function(ref, node) 88 | print(ref.value) 89 | print(node.GlobalPath) 90 | end, 91 | icon = "icon16/application_osx_terminal.png" 92 | }, 93 | { -- Pause/Resume timer 94 | name = function(ref, node, luaState) 95 | if luaState == "Client" and not timer.Exists(node.GlobalPath) then return end 96 | if luaState == "Server" and not ref.value.isAlive then return end 97 | local timeLeft = luaState == "Client" and timer.TimeLeft(node.GlobalPath) or ref.value.timeLeft 98 | if timeLeft < 0 then 99 | return "Resume" 100 | else 101 | return "Pause" 102 | end 103 | end, 104 | func = function(ref, node, luaState) 105 | if luaState == "Client" and not timer.Exists(node.GlobalPath) then return end 106 | if luaState == "Server" and not ref.value.isAlive then return end 107 | local timeLeft = luaState == "Client" and timer.TimeLeft(node.GlobalPath) or ref.value.timeLeft 108 | if timeLeft < 0 then 109 | if luaState == "Client" then 110 | timer.UnPause(node.GlobalPath) 111 | else 112 | net.Start("blobsProfiler:Timers_Control") 113 | net.WriteString(node.GlobalPath) 114 | net.WriteUInt(2, 2) 115 | net.SendToServer() 116 | end 117 | 118 | node.Icon:SetImage("icon16/clock_stop.png") 119 | else 120 | if luaState == "Client" then 121 | timer.Pause(node.GlobalPath) 122 | else 123 | net.Start("blobsProfiler:Timers_Control") 124 | net.WriteString(node.GlobalPath) 125 | net.WriteUInt(1, 2) 126 | net.SendToServer() 127 | end 128 | node.Icon:SetImage("icon16/clock_play.png") 129 | end 130 | end, 131 | onLoad = function(ref, node, luaState) 132 | if luaState == "Client" and not timer.Exists(node.GlobalPath) then return end 133 | if luaState == "Server" and not ref.value.isAlive then return end 134 | local timeLeft = luaState == "Client" and timer.TimeLeft(node.GlobalPath) or ref.value.timeLeft 135 | if timeLeft < 0 then 136 | node.Icon:SetImage("icon16/clock_stop.png") 137 | else 138 | node.Icon:SetImage("icon16/clock_play.png") 139 | end 140 | end, 141 | icon = function(ref, node, luaState) 142 | if luaState == "Client" and not timer.Exists(node.GlobalPath) then return end 143 | if luaState == "Server" and not ref.value.isAlive then return end 144 | local timeLeft = luaState == "Client" and timer.TimeLeft(node.GlobalPath) or ref.value.timeLeft 145 | if timeLeft < 0 then 146 | return "icon16/clock_play.png" 147 | else 148 | return "icon16/clock_stop.png" 149 | end 150 | end 151 | }, 152 | { -- Delete timer 153 | name = function(ref, node, luaState) 154 | if (luaState == "Client" and not timer.Exists(node.GlobalPath)) or (luaState == "Server" and not ref.value.isAlive) then 155 | node.Label:SetTextColor(Color(255,0,0)) 156 | return 157 | end 158 | return "Delete" 159 | end, 160 | func = function(ref, node, luaState) 161 | if luaState == "Client" then 162 | timer.Remove(node.GlobalPath) 163 | else 164 | net.Start("blobsProfiler:Timers_Control") 165 | net.WriteString(node.GlobalPath) 166 | net.WriteUInt(3, 2) 167 | net.SendToServer() 168 | end 169 | node.Label:SetTextColor(Color(255,0,0)) 170 | node.Icon:SetImage("icon16/clock_delete.png") 171 | end, 172 | onLoad = function(ref, node, luaState) 173 | if (luaState == "Client" and not timer.Exists(node.GlobalPath)) or (luaState == "Server" and not ref.value.isAlive) then 174 | node.Label:SetTextColor(Color(255,0,0)) 175 | node.Icon:SetImage("icon16/clock_delete.png") 176 | end 177 | end, 178 | icon = "icon16/clock_delete.png" 179 | }, 180 | { -- Remove timer reference 181 | name = function(ref, node, luaState) 182 | if (luaState == "Client" and not timer.Exists(node.GlobalPath)) or (luaState == "Server" and not ref.value.isAlive) then 183 | return "Remove reference" 184 | end 185 | end, 186 | func = function(ref, node, luaState) 187 | blobsProfiler.createdTimers[node.GlobalPath] = nil 188 | 189 | if luaState == "Server" then 190 | net.Start("blobsProfiler:Timers_Control") 191 | net.WriteString(node.GlobalPath) 192 | net.WriteUInt(0, 2) 193 | net.SendToServer() 194 | end 195 | 196 | node:Remove() 197 | end, 198 | icon = "icon16/clock_red.png" 199 | } 200 | }, 201 | ["function"] = { 202 | { 203 | name = "Toggle Profiling", 204 | func = function(ref, node) 205 | if node.Expander and IsValid(node.Expander) and node.Expander.SetChecked then 206 | local curChecked = node.Expander:GetChecked() 207 | node.Expander:SetChecked(not curChecked) 208 | node.Expander:OnChange(not curChecked) 209 | end 210 | end, 211 | condition = function(ref, node) 212 | if not node.Expander or not IsValid(node.Expander) or not node.Expander.SetChecked or not node.Expander:IsVisible() then 213 | return false 214 | end 215 | 216 | return true 217 | end, 218 | icon = "icon16/chart_bar.png" 219 | }, 220 | { 221 | name = "View source", 222 | func = function(ref, node, luaState) 223 | if not string.EndsWith(ref.value.short_src, ".lua") then 224 | Derma_Message("Invalid function source: ".. ref.value.short_src.."\nOnly functions defined in Lua can be read!", "Function view source", "OK") 225 | return 226 | end 227 | 228 | net.Start("blobsProfiler:requestSource") 229 | net.WriteString(ref.value.short_src) 230 | net.WriteUInt(ref.value.linedefined, 16) 231 | net.WriteUInt(ref.value.lastlinedefined, 16) 232 | net.SendToServer() 233 | end, 234 | icon = "icon16/magnifier.png" 235 | }, 236 | { 237 | name = "View properties", 238 | func = function(ref, node, luaState) 239 | local propertiesData = {} 240 | local propertiesTbl = table.Copy(ref.value) 241 | propertiesTbl.fakeVarType = nil 242 | propertiesData["debug.getinfo()"] = propertiesTbl 243 | 244 | local popupView = blobsProfiler.viewPropertiesPopup("View Function: " .. ref.key, propertiesData) 245 | end, 246 | icon = "icon16/magnifier.png" 247 | } 248 | } 249 | } 250 | }) -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/sh_blobsprofiler.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler = blobsProfiler or {} 2 | 3 | blobsProfiler.Client = blobsProfiler.Client or {} 4 | blobsProfiler.Server = blobsProfiler.Server or {} 5 | 6 | blobsProfiler.DebugMode = true -- This is currently only used by the logger 7 | 8 | blobsProfiler.L_NH_ERROR = -2 -- Throw error, no halt 9 | blobsProfiler.L_ERROR = -1 -- Throws error 10 | blobsProfiler.L_DEBUG = 0 -- For debug messages (can be hidden with DebugMode = false) 11 | blobsProfiler.L_LOG = 1 -- Prints, but also stores 12 | blobsProfiler.L_INFO = 2 -- Prints 13 | 14 | 15 | --[[ 16 | blobsProfiler.Log( number lLevel, string lMessage, string lSendToServer, bool lErrNHStack) 17 | - Logs a lMessage with the lLevel type on lRealm(s) 18 | 19 | number lLevel: The log level, available values: blobsProfiler.L_* (DEFAULT: blobsProfiler.L_INFO) 20 | string lMessage: The message to actually log 21 | bool lSendToServer: Send log to server (DEFAULT: false) has no effect when used on server realm. 22 | bool lErrStack: Set to true to provide the error stack, only works with error log types (DEFAULT: false) 23 | 24 | Examples: -- I think I covered every case? 25 | blobsProfiler.Log("Test message") 26 | blobsProfiler.Log("Test message", true) 27 | blobsProfiler.Log("Test message", false, true) 28 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Test message with debug") 29 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Test message with debug to server", true) 30 | ]] 31 | blobsProfiler.Log = function(lLevel, lMessage, lSendToServer, lErrStack) -- TODO: send to server 32 | -- The shit we're about to do for variable arguments.. 33 | if type(lLevel) == "string" and type(lMessage) == "boolean" then -- blobsProfiler.Log(lMessage, lSendToServer) / blobsProfiler.Log(lMessage, lSendToServer, lErrStack) 34 | lSendToServer = lMessage 35 | lMessage = lLevel 36 | lLevel = blobsProfiler.L_INFO 37 | lErrStack = lSendToServer or false 38 | elseif type(lLevel) == "string" then -- blobsProfiler.Log(lMessage) 39 | lMessage = lLevel 40 | lLevel = blobsProfiler.L_INFO 41 | lSendToServer = false 42 | lErrStack = false 43 | elseif type(lLevel) == "number" then -- Standard parameters 44 | if type(lMessage) ~= "string" then 45 | error("\nblobsProfiler.Log: Invalid parameters") 46 | end 47 | lSendToServer = lSendToServer or false 48 | lErrStack = lErrStack or false 49 | else 50 | error("\nblobsProfiler.Log: Invalid parameters") 51 | end 52 | 53 | local curTime = os.date("%H:%M:%S") -- tHaNK gOd lUa is caSe sEnSitiVE 54 | if lLevel == blobsProfiler.L_NH_ERROR then 55 | if lErrStack then 56 | ErrorNoHaltWithStack(string.format("[%s] [ERROR_S] blobsProfiler: %s", curTime, lMessage), 2) 57 | else 58 | ErrorNoHalt(string.format("[%s] [ERROR] blobsProfiler: %s\n", curTime, lMessage)) 59 | end 60 | elseif lLevel == blobsProfiler.L_ERROR then 61 | if lErrStack then 62 | error(string.format("\n[%s] [CRITICAL_S] blobsProfiler: %s", curTime, lMessage), 2) 63 | else 64 | Error(string.format("[%s] [CRITICAL] blobsProfiler: %s\n", curTime, lMessage)) 65 | end 66 | elseif lLevel == blobsProfiler.L_DEBUG and blobsProfiler.DebugMode then 67 | print(string.format("[%s] [DEBUG] blobsProfiler: %s", curTime, lMessage)) 68 | elseif lLevel == blobsProfiler.L_LOG then 69 | print(string.format("[%s] [LOG] blobsProfiler: %s", curTime, lMessage)) 70 | -- TODO: Store it 71 | else -- Default / Generic 72 | print(string.format("[%s] [INFO] blobsProfiler: %s", curTime, lMessage)) 73 | end 74 | end 75 | 76 | blobsProfiler.CanAccess = function(cPly, cArea, cRealm) 77 | return cPly:IsUserGroup("superadmin") -- TODO 78 | end 79 | 80 | blobsProfiler.SetRealmData = function(luaState, moduleName, dataTable) 81 | blobsProfiler.Log(blobsProfiler.L_DEBUG, "Setting " .. moduleName .. " ".. luaState .. " data") 82 | blobsProfiler[luaState] = blobsProfiler[luaState] or {} 83 | 84 | local moduleSplit = string.Explode(".", moduleName) -- [1] is parent, [2] is submodule 85 | 86 | if #moduleSplit == 1 then -- ew 87 | blobsProfiler[luaState][moduleSplit[1]] = dataTable 88 | else 89 | blobsProfiler[luaState][moduleSplit[1]] = blobsProfiler[luaState][moduleSplit[1]] or {} 90 | blobsProfiler[luaState][moduleSplit[1]][moduleSplit[2]] = dataTable 91 | end 92 | end 93 | 94 | blobsProfiler.GetDataTableForRealm = function(luaState, rvarType) 95 | blobsProfiler[luaState] = blobsProfiler[luaState] or {} 96 | 97 | local moduleSplit = string.Explode(".", rvarType) 98 | 99 | if #moduleSplit == 1 then -- ew 100 | return blobsProfiler[luaState][moduleSplit[1]] or {} -- brother ewwww 101 | else 102 | blobsProfiler[luaState][moduleSplit[1]] = blobsProfiler[luaState][moduleSplit[1]] or {} 103 | return blobsProfiler[luaState][moduleSplit[1]][moduleSplit[2]] or {} --- what is that brother 104 | end 105 | 106 | return blobsProfiler[luaState][rvarType] or {} 107 | end 108 | 109 | blobsProfiler.TableSort = {} 110 | blobsProfiler.TableSort.KeyAlphabetical = function(t) 111 | local keys = {} 112 | 113 | for key in pairs(t) do 114 | table.insert(keys, key) 115 | end 116 | 117 | table.sort(keys, function(a, b) 118 | return a < b 119 | end) 120 | 121 | local sortedTable = {} 122 | 123 | for _, key in ipairs(keys) do 124 | sortedTable[key] = t[key] 125 | end 126 | 127 | return sortedTable 128 | end 129 | blobsProfiler.TableSort.ByIndex = function(t, i) 130 | return table.sort(t, function(a, b) 131 | return a[i] < b[i] 132 | end) 133 | end 134 | blobsProfiler.TableSort.SQLTableColSort = function(parentTable) 135 | local parentKeys = {} 136 | for parentKey in pairs(parentTable) do 137 | table.insert(parentKeys, parentKey) 138 | end 139 | 140 | table.sort(parentKeys) 141 | 142 | local sortedParentTable = {} 143 | for _, parentKey in ipairs(parentKeys) do 144 | local childTable = parentTable[parentKey] 145 | 146 | local childKeys = {} 147 | for childKey in pairs(childTable) do 148 | table.insert(childKeys, childKey) 149 | end 150 | 151 | table.sort(childKeys, function(a, b) 152 | return childTable[a].ID < childTable[b].ID 153 | end) 154 | 155 | local sortedChildTable = {} 156 | for _, childKey in ipairs(childKeys) do 157 | sortedChildTable[childKey] = childTable[childKey] 158 | end 159 | 160 | sortedParentTable[parentKey] = sortedChildTable 161 | end 162 | 163 | return sortedParentTable 164 | end -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/sh_modules.lua: -------------------------------------------------------------------------------- 1 | blobsProfiler = blobsProfiler or {} 2 | blobsProfiler.Modules = blobsProfiler.Modules or {} 3 | 4 | blobsProfiler.RegisterModule = function(Name, ModuleConfig) 5 | blobsProfiler.Modules[Name] = ModuleConfig 6 | 7 | print("[blobsProfiler] Module: ".. Name .." - Loaded!") 8 | end 9 | 10 | blobsProfiler.RegisterSubModule = function(ParentModule, Name, ModuleConfig) 11 | blobsProfiler.Modules[ParentModule].SubModules = blobsProfiler.Modules[ParentModule].SubModules or {} 12 | blobsProfiler.Modules[ParentModule].SubModules[Name] = ModuleConfig 13 | 14 | print("[blobsProfiler] ".. ParentModule .." SubModule: ".. Name .." - Loaded!") 15 | end 16 | 17 | blobsProfiler.GetModule = function(fullModuleName) 18 | local splitModuleName = string.Explode(".", fullModuleName) 19 | if not blobsProfiler.Modules[splitModuleName[1]] then return end 20 | 21 | if #splitModuleName == 1 then 22 | return blobsProfiler.Modules[splitModuleName[1]] 23 | else 24 | return blobsProfiler.Modules[splitModuleName[1]].SubModules[splitModuleName[2]], blobsProfiler.Modules[splitModuleName[1]] 25 | end 26 | end 27 | 28 | blobsProfiler.GetRCFunctionsTable = function(fullModuleName) 29 | local moduleTable = blobsProfiler.GetModule(fullModuleName) 30 | return moduleTable.RCFunctions or blobsProfiler.Menu.RCFunctions_DEFAULT 31 | end -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/sh_netstream.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | NetStream - 2.0.0 3 | 4 | Alexander Grist-Hucker 5 | http://www.revotech.org 6 | 7 | Credits to: 8 | thelastpenguin for pON. 9 | https://github.com/thelastpenguin/gLUA-Library/tree/master/pON 10 | --]] 11 | 12 | 13 | local type, error, pcall, pairs, _player = type, error, pcall, pairs, player; 14 | 15 | if (!pon) then 16 | error("NetStream: Unable to find pON!"); 17 | end; 18 | 19 | AddCSLuaFile(); 20 | 21 | netstream = netstream or {}; 22 | 23 | local stored = netstream.stored or {}; 24 | netstream.stored = stored; 25 | 26 | local cache = netstream.cache or {}; 27 | netstream.cache = cache; 28 | 29 | -- A function to split data for a data stream. 30 | function netstream.Split(data) 31 | local index = 1; 32 | local result = {}; 33 | local buffer = {}; 34 | 35 | for i = 0, string.len(data) do 36 | buffer[#buffer + 1] = string.sub(data, i, i); 37 | 38 | if (#buffer == 32768) then 39 | result[#result + 1] = table.concat(buffer); 40 | index = index + 1; 41 | buffer = {}; 42 | end; 43 | end; 44 | 45 | result[#result + 1] = table.concat(buffer); 46 | 47 | return result; 48 | end; 49 | 50 | -- A function to hook a net stream. 51 | function netstream.Hook(name, Callback) 52 | stored[name] = Callback; 53 | end; 54 | 55 | if (SERVER) then 56 | util.AddNetworkString("NetStreamDS"); 57 | util.AddNetworkString("NetStreamHeavy"); 58 | 59 | -- A function to start a net stream. 60 | function netstream.Start(player, name, ...) 61 | local recipients = {}; 62 | local bShouldSend = false; 63 | 64 | if (!istable(player)) then 65 | if (!IsValid(player)) then 66 | player = _player.GetAll(); 67 | else 68 | player = {player}; 69 | end; 70 | end; 71 | 72 | for k, v in ipairs(player) do 73 | if (type(v) == "Player") then 74 | recipients[#recipients + 1] = v; 75 | 76 | bShouldSend = true; 77 | end; 78 | end; 79 | 80 | local encodedData = pon.encode({...}); 81 | 82 | if (encodedData and #encodedData > 0 and bShouldSend) then 83 | net.Start("NetStreamDS"); 84 | net.WriteString(name); 85 | net.WriteUInt(#encodedData, 32); 86 | net.WriteData(encodedData, #encodedData); 87 | net.Send(recipients); 88 | end; 89 | end; 90 | 91 | -- A function to start a > 64KB net stream. 92 | function netstream.Heavy(player, name, ...) 93 | local recipients = {}; 94 | local bShouldSend = false; 95 | 96 | if (!istable(player)) then 97 | if (!IsValid(player)) then 98 | player = _player.GetAll(); 99 | else 100 | player = {player}; 101 | end; 102 | end; 103 | 104 | for k, v in ipairs(player) do 105 | if (type(v) == "Player") then 106 | recipients[#recipients + 1] = v; 107 | 108 | bShouldSend = true; 109 | end; 110 | end; 111 | 112 | local encodedData = pon.encode({...}); 113 | local split = netstream.Split(encodedData); 114 | 115 | if (encodedData and #encodedData > 0 and bShouldSend) then 116 | for k, v in ipairs(split) do 117 | if #split < 3 then 118 | net.Start("NetStreamHeavy"); 119 | net.WriteString(name); 120 | net.WriteUInt(#v, 32); 121 | net.WriteData(v, #v); 122 | net.WriteUInt(k, 8); 123 | net.WriteUInt(#split, 8); 124 | net.Send(recipients); 125 | else 126 | timer.Simple(0.5 * k, function() 127 | net.Start("NetStreamHeavy"); 128 | net.WriteString(name); 129 | net.WriteUInt(#v, 32); 130 | net.WriteData(v, #v); 131 | net.WriteUInt(k, 8); 132 | net.WriteUInt(#split, 8); 133 | net.Send(recipients); 134 | end) 135 | end 136 | end; 137 | end; 138 | end; 139 | 140 | net.Receive("NetStreamDS", function(length, player) 141 | local NS_DS_NAME = net.ReadString(); 142 | local NS_DS_LENGTH = net.ReadUInt(32); 143 | local NS_DS_DATA = net.ReadData(NS_DS_LENGTH); 144 | 145 | if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then 146 | player.nsDataStreamName = NS_DS_NAME; 147 | player.nsDataStreamData = ""; 148 | 149 | if (player.nsDataStreamName and player.nsDataStreamData) then 150 | player.nsDataStreamData = NS_DS_DATA; 151 | 152 | if (stored[player.nsDataStreamName]) then 153 | local bStatus, value = pcall(pon.decode, player.nsDataStreamData); 154 | 155 | if (bStatus) then 156 | stored[player.nsDataStreamName](player, unpack(value)); 157 | else 158 | ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n"); 159 | end; 160 | end; 161 | 162 | player.nsDataStreamName = nil; 163 | player.nsDataStreamData = nil; 164 | end; 165 | end; 166 | 167 | NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil; 168 | end); 169 | else 170 | -- A function to start a net stream. 171 | function netstream.Start(name, ...) 172 | local encodedData = pon.encode({...}); 173 | 174 | if (encodedData and #encodedData > 0) then 175 | net.Start("NetStreamDS"); 176 | net.WriteString(name); 177 | net.WriteUInt(#encodedData, 32); 178 | net.WriteData(encodedData, #encodedData); 179 | net.SendToServer(); 180 | end; 181 | end; 182 | 183 | net.Receive("NetStreamDS", function(length) 184 | local NS_DS_NAME = net.ReadString(); 185 | local NS_DS_LENGTH = net.ReadUInt(32); 186 | local NS_DS_DATA = net.ReadData(NS_DS_LENGTH); 187 | 188 | if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then 189 | if (stored[NS_DS_NAME]) then 190 | local bStatus, value = pcall(pon.decode, NS_DS_DATA); 191 | 192 | if (bStatus) then 193 | stored[NS_DS_NAME](unpack(value)); 194 | else 195 | ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n"); 196 | end; 197 | end; 198 | end; 199 | 200 | NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil; 201 | end); 202 | 203 | net.Receive("NetStreamHeavy", function(length) 204 | local NS_DS_NAME = net.ReadString(); 205 | local NS_DS_LENGTH = net.ReadUInt(32); 206 | local NS_DS_DATA = net.ReadData(NS_DS_LENGTH); 207 | local NS_DS_PIECE = net.ReadUInt(8); 208 | local NS_DS_TOTAL = net.ReadUInt(8); 209 | 210 | if (!cache[NS_DS_NAME]) then 211 | cache[NS_DS_NAME] = ""; 212 | end; 213 | 214 | if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then 215 | if (NS_DS_PIECE < NS_DS_TOTAL) then 216 | if (NS_DS_PIECE == 1) then 217 | cache[NS_DS_NAME] = ""; 218 | end; 219 | 220 | cache[NS_DS_NAME] = cache[NS_DS_NAME]..NS_DS_DATA; 221 | else 222 | cache[NS_DS_NAME] = cache[NS_DS_NAME]..NS_DS_DATA; 223 | 224 | if (stored[NS_DS_NAME]) then 225 | local bStatus, value = pcall(pon.decode, cache[NS_DS_NAME]); 226 | 227 | if (bStatus) then 228 | stored[NS_DS_NAME](unpack(value)); 229 | else 230 | ErrorNoHalt("NetStream Heavy: '"..NS_DS_NAME.."'\n"..value.."\n"); 231 | end; 232 | 233 | cache[NS_DS_NAME] = nil; 234 | end; 235 | end; 236 | end; 237 | 238 | NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH, NS_DS_PIECE, NS_DS_TOTAL = nil, nil, nil, nil, nil; 239 | end); 240 | end; -------------------------------------------------------------------------------- /lua/blobsprofiler/shared/sh_pon.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | DEVELOPMENTAL VERSION; 4 | 5 | VERSION 1.2.2 6 | Copyright thelastpenguin™ 7 | 8 | You may use this for any purpose as long as: 9 | - You don't remove this copyright notice. 10 | - You don't claim this to be your own. 11 | - You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this. 12 | 13 | If you modify the code for any purpose, the above still applies to the modified code. 14 | 15 | The author is not held responsible for any damages incured from the use of pon, you use it at your own risk. 16 | 17 | DATA TYPES SUPPORTED: 18 | - tables - k,v - pointers 19 | - strings - k,v - pointers 20 | - numbers - k,v 21 | - booleans- k,v 22 | - Vectors - k,v 23 | - Angles - k,v 24 | - Entities- k,v 25 | - Players - k,v 26 | 27 | CHANGE LOG 28 | V 1.1.0 29 | - Added Vehicle, NPC, NextBot, Player, Weapon 30 | V 1.2.0 31 | - Added custom handling for k,v tables without any array component. 32 | V 1.2.1 33 | - fixed deserialization bug. 34 | 35 | THANKS TO... 36 | - VERCAS for the inspiration. 37 | ]] 38 | 39 | 40 | local pon = {}; 41 | _G.pon = pon; 42 | 43 | local type, count = type, table.Count ; 44 | local tonumber = tonumber ; 45 | local format = string.format; 46 | do 47 | local type, count = type, table.Count ; 48 | local tonumber = tonumber ; 49 | local format = string.format; 50 | 51 | local encode = {}; 52 | 53 | local tryCache ; 54 | 55 | local cacheSize = 0; 56 | 57 | encode['table'] = function( self, tbl, output, cache ) 58 | 59 | if( cache[ tbl ] )then 60 | output[ #output + 1 ] = format('(%x)', cache[tbl] ); 61 | return ; 62 | else 63 | cacheSize = cacheSize + 1; 64 | cache[ tbl ] = cacheSize; 65 | end 66 | 67 | 68 | local first = next(tbl, nil) 69 | local predictedNumeric = 1 70 | local lastKey = nil 71 | -- starts with a numeric dealio 72 | if first == 1 then 73 | output[#output + 1] = '{' 74 | 75 | for k,v in next, tbl do 76 | if k == predictedNumeric then 77 | predictedNumeric = predictedNumeric + 1 78 | 79 | local tv = type(v) 80 | if tv == 'string' then 81 | local pid = cache[v] 82 | if pid then 83 | output[#output + 1] = format('(%x)', pid) 84 | else 85 | cacheSize = cacheSize + 1 86 | cache[v] = cacheSize 87 | self.string(self, v, output, cache) 88 | end 89 | else 90 | self[tv](self, v, output, cache) 91 | end 92 | 93 | else 94 | break 95 | end 96 | end 97 | 98 | predictedNumeric = predictedNumeric - 1 99 | else 100 | predictedNumeric = nil 101 | end 102 | 103 | if predictedNumeric == nil then 104 | output[#output + 1] = '[' -- no array component 105 | else 106 | output[#output + 1] = '~' -- array component came first so shit needs to happen 107 | end 108 | 109 | for k, v in next, tbl, predictedNumeric do 110 | local tk, tv = type(k), type(v) 111 | 112 | -- WRITE KEY 113 | if tk == 'string' then 114 | local pid = cache[ k ]; 115 | if( pid )then 116 | output[ #output + 1 ] = format('(%x)', pid ); 117 | else 118 | cacheSize = cacheSize + 1; 119 | cache[ k ] = cacheSize; 120 | 121 | self.string( self, k, output, cache ); 122 | end 123 | else 124 | self[tk](self, k, output, cache) 125 | end 126 | 127 | -- WRITE VALUE 128 | if( tv == 'string' )then 129 | local pid = cache[ v ]; 130 | if( pid )then 131 | output[ #output + 1 ] = format('(%x)', pid ); 132 | else 133 | cacheSize = cacheSize + 1; 134 | cache[ v ] = cacheSize; 135 | 136 | self.string( self, v, output, cache ); 137 | end 138 | else 139 | self[ tv ]( self, v, output, cache ); 140 | end 141 | end 142 | 143 | output[#output + 1] = '}' 144 | end 145 | -- ENCODE STRING 146 | local gsub = string.gsub ; 147 | encode['string'] = function( self, str, output ) 148 | --if tryCache( str, output ) then return end 149 | local estr, count = gsub( str, ";", "\\;"); 150 | if( count == 0 )then 151 | output[ #output + 1 ] = '\''..str..';'; 152 | else 153 | output[ #output + 1 ] = '"'..estr..'";'; 154 | end 155 | end 156 | -- ENCODE NUMBER 157 | encode['number'] = function( self, num, output ) 158 | if num%1 == 0 then 159 | if num < 0 then 160 | output[ #output + 1 ] = format( 'x%x;', -num ); 161 | else 162 | output[ #output + 1 ] = format('X%x;', num ); 163 | end 164 | else 165 | output[ #output + 1 ] = tonumber( num )..';'; 166 | end 167 | end 168 | -- ENCODE BOOLEAN 169 | encode['boolean'] = function( self, val, output ) 170 | output[ #output + 1 ] = val and 't' or 'f' 171 | end 172 | -- ENCODE VECTOR 173 | encode['Vector'] = function( self, val, output ) 174 | output[ #output + 1 ] = ('v'..val.x..','..val.y)..(','..val.z..';'); 175 | end 176 | -- ENCODE ANGLE 177 | encode['Angle'] = function( self, val, output ) 178 | output[ #output + 1 ] = ('a'..val.p..','..val.y)..(','..val.r..';'); 179 | end 180 | encode['Entity'] = function( self, val, output ) 181 | output[ #output + 1] = 'E'..(IsValid( val ) and (val:EntIndex( )..';') or '#'); 182 | end 183 | encode['Player'] = encode['Entity']; 184 | encode['Vehicle'] = encode['Entity']; 185 | encode['Weapon'] = encode['Entity']; 186 | encode['NPC'] = encode['Entity']; 187 | encode['NextBot'] = encode['Entity']; 188 | encode['PhysObj'] = encode['Entity']; 189 | 190 | encode['nil'] = function() 191 | output[ #output + 1 ] = '?'; 192 | end 193 | encode.__index = function( key ) 194 | ErrorNoHalt('Type: '..key..' can not be encoded. Encoded as as pass-over value.'); 195 | return encode['nil']; 196 | end 197 | 198 | do 199 | local empty, concat = table.Empty, table.concat ; 200 | function pon.encode( tbl ) 201 | local output = {}; 202 | cacheSize = 0; 203 | encode[ 'table' ]( encode, tbl, output, {} ); 204 | local res = concat( output ); 205 | 206 | return res; 207 | end 208 | end 209 | end 210 | 211 | do 212 | local tonumber = tonumber ; 213 | local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode ; 214 | local Vector, Angle, Entity = Vector, Angle, Entity ; 215 | 216 | local decode = {}; 217 | decode['{'] = function( self, index, str, cache ) 218 | 219 | local cur = {}; 220 | cache[ #cache + 1 ] = cur; 221 | 222 | local k, v, tk, tv = 1, nil, nil, nil; 223 | while( true )do 224 | tv = sub( str, index, index ); 225 | if( not tv or tv == '~' )then 226 | index = index + 1; 227 | break ; 228 | end 229 | if( tv == '}' )then 230 | return index + 1, cur; 231 | end 232 | 233 | -- READ THE VALUE 234 | index = index + 1; 235 | index, v = self[ tv ]( self, index, str, cache ); 236 | cur[ k ] = v; 237 | 238 | k = k + 1; 239 | end 240 | 241 | while( true )do 242 | tk = sub( str, index, index ); 243 | if( not tk or tk == '}' )then 244 | index = index + 1; 245 | break ; 246 | end 247 | 248 | -- READ THE KEY 249 | 250 | index = index + 1; 251 | index, k = self[ tk ]( self, index, str, cache ); 252 | 253 | -- READ THE VALUE 254 | tv = sub( str, index, index ); 255 | index = index + 1; 256 | index, v = self[ tv ]( self, index, str, cache ); 257 | 258 | cur[ k ] = v; 259 | end 260 | 261 | return index, cur; 262 | end 263 | decode['['] = function( self, index, str, cache ) 264 | 265 | local cur = {}; 266 | cache[ #cache + 1 ] = cur; 267 | 268 | local k, v, tk, tv = 1, nil, nil, nil; 269 | while( true )do 270 | tk = sub( str, index, index ); 271 | if( not tk or tk == '}' )then 272 | index = index + 1; 273 | break ; 274 | end 275 | 276 | -- READ THE KEY 277 | index = index + 1; 278 | index, k = self[ tk ]( self, index, str, cache ); 279 | if not k then continue end 280 | 281 | -- READ THE VALUE 282 | tv = sub( str, index, index ); 283 | index = index + 1; 284 | if not self[tv] then 285 | print('did not find type: '..tv) 286 | end 287 | index, v = self[ tv ]( self, index, str, cache ); 288 | 289 | cur[ k ] = v; 290 | end 291 | 292 | return index, cur; 293 | end 294 | 295 | -- STRING 296 | decode['"'] = function( self, index, str, cache ) 297 | local finish = find( str, '";', index, true ); 298 | local res = gsub( sub( str, index, finish - 1 ), '\\;', ';' ); 299 | index = finish + 2; 300 | 301 | cache[ #cache + 1 ] = res; 302 | return index, res; 303 | end 304 | -- STRING NO ESCAPING NEEDED 305 | decode['\''] = function( self, index, str, cache ) 306 | local finish = find( str, ';', index, true ); 307 | local res = sub( str, index, finish - 1 ) 308 | index = finish + 1; 309 | 310 | cache[ #cache + 1 ] = res; 311 | return index, res; 312 | end 313 | 314 | -- NUMBER 315 | decode['n'] = function( self, index, str, cache ) 316 | index = index - 1; 317 | local finish = find( str, ';', index, true ); 318 | local num = tonumber( sub( str, index, finish - 1 ) ); 319 | index = finish + 1; 320 | return index, num; 321 | end 322 | decode['0'] = decode['n']; 323 | decode['1'] = decode['n']; 324 | decode['2'] = decode['n']; 325 | decode['3'] = decode['n']; 326 | decode['4'] = decode['n']; 327 | decode['5'] = decode['n']; 328 | decode['6'] = decode['n']; 329 | decode['7'] = decode['n']; 330 | decode['8'] = decode['n']; 331 | decode['9'] = decode['n']; 332 | decode['-'] = decode['n']; 333 | -- positive hex 334 | decode['X'] = function( self, index, str, cache ) 335 | local finish = find( str, ';', index, true ); 336 | local num = tonumber( sub( str, index, finish - 1), 16 ); 337 | index = finish + 1; 338 | return index, num; 339 | end 340 | -- negative hex 341 | decode['x'] = function( self, index, str, cache ) 342 | local finish = find( str, ';', index, true ); 343 | local num = -tonumber( sub( str, index, finish - 1), 16 ); 344 | index = finish + 1; 345 | return index, num; 346 | end 347 | 348 | -- POINTER 349 | decode['('] = function( self, index, str, cache ) 350 | local finish = find( str, ')', index, true ); 351 | local num = tonumber( sub( str, index, finish - 1), 16 ); 352 | index = finish + 1; 353 | return index, cache[ num ]; 354 | end 355 | 356 | -- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO. 357 | decode[ 't' ] = function( self, index ) 358 | return index, true; 359 | end 360 | decode[ 'f' ] = function( self, index ) 361 | return index, false; 362 | end 363 | 364 | -- VECTOR 365 | decode[ 'v' ] = function( self, index, str, cache ) 366 | local finish = find( str, ';', index, true ); 367 | local vecStr = sub( str, index, finish - 1 ); 368 | index = finish + 1; -- update the index. 369 | local segs = Explode( ',', vecStr, false ); 370 | return index, Vector( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) ); 371 | end 372 | -- ANGLE 373 | decode[ 'a' ] = function( self, index, str, cache ) 374 | local finish = find( str, ';', index, true ); 375 | local angStr = sub( str, index, finish - 1 ); 376 | index = finish + 1; -- update the index. 377 | local segs = Explode( ',', angStr, false ); 378 | return index, Angle( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) ); 379 | end 380 | -- ENTITY 381 | decode[ 'E' ] = function( self, index, str, cache ) 382 | if( str[index] == '#' )then 383 | index = index + 1; 384 | return index, NULL ; 385 | else 386 | local finish = find( str, ';', index, true ); 387 | local num = tonumber( sub( str, index, finish - 1 ) ); 388 | index = finish + 1; 389 | return index, Entity( num ); 390 | end 391 | end 392 | -- PLAYER 393 | decode[ 'P' ] = function( self, index, str, cache ) 394 | local finish = find( str, ';', index, true ); 395 | local num = tonumber( sub( str, index, finish - 1 ) ); 396 | index = finish + 1; 397 | return index, Entity( num ) or NULL; 398 | end 399 | -- NIL 400 | decode['?'] = function( self, index, str, cache ) 401 | return index + 1, nil; 402 | end 403 | 404 | 405 | function pon.decode( data ) 406 | local _, res = decode[sub(data,1,1)]( decode, 2, data, {}); 407 | return res; 408 | end 409 | end --------------------------------------------------------------------------------