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]