Making software malleable requires a core mechanism of a read-eval-print-loop or a REPL, basically some way to make changes to the running program.
Traditionally, a REPL reads code from a 1D stream of text, runs it, and writes the results to a 1D stream of text. Typically this is a non-graphical experience, and it doesn’t translate well to LÖVE. Instead of the usual sequence of instructions, LÖVE programs are atomized into various handlers that get called by an invisible event loop. Instead of interactively accumulating sequences of instructions, it feels more natural to interactively reload the contents of an editor that might contain definitions for multiple handlers like:
Until recently, I was aware of two ways to build a REPL in LÖVE:
Evaluate a dynamic string using Lua’s
load(). lines.love basically uses this approach. It suffers from the drawback that any errors crash the system and you have to restart. Worse, since lines.love implicitly runs the code on startup, errors persist across restarts. You have to go outside the program to fix things, a non-malleable experience that will stymie a lot more people.
xpcall()for error recovery. This works, but scripts can no longer call love.draw directly. You have to instead define a new convention. Edit say
app.mousepressed, etc. and then call each within a
love.mousepressed, etc. respectively. And code in the editor now must respect this convention. My Freewheeling Apps have so far used this approach.
However, I just discovered a much cleaner approach that lets you forget about the editor and build naive, idiomatic LÖVE apps within the editor.
Here is the LÖVE app again. The basic idea is to redefine the main event loop to wrap calls to all the
love.* handlers in
xpcall() with a custom error handler. The error handler restores the
love.* handlers to what the REPL needs. As a result, you get a chimerical app that can shape-shift to include whatever scripts you type into the editor, and shape-shift back to the editor at any event of your choosing, or on any error.
Code in the editor can also call out to any libraries included in the app. The membrane between app and scripts is porous.
You can see the raw code in this gist.
You can do almost anything LÖVE allows, with one exception: redefining the main event loop at
love.run. Any errors there will require restarting the app.
While the app can recover from errors, it doesn’t protect you against infinite loops. It’s just a proof of concept.
In addition to the 100 lines in main.lua, the app includes a standard immediate-mode GUI library for the text editor widget. But I made no changes there, and you could swap it out for something else without breaking the core idea.
Here’s a simple script to try typing into the app:
N = 1 function love.draw() love.graphics.setColor(1,0,1) love.graphics.print(tostring(N), 100,100) end function love.update(dt) -- burn, baby, burn! end function love.mousepressed(x,y, button) N = N+1 end function love.keypressed(key) error('back to IDE') end
Click on the mouse to increment the number on screen. Press any key to go back to the IDE and see how error messages are displayed.