Kolf: Observations on code quality and the engine design
February 21, 2010
The third blog entry in 24 hours. That’s a new record for me, though not the foremost reason for this post.
Kolf 2 has, after about 4 days of intense coding, reached a size of 3000 lines of code. (I admit that much code has been ported from kolf-ng, so you cannot really conclude that I can write 750 LOC a day.) With the base engine nearly finished (the terrain, some object types and general bling will be added later), this is a good moment to stop and take a look at code quality.
On the heuristic side, I’m quite happy with the overall stability. Box2D has turned out to be a good choice: Its documentation is not very extensive, but I could get started with it much quicker than with ODE. The simulation code is simpler and at the same time more flexible than in Kolf-NG, and the simulation feels very natural (even with missing terrain friction). Kolf 2 does now offer some nice convenience classes around Box2D, which hide the non-Qt data types and make the API feel more Qt-ish. If you would like to add some new object types to Kolf 2 (e.g. the black hole is missing currently), drop me a mail or a comment here and I’ll get you started on Kolf 2 hacking (some C++/Qt experience required).
Moving back to the original intention of this post, there are of course some metrics for code quality. Take for example the length of the Krazy report. Recent problems with EBN got me to installing Krazy on my local machine, which went really smooth. (I did not notice at that time that openSUSE has a perl-krazy2 package.) After some smaller changes and two justified “krazy:exclude” lines, krazy did not find any issues.
So if code quality is good, can we at least reduce the code length? Yes, we can! Checking 29 header and source files each, I could remove 26 unused #include directives. I unfortunately forgot to check the compile time before this cleanup (and I’m too tired to rollback my git), but now “make -j3” is running in 19 seconds on my machine. (I’m speaking of real-life seconds, user time is 27 seconds.) That is approx. 6,2 seconds per KLOC. If only I had numbers from other software to compare this to. (Quick check: kolf-ng is 6,9 seconds per KLOC. About 11% worse.)
In this image, dashed lines indicate very weak relations, which are only used for transfering change notifications, and bold lines indicate parent-child relations. Yes, both the ManagedObject and the Scene are in a certain sense parents of each Object. This works without any problems, because both will notice if the other one does for some reason kill an Object. While creating this graph, I have confirmed that every single edge in this graph is justified. Two main observations can be made:
- That does absolutely not look like a mess. There are cyclic dependencies, but these cannot be prevented on the intra-module level.
- Nearly everything has a direct connection to Object.
The second point is intentional: Shape and Overlay are two nearly self-contained parts of Object’s behavior (the physical shape and the editing interface), which have been split off to increase code reusability. The whole game engine consists of interactions between different objects, and all properties of the world are modeled as properties for objects. For example, there is an invisible course, which is used to spawn balls and record the course size. In Kolf-NG, there were also light objects for 3D rendering.
This generic design of a world as a bunch of objects makes Kolf 2 feel more like a generic game engine than an actual minigolf game, at least from the developer POV. I try not to change this trend very much when designing the interface. What is a course with holes in a minigolf game, could also be a level in a levelset, and so on. The Object class is designed such that new subclasses can be added through plugins, even if there is no sign of a plugin infrastructure in Kolf 2 currently.
The fine print: All of this does not mean that I am actively planning to turn Kolf into a generic game engine. There are more advanced and sophisticated projects like Gluon. What I am doing is not using the opportunity to design a game engine, I am reducing the probability to miss this opportunity when I might need it in the later run.
The other fine print: Writing this blog article took me two hours (to be attributed mainly to over one hour of reading Stack Overflow discussions on code metrics), so it’s actually not the third blog entry in 24 hours. Damn, no new record.