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.
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)() |
[Please log in to post a comment]



