The Upvoid Engine design depends heavily on scripting. Not only the behavior of entities and AI, but basically all game logic is done by scripts. Because of this, it was clear that we had to choose a scripting language with care. It had to be very fast and easy to use. In the end, we decided to use Mono. Here is why.
Note: This is an updated version of a devblog article I wrote in 2013 for the Upvoid Devblog.
List of Languages
This is a list of the languages we looked at:
|Lua (LuaJIT)||Dynamic||MIT||WoW, Freelancer, Many others|
|Angelscript||Static||zlib||Penumbra, Overgrowth, others|
|Python||Dynamic||Python||Battlefield 2, Eve Online, others|
|Tcl||Everything is a string||BSD||?|
|Squirrel||Dynamic||MIT||L4D2, Portal2, Alien Swarm|
|Mono||Static||MIT, LGPL||SecondLife, Unity3D|
Narrowing it down
We decided to take a closer look on Angelscript, Lua, and Mono. Those three had proven, stable implementations and were already used in several games. I wrote small benchmark programs for each of those, getting an idea of the documentation, API and, of course, the performance. Benchmarks are always biased and in many ways problematic regarding their value of information, but they give an important impression of the overall performance of a specific language/interpreter/compiler configuration.
Benchmarking was done on my old Core i5, with the highest optimization settings available. The results are averages from enough consecutive executions to eliminate most of the caching and JIT stuff.
|Language Configuration||A* Pathfinding||Script Call||C Function Call|
|C++ reference (gcc 4.7.2)||0.3 ms||< 0.001 µs||< 0.001 µs|
|C# / Mono 2.10||3.0 ms||0.14 µs||0.01 µs|
|Lua / LuaJIT 2.0||3.3 ms||0.06 µs||0.05 µs|
|Angelscript 2.25.1||9.7 ms||0.17 µs||0.09 µs|
Lua seems to be the default choice in the video game world. Lua is lightweight in every aspect. The syntax is incredibly simple, the interpreter fits in 120kb. It is well documented, widely used and actively developed at some university in Brazil. Many libraries exist to make it easier to register C functions. Debugging is possible, but not directly supported. We would probably want to implement our own debugger.
LuaJIT seems to be one of the best JIT compilers around. Even when using more complex data structures, it reaches the performance of C# code compiled with Mono, which is impressive if you keep in mind that Lua is as dynamic as a language can be: Everything is stored in tables, even local variables or functions are actually just table entries.
And this leads to the main problem with Lua: It is extremely powerful in the sense that you can implement virtually every paradigm (thanks to the almighty tables), but the syntax is reduced to such a small set of operators and keywords that as soon as you want to do something fancy, it becomes painful. Lua is absolutely capable of doing OOP, but the code becomes so fucked up that you just don't want to do it -just look at the Lua equivalents of these Moonscript snippets. To be fair, the implementation of the A*-benchmark has about the same number of lines as the C# or C++ implementations (see the Lua and C# implementations).
Conclusion: Lightweight, fast and mature. Syntax is only pretty as long as you don't touch the metatables.
Angelscript is a relatively unknown scripting language. It has a statically typed syntax that is almost identical to C++. Its static types allow the VM to be notably faster than Lua's default interpreter. Recently, a first JIT compiler for Angelscript has been released, which I was unable to get running for anything else than a "Hello World"-script.
Angelscript is actively developed by a single guy since 2006, so one could argue whether to call this a mature library or not. However, it worked without problems in my benchmarks. The documentation is complete, but not very helpful in some details. It does not come with its own debugger, but, similar to Lua, provides line callbacks and other stuff that makes writing a debugger easy. Angelscript is used in some indie projects like Amnesia:The Dark Descent or Overgrowth.
Conclusion: Nice syntax, but slower than the other candidates without a stable JIT compiler.
Mono is Novell's implementation of the CLI (Microsoft's .Net). This means that it's actually not a language, but a VM that can run any .Net bytecode. The language I used was C#, but other .Net compilers exist for F#, Python, C++, Java, Lua, Ruby, Scala and many others. The CLI has a unified interface that allows all supported languages to make use of other .Net code (even classes can be used from other languages) and allows us to provide a single scripting API without having to care about the language that's actually used.
Mono is a big actively developed project with almost 100k commits from >500 developers. We can probably expect continuous improvements for the next years. In the Linux world, it is used widely for running .Net applications. In contrast to that, only few projects use Mono for scripting (Unity3D and Secondlife). However, Mono officially encourages the use of Mono for scripting. The documentation is a bit sparse on some points, but should be okay to work with outdated or nonexistent in many important cases, you often have to rely on google and StackOverflow. In some cases I had to read the source code to understand why a function did what it did. Debugging can easily be done right from MonoDevelop, providing a fully functional debugger with everything one could wish for.
C#/Mono got the best results in raw performance of all language/compiler configurations tested, being "only" 10 times slower than the reference C++ implementation. Microsoft's .Net seems to be even faster.
Conclusion: This is a monster with 6M lines of code. However, it's fast and stable. Lots of libraries and compilers for different languages exist.