Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 553
Grant Avatar asked Jul 06 '12 12:07

Grant


2 Answers

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.

like image 100
marco.m Avatar answered Nov 06 '22 13:11

marco.m


I know this is an old question, these exit->enter on the same state is annoying.

It seems that to prevent reentry to self you need to: 1. Write custom handler in "self state" 2. Write guards in the parent handler that triggers the reentry to child state.

Imho it is a flaw in StateChart that I haven't found a nice solution for yet - a property call "skip reentry state transitions" on the statemachine object would be great.

like image 40
Laro88 Avatar answered Nov 06 '22 11:11

Laro88