Developing game software is a complex, intricate, math-intensive and error-prone business. So it should be no surprise that virtually every professional game team builds a suite of tools for themselves, in order to make the game development process easier and less error-prone. In this chapter, we’ll take a look at the development and debugging tools most often found in professional-grade game engines. 9.1 Logging and Tracing Remember when you wrote your first program in BASIC or Pascal? (OK, maybe you don’t. If you’re significantly younger than me—and there’s a pretty good chance of that—you probably wrote your first program in Java, or maybe Python or Lua.) In any case, you probably remember how you debugged your programs back then. You know, back when you thought a debugger was one of those glowing blue insect zapper things? You probably used print statements to dump out the internal state of your program. C/C++ programmers call this printf debugging (after the standard C library function, printf()). It turns out that printf debugging is still a perfectly valid thing to do— even if you know that a debugger isn’t a device for frying hapless insects 411 412 9. Tools for Debugging and Development at night. Especially in real-time programming, it can be difficult to trace certain kinds of bugs using breakpoints and watch windows. Some bugs are timing-dependent: they only happen when the program is running at full speed. Other bugs are caused by a complex sequence of events too long and intricate to trace manually one-by-one. In these situations, the most powerful debugging tool is often a sequence of print statements. Every game platform has some kind of console or teletype (TTY) output device. Here are some examples: • In a console application written in C/C++, running under Linux or Win32, you can produce output in the console by printing to stdout or stderr via printf(), fprintf() or STL’s iostream interface. • Unfortunately, printf() and iostream don’t work if your game is built as a windowed application under Win32, because there’s no console in which to display the output. However, if you’re running under the Visual Studio debugger, it provides a debug console to which you can print via the Win32 function OutputDebugString(). • On the PlayStation 3 and PlayStation 4, an application known as the Target Manager (or PlayStation Neighborhood on the PS4) runs on your PC and allows you to launch programs on the console. The Target Manager includes a set of TTY output windows to which messages can be printed by the game engine. So printing out information for debugging purposes is almost always as easy as adding calls to printf() throughout your code. However, most game engines go a bit farther than this. In the following sections, we’ll investigate the kinds of printing facilities most game engines provide. 9.1.1 Formatted Output with OutputDebugString() The Windows SDK function OutputDebugString() is great for printing debugging information to Visual Studio’s Debug Output window. However, unlike printf(), OutputDebugString() does not support formatted output— it can only print raw strings in the form of arrays. For this reason, most Windows game engines wrap it in a custom function, like this: #include // for va_list et al #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 #endif #include // for OutputDebugString() 9.1. Logging and Tracing 413 int VDebugPrintF(const char* format, va_list argList) { const U32 MAX_CHARS = 1024; static char s_buffer[MAX_CHARS]; int charsWritten = vsnprintf(s_buffer, MAX_CHARS, format, argList); // Now that we have a formatted string, call the // Win32 API. OutputDebugString(s_buffer); return charsWritten; } int DebugPrintF(const char* format, ...) { va_list argList; va_start(argList, format); int charsWritten = VDebugPrintF(format, argList); va_end(argList); return charsWritten; } Notice that two functions are implemented: DebugPrintF() takes a variable-length argument list (specified via the ellipsis, . . . ), while VDebugPrintF() takes a va_list argument. This is done so that programmers can build additional printing functions in terms of VDebugPrintF(). (It’s impossible to pass ellipses from one function to another, but it is possible to pass va_lists around.)