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