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.