Making of Grimrock: Rapid ProgrammingJuly 25, 2012|
In this week’s chapter of “making of Grimrock” I’m going to talk about programming. This might be a little lengthier post and more technical and therefore probably not for everybody, so it’s fine if you want to just skim ahead! 🙂
First a bit of background. I’ve been programming for more than 20 years, starting from Basic on the venerable C64, moving on to AMOS, 68000 assembly and C on the Amiga, and after that I’ve been programming mostly in C++ for the past 10 years or so. I’ve also tried other programming languages such as Scheme, Java, D, Objective-C and Python (although very briefly). Today games are written mostly in C++ because it has very good performance but more importantly in my opinion because it is the de facto standard, in other words almost all libraries and tools (especially on the consoles) are geared towards C++ development.
A typical game development workflow when working with C++ is something like this:
1. Design and write code in IDE (IDE being basically a glorified text editor)
2. Compile code (compilation turns the source code into machine executable sequence of instructions)
3. Run the game and wait for it to load
4. Test your changes (play the game to the spot where you have made your changes or load a saved game if you’re lucky and the save game is still compatible with your code changes)
5. Go back to step 1
Steps 1 through 5 is usually collectively called the “iteration loop”. Essentially step 1 is where all the innovation and fun happens, everything else is just lost time. The length of the iteration loop is usually somewhere between 30 secs to a couple of minutes (if it’s longer than that you’re pretty much screwed). On consoles and mobile devices the iteration loop is usually even longer because the code and/or data has to be transmitted to the device for testing.
In game development, especially when tuning game play, the code changes you make in step 1 are often very quick to make and development speed can become bottlenecked by redundant steps 2-5. There are two factors in this. First, the actual time lost to waiting due to compilation, waiting for the game to start up and getting the game state to where you want it to be in order to test the new feature. Second, and this is far more important in my opinion, with a long iteration loop your mind easily wanders off and you lose the trail of thought. For example, if you have to wait for a minute for the code to compile, there’s a pretty good chance that you check your mails, open a browser to check the news and suddenly you’re thinking about your girlfriend, wife, dog, whatever :). And it’s this context switch that really hurts because getting back into the flow of programming needs considerable amount of time.
So clearly we have now established that the iteration loop is critical, especially for us because we are so small, we have only one programmer and development times are short.
So the first thing we did when starting up the company was to look into ways to shorten the iteration loop. This is not something that can be fixed by doing small tweaks to the development process. For example, in the past I’ve tried creating gui systems for tweaking various gameplay and graphics parameters on the fly and it helps a bit but not significantly because it’s very limited what can be done. The root problem is the use of statically compiled programming languages, usually C++, which does not allow making changes to the code on the fly. Fortunately there are better options. One of them is Lua and particularly an optimized jit-compiled variant called LuaJIT (more about LuaJIT later). Lua is a very neat dynamic extendable language that is almost ideal for game development: it’s relatively fast, it has a small memory footprint, liberal license and compiles and runs on practically every device. It is a very expressive language with many higher level features such as first-class functions, garbage collection, closures and coroutines. Basically a single line of code can do much more in Lua than in C++ which on its own can increase productivity a lot.
Using Lua for game development is hardly a new innovation. Many games use Lua for scripting and no wonder why with its impressive feature list. However most big games use Lua just for scripting game logic or in some other limited sense. We decided to turn the table completely around! In Legend of Grimrock we decided to fully embrace Lua and code everything that we possible could in Lua to really leverage all the good things in Lua. That means that almost everything that makes Grimrock what it is, is written in Lua. Only the low level systems such as rendering and audio engines are written in C++ because that’s where we really need the real high performance of C++ or need to get close to the hardware.
So Lua is great but what about the iteration loop? The single best thing about Lua for us is that code can be compiled dynamically while the game is running. This is such a major difference to working with C++ that most jaded C++ programmers can’t even grasp the full implications of this. At least I couldn’t before giving Lua a go.
Basically with Lua all the unnecessary steps 2-5 vanish completely. No more waiting for code to compile; the dynamic compilation step is lightning fast in Lua. No more waiting for the game to launch because the game is running all the time. No need to play the game to the right spot because you’re there all the time!
With Lua I can actually inject new code into the running game and make all sorts of modifications. For example, practically the whole monster AI was programmed while I was watching those poor monsters whose brains I was fiddling with. Talk about live brain surgery! 😀
Basically the workflow I have set up is that I have an in-house code editor built on top of wxWidgets and wxStyledTextCtrl (I’m planning to rewrite the editor in Lua, can you guess why? :)). The editor is running as a separate application while the game runs on its own listening to incoming network connections. By pressing Ctrl+E in the editor I can send the current function (or selected lines) over the net to the game application which might even be running on a different machine. The game then dynamically loads the incoming source code into the Lua state and the effects of changes appear almost magically on the screen of the running game. This still amazes me every day and also makes game development and trying crazy ideas sheer fun which I think is crucial if you’re trying to make a good game.
Ok, that said everything is not perfect yet. Firstly, there are still some native code, the renderer most importantly and every once in a while I need to shutdown the game, go back to Visual Studio and work the old fashioned way. But I can live with that because it happens quite rarely and most of my time is spend in the Lua universe. Sometimes weeks passes between interludes to the C++ land. Also Lua being executed by a virtual machine is not as fast as C++ but luckily LuaJIT helps with this. LuaJIT, made by Mike Pall, is a version of Lua that does just in time compilation of Lua to machine code. It’s completely compatible with Lua (it’s a drop in replacement) and at least an order of magnitude faster. However it’s not available for all platforms. iOS and game consoles do not currently allow modifying code pages when the app is running, so LuaJIT can’t be used on those. So it’s going to be plain Lua on these devices which is a shame because these slower devices could really benefit from faster execution. It remains to be seen how big a problem this is.
Another problem with Lua is the lack of proper development tools although the situation is getting better slowly. I’m missing the Visual Studio debugger – still the best debugger I’ve ever used – and other nice to have features with proper IDEs such as call tips and refactoring capabilities. However, we have plans to expand our in-house code editor, but I have so far been too busy with the game project itself to spend more time with the tools. The lack of debugger might sound horrible at first but actually with live code injection it is not that bad (I can always sprinkle the code with prints on the fly). But still certainly a debugger would be very nice to have.
Another problem is with locals and up values in dynamically compiled code. If a reloaded function refers to locals in the chunk scope, the new version of the function does not see them because they are not declared when the function is reloaded (only the function body is re-evaluated). Therefore I’m hardly using any chunk level locals in the code at the moment and most of the functions are either globals or more often methods in classes which can be replaced easily (the classes themselves are stored in global variables). This prevents using some features of the language and sometimes does not make the code look so pretty, so this is something I’d like to improve at some point. Unfortunately merging upvalues/locals of patched functions is a nontrivial problem and I’m not even sure if it can be solved without changing Lua in some way.
But this is getting out of the scope of this article and so I think it’s time to stop now. Thanks for reading. Until next time! 😀