When I hear developers complain about long build times, it immediately makes me suspicious about the quality of their code base. They have simply built a big ball of mud, a.k.a. spaghetti code. What has happened is that the compiler gets bogged down by an avalanche of include files. Don't fight the symptom by using precompiled headers! Instead, fix the root cause: the crappy architecture of your game. What follows in this article, is a recipe for a clean code base that builds in a couple of seconds, not minutes.
Here's the TL;DR version of this article: do not include header files in header files. The dependencies that foo.cpp uses to implement its functionality should never leak into the foo.h interface. I'll state up front that a C-like programming style lends itself better for this than a C++ programming style. But even when using C++ classes, it is often possible to avoid include directives.
If the interface of class Foo only contains uses of class Bar that are references or pointers, then the compiler does not need to know what Bar looks like when parsing the header for Foo. All that is required is that Bar is forward declared for the Foo interface. You tell the compiler that there is a class named Bar, and leave it at that. Typically, there will be some classes in your game that need to include large third party frameworks. It is paramount that these frameworks do not leak into the users of class Bar, like class Foo.
If you constantly ask yourself: "How can I keep the implementation details out of the interface?" then you can cut down on the include directives. Also preferring primitive types char/int/float/bool and pointers over custom object types is a great strategy for pruning include hierarchies.
So where can this get you? To illustrate this, I will examine the code base of the game I am currently creating. The only uses of middleware are OpenAL and OpenDE. It is a fully featured game with biplanes, AI, formation flying, dogfighting, explosions, menus, tutorials, whathaveyou, totaling roughly 50K lines of code. But building from scratch (including linking) takes less than three seconds.
This is because the include hierarchies are extremely flat. For instance, here is what gets pulled in to implement the logical representation of my game world.
And here is what the code that uses the game world sees when it includes wld.h:
In my code, dependencies are hidden, and almost never propagated throughout the system. The notable exceptions are the leaking of my vector math header and my rendering context data into the interface. The latter includes no further files, and the former only three system headers.
The world contains terrain, planes, bullets, hangars, explosions and much more. But the code that uses (creates, destroys, renders) this world should not be bothered by these details.
Personally I think that distributed build systems, build caches, precompiled headers are all curses disguised as blessings. These mechanisms that make your build go faster, allow you to ignore the root problem for longer. The mud ball grows and grows, and when you are finally fed up with long build times, the tangled mess may be beyond repair. Top tip: disable precompiled headers!
Let me conclude with a tip on how to inspect the sanitation conditions in your code. Snowballing include directives are brutally exposed with the 'Doxygen' tool if you tell it to generate include graphs with graphviz's dot program. Use the following directives in your Doxygen configuration file:
DIRECTORY_GRAPH = YES
INCLUDE_GRAPH = YES
HAVE_DOT = YES
If the Doxygen output includes huge dependency graphs, you know that you are on the wrong path. Also, cycles in the graph with lower level files pointing back to higher level files are a huge red flag.
I'm curious to know how my peers view this issue. Also, what is the longest build time that you have encountered? Drop your remarks in the comments, they are highly appreciated!