Saturday, May 8, 2010

State design refactoring

Abstraction is a great thing to have in your program for many reasons. It allows the separation of different levels of functionality and it minimizes the amount of work you need to add/update sections of code. It also encourages modularity. However, I took the abstraction a bit too far for level editor version 1.0. I achieved a nice amount of modularity, but updating code was a painful process.

When writing version 1.0 I had decided to decouple the dependencies between the GUI panes as much as possible, such that, the GUI panes never talked to each other directly. In fact the GUI panes merely indicated which operation needed to be performed. The code for each operation was then packaged in sections at one location, which also handled the communication between GUI panes. This design made it real easy to rearrange the GUI components without affecting the operational code.

Essentially I implemented a finite-state machine. The main loop of the program simply did a switch/case on the current game state and performed to corresponding operations. The GUI panes indicated which operation needed to be performed by changing the editor's game state. When an operation finished, the editor's game state was changed again. It was a pretty neat design but it required that every operation have its own corresponding game state (over 35 game states). Below is a simplified state digram of the editor.

[state diagram, version 1.0] (click to enlarge)

With this model the GUI buttons served only to change the current game state. Only after the game state was changed, did the code for the operation run. It was an indirect way of calling code. Below is a UML sequence diagram showing the resulting function call communication of an example operation.


[sequence diagram, version 1.0] (click to enlarge)

Unfortunately this design produced a complex and unintuitive web of game states. Adding GUI components turned out to be a headache, since determining a button's game state required intimate understanding of the game state transitions. I actually had to draw out the game state diagram to reference when coding. Another hiccup with this design was that much of the information required to perform an operation resided on the GUI panes. It was a hassle to correctly manipulating the information and keeping the GUI panes in sync.

I needed to simplify the game states. After applying bits of the Hopcroft DFA minimization algorithm, it was able to drastically trim down the number of game states by allowing the GUI panes to directly talk with each other. It greatly reduced the communication burden on the main loop.


[sequence diagram, version 2.0] (click to enlarge)

It eliminated all of the one-shot game states (states which only existed one pass through the main loop). Now, the only operations which warrant a new game state are the operations which alter the way the input is handled.


[state diagram, version 2.0] (click to enlarge)

In the end I lost a bit of abstraction and modularity in exchange for cleaner and simpler code. Updates to the code are now a breeze.

State diagrams were created using graphviz. Here are the .gv file for v1.0 and v2.0.
UML sequence diagrams were created with http://www.websequencediagrams.com/

No comments:

Post a Comment