Exploring directions to fix game programming

Game development carries multiple issues about coding. I’ll cover some areas that game industry should explore to improve gamedev craft.

What’s wrong?

To me, game development feels pretty much always slower than imagined. “This feature? Alright, will be done tomorrow”. Then, hours later, you’re looking for a cause of access violation or mem leaks and can’t finish the task in time. Sometimes a game engine needs to be modified, subtly, let’s say. If it’s open-sourced then we dive deep our teeth into the source and fix it. Then compile. However, hundreds of C++ files compile a lot of time to just test a fix. In game engines that are not open-sourced it’s not even possible to make changes, so looking for and working on workarounds takes even more time.

And I’m only talking about coding. Let’s leave the topic of external tooling and other professional humans like level designers, 2D/3D graphics and so on.

I can’t say it’s all OK. I’ve noticed there are some directions available to be explored so game programming could be improved.

  1. Happily, I’m not the only one who sees that’s possible.
  2. Sadly, I hardly can see any movement of big companies towards fixing the situation.

1. Architecture

First of all, game engines still promote Object Oriented programming. And to some sick extense. Unity3D makes you create a sound source and put it into the scene (world) just to play one sound. Unity3D lies about using component architecture but I’ve covered this before. Some other engines are all about classes like Entity, GameObject or any other name. Then, this base class for objects appearing in a virtual world is to be extended for various implementations about rendering and logic. Deriving from the class brings it’s all functionality – both wanted and unwanted. This is a huge problem since gamedev is all about prototyping ideas which takes a lot of time to refactor. Otherwise, we create constraints for our designers.

In game industry, there are some examples of using Entity Component Systems architecture. Recently, on GDC 2017 it was claimed that Overwatch netcode is all about ECS. Authors pre-claim that it would not be that easy to prototype so many different characters interacting between each other if not ECS.

Adam Martin has declared few years before that some MMO games were made with this architecture.

So it’s an ECS vs OOP battle. Splitting data from logic. Forgetting about romantic “objects” which resemble to be like real objects behaving on their own. Having a group of soldiers in a strategy game like Total War you can’t code behavior for each soldier separately because groups behave atomically, together. Every single soldier could feal the fear of a fight, be sick, hungry and so on but we don’t implement those simulations anyway. It’s exactly for the same case as with lightning and shadows in games – we don’t do that with raytracing yet – because performance. We use workarounds. Simplifications. So we should do with “objects” – let’s forget about the “Real world” since it can’t be entirely implemented.

To me, it’s obvious. Self-behaving objects in virtual world is a lie. And here I’m even curious. Aren’t structs (classes without functions) enough? Do we even need classes in programming language? Well, methods are useful for helpers that improve readability. Otherwise, I’m not even sure about encapsulation (private fields). Python language doesn’t have access modifiers and they still manage to work with it pretty well.

2. Workflow

As you’ll see this space of thoughts covers tooling, programming languages and architecture. Indeed, it all together impacts on the workflow – how fast we can iterate with the idea or actual development. This impacts the workflow.

Tweaking assets. Some game engines implement this in a convenient way. Situation: designer modifies some texture, saves it and it instantly reloads in game world without restarting it. It sure needs some work in the engine since old texture has to be overwritten with a new one. Of course, there will appear corner cases like different texture size. It’s even worse with 3D models. Let’s say we have a skeleton-based model of a person. If the skeleton is modified the whole structure of game object has to be reinstantiated. Or, if it’s not OOP then it may be just modifying some piece of data. Anyway, OOP or not, pointers could be problematic here. Let’s say some animation points at some skeleton parts and we have some pointer like “currentAnim” – then it may break. The point here is that engine has to be foreseen to be used in a tweaking manner. And yet, not every engine provides such feature.

Tweaking your game code. Testing small changes like a color of rendering or tweens. It’s not that rare that some parts of game are rendered procedurally. Or, at least, there is a render() method called periodically for some entities. We often end up with some a big code infrastructure needed to create remotely changeable variables to provide configurable parameters. It takes time.

Bringing back game state. The “load game” option. Big games need manual testing, we can’t cover that with unit tests. It’s obvious. However, it’s a lot of work to provide serialization or anything else that’ll always work during development of game and engine altogether. The more I think about it – it could be provided by joining power of architecture and/or a language that would make serialization more “automatic”. Of course is not a trivial task since there are multiple things in memory that are based on something else like a texture based on file path. And the texture itself may be saved in GPU so it’s about calling another API (OpenGL, Direct3D, Vulkan, whatever).

Network communication. Data format, context and so on. If game heavily depends on backend then it’s a development of two programs at least – client (the game) and server (or “backend”). Data format should be the smallest possible thing for action games because it’s changed very often. More to that, if anything is “serialized” then the serialization has to be super fast. I tried using .NET built-in serialization for small shooter (2D Quake-like game, much simpler). I’ve totally failed with this because CPU couldn’t handle it 15 times per second on my netbook – garbage collection was totally killing it. The other thing is that the architecture needs to be planned for lag compensation, movement prediction and synchronization – to be ready for “simple” algorithms like snapshot interpolation and extrapolation. Especially in racing games, shooters or real time strategies. The message here is that dev teams pretty often forget about netcode until it’s somewhat too late. Testing and development of netcode are hard. One method to improve that could be writing some integration tests between client and server in various situations.

In fact, all those parts nearly everything about running software. Assets are data. Code is the software. Code may also be launched in multiple instances so it needs (network?) communication. In the end, there is always some state persistence – storage – which would contain game saves.

3. Programming languages

Boilerplate. Like header files – .h or .hpp files in C++. A split between declaration and definition? Is it really useful? Well, it was questioned on StackOverflow about what it is but I’ll state that it’s simply not needed. It’s inheritance from C and no one tells the thing but to me, it seems to be a performance fix rather than implementation encapsulation. C#, Java, Clojure, Scala – all those play well without separate declarations.

Compilation / build time. Often multiple things could be cached. Sometimes could be read from memory instead of hard disk. Multi-thread compilation is another thing (this, actually, is implemented more and more often these days).

Stupid compilation errors. Semicolon on the end of lines in C++ is there because it was more performant and easier to implement parsers in those days. Templates in C++ often break so badly that we can see errors following 20+ types deeper in the structure and it’s hard to guess what’s really wrong. A compiler has to do modern work like helping programmer, not just outputting bytecode or machine code.

Generated code is hard to debug. I’m talking about macros here. But they are pretty useful to type less code. What if compiler printed code after applying macros? If that would be able to choke breakpoints in – it would be awesome.

Type information. Remember remotely changeable variables? In Java or C# I have I can go with Reflection. In C++ there is no such thing (let’s not mention RTTI, it’s too simplistic). But why not have this built-in into language rather than API and VM? I don’t mean the network part but information about types that would be casted to objects would be useful to have while making a GUI for field modification in entities without any boilerplate code like macros.

Memory leaks. Memory allocation should be done manually. Same with deallocation – garbage collector is useless since we often have to implement Object Pooling in C# or Java. In games, we often know when this job should be done. In good architecture (bringing ECS again) it is very simple, though. However, a language could help identify places of allocation which were not deallocated in certain moments. For instance, at the moment of exiting game and going to main menu we should be able to list all memory allocations that were done AFTER entering a game. I’ve made such thing once in our engine with ActionScript 3. Was ultra helful to find memory leaks. In C++ it can be done with a custom allocator. But it’s another cumbersome work to improve debugging. It would be nice having it purely supported by a language.

Preventing access violation. We should never be able to access word place in memory by a mistake. However, this stuff is about performance, so I can’t tell if there is a good balance between garbage collector or safe pointers (with counters). Some languages (like Objective-C) have counters built-in into pointer variables. In C++ we have a class to do that but still – not everyone everywhere uses that. If that was built-in we may be able to track where and when a value was built. I mean, pointer would be a trackable thing!

Preventing NullPointerExceptions. It’s actually the same problem as with access violation but in higher level (language’s Virtual Machine or built-in class tool rather than Operating System). However, it tells us something about types. Scripting languages (like JavaScript) are even not to be considered in game development in my opinion. We need types. And we need to specify them well. If function is defined to return a list it should never return something like nil or null. And if it wants to return list OR null then it should be explicitly specified. Then, anyone using this function would have to cover both return cases – list and null. Compiler should detect all situations of null treated as a list. This feature is called “Null Safety” and I believe it would be useful in gamedev. Kotlin has this exact feature while it’s not really functional!

Java has Hot Code Replace which should be available in every language! I’ve mentioned tweaking some code parts without restarting the software. That’s what it is. It speeds up some tweaking a lot. In Java it stops working when class structure is modified. So it works only when the function definition is changed, even adding a method breaks bytecode replacement. But let’s discuss this feature. If I would add some field to existing type and this type is already instantiated at least once, then we would have to reallocate all entities of this type. And that’s actually possible to be done but no one did that in any language, yet.

Immutability? It’s not necessary but useful sometimes. Especially with some logic. Turn-based games are expected to benefit from it the most. Do we need to go with functional paradigm then? No. Immutability was provided into Kotlin which is rather OOP language. And it was done even in JavaScript libraries like Immutable.js.

R&D: Some people just do that

The ECS architecture is bringing more and more attention to devs. Especially to those who tried to build bigger things while constantly changing it. Recently, we could hear some words from Blizzard Entertainment about ‘Overwatch’ Gameplay Architecture and Netcode. There is also an interesting discussion starting from Erin Catto about implementing ECS vs EC in Overwatch. Which is actually purely amazing. A big and trusted company told us that ECS is great. And since this architecture can be implemented in any language (even pure C) there is not much more to discuss than just taking this approach live.

Now let’s talk about programming languages. Programming language impacts on workflow which is: error count, build time, “debugability” and saving keystrokes.

First of all, someone tried to go with LISP-like language called Clojure into Unity3D. Meet – “The integration of the Clojure Programming Language with the Unity 3D game engine.” While this is horrible solution someone really needed functional paradigm in gamedev. ekhart presented his efforts testing Arcadia. The positive here is that the code can be dynamically changed without relaunching a game. The drawback might be that it’s compiled in the game itself.

Another effort towards having functional for gamedev was Michael’s Shaw talk about game development with ECS in Scala. He did end up with event architecture so it felt natural to Scala developer to work with it. It’s worth to mention that Michael also tried Rust for game development on game jam.

Some people love functional paradigm because it’s pure, testable and helps a lot with just proper calling functions. Because it’s all about functions. One problem about is garbage collection. There is a video made by Brian Will where he explains his idea about solving this exact problem:

John Carmack (creator of Doom, Quake and Oculus VR) has stated few years ago that he tried to use functional paradigm to implement games but that was a little ackward sometimes. He updated on this during his live coding session on Oculus Connect 2 at 22:26 until 25:30. It started from declaring global variables in this pure functional language. The demo was run on Android is written in Racket language. You could find some more scripts on this repo: jb55/vrscript-samples. However, as he mentioned next year Oculus Connect 3 at 7:50 some people reacted like “what the hell, John? LISP? Are you serious?” To bring more attention with VR they had to choose JavaScript:

In my opinion, it’s just ridiculous and understandable at the same time.

Finally, there is a Jonathan Blow who decided to create a new language fixing all problems that he had during his long gamedev career. In fact, these are very common problems. It’s worth to see at least this talk:

which shows just a part of all interesting features. I’ve mentioned some problems in previous part of this article, he just tackles most of them:

  • compile/build time is quick
  • full compile-time execution – have a code that runs during compilation and can produce code – using conditions, loops and whatever
  • type info available during the compilation phase gives an ability to easily inspect structures (AST is actually available!)
  • “array of structure vs structure of arrays” is a matter of using a keyword
  • efficient allocation thanks to custom memory composition
  • hot code replace
  • lots of nice instructions and syntax sugar

There are lots of videos on youtube – some of them are demos of the language/compiler and some of them are live coding sessions made during the development of the language. There is also an interesting introduction talk before Jonathan started streaming videos:

Jai (codename of the programming language) is still in development and it’s release date is unknown to this day, yet. However, it’s worth to wait for.

References

Articles

Talks

Others

  • Great article and great job! I’ll be definitely returning to this post.
    And thank you for mentioning me ;p

    Could you provide more details, why do you think that functional programming is such bad idea for game development?

    About compilation in game, I think that, it isn’t a bad thing, because Lisp compiler work different than others. It uses incremental compilation, which compiles only portions of code. It’s not optimal for sure. But this is why we have more live programming and shorten feedback loop. Optimization is done when you preper release so there isn’t any drawbacks of this method.

  • Pingback: Ciekawe blogi uczestników DSP - ucgosu.pl()

  • Pingback: Jak będzie wyglądał świat IT za 50 lat? - Przewidywanie przyszłości - StormIT.pl()