I don't know how good this book is on the topic it claims to be about (as reflected in its title), but I definitely wouldn't use it as a C++ or Win32 textbook. A few blunders I've spotted straight away:
void main(void)
Neither ISO C nor ISO C++ allows return type of void
for main
. You'll get at least a warning from any decent compiler (including modern versions of VC) for such code.
WIN32_LEAN_AND_MEAN
instructs the compiler (header file logic, actually) not to include extraneous MFC overhead.
See here for an explanation of what it actually does.
// enter main event loop
while(GetMessage(&msg,NULL,0,0))
See the warning in "Return Value" section in documentation for GetMessage
However, some statements make me extremely skeptical about it being a good reference book for game development as well. For example:
Many computers will speed up or slow down due to the game's level of complexity. For example, if there are 1,000 objects running on the screen, the CPU is going to have a higher load than if there were only 10 objects. The frame rate of the game will vary, which isn't acceptable. Hence, you must synchronize the game to some maximum frame rate and try to hold it there using timing and/or wait functions. Usually, 30fps is considered to be optimal.
Any modern game will try to get as much FPS as possible. Timing the game logic to display update, and consequently delaying the redraw to ensure timing, hasn't been acceptable for over a decade. In addition, 30 FPS is rather low by today's standards; 60+ is considered to be optimal.
Don't be afraid to use global variables. Many video games don't use parameters for a lot of time-critical functions, instead using a global parameter passing area. For example, say a function looks like this:
void Plot(int x, int y, int color)
{
// plots a pixel on the screen
video_buffer[x + y*MEMORY_PITCH] = color;
} // end Plot
Here the body of the function takes less time than the function call. This is due to the parameter pushing and popping on the stack. In this case a better method might be to create a global parameter passing area and then make assignments before a call, like this:
int gx,gy,gz,gcolor; // define some globals
void Plot_G(void)
{
// plot a pixel using globals
video_buffer[gx + gy*MEMORY_PITCH] = gcolor;
} // end Plot_G
Aside from obvious stylistic problems with globals, the claim that using them is faster is false. In C++, the default calling convention for functions typically puts as many arguments as possible into registers. Thus, the first version of the function, with parameters, will receive both in registers, and do all arithmetic using registers as well (which is also faster). The one with globals will have to read and write the actual memory locations.
Use inline functions. You can improve the preceding trick even more by using the inline directive to get rid of the function call completely. The inline directive instructs the compiler to make its best attempt to put the code for the function right where it's called, rather than making the actual function call. Of course, this makes bigger programs, but speed is more important. Here's an example:
inline void Plot_I(int x, int y, int color)
Any modern C compiler treats inline
specifier much like it treats registry
- "whatever you say, I'm going to do as I see fit anyway". In other words, marking (or not marking) the function as inline
has absolutely no relation to whether it will actually be inlined by compiler.
Always use 32-bit variables rather than 8- or 16-bit. The Pentium and later processors are totally 32-bit. This means that they don't like 8- or 16-bit data words, and in fact, smaller data can slow them down due to caching and other related memory addressing anomalies. For example, you might create a structure that looks something like this:
struct CPOINT
{
short x,y;
unsigned char c;
} // end CPOINT
Although creating this structure may seem like a good idea, it's not! First, the structure itself is now 5 bytes long— (2*sizeof(short) + sizeof(char))
= 5. This is really bad, and it's going to wreak havoc on the memory addressing. A better approach is the following structure:
struct CPOINT
{
int x,y;
int c;
} // end CPOINT
A bunch of wrong things here. The whole part about 32-bit words being faster than 8- and 16-bit is broadly wrong. In many cases you actually want smaller words for many reasons - first, because if you use them as arguments, they can often pack into registers (e.g. you can pack 2 shorts into a 32-bit register, but 2 ints would take two registers, contributing to starvation, and forcing something else on the stack). Second, when you copy those structures around, again, copying two shorts is just copying 32 bits as one word, but copying two ints is copying two 32-bit words.
In fact, the very claim that working with 32-bit words is faster than working with 8-bit chars is most likely a confusion with memory alignment (you definitely want stuff to be aligned at 32-bit boundaries).
Second, the claim that first version of struct - the one with char
- consists of 5 bytes, and is "going to wreak havoc" - is totally wrong. The compiler will insert padding to align struct fields such that they are accessed with optimal performance. In this case, VC with default settings would pad the struct with 3 bytes at end, giving 8 bytes - and ensuring that arrays of this struct have all fields of all elements aligned at 32-bit boundaries.
Use binary shifts for simple multiplication of integers by powers of 2.
Mostly pointless, as compilers will automatically use shifts for that purpose whenever possible. In fact, this is a very old optimization, one of the first that appeared (because of its triviality to implement). Furthermore, compiler can actually do even better, and use some special purpose instructions, such as x86 LEA
, to do certain arithmetic operations even more efficiently.