C++ Tricks is a series of posts on core libraries for game engines and language experiments shared as the Kahncode Core Libraries.
For a long time now, I have been meaning to write about core libraries for game engines, and share my own take on them.
I’ve always been fascinated by those tools that make your code so much more expressive and elegant. Over the course of my side projects, but also as fun little language experiments, I have compiled a collection of those core idioms of my own. Each of them emulates a feature of the standard library or adds some expressive feature to your toolbox.
Today I’d like to kick off a series of articles introducing the Kahncode Core Libraries. In each post, I will focus on one feature and shed some light on the implementation details. I will also be progressively releasing the code as I go through this series. The repository can be found on GitHub and is open for comments and contributions.
I hope you will enjoy, learn, correct and teach me something, and perhaps make use of this in one of your projects, game or not.
If you don’t know what I’m talking about and wonder what is a core library, read on…
Core libraries?
Every program has some sort of core library, also called common library. It’s usually the lowest layer over the language itself that serves as building blocks to the rest of the program.
In C++ especially, the need to standardize the code, and the growing complexity brought by multiple compilation targets and platforms, quickly leads to some common macros and template utilities that enhance the codebase and make life easier for the developers. These core libraries act as an extra layer over the standard library in these codebases. Here is one of the prime examples: the Qt Core Library.
In game engines, however, the core libraries are usually a lot more complex. In there you will commonly find the following features:
- Hardware Abstraction Layers for all OS calls
- Container libraries
- Smart pointer libraries
- Threading libraries
- Allocators
- Math libraries
- Serialization libraries
As you may have noticed, most of those features are available in the C++ language already in the form of the standard library. There are also numerous popular libraries out there offering this and more such as boost. So why do game engines reinvent the wheel or have their own versions ?
Why reinvent the wheel?
Game engines usually avoid using the standard library. While part of it is due to historical hurdles with exotic platforms and compiler support, there are some strong benefits to it:
- An opinionated and partial implementation allows useful performance gains. We can focus on performance and on the features used in practice. Besides some features, the focus on performance usually means tradeoffs such as executable size, memory usage, compilation times, feature set, thread safety and so on.
- Avoid usage of RTTI and exceptions. These features are commonly frowned upon in game engines.
- Compatibility and consistent behavior across all compilers and platforms. This is very valuable for game engines.
- No dependency on the C++ language, you can add features to fit your own needs.
- Control over allocation strategies with custom allocators, which in turn increase performance and alleviate fragmentation issues.
- Better compilation times.
- Easier readability, debuggability, and extendability, although this depends on your implementation! I’ve definitely seen some “here be dragons” code in my times.
- Legacy from pre-C++11 and habit. If it works don’t fix it.
Boost sees some use but is somewhat frowned upon, with compilation times being one of the strongest argument against it. Game engines generally try to minimize dependencies, as it quickly becomes a nightmare to maintain against the multitude of compilation targets and platforms.
All of this combined means that reinventing the wheel can sometimes be the path of least resistance for game engines.
What are the downsides?
- Compatibility with C++ libraries. Marshaling the data from your containers and types to standard is costly and impractical. Luckily most game engines integrate C libraries instead of C++, which avoids the issue. This is partly because C++ libraries often use unwanted features such as exceptions.
- Core libraries are hard to get right, and hard to maintain. Notably, the upgrade to C++11 was a significant effort for all of these companies. Meanwhile, the STL gets constant updates, even more so these days as the language continues to evolve.
Conclusion
In the end, avoiding the STL at all costs could be attributed to habit and legacy. While this may not have been true in the past, STL should now be well-supported on all relevant game development platforms, so the Core libraries are not strictly necessary. However, game engine code is often kept around and updated for decades, so most historical game engines have a legacy of core libraries already available and the migration may not be worth the cost.
As the pace of new features in STL increases, as well as the reactivity of compiler vendors, some of the original arguments may no longer be valid. What remains as the strongest motivation is the ability to create an opinionated implementation that you can fine-tune to benefit your specific use case, on a per-program basis.
Some game engines are adopting the standard library progressively. CryEngine is one of these. Others have continued investing in their core libraries to support the new features of the standard and more. Unreal Engine core libraries are a great example of what can be done, and I invite everyone to take a look in the code as it is available.
There are some notable attempts to standardize or at least share more of this code. Game developers tend to have similar needs, so we could all benefit from an alternate standard adapted to our use case. One of the most successful example is EASTL. If you are starting your engine from scratch, or if you want a better standard library for your high performance program, then you should probably consider it. For research purposes, you can also dive into the code of the various compiler vendor’s implementation of the STL (GCC, LLVM, Microsoft).
Meanwhile I will share some of my modest attempts in the next article of this series: a simple and fast implementation of RTTI and dynamic casts.
Good read. There is a typo in Reinventing the “wheel”.
Glad you liked it. Thanks for the correction.