We are definitely moving into the part of the dev cycle where we can craft the “mid game” experience of Clockwork Empires effectively. One of the issues we are currently facing with the middle game is one of performance. We have a requirement that the game perform 10 simulation ticks per second, as mandated by our AI. This means that every update to the game state needs to take place in approximately 100 msec to avoid stuttering between frames – the renderer will keep rendering, if necessary, while it waits for the game AI to catch up. If you have 100 characters to be simulated, this gives you a budget of slightly under 1 msec per character. With Revision 36, we started to see people sending us save games with functioning colonies of 70 or so colonists, where stuttering was frequently occurring, and where in general the frame took more than 100 msec to run on my PC at the office. What can be done?
Optimization for game AI is, not surprisingly, basically the same as optimizing a renderer. The goal is to eliminate unnecessary work. You can do this in a number of ways: for instance, pathfinding can (and should!) use a hierarchical structure to accelerate A* queries. We have a connected component system in place which stops people from spending a lot of time trying to path from one area to another area if they can’t reach them, but pathfinding still takes a certain amount of our budget and needs more optimization. In the mean time, however, it is interesting to look at what our actual hotspots were, thanks (as always) to the miracle of RAD Game Tools’ Telemetry. Here’s an itemized list of problems and how they were fixed:
- “Unnecessary dependency checking.” Our job requirement system can check to see if a job dependency can be filled by doing another job, and if that job has a dependency that can be filled by doing another job. Very often, a job requires empty hands; if you have a tool, this requires you to stow the tool. This requires you to be wielding a tool; if you weren’t wielding a tool (for instance, if you are holding a Crate of Pickled Black Fungus) the game would then try to fill the stowing a tool dependency by seeing if you could wield some tool somewhere, which in turn would try to see if it could get you to drop the Crate of Pickled Black Fungus to wield the tool to stow the tool to get your hands emptied. We cleaned this up by adding a flag saying “don’t evaluate dependencies for this requirement.” We also do this in a few other places: most notably, if we don’t have an enemy, we see if there is a job we can perform to create one. All this got cleaned up.
- “Unnecessary round trips to and from Lua.” This shows up in the stockpile code, amongst other things. Everybody hates how stockpiles currently work (which is to say not at all), so I’m just moving the entire mess to C++ space and rewriting them! Yay!
- “Getting all the objects in a region with a certain tag, and then ignoring the tag.” This shows up a bunch of times and is related to “Unneccessary Round Trips To And From Lua.” If you ask the engine from the script system for “all the objects in this radius with this tag”, it has to search for them (slow), put them in an array (allocation of memory!), send the array back to Lua, and then delete it once Lua is done with it (Garbage Collection!) This adds up a lot, especially when you consider the amount of round trips that the engine actually requires us to take. The solution is two-fold: first, just ask the engine “Are there any entities with this tag in this radius” and have it return a Yes or No answer. (In fairness, this code didn’t exist.) Second, for a sufficiently large radius and a sufficiently sparse object quantity (for instance, all alarm waypoints in a 50 tile radius), it is better to just reverse the lookup and check all alarm waypoints in the game, rather than searching 10,000 squares by hand for the alarm waypoints.
- “Unnecessary string access.” Tags are designated as strings because, well, scripters like strings. For fast comparison, we hash the string; for elimination of programming bugs, we convert the string to lower case first. In a handful of places, however, we would hash the string and lower case it every time we did a comparison, without re-using it. Fixed!
With these changes, people should be able to have a lot more colonists running around in the simulation without the game side lagging. Have fun!