1local DataDumper; -- (value, varname, fastmode, ident) 2 3 4--[[ DataDumper.lua 5Copyright (c) 2007 Olivetti-Engineering SA 6 7Permission is hereby granted, free of charge, to any person 8obtaining a copy of this software and associated documentation 9files (the "Software"), to deal in the Software without 10restriction, including without limitation the rights to use, 11copy, modify, merge, publish, distribute, sublicense, and/or sell 12copies of the Software, and to permit persons to whom the 13Software is furnished to do so, subject to the following 14conditions: 15 16The above copyright notice and this permission notice shall be 17included in all copies or substantial portions of the Software. 18 19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26OTHER DEALINGS IN THE SOFTWARE. 27]] 28 29local dumplua_closure = [[ 30local closures = {} 31local function closure(t) 32 closures[#closures+1] = t 33 t[1] = assert(loadstring(t[1])) 34 return t[1] 35end 36 37for _,t in pairs(closures) do 38 for i = 2,#t do 39 debug.setupvalue(t[1], i-1, t[i]) 40 end 41end 42]] 43 44local lua_reserved_keywords = { 45 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 46 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 47 'return', 'then', 'true', 'until', 'while' } 48 49local function keys(t) 50 local res = {} 51 local oktypes = { stringstring = true, numbernumber = true } 52 local function cmpfct(a,b) 53 if oktypes[type(a)..type(b)] then 54 return a < b 55 else 56 return type(a) < type(b) 57 end 58 end 59 for k in pairs(t) do 60 res[#res+1] = k 61 end 62 table.sort(res, cmpfct) 63 return res 64end 65 66local c_functions = {} 67for _,lib in pairs{'_G', 'string', 'table', 'math', 68 'io', 'os', 'coroutine', 'package', 'debug'} do 69 local t = _G[lib] or {} 70 lib = lib .. "." 71 if lib == "_G." then lib = "" end 72 for k,v in pairs(t) do 73 if type(v) == 'function' and not pcall(string.dump, v) then 74 c_functions[v] = lib..k 75 end 76 end 77end 78 79DataDumper = function(value, varname, fastmode, ident) 80 local defined, dumplua = {} 81 -- Local variables for speed optimization 82 local string_format, type, string_dump, string_rep = 83 string.format, type, string.dump, string.rep 84 local tostring, pairs, table_concat = 85 tostring, pairs, table.concat 86 local keycache, strvalcache, out, closure_cnt = {}, {}, {}, 0 87 setmetatable(strvalcache, {__index = function(t,value) 88 local res = string_format('%q', value) 89 t[value] = res 90 return res 91 end}) 92 local fcts = { 93 string = function(value) return strvalcache[value] end, 94 number = function(value) return value end, 95 boolean = function(value) return tostring(value) end, 96 ['nil'] = function(value) return 'nil' end, 97 ['function'] = function(value) 98 return string_format("loadstring(%q)", string_dump(value)) 99 end, 100 userdata = function() error("Cannot dump userdata") end, 101 thread = function() error("Cannot dump threads") end, 102 } 103 local function test_defined(value, path) 104 if defined[value] then 105 if path:match("^getmetatable.*%)$") then 106 out[#out+1] = string_format("s%s, %s)\n", path:sub(2,-2), defined[value]) 107 else 108 out[#out+1] = path .. " = " .. defined[value] .. "\n" 109 end 110 return true 111 end 112 defined[value] = path 113 end 114 local function make_key(t, key) 115 local s 116 if type(key) == 'string' and key:match('^[_%a][_%w]*$') then 117 s = key .. "=" 118 else 119 s = "[" .. dumplua(key, 0) .. "]=" 120 end 121 t[key] = s 122 return s 123 end 124 for _,k in ipairs(lua_reserved_keywords) do 125 keycache[k] = '["'..k..'"] = ' 126 end 127 if fastmode then 128 fcts.table = function (value) 129 -- Table value 130 local numidx = 1 131 out[#out+1] = "{" 132 for key,val in pairs(value) do 133 if key == numidx then 134 numidx = numidx + 1 135 else 136 out[#out+1] = keycache[key] 137 end 138 local str = dumplua(val) 139 out[#out+1] = str.."," 140 end 141 if string.sub(out[#out], -1) == "," then 142 out[#out] = string.sub(out[#out], 1, -2); 143 end 144 out[#out+1] = "}" 145 return "" 146 end 147 else 148 fcts.table = function (value, ident, path) 149 if test_defined(value, path) then return "nil" end 150 -- Table value 151 local sep, str, numidx, totallen = " ", {}, 1, 0 152 local meta, metastr = (debug or getfenv()).getmetatable(value) 153 if meta then 154 ident = ident + 1 155 metastr = dumplua(meta, ident, "getmetatable("..path..")") 156 totallen = totallen + #metastr + 16 157 end 158 for _,key in pairs(keys(value)) do 159 local val = value[key] 160 local s = "" 161 local subpath = path or "" 162 if key == numidx then 163 subpath = subpath .. "[" .. numidx .. "]" 164 numidx = numidx + 1 165 else 166 s = keycache[key] 167 if not s:match "^%[" then subpath = subpath .. "." end 168 subpath = subpath .. s:gsub("%s*=%s*$","") 169 end 170 s = s .. dumplua(val, ident+1, subpath) 171 str[#str+1] = s 172 totallen = totallen + #s + 2 173 end 174 if totallen > 80 then 175 sep = "\n" .. string_rep(" ", ident+1) 176 end 177 str = "{"..sep..table_concat(str, ","..sep).." "..sep:sub(1,-3).."}" 178 if meta then 179 sep = sep:sub(1,-3) 180 return "setmetatable("..sep..str..","..sep..metastr..sep:sub(1,-3)..")" 181 end 182 return str 183 end 184 fcts['function'] = function (value, ident, path) 185 if test_defined(value, path) then return "nil" end 186 if c_functions[value] then 187 return c_functions[value] 188 elseif debug == nil or debug.getupvalue(value, 1) == nil then 189 return string_format("loadstring(%q)", string_dump(value)) 190 end 191 closure_cnt = closure_cnt + 1 192 local res = {string.dump(value)} 193 for i = 1,math.huge do 194 local name, v = debug.getupvalue(value,i) 195 if name == nil then break end 196 res[i+1] = v 197 end 198 return "closure " .. dumplua(res, ident, "closures["..closure_cnt.."]") 199 end 200 end 201 function dumplua(value, ident, path) 202 return fcts[type(value)](value, ident, path) 203 end 204 if varname == nil then 205 varname = "return " 206 elseif varname:match("^[%a_][%w_]*$") then 207 varname = varname .. " = " 208 end 209 if fastmode then 210 setmetatable(keycache, {__index = make_key }) 211 out[1] = varname 212 table.insert(out,dumplua(value, 0)) 213 return table.concat(out) 214 else 215 setmetatable(keycache, {__index = make_key }) 216 local items = {} 217 for i=1,10 do items[i] = '' end 218 items[3] = dumplua(value, ident or 0, "t") 219 if closure_cnt > 0 then 220 items[1], items[6] = dumplua_closure:match("(.*\n)\n(.*)") 221 out[#out+1] = "" 222 end 223 if #out > 0 then 224 items[2], items[4] = "local t = ", "\n" 225 items[5] = table.concat(out) 226 items[7] = varname .. "t" 227 else 228 items[2] = varname 229 end 230 return table.concat(items) 231 end 232end 233 234local fnlist = { 235 "color_surface", 236 "fill_surface", 237 "alloc_surface", 238 "raw_surface", 239 "render_text", 240 "define_rendertarget", 241 "define_linktarget", 242 "define_recordtarget", 243 "define_calctarget", 244 "define_linktarget", 245 "define_nulltarget", 246 "define_arcantarget", 247 "launch_target", 248 "accept_target", 249 "target_alloc", 250 "load_image", 251 "launch_decode", 252 "launch_avfeed", 253 "load_image_asynch", 254}; 255 256local fnbuf 257local alist 258 259local function toggle_alloc() 260 if fnbuf then 261 print("disable alloc tracing") 262 for k,v in pairs(fnbuf) do 263 _G[k] = v 264 end 265 fnbuf = nil 266 for k,v in pairs(alist) do 267 if valid_vid(k) then 268 print("alive", image_tracetag(k), v) 269 end 270 end 271 272 alist = nil 273 return 274 end 275 276 fnbuf = {} 277 alist = {} 278 print("enable alloc tracing") 279 280 for _,v in ipairs(fnlist) do 281 fnbuf[v] = _G[v] 282 _G[v] = function(...) 283 print(v, debug.traceback()) 284 local vid, a, b, c, d, e, f, g = fnbuf[v](...) 285 if valid_vid(vid) then 286 print("=>", vid) 287 alist[vid] = debug.traceback() 288 end 289 return vid, a, b, c, d, e, f, g 290 end 291 end 292end 293 294local function spawn_debug_wnd(vid, title) 295 show_image(vid); 296 local wnd = active_display():add_window(vid, {scalemode = "stretch"}); 297 wnd:set_title(title); 298end 299 300local function gen_displaywnd_menu() 301 local res = {}; 302 for disp in all_displays_iter() do 303 table.insert(res, { 304 name = "disp_" .. tostring(disp.name), 305 handler = function() 306 local nsrf = null_surface(disp.tiler.width, disp.tiler.height); 307 image_sharestorage(disp.rt, nsrf); 308 if (valid_vid(nsrf)) then 309 spawn_debug_wnd(nsrf, "display: " .. tostring(k)); 310 end 311 end, 312 label = disp.name, 313 kind = "action" 314 }); 315 end 316 317 return res; 318end 319 320local function gettitle(wnd) 321 return string.format("%s/%s:%s", wnd.name, 322 wnd.title_prefix and wnd.title_prefix or "unk", 323 wnd.title_text and wnd.title_text or "unk"); 324end 325 326local dump_menu = { 327{ 328 name = "video", 329 label = "Video", 330 kind = "value", 331 description = "Debug Snapshot of the video subsystem state", 332 hint = "(debug/)", 333 validator = strict_fname_valid, 334 handler = function(ctx, val) 335 zap_resource("debug/" .. val); 336 system_snapshot("debug/" .. val); 337 end 338}, 339{ 340 name = "global", 341 label = "Global", 342 kind = "value", 343 description = "Dump the global table recursively (slow)", 344 hint = "(debug/)", 345 validator = strict_fname_valid, 346 handler = function(ctx, val) 347 end 348}, 349{ 350 name = "active_display", 351 label = "Active Display", 352 description = "Dump the active display window table", 353 hint = "(debug/)", 354 kind = "value", 355 validator = strict_fname_valid, 356 handler = function(ctx, val) 357 print(DataDumper(val, _G)); 358 end 359}, 360}; 361 362local counter = 0; 363return { 364 { 365 name = "dump", 366 label = "Dump", 367 kind = "action", 368 submenu = true, 369 handler = dump_menu 370 }, 371 -- for testing fallback application handover 372 { 373 name = "broken", 374 label = "Broken Call (Crash)", 375 kind = "action", 376 handler = function() does_not_exist(); end 377 }, 378 { 379 name = "testwnd", 380 label = "Color window", 381 kind = "action", 382 handler = function() 383 counter = counter + 1; 384 spawn_debug_wnd( 385 fill_surface(math.random(200, 600), math.random(200, 600), 386 math.random(64, 255), math.random(64, 255), math.random(64, 255)), 387 "color_window_" .. tostring(counter) 388 ); 389 end 390 }, 391 { 392 name = "worldid_wnd", 393 label = "WORLDID window", 394 kind = "action", 395 handler = function() 396 local wm = active_display(); 397 local newid = null_surface(wm.width, wm.height); 398 if (valid_vid(newid)) then 399 image_sharestorage(WORLDID, newid); 400 spawn_debug_wnd(newid, "worldid"); 401 end 402 end 403 }, 404 { 405 name = "display_wnd", 406 label = "display_window", 407 kind = "action", 408 submenu = true, 409 eval = function() 410 return not gconfig_get("display_simple"); 411 end, 412 handler = gen_displaywnd_menu 413 }, 414 { 415 name = "animation_cycle", 416 label = "Animation Cycle", 417 kind = "action", 418 description = "Add an animated square that moves up and down the display", 419 handler = function() 420 if not DEBUG_ANIMATION then 421 DEBUG_ANIMATION = {}; 422 end 423 local vid = color_surface(64, 64, 0, 255, 0); 424 if (not valid_vid(vid)) then 425 return; 426 end 427 show_image(vid); 428 order_image(vid, 65530); 429 move_image(vid, 0, active_display().height - 64, 200); 430 move_image(vid, 0, 0, 200); 431 image_transform_cycle(vid, true); 432 table.insert(DEBUG_ANIMATION, vid); 433 end 434 }, 435 { 436 name = "stop_animation", 437 label = "Stop Animation", 438 eval = function() return DEBUG_ANIMATION and #DEBUG_ANIMATION > 0 or false; end, 439 handler = function() 440 for _,v in ipairs(DEBUG_ANIMATION) do 441 if (valid_vid(v)) then 442 delete_image(v); 443 end 444 end 445 DEBUG_ANIMATION = nil; 446 end 447 }, 448 { 449 name = "alert", 450 label = "Random Alert", 451 kind = "action", 452 handler = function() 453 timer_add_idle("random_alert" .. tostring(math.random(1000)), 454 math.random(1000), false, function() 455 local tiler = active_display(); 456 tiler.windows[math.random(#tiler.windows)]:alert(); 457 end); 458 end 459 }, 460 { 461 name = "stall", 462 label = "Frameserver Debugstall", 463 kind = "value", 464 eval = function() return frameserver_debugstall ~= nil; end, 465 validator = gen_valid_num(0, 100), 466 handler = function(ctx,val) frameserver_debugstall(tonumber(val)*10); end 467 }, 468 { 469 name = "dump_tree", 470 label = "Dump Space-Tree", 471 kind = "action", 472 eval = function() return active_display().spaces[ 473 active_display().space_ind] ~= nil; end, 474 handler = function(ctx) 475 local space = active_display().spaces[active_display().space_ind]; 476 local fun; 477 print("<space>"); 478 fun = function(node, level) 479 print(string.format("%s<node id='%s' horiz=%f vert=%f>", 480 string.rep("\t", level), gettitle(node), 481 node.weight, node.vweight)); 482 for k,v in ipairs(node.children) do 483 fun(v, level+1); 484 end 485 print(string.rep("\t", level) .. "</node>"); 486 end 487 fun(space, 0); 488 print("</space>"); 489 end 490 }, 491 { 492 name = "cursor_vid", 493 label = "Print Cursor", 494 kind = "action", 495 description = "Dump the tracetag of vid beneath cursor as warning", 496 handler = function(ctx) 497 local mx, my = mouse_xy(); 498 local list = pick_items(mx, my, 10, true, active_display(true)); 499 500 print(#list, "items at: ", mx, my); 501 for i, v in ipairs(list) do 502 print(string.format("%s%s", string.rep("-", i), image_tracetag(v))) 503 end 504 end 505 }, 506 { 507 name = "dump_alloc", 508 label = "Dump Allocations", 509 kind = "action", 510 description = "Toggle vid tracing allocation as messages on stdout", 511 handler = function(ctx) 512 toggle_alloc(); 513 end 514 } 515}; 516