05/25/05: MY STATE-MANAGED OBJECT DEBUGGER
ENEMY OF THE STATE
Breakpoints...the old standby debugging technique of single stepping through code. Invaluable, but sometimes not the best way to see what's happening with a single object over the course of many frames. Complex AI objects in TCFH go through N layers of class hierarchy, and core base-class functions are shared by dozens of classes and thousands of instantiation objects. Tracing the code path for a single object can be convoluted, and breakpoints aren't as useful when code is shared and inherited among many instances of objects. You may hit a breakpoint thousands of times before you inspect the object you're actually interested in, and then have to do it over and over again to watch it over multiple processing frames. And sometimes, like when debugging a fullscreen DirectX app, breakpoints just can be a pain.
The next step is a conditional debugger, which lets you set breakpoints based on data, skip N loops, etc. But even so, conditional breakpoints are actually a bit unstable in my IDE (integrated development environment), so I tend not to rely on them. And for trapping a single object, you still need to know that object's address or other unique identifier before you can set that breakpoint. Sometimes it's not easy to determine just which object is which, and you often need to know that during runtime when it's less accessible. To simplify that, I've implemented a hotkey which will force a hard breakpoint when the object under the mouse cursor begins its processing phase.
Sometimes you want to look at the big picture, especially for AI debugging which has extra layers of complexity with messaging systems and scripting. Or you may want to analyze objects that are offscreen, or compare the processing of groups of like objects. For that I have special debug logging messages liberally peppered throughout the code, which output into a formatted logfile. Each entry of the logfile contains the object's state, position, velocity, facing, game and world time information as well as the text of the message itself. Log entries are prioritized and marked with where in the code they originate. By toggling a flag in the build, or a hotkey in the game on a mouseover, I can enable the log flooding to begin for a given object.
I then load this formatted log into a special debugger I wrote (see screenshot) which graphically shows a single object's 2D motion through the world (top down view), facing, state, etc. This lets me see a recording of the object's path and states through space and game-time, which I can play back, single step, and reverse. When it comes to motion over time, it's usually a lot more illuminating to visualize things spatially instead of staring at endless numbers. My tool also has measuring capability (distance, angle, etc.) as shown by the dark purple line and numbers. It will show markers and waypoints, or any relevant point called out in a log entry -- shown here as the white X and number 1. A single click will jump me to the exact line of code that created the log entry in the IDE. It can load multiple logs and jump between them at the same game-time tick for quick comparisons. But probably the most important feature is filtering: I can hide or highlight specific events, filter by color-coded priority, find similar events, find changes in a specific object state tag, etc. which lets me find the information I'm looking for quickly within tens of megabytes of logfiles.
It's a great tool to see what's going on with an object's AI and physics at the high level -- often I don't need to use breakpoints to fix a bug after looking at a log, but even when I do, it puts me ten steps ahead by knowing where to start single-stepping.