It's not about technology for its own sake. It's about being able to implement your ideas.
As an experiment, I decided to port an action game that I wrote in [1996 and] 1997 to mostly-pure Erlang. It wasn't a toy, but a full-featured game chock full of detail and special cases. I never finished the port, but I had the bulk of the game playable and running smoothly, and except for a list of special cases that I could write on a "Hello! My Name is" label, it was purely functional. I wrote about what I learned in Purely Functional Retrogames.
I didn't mention the most unusual part: this may be the world's only port of a game from RISC (PowerPC) assembly language to a functional language (Erlang).
Exactly why I chose to write an entire video game in PowerPC assembly language in the first place is hard to justify. I could point out that this was when the 300,000+ pixels of the lowest resolution on a Macintosh was a heavy burden compared to the VGA standard of 320x200. Mostly, though, it was a bad call.
Still, it's a fascinating bit of code archaeology to look at something developed by my apparently mad younger self. By-the-book function entry/exit overhead can be 30+ instructions on the PowerPC--lots of registers to save and restore--but this code is structured in a way that registers hardly ever need to be saved. In the sprite drawing routines, option flags are loaded into one of the alternate sets of condition registers so branches don't need to be predicted. The branch processing unit already knows which way the flow will go.
In Erlang, the pain of low-level graphics disappeared. Instead of using a complicated linkage between Erlang and OpenGL, I moved all of the graphics code to a small, separate program and communicated with it via local socket. (This is such a clean and easy approach that I'm surprised it's not the go-to technique for interfacing with the OS from non-native languages.)
With sprite rendering out of the way, what's left for the Erlang code? Everything! Unique behaviors for sixteen enemy types, a scripting system, collision detection and resolution, player control, level transitions, and all the detail work that makes a game playable and game design fun. (The
angle_diff problem came from this project, too, as part of a module for handling tracking and acceleration.)
All of this was recast in interpreted Erlang. Yes, the purely functional style resulted in constant regeneration of lists and tuples. Stop-the-world garbage collection kicked in whenever needed. Zoom in on any line of code and low-level inefficiencies abound. Between all the opcode dispatching in the VM and dynamic typing checks for most operations I'm sure the end result is seen by hardware engineers as some kind of pathological exercise in poor branch prediction.
All of this, all of this outrageous overhead, driving a game that's processing fifty or more onscreen entities at sixty frames per second, sixteen milliseconds per frame...
...and yet on the 2006 non-pro MacBook I was using at the time, it took 3% of the CPU.
If anything, my port was too timid. There were places where I avoided some abstractions and tried to cut down on the churning through intermediate data structures, but I needn't have bothered. I could have more aggressively increased the code density and made it even easier to work with.
(If you liked this, you might enjoy Slow Languages Battle Across Time.)
permalink November 26, 2012
I'm James Hague, a recovering programmer who has been designing video games since the 1980s. Programming Without Being Obsessed With Programming and Organizational Skills Beat Algorithmic Wizardry are good starting points. For the older stuff, try the 2012 Retrospective.
Where are the comments?