Live coding (wikipedia) can be an intense and rewarding experience. Here are some notes on how it works in LuaAV.
script.live = true
LuaAV is responsive to user editing by reloading the script whenever it is saved. However, reloading will destroy and re-create any resources that were running, such as synthesis routines and OpenGL windows. The reloading behavior can be changed in any script by setting the global property script.live = true.
Now when a script is saved in the external editor, LuaAV does not close the script, but instead re-runs it using the existing Lua state. That means that every line of your script is run again, which may or may not modify state and resources that had been created previously.
This feature is possible because Lua is a fully re-entrant language. However there are several things to be careful about when using script.live=true:
locals and globals
A local variable is only visible in the block in which it is declared. When a script is run, variables declared local in the script will be available only for that run, and for any functions defined in that run. (You can imagine that running a script is like putting “function()” … “end” around the entire script, and calling that function) Re-running the script will not be able to see the locals created in a previous run.
Therefore, if you want a value to be visible in a future run, it should be not declared local.
However, if your script changes were minor, it is likely that many global variables will be overwritten by reloading the script. If you want to avoid this behavior, you should test and set the global only if it doesn’t already exist; a handy idiom looks like this:
On the first run of the script, the variable myglobal does not exist, so it is created and initialized with a value of 100. On a subsequent run, myglobal already exists and is not modified.
This is especially important for creating persistent resources such as OpenGL windows and audio synths. Failing to use the win = win or Window() idiom will lead to opening new windows every time the script is re-run.
coroutines and go()
Similarly, a coroutines launched using go() in a script, or by while true … wait() … end at global scope, will spawn another coroutine each time the script is re-run, potentially leading to many parallel tasks. If you want these coroutines only to be launched on the first load of a script, one strategy could be to use an initialization variable:
initialized = true -- initialized must not be a local variable
synths with no envelopes
Synths may have a similar problem: if they do not have an envelope to stop themselves, you need to make sure you can still reach a reference to them to call their :stop() method, otherwise you can end up with playing synths that cannot be canceled.