Codea+ : Debugger & Threading Library (On WebRepo)

Latest feature list:

  • Threads
  • Debugger
  • Promises
  • Semaphores
  • Preprocessor
  • Async / Await (used in conjunction with Promises)
  • Documentation generator powered by annotated comments.

Hi All,

This is just a little something I’ve been working on I’m sure some of you may appreciate :smile:

Codea+ is a threading library allowing complex thread based systems to be created with ease.

As an added bonus, having everything run on a thread allows us to integrate a debugger directly into Codea’s side panel!

Steppers,
o7

P.S. Simeon & John I hope you don’t mind the name :wink:


Further documentation will follow but for now:

  • dbg([optional boolean condition]) : Break into the debugger

While in the debugger, you may enter the commands below into the sidepanel input.

Flow:

  • c() : Exit the debugger and continue execution.

View Backtrace:

  • t() : Print the backtrace again.

Viewing Local Variables:

  • l() : List the local variables in the top stack frame.
  • l(stackframe_number) : List the local variables in the given stack frame.
  • l(variable_name) : Display the value of the named local variable (scans stack frames automatically).
  • l(variable_name, stackframe_number) : Display the value of the named local variable in the given stack frame.

Setting Local Variables:

  • sl(variable_name, value) : Sets the value of the named local variable (scans stack frames automatically).
  • sl(variable_name, value, stackframe_number) : Sets the value of the named local variable in the given stack frame.

Example:

function setup()
    local test = 19
    dbg()
    print("Test value is: " .. tostring(test))
end

Flow

OUTPUT:

Debug break:
[Main]:3
[Thread]:141

USER INPUT: `l()` OUTPUT: ``` Locals (level 1): test = number: 19 ```
USER INPUT: `sl("test", 180) ` OUTPUT: ``` Local 'test' set in stack frame 1 ```
USER INPUT: `l()` OUTPUT: ``` Locals (level 1): test = number: 180 ```
USER INPUT: `c()` (exits debugger) OUTPUT: (from user program) ``` Test value is: 180 ```

Holy heck @Steppers i haven’t tried this yet but I must say between you and @jfperusse you seem to be single-handedly (double-single-handedly?) addressing lots of the biggest obstacles to using Codea.

@Steppers i don’t know if you ever saw my debugger—it was glitchy to the point where @RonJeffries swore he would never run it again on his projects, so it probably was around 80% trash, but it did have a line-by-line step-through feature. If you check it out and there’s anything there that looks salvageable please take it with my blessing!

@Steppers wow this is a very clever use of co-routines and tween(). I’m surprised how much tween gets used to hack in special updates outside of the draw() function.

I’m currently looking into proper debug support in Codea 4 although this looks like a nice stop gap in the mean time

@UberGoober Oh nice! I’ve just taken a look. As much as I’d really love to have stepping functionality, due to the usage of coroutines in all of this (avoids locking up of Codea’s UI) I can’t call coroutine.yield() from inside a debug hook :disappointed:

@John Thanks, I’d heard you were adding better support so I’m looking forward to it!

A little weird that coroutines won’t yield during debugging. I wonder why.

@UberGoober Lua has a limitation that you can’t yield across a C function boundary and as the hook is triggered from the C side of Lua it hits that.

I’ve got something working based on this: https://github.com/devcat-studio/VSCodeLuaDebug

This gives complete debugger support via TCP sockets. The protocol is fairly straight forward (basically REST over TCP). So that means breakpoints, step (in, over, out), stacktrace, view/change local variables, etc… The main difficulty is making a working UI for it. It also freezes the runtime in place (which is pretty typical of any debugger, the entire Unity editor freezes during debugging for instance).

I did have to revert to Lua 5.3 in order to apply the HALT_OP patch, which lets you breakpoints with virtually no overhead (including conditional breakpoints). But at some point it should be possible to apply it to Lua 5.4

This is still for Codea 4 but if we’re able to integrate it into the editor, it should make debugging far easier (I’ve been wanting this for my own projects for a while)

Got a few more goodies some of you may like! I just need to tidy some of it up before I update it on WebRepo.

Very JavaScript-like Promises!

    -- Async functions return a Promise object
    -- and don’t execute immediately.
    local sendMessage = async(function(delay)
        delay = delay or 1 -- Delay for 1 second by default
        Thread.sleep(delay)
        if delay > 2 then
            error("Failed to send message!")
        end
        return "Message sent!"
    end)
    
    -- Call the async 'sendMessage' function
    sendMessage()
    -- Add a success handler
    :thenDo(function(msg)
        print(msg)
    end)
    -- Add an error handler
    :catch(function(err)
        objc.warning("Err: " .. err)
    end)
    -- Add a 'finally' handler
    :finally(function()
        print("Promise chain complete!")
    end)
    
    -- Use of 'await()' to wait for a Promise
    -- to be fulfilled.
    --
    -- Try to send a message with a delay of 3!
    -- > 2 seconds throws an error so we add
    -- a 'catch' handler here to catch the error.
    --
    -- Prints 'nil'
    print(await(sendMessage(3):catch(function()end)))
    
    -- Same thing but without the error handler.
    -- This causes a crash instead
    print(await(sendMessage(3)))

All of this is still compatible with the debugger too :smile:

Steppers
o7

been testing this out, and i’m having a couple trouble spots - here’s my experience

can’t resume a coroutine ( if dbg() is used in a coroutine then c() will not resume that)

can’t index “self” with l(self) or l(self.myVar)

l(table) just says it’s a table, not useful to as what’s inside

Hey @skar I’ll look into the coroutine issue but you should be able to do:
‘l(“self”).myVar’

l() only works with the immediate value and also returns it so you can use the value in other ways.

@skar The latest version ?1.3.0 on WebRepo should now fully support the debugger within coroutines :smile:

@Simeon @John Any chance we could pass in the asset key corresponding to the current source file when it’s loaded? I have something in the works and it would be really very useful.

Like this (Main.lua):

local thisFileAsset = …
assert(thisFileAsset == asset.Main)

function setup()
    print(thisFileAsset)
end

If passing a plain filepath would be easier then that would work too.

Cheers!

@Simeon @John OR even better, a callback that would be called to modify source code that will be loaded prior to it actually being loaded to allow for dynamic code modifications?

function onTabLoad(source, tab)

    -- Modify the source here

    return source -- return the modified source to be loaded.
end

Pretty much, I’m working on a preprocessor of sorts but at the moment I have to rely on hardcoded file paths in each file I want processed.

if Preprocess(asset.Main) then return end -- Preprocess

-- Compile time constant
-- Occurances of 'kClearColor' are replaced with '40, 40, 50'
-- at LOAD time. kClearColor is NOT a variable!
MACRO(kClearColor, 40, 40, 50)

-- Another compile time constant.
MACRO(kStartupMessage, "Hello Preprocessor!")

function setup()
    print(kStartupMessage)
end

function draw()
    background(kClearColor)
end

another small caveat i’ve encountered, if the high level project uses coroutines like mine do, a call to coroutine.yield() can have unexpected results.

basically it can give a false impression that the local coroutine is yielding when in fact it’s just the codea+ thread that is yielding, this is unfortunately because we can’t pass a coroutine to yield specifically, instead the function will decide what the current coroutine is to yield

probably nothing that can be fixed about this, just need to be aware

@skar What unexpected results are you seeing?

Codea+ threads are just managed coroutines and the overridden coroutine functions just wrap around the Codea+ thread objects.

The behaviour itself should match original behaviour (without Codea+).

yes I guess I wasn’t completely clear, it’s not a fault of Codea+, it’s a pitfall of understanding that can be easy to fall into

For example, in my new bitmapfont I use a coroutine to compute the text to mesh, and this coroutine will attempt to yield once every letter, but let’s say I as the developer forgot to actually write my coroutine and all I did was call coroutine.yield? Well if I have Codea+ running in the back, nothing happens, but if I don’t have Codea+ as a dependency then I get an error saying “attempted to yield outside of a coroutine”

@skar Ah, I see what you mean. In that specific case I can cover that in Codea+ too.

Can’t run latest Codea+

@UberGoober What project is that? Try deleting the Codea+ project and redownload it. The TweenMod tab should have been deleted.

Edit: yep, that’s the issue. WebRepo currently doesn’t handle deleted files during a project update so I’ll look into fixing that soon.