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.

P#76213 2020-05-09 02:37 ( Edited 2020-05-09 03:04)

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)()
P#76291 2020-05-10 05:14

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

P#79202 2020-07-12 12:39

[Please log in to post a comment]