
Phil Endecott wrote:
Dear All,
I have just spent a few minutes comparing an ad-hoc FSM implementation with an implementation using the proposed library. The example that I've used is the USB device state machine, which I recently had to understand for an embedded application, and I've put an image of the state graph here:
[snip]
Here is my ad-hoc implementation (note that I haven't tried to even compile any of this code):
[snip code] Well, your code is not an FSM. I mean, the usb_device behavior does not in any way depend on its state. I could simply remove the state variable and have the same result. And I agree, in case when there are two variables that need to be changed in arbitrary order, there is no need in an FSM library (no matter which FSM library we may have in mind). However, if you try to implement state/event correctness checks and define the device behavior in each state (given that the behavior differs, of course), I'm sure that your code will eventually become less obvious.
In comparison, here's what I've come up with using Boost.FSM:
[snip]
The latter is clearly more verbose than the former. Some of the verbosity would go if I put the common events (e.g. PowerOff) into a base class, but that adds verbosity of its own.
Why? Base classes for states help a lot to reduce code redundancy. I've attached a slightly modified version of the code above, with base classes and library-provided events (this allows you not to define event classes, which also reduces the code). See usb_fsm_state_based.cpp.
There are a few characteristics of this particular design that favour the ad-hoc code. Firstly, the device's behaviour is allowed to be undefined if an unexpected event occurs.
IMO, undefined behavior usually is a design flaw, and this case is clearly one of the "usual" ones. Otherwise, like I said, you don't need FSM, because its whole purpose is to _define_ the behavior.
Secondly, many events have the same effect irrespective of the state (e.g. power off).
This, as you noted yourself, can be handled with base classes. Also, one can use transition maps to define state changes, and transition maps provide an any_state placeholder for this purpose. I've attached another version of the example with a transition map (see usb_fsm_transition_based.cpp). Note that all transition logic is encapsulated in transitions and all processing is in states.
Finally, the effect of events often depends on their data (i.e. zero has a special meaning).
In fact, this is quite common, according to my experience. That's why the events can be processed in run time both in transitions and states.
Perhaps more complex FSMs would benefit more from what this library offers. In that case, can we see an example? I note that this library is targeted at simpler FSMs than Boost.Statechart (which I have not used). Is there a category of FSMs that are simple enough that Boost.Statechart's features are not needed (e.g. hierarchy) yet too complex for an ad-hoc implementation?
Generally, if performance matters and if you don't need to span your FSM across several dlls and you don't need to create an asynchronous FSM, you should be fine with Boost.FSM. For example, various protocols (INAP, for what I had experience with) and their clients may benefit from using Boost.FSM. Simple parsers can also be implemented on top of the library.