Monday 31 July 2017

Platform-Agnostic Hot-Swapping for C

Quick post discussing some coding things that are pretty old but also something possibly not obvious for people who are new or used to modern tools (IDEs) that remove the need for doing it yourself.

I've been really busy but tried to jump into a quick game jam to bash out a new renderer in Vulkan over a weekend. Doing so the core requirements were rapid iteration and low overhead (verbose, explicit everything Vulkan was an interesting constraint). As I'm back on Windows (after five+ years basically exclusively doing serious work on Linux) then I'm using Visual Studio and have access to Edit and Continue. But what if I didn't or was using a programming language not supported or wanted this to be something that worked over several IDEs (including ones without this feature)?

We're used to building our project, running it, and then rebuilding it for another test. But that's not rapid. Here's the old alternative that's possibly as old as shared libraries (maybe even older). Start your main program loop setting up some data storage and then:

[do all your initialisation here - don't leave it in your dll load]
while (notQuitting) {
  newTimestamp = getDllModifiedTimestamp();
  if (newTimestamp != currentTimestamp) {
    if (dllLoaded) {gameUnload(&data); FreeLibrary(tempDll);} // empty old library.
    CopyFile(Dll, tempDll); // so compiler can write Dll later, not blocked.
    LoadLibrary(tempDll); // get new library.
    gameLoad = (loadCall*)GetProcAddress(tempDll, "load");
    gameTick = (tickCall*)GetProcAddress(tempDll, "tick");
    gameUnload = (unloadCall*)GetProcAddress(tempDll, "unload");
    gameLoad(&data);
    currentTimestamp = getDllModifiedTimestamp();
  }
  gameTick(&data); // run the actual game.
  checkForQuitMsg();
}

Which gives us a rather simple implementation of a program that waits for us to swap out the dll it uses to actually do the work and then reloads that new dll. Use the storage you pass in as somewhere to pass through any data you want to persist (so ideal for the persistent game state if you're making a jam project). Just make sure you encode any tweaks to the structures you need into the updated OnLoad() method so you can tweak anything. No, it's not as powerful as stop-anywhere Edit and Continue with the automatic editing of locals to allow the program to resume but if you're writing a game then you really don't need that granularity - just reshape things as you need at the outside of the loop between game ticks.

Oh, and one final thing: when Visual Studio is actively debugging the loader application you're in, it doesn't want to let you build another project in the same solution. The command line gets round this issue but I have no idea why anyone thought that compiling a different project should be blocked when anything in the solution is being debugged.

msbuild gameDll.vcxproj /p:configuration=release /p:platform=x64