Sunday 11 June 2017

Teach Yourself Game Programming in 21 Days - Day 8

This is the chapter I've been waiting for.

I'd messed around with coding and writing games before (on an ancient Casio handheld, among other things) but this chapter made me seriously reconsider everything I'd written until then. It's called "Warp Speed, Anyone?" and it's all about optimising code; optimising for speed, that is. A game can be utterly beautiful, but if it isn't responsive, then it's probably not going to be very good.

The first thing the author mentions is compiler optimisation. Essentially, a compiler is free to reorganise or eliminate code that it deems is in the wrong place or unnecessary. Generally the compiler lets you control how much it tries to 'help' you, which is good, because sometimes it can make the wrong assumptions and make it virtually impossible to track down bugs. Knowing how to write code is one thing; being able to debug the resulting assembly language is another. I've done this a few times in my life, so it's a skill I have (kind of) but I will point out that it's often not the best use of your time. Accordingly, André recommends leaving compiler optimisation settings low, despite the potential speed increase. Games have some of the most complicated code out there; you want it to work, first.

The next major subject is that of the memory model. This will be completely alien to anybody who hasn't worked with DOS before. Early on in IBM's history, the decision was made to separate code (executable memory) from data (read-only memory) by the use of segments. This prevents code from overwriting itself. This is a good idea. However, as the PCs of the time had very little memory, each of these segments was only 64KiB large. This was a bad idea, but nobody knew just how much storage was going to expand in the years to come.

All of the programs I've written so far have fit into the Tiny memory model; only one segment required for both code and data. For a little future-proofing, I've been compiling with Compact (one code, many data) but the author actually recommends Medium (many code, one data)! The reason given is that having only one data segment means it's easier to write inline assembly, because DS always has the same value. If more than 64K of memory is needed (and it will be), it will have to be allocated from the FAR heap, which slows down data access slightly. This might be how DOSJUN ends up; it has a lot of code already. I'll have to write 'far' next to more of my pointers.

The next part makes me sad. The Plot_Pixel_Fast function a large part of the book code has been using is an important candidate for improvement; every use involves a function call and pushing of three parameters, which seems like slight overkill considering all it's doing is setting one byte. My solution to this would be to declare the function as a macro, but the author chooses instead to declare the function's parameters as globals, then add a new Plot_Pixel_Global function which uses these globals. André was probably going for maximum compatibility here, but I can't help but note he missed a trick.

Next come standard techniques - aliasing structures or pointers to temporary variables so they don't have to be repeatedly dereferenced (compilers probably do this automatically) and unrolling loops to reduce the time spent incrementing a counter and jumping. There's also a mention of marking certain important variables as 'register' to ask the compiler to optimise them differently.

Then, lookup tables. This is an easy measure to implement in future programs. It's just a matter of trading memory for execution time. This was already done in Chapter 5 to store the results of sin and cos calculations, but it could be done for a lot more... I'm thinking of using the technique for DOSJUN's graphics.

The largest treatment is given to the technique of fixed-point numbers. The gist is to use integers to store non-integral amounts by keeping some of the bits for the fractional part. This allows us to use the efficient integer addition operations instead of the slower floating point calculations that are offloaded to the co-processor. This is the largest code listing in the chapter, and it has been dutifully typed out here.

The chapter finishes with a fairly lengthy and snarky look at DOS extenders, a sad but necessary evil in the transition period between real mode and protected mode processing. This enabled 32-bit programs on the otherwise 16-bit DOS. I'm not sure if I'll need this for DOSJUN, but I hope not, because it's something I've never done. I think it would be quite bizarre to write a DOS program that required 4GB of RAM. Also, I'd have to find a free online one, or write my own.

Despite the dearth of code, this was a difficult chapter to get through as it is quite dense and there's no pictures for me to take! The next chapter is about playing sounds, which would be great if I could get it to work.