live code editing

Fast iteration times on large projects is often cited as one of the primary reasons to incorporate a scripting language, such as Lua, Python or JavaScript, into the project. The primary downside to this is overall complexity, performance, maintenance, and debugging. So wouldn't it be great if we can get the benefits of scripting languages with none of, or close to none of, the downsides?

This post is written from the perspective of game development. With that said, most if not all principles are applicable to any software project of similar size and complexity.

Certainly there are other benefits to a more high level scripting language, particularly if the rest of the code base is in a rather low level programming language, such as C or C++. It allows people who would generally not classify themselves as programmers for their primary skill or interest, like designers, artists and writers, to put their content into the game and seeing how it works in practice.

This is not a benefit I'm going to put a lot of effort into exploring or considering, simply because I can't speak for what kind of tangible benefits designers, artists and the like can derive from a scripting language when looking at the project as a whole compared to the amount of man hours required to make a scripting language integration perform well, not to mention that the debugging facilities for such an integration tend to be far worse than what we have available for native languages.

So how do we get the fast iteration time of scripting languages using ordinary C or C++? It turns out, it's actually not that complicated. In fact, most of us have been only a few steps away from achieving it while doing other things. The solution is dynamically linked libraries, .dll on Windows, .so on Linux.

// file: foo.c void foo(int a, int b) { // ... } // file: bar.c typedef void foo_t(int, int); foo_t *foo = nullptr; void foo_stub(int, int) {} void* load_foo() { void *lib = dlopen("foo.so", RTLD_NOW | RTLD_GLOBAL); if (lib) { foo = (foo_t*)dlysym(lib, "foo"); // ... } if (!foo) foo = &foo_stub; // ... return lib; }

The above snippet is a simple example of how to dynamically load code at runtime, the code is written for Linux using dlopen and dlsym functions, the Windows equivalent is copied verbatim but with the necessary replacements to use LoadLibrary and GetProcAddress instead of dlopen and dlsym.

So, first of, there are a couple of different things that needs to be done to get the code base ready to be loaded at runtime. I've already implemented live code editing in one of my main hobby projects, Leary (https://github.com/grouse/leary), so I'l be using that as a reference to describe what needs to be done and how to achieve it.

Figure out what to reload

The first thing that needs to be done is deciding which parts of the code base should be loaded at runtime and start making the changes necessary to make it possible to compile these parts into a .dll or .so. For Leary I wanted as much as possible to fall in this category. That means initialisation, input handling, simulation, and rendering.

Make a few small, concise entry points to the reloaded code

We could simply reload every single function in the code, but that is quickly going to turn into a nightmare to maintain. Instead, I suggest you provide a very simple interface to the code that is reloaded with just a few different functions and move all code behind those, making a concise and easy to maintain interface between the dynamically loaded code and the main entry point of the program.

In Leary, I decided to make this interface four functions: game_init, game_pre_reload, game_reload, and game_update. The purpose for game_pre_reload might not be immediately apparent, and at the time of writing this post it actually isn't required for Leary, but it's there so that if anything relies on other outside state, such as Vulkan driver state, the game code has a chance to make sure that this state is ready to be reloaded. In the case of Vulkan, which is the most immediate case it'll be needed for in Leary, it means I can make sure the graphics device is idle and have finished all its work before I reload the code and recreate shader, buffer and texture resources.

game_reload is called after the game module has been reloaded. The function is used so that we can pass all the global state that we've gathered from the module back to be set accordingly. As you might imagine, the less global state you rely on the less this function has to be doing.

game_init and game_update are fairly self evident. game_init is called once at program startup to set up the primary state object that it then returns for the primary program to keep track of across module reloads. game_update is our single entry point into the module that we call every frame with the game state object from game_init to perform work.

In a more traditional input-based program without a non-stalling infinite main loop traditional to games, you can imagine a fifth function work_available which would block the main loop in the main program until there are input events that need processing or we have found in some other way that we need to perform work.

After these code changes the function to load all the game code in Leary off of the disk looks like this:

#define DLOAD_FUNC(lib, name) (name##_t*)dlsym(lib, #name) void* load_code() { void *lib = dlopen("/path/to/game.so", RTLD_NOW | RTLD_GLOBAL); if (lib) { game_init = DLOAD_FUNC(lib, game_init); game_pre_reload = DLOAD_FUNC(lib, game_pre_reload); game_reload = DLOAD_FUNC(lib, game_reload); game_update = DLOAD_FUNC(lib, game_update); } if (!game_init) game_init = &game_init_stub; if (!game_pre_reload) game_pre_reload = &game_pre_reload_stub; if (!game_reload) game_reload = &game_reload_stub; if (!game_update) game_update = &game_update_stub; return lib; }

Coalesce state

If all you do is run the above code in your main entry point once, it should 'just work'. But that's not why we're here. We want to be able to continuously reload the code as it changes.

If you're not relying on any static variables, global or local, in your code base, you're in luck. Just make sure game_init returns the primary state object that you pass into game_update and you're pretty much good to go.

If you do use any static variables you need to understand how static initialisation works, in particular in regards to dynamically linked libraries.

Static local variables

Static variables that are declared locally in function or block scope is initialised at first execution of that scope. The deinitialisation of the static variables occurs at program or module exit. Because we'll be reloading the code continuously we'l be calling dlclose followed by dlopen every time we reload. Static deinitialisation of all block or function scoped static variables in the dynamic library will occur at dlclose.

There are a few potential pitfalls with this that we need to take care of. The first obvious one is if the static variable is being allocated on the heap that means we end up leaking the memory of that static variable every time we reload the code. That'd be bad. The other pitfall is if the static variable is initialised by executing some non-trivial code. That might be bad.

There isn't really any simple solution that fits all in this case. You're just gonna have to go through each static variable and figure out if multiple initialisations and deinitialisatons of the variable would result in bad behaviour. As a personal preference, I tend to avoid the type of static variables that would cause these types of problems for a number of different reasons, live code editing being just one minor one of them. These reasons may become the subject of a future post.

Static global variables

Static global variables will be initialised with dlopen and deinitialised with dlclose. Fortunately, these are a lot easier to handle cleanly in our use case of reloading the module.

Put simply, avoid initialising static variables using function calls, e.g. static Foo foo = some_foo_init();. Similarly as with the static block and function scoped variables, these assignments mean that some_foo_init will be executed every time we reload the module. The same thing applies if the static variable is used to track state that needs to persist across reloads, as the reload will reset the state.

Instead, put all these initialisations behind an init or reload function. In the case of Leary, that's exactly what game_reload is used for. The state object from game_init is used to store and restore the values of the variables.

Putting it all together

At last. We've coalesced all the state required into a single object that we can pass into the drastically reduced entry point interface that we've defined, and we've (hopefully) cleaned up all static variables or made sure they all behave well when being initialised multiple times. It's time to write the main loop that pulls it all together and reloads the code when appropriate.

typedef void game_init_t(GameState*); typedef void game_pre_reload_t(GameState*); typedef void game_reload_t(GameState*); typedef void game_update_t(GameState*); void game_init_stub(GameState*) {} void game_pre_reload_stub(GameState*) {} void game_reload_stub(GameState*) {} void game_update_stub(GameState*) {} game_init_t *game_init = nullptr; game_pre_reload_t *game_pre_reload = nullptr; game_reload_t *game_reload = nullptr; game_update_t *game_update = nullptr; #define DLOAD_FUNC(lib, name) (name##_t*)dlsym(lib, #name) void* load_code() { void *lib = dlopen("/path/to/game.so", RTLD_LAZY | RTLD_GLOBAL); if (lib) { game_init = DLOAD_FUNC(lib.handle, game_init); game_pre_reload = DLOAD_FUNC(lib.handle, game_pre_reload); game_reload = DLOAD_FUNC(lib.handle, game_reload); game_update = DLOAD_FUNC(lib.handle, game_update); } if (!game_init) game_init = &game_init_stub; if (!game_pre_reload) game_pre_reload = &game_pre_reload_stub; if (!game_reload) game_reload = &game_reload_stub; if (!game_update) game_update = &game_update_stub; return lib; } void main(int, char **) { void *lib = load_code(); GameState game = {}; game_init(&game); while (true) { game_update(&game); game_pre_reload(&game); dlclose(lib); lib = load_code(); game_reload(&game); } return 0; }

Naturally, reloading the game code every frame is a horrendously bad idea. It'll tank the performance and you're pretty much asking for trouble. There are a number of ways to get around that problem: either reload the code after some predetermined, arbitrary amount of time, or use a way to determine whether the game.so file has changed and only reload the code when that happens. To accomplish the latter you can simply query the last modified date of the file whenever you want to reload it, or you can look into the various platform specific ways that exist to be notified via callbacks or events when a file changes. Right now in Leary I simply query the last modified time of the dynamic library every second and reload it if it has changed.

Final words

There you have it. Natively compiled C/C++ code reloaded dynamically at runtime, allowing you to very rapidly change a piece of code, compile it, and have the changes immediately take effect in the running program. It's magic.

Some caveats still exist. For example, if the layout of your data changes, by changing the order of variables in a struct or their type you're going to have to do a proper full reload of the game. Secondly, as alluded to earlier in the post, you're going to have to take extra care that any libraries that you use won't behave badly when reloaded dynamically like this.

Debugging can also be somewhat temperamental at times. Visual Studio in particular likes to lock both the .pdb and executable files when it's debugging so that they can't be changed at all, and GDB seems to freak out somewhat when symbols have changed without it realising.

In the case of Visual Studio's locking you pretty much just have to make sure to copy the binaries and debug symbols to intermediate copies before you load them, so that the symbols and binaries being generated by the compiling isn't the ones you're actually loading.

For GDB's freak out, I'll get back to you on that one. I'm sure there are reasonable solutions to work around it.

These problems have, so far, for me, been rather secondary in priority. Generally when I want to debug something I'm not very interested in being able to do live code editing, so I have a simple #define to turn it off in favour of traditional static linking.

As a programmer very comfortable in the low level trenches of C/C++, this then gives me the best of both worlds. When I need fast iteration testing gameplay I turn on the live code editing and I just have to compile for my changes to take effect immediately, when I need to debug I turn it off and I get the stable, 'normal', statically linked code that GDB and Visual Studio plays nicely with. All with none of the massive performance cost of scripting languages or the immense man hours required for the maintenance.