Monday 28 November 2011

Code Mines

This is another no update blogpost, but a real update is getting closer.  A previous code review left me pondering... What if I just did it right?
So I thought I'd practice what I preach and do all of those fancy things that smart programmers dao.

The Pioneer codebase has always been dominated by the Brick class which describes the size, shape, position and colour of a building brick, and the Chunk class which measures 16x16 studs, contains zero to many Bricks and is arranged spatially with other Chunks to form the World.  Almost everything was either in one of those classes, or operated on them.

So the code was largely a bit of a lump. The Brick and Chunk classes became tightly coupled, with the World becoming a resource locator for chunks. A simple shared pointer made it far too easy to pass ChunkPtrs around and hold on to them even though - theoretically - they were short lifespan objects.

While I stuck to reasonable guidelines and practices, it became easy to add lazy features and it wasn't until I wanted to add mesh LoD and runtime LoD switching and discovered that it was a bit tricky. The possible solutions
- expose more members as public, either through access specifiers or get accessors.
- bundle more responsibility onto the already bloated Chunk class
- refactor so that the object graph makes the new feature easy to add.

Both of the least-effort solutions felt pretty bad, but it looked like it should be fairly easy to extract a responsibility FROM the chunk and isolate the rendering in a new object.  Even if it was a RenderableChunk class, it could still steal all of the occlusion, rendering and become a container for that stuff where it should be easy to add that mesh LOD functionality too.

And so the project went through whatever the opposite of suffering is as I pruned back the almighty Chunk. It lost a lot of fat, it also became a Directed Acyclic Graph and I could strip out a now redundant cycle.

Compile times were a little slow so I retypedeffed my shared pointers to incomplete types and made all of their clients rely on the abstract interface instead of the implementation.  This reduced the amount of include files getting chained together and allowed me to hack away in small, fast iterations.

The new acyclic object graph made the test suite simpler and much easier to expand to be more comprehensive. I was never satisfied with my test coverage and having that easy to improve is a load off. Its almost as if the "better" code was also easier to unit test. Astounding!

Programming can be a bit like a new LEGO build. You know what its supposed to be when its finished but you haven't got any instructions so you add and remove bricks, and each iteration will get you incrementally closer to your goal. One brick removed, two bricks added.

There will need to be more time at the keyboard before anything appears, but the last of the big changes has happened. From here it looks like I'll just be putting bricks back together. Maybe the odd one or two will be shifter, but I've solved the problem I wanted to solve and a few more to boot.