Coding state machines (was Re: Case-logic)

Ned Konz ned at squeakland.org
Fri Oct 29 18:36:52 UTC 2004


On Tuesday 26 October 2004 10:15 pm, Blake wrote:
> Can anyone recommend some state-machine type stuff in the codebase for me  
> to look at? Or thoughts on how you'd code something like the code below?

Yes. In my Connectors 2 package (available on SqueakMap) there is code for a 
hierarchical state machine (supporting the full Harel semantics), and a Morph 
class that uses these state machines for event handling.

You can add to or modify existing state machine definitions in two directions: 
you can define sub-states to provide specialization, and/or you can inherit 
from existing event handler classes and override their event handling 
methods.

The stock QHsmMorph has the following state hierarchy. Note that it does not 
use the MouseClickState state machine, handling click and double-click 
detection in its own handlers.

Top
 Global
  WaitingForDoubleClick
   FirstClickUp (MouseDown)
   WaitingForDoubleClickWithMouseDown
    SecondClickDown (MouseUp)
    FirstClickDown (MouseUp)
  Idle
  Dragging
 Initial

Events that occur in a sub- (nested) state like FirstClickUp can be handled in 
the state handlers for FirstClickUp, its parent WaitingForDoubleClick, or its 
parent Global. There is one method per state. Optionally, you can specialize 
by providing a method for a given kind of event in a given state. There are 
three examples of this here (they are shown by parentheses).

This is based on the idea (strange as it seems) of generating method selectors 
based on the current state and maybe also the type of the event.

So in this case, the corresponding method selectors are:

Top => stateTop
 Global => stateGlobal
  WaitingForDoubleClick => stateWaitingForDoubleClick
   FirstClickUp (MouseDown)  => handleMouseDownInFirstClickUp
   WaitingForDoubleClickWithMouseDown => 
stateWaitingForDoubleClickWithMouseDown
    SecondClickDown (MouseUp) => handleMouseUpInSecondClickDown
    FirstClickDown (MouseUp)  => handleMouseUpInFirstClickDown
  Idle => stateIdle
  Dragging => stateDragging
 Initial  => stateInitial

Each event has a symbolic type and an optional event (perhaps a MorphicEvent) 
associated with it.

When an event is handled, first we make a specialized selector using the 
symbolic type of the event pasted together with the current state name:

 'handle' + <event type> + 'In' + <current state name>

If there is no interned symbol matching that, or if the event handler does not 
understand that selector, then we try the more general state handler:

 'state' + <current state name>

If neither selector matches, there is a catch-all state handler for unhandled 
states.

As goofy as this sounds, it results in a clean separation of state handling 
code.

Of course, I sometimes end up with a case switch inside the state handler 
methods to handle the individual event responses. Here's a snippet from the 
above QHsmMorphEventHandler (event is an instance variable that holds the 
MorphicEvent being handled):

stateDragging: evt 
 ^evt caseOf: { 
    [#entry] ->  [self startDrag: event. nil].
    [#exit] ->  [self stopDrag: event. nil].
    [#mouseUp] -> [ self returnToIdleHistory ]}
  otherwise: [self state: #Global]

It's possible to hold states in variables, like here where we return to 
history:

returnToIdleHistory
 "Idle history has been saved; transition back to it (and clear it)"
 | history |
 history _ idleHistory ifNil: [ #Idle].
 idleHistory _ nil.
 mySource ~= myState
  ifTrue: [self log: #newState items: {myState stateName. mySource stateName. 
history. 'idle history' }]
  ifFalse: [self log: #newState items: {myState stateName. history. 'idle 
history'}].
 ^self newDynamicState: history.


The return value from the state handlers is either nil if there is no change 
in state, or a QState object representing the new state when a transition is 
desired.

Hope this helps,
-- 
Ned Konz
http://bike-nomad.com/squeak/



More information about the Squeak-dev mailing list