Log In  


Variable inspector window

Debugging carts with "print" and "printh" can be cumbersome, so I made a little snippet to help.
It adds a little window where you can view variables and drill down into them.

To use it, add the snippet somewhere into your program:

dbg=(function()

	poke(0x5f2d, 1)

	-- watched variables
	local vars,emp={},true

	-- window state
	local exp=false

	-- text cursor
	local x,y

	-- scrollbar
	local sy=0 
	local sdrag

	-- mouse state
	local mx,my,mb,pb,click,mw

	function clicked(x,y,w,h)
		return click and mx>=x and mx<x+w and my>=y and my<y+h
	end

	function butn(txt,x,y,c)
		print(txt,x,y,c)
		return clicked(x,y,4,6)
	end

	-- convert value into something easier to traverse and inspect
	function inspect(v,d)
		d=d or 0
		local t=type(v)	 
		if t=="table" then
			if(d>5)return "[table]"
			local props={}
			for key,val in pairs(v) do
				props[key]=inspect(val,d+1)
			end
			return {
				expand=false,
				props=props
			}
		elseif t=="string" then
			return chr(34)..v..chr(34)
		elseif t=="boolean" then
			return v and "true" or "false"
		elseif t=="nil" or t=="function" or t=="thread" then
			return "["..t.."]"
		else 
			return ""..v
		end
	end

	function drawvar(var,name)
		if type(var)=="string" then	  
			print(name..":",x+4,y,6)
			print(var,x+#(""..name)*4+8,y,7)
			y+=6
		else

			-- expand button
			if(butn(var.expand and "-" or "+",x,y,7))var.expand=not var.expand

			-- name
			print(name,x+4,y,12) y+=6

			-- content
			if var.expand then
				x+=4
				for key,val in pairs(var.props) do
					drawvar(val,key)	   
				end
				x-=4
			end
		end
	end

	function copyuistate(src,dst)
		if type(src)=="table" and type(dst)=="table" then
			dst.expand=src.expand
			for key,val in pairs(src.props) do
				copyuistate(val,dst.props[key])
			end
		end
	end

	function watch(var,name)
        name=name or "[var]"
        local p,i=vars[name],inspect(var)
        if(p)copyuistate(p,i)
        vars[name]=i
        emp=false
    end 

	function clear()
		vars,emp={},true
	end 

	function draw(dx,dy,w,h)
		dx=dx or 0
		dy=dy or 48
		w=w or 128-dx
		h=h or 128-dy

		-- collapsed mode
		if not exp then
			dx+=w-10
			w,h=10,5
		end

		-- window
		clip(dx,dy,w,h)
		rectfill(0,0,128,128,1)
		x=dx+2 y=dy+2-sy

		-- read mouse
		mx,my,mw=stat(32),stat(33),stat(36)
		mb=band(stat(34),1)~=0
		click=mb and not pb and mx>=dx and mx<dx+w and my>=dy and my<dy+h
		pb=mb

		if exp then		

			-- variables							
			for k,v in pairs(vars) do
				drawvar(v,k)
			end

			-- scrollbar
			local sh=y+sy-dy
			if sh>h then
				local sx=dx+w-4
				local by=sy/sh*h+dy
				local bh=h/sh*h
				rectfill(sx,dy,dx+w,dy+h-1,5)
				rectfill(sx,by,dx+w,by+bh-1,sdrag and 12 or 6)
				rect(sx,by,dx+w-1,by+bh-1,13)
				if click and mx>=sx then
					if my<by then
						sy-=h
					elseif my>by+bh then
						sy+=h
					else
						sdrag=by-my
					end             
				end
				if sdrag then 
					sy=(my+sdrag-dy)*sh/h
				else
					sy-=mw*8
				end
				if(sy<0)sy=0
				if(sy+h>sh)sy=sh-h
				if(not mb)sdrag=nil
			else
				sy=0
			end

			-- clear btn
			if(butn("x",dx+w-15,dy,14))clear()
		end

		-- expand/collapse btn
		if(butn(exp and "-" or "+",dx+w-10,dy,14))exp=not exp

		-- draw mouse ptr
		clip()			
		line(mx,my,mx,my+2,8)
		color(7) 
	end

	function show()
		exp=true
		while exp do
			draw()
			flip()
		end
	end

	function prnt(v,name)
		watch(v,name)
		show()
	end

	return{
		watch=watch,
		clear=clear,
		empty=function()
			return emp
		end,
		expand=function(val)
			if(val~=nil)exp=val
			return exp
		end,
		draw=draw,
		show=show,
		print=prnt
	}
end)()

There are a few different ways to use it.

Global variables

You can view global variables when your program is paused by typing:

dbg.print(myvariable)

The window will popup and run until you click "-" at the top right to exit it.
Note: If you press "Esc" to exit the inspection window, and try to resume your game with "resume", it will resume the popup window instead! To avoid this, use the "-" button.

Local variables

To view a local variable inside a function you need to add code to that function to capture it.

dbg.watch(myvariable,"my variable")

This will capture it's value and add it to your variable list. The second parameter is the name. You can add multiple variables so long as they all have different names. Calling dbg.watch again with the same name causes it to replace the previous value.

To view your captured variables, pause your program by pressing Esc, and type:

dbg.show()

Be aware that the variable inspector window shows the variable's value at the time dbg.watch() was executed. If the variable has been changed since, the changes will not show in the inspector. (This is deliberate.)

Displaying variables while the program is running

If you want to run the inspector window directly in your program, add:

dbg.draw()

to your _draw() function.

You can optionally specify the window position (x,y,width,height), e.g.

dbg.draw(64,0,64,48)

By default the window displays collapsed, until you click the "+" button.

Remember you still need to capture variables with "dbg.watch()", otherwise the window will be empty.

Pausing while the inspector is open

The game will continue running while the inspector window is open.
If you'd prefer it to pause the game, you can add:

if(dbg.expand())return

to the top of your "_update" function.

dbg.expand()

returns true when the window is expanded or false when collapsed.
You can also force it open with:

dbg.expand(true)

or collapse it with:

dbg.expand(false)

For example, to invoke the editor from the Pico-8 built in menu:

menuitem(1,"debug",function()dbg.expand(true)end)

Performance considerations

This snippet works by traversing your variables and building a "snapshot" tree structure. If you capture large variables every frame you might get some slow down, due to the traversal and possibly the Lua garbage collector cleaning up snapshots from previous frames. So make sure to remove your dbg.watch() calls when you want your program to run fast again.

13


4

Slightly more compact version (585 tokens instead of 771).
No scrollbar - use mouse wheel instead.

dbg=(function()

	poke(0x5f2d, 1)

	-- watched variables
	local vars,sy={},0

	-- mouse state, expanded, text cursor
	local mx,my,mb,pb,click,mw,exp,x,y

	function butn(exp,x,y)
		local hover=mx>=x and mx<x+4 and my>=y and my<y+6
		print(exp and "-" or "+",x,y,hover and 7 or 5)
		return hover and click
	end

	-- convert value into something easier to traverse and inspect
	function inspect(v,d)
		d=d or 0
		local t=type(v)	 
		if t=="table" then
			if(d>5)return "[table]"
			local props={}
			for key,val in pairs(v) do
				props[key]=inspect(val,d+1)
			end
			return {
				expand=false,
				props=props
			}
		elseif t=="string" then
			return chr(34)..v..chr(34)
		elseif t=="boolean" then
			return v and "true" or "false"
		elseif t=="nil" or t=="function" or t=="thread" then
			return "["..t.."]"
		else 
			return ""..v
		end
	end

	function drawvar(var,name)
		if type(var)=="string" then	  
			print(name..":",x+4,y,6)
			print(var,x+#(""..name)*4+8,y,7)
			y+=6
		else

			-- expand button
			if(butn(var.expand,x,y))var.expand=not var.expand

			-- name
			print(name,x+4,y,12) y+=6

			-- content
			if var.expand then
				x+=2
				for key,val in pairs(var.props) do
					drawvar(val,key)	   
				end
				x-=2
			end
		end
	end

	function copyuistate(src,dst)
		if type(src)=="table" and type(dst)=="table" then
			dst.expand=src.expand
			for key,val in pairs(src.props) do
				copyuistate(val,dst.props[key])
			end
		end
	end

	function watch(var,name)
		name=name or "[var]"
		local p,i=vars[name],inspect(var)
		if(p)copyuistate(p,i)
		vars[name]=i
	end 

	function clear()
		vars={}
	end 

	function draw(dx,dy,w,h)
		dx=dx or 0
		dy=dy or 48
		w=w or 128-dx
		h=h or 128-dy

		-- collapsed mode
		if not exp then
			dx+=w-10
			w,h=10,5
		end

		-- window
		clip(dx,dy,w,h)
		rectfill(0,0,128,128,1)
		x=dx+2 y=dy+2-sy

		-- read mouse
		mx,my,mw=stat(32),stat(33),stat(36)
		mb=band(stat(34),1)~=0
		click=mb and not pb and mx>=dx and mx<dx+w and my>=dy and my<dy+h
		pb=mb

		if exp then		

			-- variables							
			for k,v in pairs(vars) do
				drawvar(v,k)
			end

			-- scrolling
			local sh=y+sy-dy
			sy=max(min(sy-mw*8,sh-h),0)
		end

		-- expand/collapse btn
		if(butn(exp,dx+w-10,dy))exp=not exp

		-- draw mouse ptr
		clip()			
		line(mx,my,mx,my+2,8)
		color(7) 
	end

	function show()
		exp=true
		while exp do
			draw()
			flip()
		end
	end

	function prnt(v,name)
		watch(v,name)
		show()
	end

	return{
		watch=watch,
		clear=clear,
		expand=function(val)
			if(val~=nil)exp=val
			return exp
		end,
		draw=draw,
		show=show,
		print=prnt
	}
end)()

Thank you, this is helpful for what I'm working on right now



[Please log in to post a comment]