Jeff Flinn | 29 Aug 05:01
Picon

Re: [review] FSM Second Call for Reviews

My review appears here with an attached cpp file implementing four 
approaches (not compiled): a manual nested switch/if function, the TMP 
book's mpl state table, boost.statechart, this FSM library.

> What is your evaluation of the implementation?
> Did you try to use the library? With what compiler? Did you have any
> problems?

Didn't download the library code, just read through docs and tried to 
implement a simple state machine comparing other approaches.

> What is your evaluation of the potential usefulness of the library?

I think there are many applications for a lightweight, expressive FSM 
library in areas not typically using FSM's such as mouse event processing, 
filtering/transforming iterators,...

> What is your evaluation of the design?
> What is your evaluation of the documentation?

My view of the design is limited to what is described in the documentation. 
I was a little dismayed that some of the basic features that I needed are 
considered Advanced features such as sharing state and accessing the current 
state from outside the state machine. These are very easily accomplished in 
the TMP and StateChart libraries. The section on accessing state is not 
sufficient for me to determine which state the fsm is in between calls to 
process(). The data sharing approach between sibling states using a virtual 
base class is perplexing... there's no discussion of just what is the 
structure of the state machine. There is a lot of repititious boilerplate 
required relative to other libraries. There is a mixing of the transition 
"structure" in the procedural code which is better separated in the 
transition table approach. The FSM transition map may improve upon this, but 
I started getting lost in the description.

Documentation terminology drifts from the familiar "transition", "table",... 
introducing new terms "switch_to" and "transition map" without describing 
how/why they are different from the standard terms. The documentation seems 
to become less clear as the interface becomes more complex and verbose. The 
StateChart library addresses much of the same issues but always seems to 
remain comprehensible and keeps the reader grounded in familiar concepts 
while introducing new ones. I don't seem to be able to grasp the overall 
design concepts of FSM from it's documentation.

> How much effort did you put into your evaluation? A glance? A quick 
> reading?
> In-depth study?
> Are you knowledgeable about the problem domain?

I spent several hours reading the docs and putting together the attached 
example using the 4 different libraries/implementations applying them to a 
"paraphrased-from-an-in-production" use case. The state machines are used to 
iterate through an input stream of integers, extracting triplets of integers 
that are even or odd. Odd integers appearing within a even triplet are 
skipped, triplets of odd integers are extracted when they occur between even 
triplets. The transition table in the attached cpp file clearly depicts the 
UML state chart upon which the example is derived.

I've spent several years developing software that integrates 
Matlab/StateFlow generated code with hardware in the loop systems. So I'm 
familiar with many of the concepts.

> Do you think the library should be accepted as a Boost library?

No. I'd prefer that the performance and missing functionality be added to 
improve the existing StateChart library, and/or a true transition table 
approach be provided ala the TMP book's transition table. I dislike FSM's 
verbose interface along with it's procedural leaning paradigm.

Jeff

#include "MplStateTable.h"

#include <boost/array.hpp>
#include <boost/mpl/list.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/statechart/event.hpp>
#include <boost/statechart/state_machine.hpp>
#include <boost/statechart/simple_state.hpp>
#include <boost/statechart/transition.hpp>

typedef boost::array< int, 3 > Triplet;

bool is_odd(int i) { return i % 2; }

namespace Manual
{
    struct  odd_event {  odd_event(int b):b(b){} int b; };
    struct even_event { even_event(int b):b(b){} int b; };

    struct TripletExtractor
    {
        template< class Iterator >
        static Triplet extract(Iterator& itr, const Iterator& end)
        {
            enum State{ between_triplets
                      , in_triplet_w_1_even
                      , in_triplet_w_2_even
                      , in_triplet_w_1_odd
                      , in_triplet_w_2_odd
                      , initial_state = between_triplets
                      };

            State s = initial_state;

            int b1, b2;

            for( ; itr != end ; ++itr )
            {
                int  b = *itr;
                bool o = is_odd(b);

                switch(s)
                {
                    case between_triplets    : if(o){ s = in_triplet_w_1_odd ;                          }else{ s = in_triplet_w_1_even;                          } b1 =  b;  break;
                    case in_triplet_w_1_even : if(o){ s = in_triplet_w_1_even;                          }else{ s = in_triplet_w_2_even;                 b2 =  b; }           break;
                    case in_triplet_w_2_even : if(o){ s = in_triplet_w_2_even;                          }else{ s = between_triplets   ; return
Triplet(b1,b2,b); }           break;
                    case in_triplet_w_1_odd  : if(o){ s = in_triplet_w_2_odd ;                 b2 =  b; }else{ s = in_triplet_w_1_even;                 b1 =  b; }           break;
                    case in_triplet_w_2_odd  : if(o){ s = between_triplets   ; return Triplet(b1,b2,b); }else{ s =
in_triplet_w_1_even;                 b1 =  b; }           break;
                    default                : assert(false);                                                                                                                  break;
                }
            }
            return Triplet();
        }
    };
}

namespace StateTable
{
    using namespace MplStateTable;

    struct  odd_event {  odd_event(int b):b(b){} int b; };
    struct even_event { even_event(int b):b(b){} int b; };

    class extractor : public state_machine<extractor>
    {

        friend class state_machine<extractor>;
        typedef extractor e; // makes transition table cleaner

    public:
        int b1, b2, b3;

        extractor() : state_machine<extractor>(), b1(), b2(), b3() {}
    private:
        enum State{ between_triplets
                  , in_triplet_w_1_even
                  , in_triplet_w_2_even
                  , in_triplet_w_1_odd
                  , in_triplet_w_2_odd
                  , extracted_triplet
                  , initial_state = between_triplets
                  };
    public:

        bool extracted()const{ return current_state() == extracted_triplet; }

        void set1(even_event const& ev){ b1 = ev.b; }
        void set2(even_event const& ev){ b2 = ev.b; }
        void set3(even_event const& ev){ b3 = ev.b; }
        void set1( odd_event const& ev){ b1 = ev.b; }
        void set2( odd_event const& ev){ b2 = ev.b; }
        void set3( odd_event const& ev){ b3 = ev.b; }
        void skip( odd_event const&   ){}

        struct transition_table : mpl::vector10
        //---+---------------------+------------+---------------------+----------+
        //        Start State          Event           Next State        Action  
        //---+---------------------+------------+---------------------+----------+
        < row< between_triplets    , even_event , in_triplet_w_1_even , &e::set1 >
        , row< between_triplets    ,  odd_event , in_triplet_w_1_odd  , &e::set1 >
        //---+---------------------+------------+---------------------+----------+
        , row< in_triplet_w_1_even , even_event , in_triplet_w_2_even , &e::set2 >
        , row< in_triplet_w_1_even ,  odd_event , in_triplet_w_1_even , &e::skip >
        //---+---------------------+------------+---------------------+----------+
        , row< in_triplet_w_2_even , even_event , extracted_triplet   , &e::set3 >
        , row< in_triplet_w_2_even ,  odd_event , in_triplet_w_2_even , &e::skip >
        //---+---------------------+------------+---------------------+----------+
        , row< in_triplet_w_1_odd  , even_event , in_triplet_w_1_even , &e::set1 >
        , row< in_triplet_w_1_odd  ,  odd_event , in_triplet_w_2_odd  , &e::set2 >
        //---+---------------------+------------+---------------------+----------+
        , row< in_triplet_w_2_odd  , even_event , in_triplet_w_1_even , &e::set1 >
        , row< in_triplet_w_2_odd  ,  odd_event , extracted_triplet   , &e::set3 >
        //---+---------------------+------------+---------------------+----------+
        >  {};
    };
}

namespace StateChart
{
    namespace sc  = boost::statechart;
    namespace mpl = boost::mpl;

    struct  odd_event : sc::event<  odd_event > {  odd_event(int b):b(b){} int b; };
    struct even_event : sc::event< even_event > { even_event(int b):b(b){} int b; };

    struct between_triplets;      // forwarded states
    struct in_triplet_w_1_even;
    struct in_triplet_w_2_even;
    struct in_triplet_w_1_odd;
    struct in_triplet_w_2_odd;
    struct extracted_triplet;

    struct extractor : sc::state_machine< extractor, between_triplets >
    {
        int b1, b2, b3;

        bool extracted()const{ return state_downcast< const extracted_triplet* >(); }

        extractor():sc::state_machine< extractor, between_triplets >(),b1(),b2(),b3(){ initiate(); }

        void set1(even_event const& ev){ b1 = ev.b; }
        void set2(even_event const& ev){ b2 = ev.b; }
        void set3(even_event const& ev){ b3 = ev.b; }
        void set1( odd_event const& ev){ b1 = ev.b; }
        void set2( odd_event const& ev){ b2 = ev.b; }
        void set3( odd_event const& ev){ b3 = ev.b; }
        void skip( odd_event const&   ){}
    };

    typedef extractor e; // makes transition table cleaner

    
    struct between_triplets    : sc::simple_state< between_triplets   , extractor >
    {
        typedef mpl::list< sc::transition< even_event, in_triplet_w_1_even, extractor, &e::set1 >
                         , sc::transition<  odd_event, in_triplet_w_1_odd , extractor, &e::set1 > > reactions;
    };
    struct in_triplet_w_1_even : sc::simple_state< in_triplet_w_1_even, extractor >
    {
        typedef mpl::list< sc::transition< even_event, in_triplet_w_2_even, extractor, &e::set2 >
                         , sc::transition<  odd_event, in_triplet_w_1_even, extractor, &e::skip > > reactions;
    };
    struct in_triplet_w_2_even : sc::simple_state< in_triplet_w_2_even, extractor >
    {
        typedef mpl::list< sc::transition< even_event, extracted_triplet  , extractor, &e::set3 >
                         , sc::transition<  odd_event, in_triplet_w_2_even, extractor, &e::skip > > reactions;
    };
    struct in_triplet_w_1_odd  : sc::simple_state< in_triplet_w_1_odd , extractor >
    {
        typedef mpl::list< sc::transition< even_event, in_triplet_w_1_even, extractor, &e::set1 >
                         , sc::transition<  odd_event, in_triplet_w_2_odd , extractor, &e::set2 > > reactions;
    };
    struct in_triplet_w_2_odd  : sc::simple_state< in_triplet_w_2_odd , extractor >
    {
        typedef mpl::list< sc::transition< even_event, in_triplet_w_1_even, extractor, &e::set1 >
                         , sc::transition<  odd_event, extracted_triplet  , extractor, &e::set3 > > reactions;
    };
    struct extracted_triplet   : sc::simple_state< extracted_triplet  , extractor >
    {
    };
}

namespace FSM
{
    namespace fsm = boost::fsm;
    namespace mpl = boost::mpl;

    struct  odd_event : fsm::event<  odd_event > {  odd_event(int b):b(b){} int b; };
    struct even_event : fsm::event< even_event > { even_event(int b):b(b){} int b; };

    struct between_triplets;      // forwarded states
    struct in_triplet_w_1_even;
    struct in_triplet_w_2_even;
    struct in_triplet_w_1_odd;
    struct in_triplet_w_2_odd;
    struct extracted_triplet;

    typedef mpl::vector< between_triplets   
                       , in_triplet_w_1_even
                       , in_triplet_w_2_even
                       , in_triplet_w_1_odd 
                       , in_triplet_w_2_odd 
                       , extracted_triplet  >::type StateList;

    struct data { int b1; int b2; int b3; data() : b1(0), b2(0), b3(0); };

    struct between_triplets    : fsm::state< between_triplets   , StateList >, virtual data
    {
        void on_process(even_event const& ev){ b1 = ev.b; switch_to< in_triplet_w_1_even >(); }
        void on_process( odd_event const& ev){ b1 = ev.b; switch_to< in_triplet_w_1_odd  >(); }
    };
    struct in_triplet_w_1_even : fsm::state< in_triplet_w_1_even, StateList >, virtual data
    {
        void on_process(even_event const& ev){ b2 = ev.b; switch_to< in_triplet_w_2_even >(); }
        void on_process( odd_event const& ev){            switch_to< in_triplet_w_1_even >(); }
    };
    struct in_triplet_w_2_even : fsm::state< in_triplet_w_2_even, StateList >, virtual data
    {
        void on_process(even_event const& ev){ b3 = ev.b; switch_to< extracted_triplet   >(); }
        void on_process( odd_event const& ev){            switch_to< in_triplet_w_2_even >(); }
    };
    struct in_triplet_w_1_odd  : fsm::state< in_triplet_w_1_odd , StateList >, virtual data
    {
        void on_process(even_event const& ev){ b1 = ev.b; switch_to< in_triplet_w_1_even >(); }
        void on_process( odd_event const& ev){ b2 = ev.b; switch_to< in_triplet_w_2_odd  >(); }
    };
    struct in_triplet_w_2_odd  : fsm::state< in_triplet_w_2_odd , StateList >, virtual data
    {
        void on_process(even_event const& ev){ b1 = ev.b; switch_to< in_triplet_w_1_even >(); }
        void on_process( odd_event const& ev){ b3 = ev.b; switch_to< extracted_triplet   >(); }
    };
    struct extracted_triplet   : fsm::state< extracted_triplet  , StateList >, virtual data
    {
    };

    struct extractor : fsm::state_machine< StateList > 
    {
        template<typename EVENT> void process_event(const EVENT& ev) { process(ev); }

        bool extracted()const{ return ?????; }

    };
}

template< class Extractor >
struct TripletExtractor
{
    template< class Iterator >
    static Triplet extract(Iterator& itr, const Iterator& end)
    {
        Extractor e;

        for( ; itr != end ; ++itr )
        {
            int  b = *itr;
            bool o = is_odd(b);

            if(o)
            {
                e.process_event(odd_event(b));
            }
            else
            {
                e.process_event(even_event(b));
            }

            if(e.extracted())
            {
                return Triplet(e.b1,e.b2,e.b3);
            }
        }

        return Triplet(int(0), int(0), int(0));
    }
};

template<class TripletFunctor,  class ITERATOR > 
class TripletIterator : public boost::iterator_facade< TripletIterator<TripletFunctor,ITERATOR>
                                                     , Triplet
                                                     , boost::single_pass_traversal_tag
                                                     , const Triplet
                                                     >
{
    friend class boost::iterator_core_access;

public:
    typedef ITERATOR Iterator;

    TripletIterator(Iterator itr, Iterator end):m_itr(itr),m_end(end),m_triplet(TripletFunctor::extract(m_itr,m_end)){}

    void increment()
    {
        ++m_itr;

        m_triplet = TripletFunctor::extract(m_itr, m_end);
    }
    const Triplet dereference()const{ return m_triplet; }

    bool equal(TripletIterator const& other)const{ return m_itr == other.m_itr; }

private:
    Iterator m_itr;
    Iterator m_end;
    Triplet  m_triplet;
};

int main()
{
    boost::array<int,12> ints = { 2, 7, 7, 7, 4, 3, 5, 6, 7, 7, 7, 8 };

    // triplet: (2,4,6), (7,7,7)

    Triplet t1 = Manual::TripletExtractor::extract(ints.begin(), ints.end());

    Triplet t1 = TripletExtractor<StateTable ::extractor>::extract(ints.begin(), ints.end());
    Triplet t2 = TripletExtractor<StateTChart::extractor>::extract(ints.begin(), ints.end());
    Triplet t3 = TripletExtractor<FSM        ::extractor>::extract(ints.begin(), ints.end());

    return 0;
}
_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Gmane