Keep Your Sanity With Event Handling State Machines
In my previous post I taught you how to wrangle your game initialization code in order to keep it from breaking with every refactor. Today, I’d like to talk about event handling and how to get a handle on it (pun intended). If you’re anything like me, your event handling code (generally your touchesEnded:withEvent:) is the longest method in your whole application; littered with switch statements, if/else clauses and enough boolean flags to make George Boole turn over in his grave.
It starts out simple enough, you add some code to your event handling method to kill zombies or harvest crops or what have you. Then you decide you want an edit mode where users can move cows around or redecorate their farm, so you add an ‘inEditMode’ flag. You execute the appropriate code based on that flag in an if/else statement. Additionally, you realize some functionality (like scrolling the world, for instance) happens in edit mode and in regular mode, so you put that functionality right after your if/else block; allowing it to be executed in either mode. A while later, you decide you want to be able to scroll in edit mode, but only if the user isn’t dragging a farm around the map. You create an ‘isDragging’ flag to indicate whether the user is dragging an object in the world; if so the ‘touchMoved’ event drags the object, if not, it causes the world to scroll.
This goes on for many months and eventually your ‘touchEnded:’ method is four screens long, stepping though is takes 10 minutes and you have no idea what the code is doing. You fear making changes to the monolithic method because altering edit mode will break regular game mode and vis versa. The ‘inEditMode’ flag is set to ‘false’ and ‘isDragging’ is set to ‘true’, which is logically impossible, and you have no idea how it happened. And this is where the hair pulling begins.
Do not fear. I am here to save you from your premature balding. I propose using a state machine to separate event handling code into logical pieces which can be swapped in and out depending on the state of your game at run time. If your knowledge of this particular design pattern is a little rusty, allow me to describe it briefly.
State Machine? What is it?
A state machine represents the state of an object at any given time. It is able to respond to input; causing actions to take place or possibly causing the state machine to transition to a new state. A state machine contains a current state. That state can contain a single substate which in turn can have its own substate. Messages passed to the state machine are passed on to the current state, the current state can decide to handle the message or pass it on to its substate and on down the chain. A state machine and its current state hierarchy can also decide not to handle a message at all and simply throw it out. States are allowed to change their substate at runtime and a state machine can have its current state replaced as well. Using a state machine for your event handling will make your code more flexible, more maintainable and save you hours of debugging time.
What’s In the Box?
In this post, I have included a state machine, a base state class and some example code to get you started. The state machine library consists of two pieces. The first is a StateMachine class who’s responsibilities include maintaining a reference to the current state and passing messages to that state. The second piece is the State class itself, which is meant to be subclassed; one subclass for each of our game’s modes (I use the term ‘mode’ and ‘state’ interchangeably). The messages passed are actual Objective-C messages, just like you are used to. There are two types of messages the state machine can forward. The first type are the predefined touch handling messages: ‘touchesBegan:’, ‘touchesMoved:’, ‘touchesEnded:’ and ‘touchesCancelled:’. The second type of messages are user defined. These two types of messages behave slightly differently and its important to understand the difference. The first type is defined in the base State class and its default implementation is to simply forward the message on to the substate. If no state handles that particular message, the message is silently discarded. The second type of message is a little different. For this type of message, we use an esoteric feature of Objective-C call “Message forwarding”. When a message is passed to a state which the state does not handle (ie. doesn’t implement), it attempts to pass the message on to its substate, instead of throwing the familiar “selector not recognized” exception. If the substate does not handle the method, it has the opportunity to pass the message on to its own substate and so on. If none of the states in the state hierarchy handle the message, an exception is thrown.
Basic State Machine Functionality
“But how can I use this to clean up my event handling?” I hear you asking. Maybe an example is the best place to begin. Using our scenario above, implementing scrolling functionality, an edit mode and a normal mode, I would begin by making three subclasses of State: ScrollState, GamePlayState, and EditState. These three states would each encapsulate their respective functionality in the three ‘touches’ methods mentioned above. When the game initializes, a ScrollState is created and an instance of GamePlayState is set as its substate. The ScrollState would be set as the current state of the state machine with GamePlayState as its substate. When a touch event occurred, event data would be passed to the appropriate ‘touches’ handler of the state machine, which would then forward them on to the current state ie. the ScrollState. At this point, it is up to the ScrollState to decide what to do with the message. It could either implement the ‘touches*’ method and handle the touch itself, it could implement the method itself but let the substate handle it first and see what the return value is, or it could not implement it at all and let a substate handle it directly.
If you recall from earlier, there were certain times when we want to inhibit scrolling of the world. In edit mode, when we were dragging an object on the map to move it, we want to disallow scrolling of the map. To achieve this, when a ‘touchesMoved:’ event is passed to the ScrollState, it would pass the message on to its substate; EditState, before handling it itself. If EditState were dragging an object, it would update the position of the object and return back ‘true’ to its parent state, indicating that it had handled the particular event. If the ScrollState had found that the EditState had handled the event, it would not run any of its scrolling code. If EditMode had returned ‘false’, ScrollState would scroll the world appropriately. States can decide what to do based on whether their substate has handled an event.
Changing States
To facilitate changing states, we would add two custom event handers; the second of the two types of messages mentioned earlier. In ScrollState we would define an ‘enterEditMode’ method and a ‘returnToGameState’ method. When the user decided to enter edit mode, the state machine would be sent an ‘enterEditMode’ message, which would be forwarded to the ScrollState. In the ‘enterEditMode’ implementation an instance of EditMode would be assigned to the ScrollState’s substate, discarding the current GamePlayState. The game would now be in edit mode and all touch events would be forwarded to ScrollState and then on to EditState. There is no chance of GamePlayState code being executed while in edit mode because there is no GamePlayState in existence.
State Lifecycle
When a state is set as the current state of the state machine, its ‘activate’ method is called. Similarly, when a state is set as the substate of a state ‘activate’ is called. When a substate is no longer needed and a new state is assigned in its place, the state which is being replaced will have its ‘deactivate’ method called before then new state has its ‘activate’ methed called. These calls can be used to bring up or tear down UI or any other initialization that needs to be done for that particular mode. Note: it is important to call ‘[super activate/deactivate]’ if you subclass those calls yourself.
Pushing and Popping States
In addition to explicitly setting and resetting a state’s substate, each state maintains a state stack (the state machine also maintains a state stack)*. It is possible to call ‘pushState’ on a state instead of ‘currentState’. This will call ‘deactivate’ on the current state, push it onto the stack and call the new state’s ‘activate’ method. When ‘popState’ is called, the current states ‘deactivate’ method is called and the state previously pushed onto the stack’s ‘activate’ method is called again and it is set to the current state. ‘currentState’ always returns the state on the top of the stack, regardless of whether it was put there by a ‘pushState’ or a ‘setCurrentState’ call. In fact all set current state does is replace the state on the top of the state stack if there is one, or call ‘pushState’ if not.
Conclusion
State machines are a great way to split independent event handling functionality out into their own modules. They allow combining modules together into a responder chain capable of passing events to one another; delegating event handling responsibility. If you are interested in incorporating a state machine into your next project, you can check out the latest version of the code at https://github.com/Tylerc230/statemachine_blog_2. It includes the state machine library, unit tests and an example app demonstrating the state machine in action.
*The curious programmer will notice that StateMachine is, itself, a subclass of State and ‘currentState’ is just a wrapper around ‘substate’ . This allows it to share the state stack and activate/deactivate logic with the State class.
I'm a freelance iOS developer based in San Francisco. Feel free to contact me.