Search code examples
c++boostboost-statechart

Boost Statechart - Local transitions


I'm hoping that someone can help me out with this problem, or at least point out the error of my ways...

As a simple illustration of my problem consider a part of an application where you can enter a "Functions Mode" state of operation. Four sub-modes are then available depending on which function key F1-F4 that the user presses. By default, F1 mode is entered. The state diagram starts off as follows:

Diagram 1

The user can press F1-F4 at any time to switch to the corresponding mode. Adding these transitions to the inner states leads to the following:

Diagram 2

Obviously this is (a) a mess, and (b) a lot of transitions to define. If at some point I want to add an F5Mode then... well, you get the picture. To avoid this I'd like to do the following:

Diagram 3

Boost Statechart allows me to define transitions from FunctionMode to any of the inner states, but the result isn't what I expected. The actual outcome is as follows:

Diagram 4

I.e. pressing F1-F4 to switch modes causes the outer FunctionMode state to be exited and re-entered along with triggering the unwanted exit and entry actions.

Way back in 2006, this thread between the library author and a user seems to describe the same problem. I think that the author suggests doing the following as a work-around:

Diagram 5

However, that work-around doesn't seem very appealing to me: It has added an extra state level to be compiled, the code has become less readable, deep-history would have to be used to return to any of the function mode sub-states and the Intermediate state object is needlessly being destructed and constructed again.

So... where am I going wrong? Or what are the alternatives? I've had a brief look at Boost Meta State Machine (msm) but from what I've seen so far I much prefer the look of Statechart.

I'm surprised that more users haven't faced the same problem... which makes me think that perhaps my approach is completely wrong!


Solution

  • Have you looked at the in-state reaction explained in the statechart tutorial? It seems to be doing what you are looking for.

    Since you are asking for alternatives, in this period I am evaluating various C++ Harel statechart implementations. I looked at Boost statechart and Boost MSM. I wrote code with both. They hurt my feeble brain :-)

    Then I found Machine Objects (Macho), very simple and small, and I love it. It supports hierarchical state machines, entry/exit actions, history, state machine snapshots, guards, internal transitions, event deferring, state-local storage (with optional persistence), so to me it is a satisfying Harel statechart implementation.

    This code implements the FunctionMode part of the statechart with Macho:

    #include "Macho.hpp"
    
    #include <exception>
    #include <iostream>
    using namespace std;
    
    namespace FunctionMode {
    
    struct FunctionMode;
    struct F1Mode;
    struct F2Mode;
    
    // The Top state, containing all the others.
    TOPSTATE(Top) {
        STATE(Top)
        // All the events of the state machine are just virtual functions.
    
        // Here we throw to mean that no inner state has implemented the event
        // handler and we consider that an error. This is optional, we could
        // just have an empty body or log the error.
        virtual void evF1() { throw std::exception(); }
        virtual void evF2() { throw std::exception(); }
        // evF3 and so on...
    private:
        void init() { setState<FunctionMode>(); } // initial transition
    };
    
    SUBSTATE(FunctionMode, Top) {
        STATE(FunctionMode)
        virtual void evF1() { setState<F1Mode>(); }
        virtual void evF2() { setState<F2Mode>(); }
        // evF3, ...
    private:
        void entry() { cout << "FunctionMode::entry" << endl; }
        void exit() { cout << "FunctionMode::exit" << endl; }
        void init() { setState<F1Mode>(); } // initial transition
    };
    
    SUBSTATE(F1Mode, FunctionMode) {
        STATE(F1Mode)
        virtual void evF1() {} // make the event an internal transition (by swallowing it)
    private:
        void entry() { cout << "F1Mode::entry" << endl; }
        void exit() { cout << "F1Mode::exit" << endl; }
    };
    
    SUBSTATE(F2Mode, FunctionMode) {
        STATE(F2Mode)
        virtual void evF2() {} // make the event an internal transition (by swallowing it)
    private:
        void entry() { cout << "F2Mode::entry" << endl; }
        void exit() { cout << "F2Mode::exit" << endl; }
    };
    
    } // namespace FunctionMode
    
    int main() {
    
        Macho::Machine<FunctionMode::Top> sm;
        // Now the machine is already in F1Mode.
    
        // Macho has 2 methods for synchronous event dispatching:
        // First method:
        sm->evF1(); // <= this one will be swallowed by F1Mode::evF1()
        // Second method:
        sm.dispatch(Event(&FunctionMode::Top::evF2));
    
        return 0;
    }
    

    Running it, the output is:

    FunctionMode::entry
    F1Mode::entry
    F1Mode::exit
    F2Mode::entry
    F2Mode::exit
    FunctionMode::exit
    

    that shows that the transitions are internal.

    In my opinion, clean, easy and compact code :-)

    [EDIT1] The first version of the code didn't perform the initial transition FunctionMode -> F1Mode. Now it does.