Advanced FSM Tidbits
request vs. demand
As stated previously, you normally request an FSM to change its state by
calling either fsm.request('NewState', arg1, arg2, ...)
, or
fsm.request('inputString', arg1, arg2, ...)
, where arg1, arg2, …
represent optional arguments to the destination state’s enter function (or to
the filter function). The call to request()
will either succeed or fail,
according to what the filter function for the current state does. If it
succeeds, it will return the tuple ('NewState', arg1, arg2)
, indicating
the new state it has transitioned to. If it fails, it will simply return None
(unless the filter function was written to throw an exception on failure).
If you request an FSM to make a transition, and the request fails, you might
consider this an error condition, and you might prefer to have your code to
stop right away rather than continuing. In this case, you should call
fsm.demand()
instead. The syntax is
the same as that for request()
, but instead of returning None on failure,
it will always raise an exception if the state transition is denied. There is
no return value from demand()
; if it returns, the transition was
accepted.
FSM.AlreadyInTransition
An FSM is always in exactly one state, except while it is in the process of
transitioning between states (that is, while it is calling the exitStateName
method for the previous state, followed by the enterStateName method for the
new state). During this time, the FSM is not considered in either state, and
if you query fsm.state
it will contain None.
During this transition time, it is not legal to call fsm.request()
to
request a new state. If you try to do this, the FSM will raise the exception
FSM.AlreadyInTransition
.
This is a particularly common error if some cleanup code that is called from
the exitStateName method has a side-effect that triggers a transition to a
new state.
However, there’s a simple solution to this problem: call fsm.demand()
instead. Unlike request()
, demand()
can be called while the FSM is
currently in transition. When this happens, the FSM will queue up the demand,
and will carry it out as soon as it has fully transitioned into its new
state.
forceTransition()
There is also a method
fsm.forceTransition()
.
This is similar to demand()
in that it never fails and does not have a
return value, but it’s different in that it completely bypasses the filter
function. You should therefore only pass an uppercase state name (along with
any optional arguments) to forceTransition, never a lowercase input string.
The FSM will always transition to the named state, even if it wouldn’t
otherwise be allowed. Thus, forceTransition()
can be useful in special
cases to skip to another state that’s not necessarily connected to the
current state (for instance, to handle emergency cleanup when an exception
occurs). Be careful that you don’t overuse forceTransition()
, though;
consider whether demand()
would be a better choice. If you find yourself
making lots of calls to forceTransition()
, it may be that your filter
functions (or your defaultTransitions) are poorly written and are
disallowing what should be legitimate state transitions.
Filtering the optional arguments
The filterStateName method receives two parameters: the string request, and a tuple, which contains the additional arguments passed to the request (or demand) call. It then normally returns the state name the FSM should transition to, or it returns None to indicate the transition is denied.
However, the filter function can also return a tuple. If it returns a tuple,
it should be of the form ('StateName', arg1, arg2, ...)
, where arg1,
arg2, … represent the optional arguments that should be passed to the
enterStateName method. Usually, these are the same arguments that were passed
to the filterStateName method (in this case, you can generate the return
value tuple with the python syntax ('StateName',) + args
).
The returned arguments are not necessarily the same as the ones passed in, however. The filter function is free to check, modify, or rearrange any of them; or it might even make up a completely new set of arguments. In this way, the filter function can filter not only the state transitions themselves, but also the set of data passed along with the request.