This post is mirrored on my blog.
I have been hard at work on my dynamic environment based on Tiny C Compiler. It has been going worse than I hoped. I am attempting to static link SDL 2 with an application I want to test the dynamic environment on. I am soldiering on, and hope to have something useful before the end of the month. If I can't get it by then, I may need to look for other options. It has been an uphill battle mainly because these tools were never intended to be used this way, but provide enough value that rewriting from scratch doesn't feel like a viable option for me.
Cakelisp itself has some new features worth mentioning.
File and line macros
I added (this-file)
and (this-line)
macros to Cakelisp's runtime
macro library. These macros replace their invocation with the filename
as a string or the Cakelisp line number, respectively.
The most obvious use-case for these is debugging, e.g. outputting:
MyFile.cake:1234: Hello
via something like:
(fprintf stderr "%s:%d: Hello\n" (this-file) (this-line))
The more interesting use-case is for code navigation straight from the program. I am writing a 2D vector animation program that uses the immediate-mode GUI paradigm for its UI. A button can be drawn to the screen and its click responded to in the following code:
(when (do-button renderer (addr (path regular-font > atlas)) (path regular-font > texture) (+ (path state > ui-pane-position . X) 5) button-y 250 75 "Toggle atlas") (set (path state > view-atlas) (not (path state > view-atlas))))
do-button
will render the button and return true
if the button was
clicked.
I wanted to go straight from visible buttons on screen to the code that
caused that button to exist. I added file and line arguments to
do-button
, then created a macro to automatically populate those fields
whenever I called do-button
. The do-button
function then calls
handle-ui-meta-inquiry
, which opens Emacs for me at the file and line:
;; The UI element is trying to describe itself somehow. Do something helpful for the programmer (defun handle-ui-meta-inquiry (element-name (addr (const char)) filename (addr (const char)) line int) (fprintf stderr "%s:%d: %s\n" filename line element-name) (var goto-line (array 32 char) (array 0)) (snprintf goto-line (sizeof goto-line) "+%d" line) (runtime-run-process-sequential-or ("emacsclient" goto-line filename "-n") (return)))
This handle-ui-meta-inquiry
would of course need to be fleshed out if
I plan to ship it to end-users. Even this simple version enables a great
quality-of-life improvement for me while I'm iterating on an interface.
Now, if I want to make a change, I press a key while hovering over the
button and I'm taken straight to the relevant code.
Note that I implemented the "meta inquiry" feature as an immediate mode function as well. I did not need to set up fancy metadata or describe my whole UI up-front. I can still extend the meta inquiry to render all the possible things I could inspect via building a list each frame instead of making the UI element decide whether it has been inquired about. These sorts of features are fantastic when making complex UIs.
This was made possible by the humble this-file
and this-line
macros!
RunProcess is now C-compatible
Cakelisp uses sub-processes to compile and link comptime and runtime
code. I wrote several macros to make an easy interface to creating such
processes in user Cakelisp code as well. One such macro is
runtime-run-process-sequential-or
, which I used in the previous
section to open Emacs.
The macro has a simple form:
(runtime-run-process-sequential-or (command) on-failure)
The command uses a strict form where each argument is wrapped in quotes. This leaves no ambiguity for arguments with spaces, and allows you to freely intermix string variables with string literals in commands.
Here's a simple example:
(runtime-run-process-sequential-or ("gcc" "-c" filename "-o" output :in-directory "my/build/dir") (fprintf stderr "Failed to compile %s\n" filename))
The sequential
part of the name indicates that the current function
will wait there until the process closes, thereby letting you easily run
processes which have dependencies on the previous one. This is used
frequently in GameLib to build
3rd party code during Cakelisp's compile-time phase.
There are start
variants that start the process and continue execution
immediately after for cases where you want to run processes in parallel
instead.
This interface has been in for a while, and has proven its value. The recent change was to make the RunProcess file C instead of C++, which makes it compatible with more compilers (like Tiny C Compiler).
Needless to say, if you want to do more complex things like redirect pipes and so on, you'll need to use a different interface to run your sub-processes. However, in my use-cases these macros are more than enough.