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
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
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
@John Thanks, I’d heard you were adding better support so I’m looking forward to it!
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
@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.
@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
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”