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,