Search code examples
qtqstatemachine

How do you implement a choice in a state machine in Qt?


Imagine a part of your state machine looks like this:

How do you properly implement the choice part in Qt? I know there are guarded transitions, but that would mean that I need to:

  • Create a subclass of a QAbstractTransition which accepts e.g. an std::function<bool()> and a flag which determines if the transition happens when that boolean result is true, or when it is false
  • Create two instances of this class with the same boolean function, but opposite transition guards
  • Add two transitions from S1 using these two instances.

That approach seems kind of clumsy and error prone for something as simple as a choice. Is there a more maintainable approach to implement this?


Solution

  • License Notice:

    Alternatively to the default StackOverflow license you are hereby allowed to use this code through the MIT License.

    I've created a BooleanChoiceTransition class with a constructor like this (might contain errors, the code is not on this machine, so I typed it by heart):

    BooleanChoiceTransition::BooleanChoiceTransition(std::function<bool()> choiceFunc, QState* targetForTrueCase, QState* targetForFalseCase)
      : QState{}
    {
        this->addTransition(this, &BooleanChoiceTransition::transitionToTrueTarget, targetForTrueCase);
        this->addTransition(this, &BooleanChoiceTransition::transitionToFalseTarget, targetForFalseCase);
        (void)QObject::connect(this, &QAbstractState::entered, [this]() {
            if(choiceFunc())
            {
                emit transitionToTrueTarget();
            }
            else
            {
                emit transitionToFalseTarget();
            }
        });
    }
    

    with transitionToTrueTarget and transitionToFalseTarget being signals of course.

    For the case of the example in the question, the class can be used like so:

    auto choiceState = new BooleanChoiceTransition([this](){ return _someConditionFullfilled; }, s2, s3);
    s1->addTransition(this, &MyClass::someTrigger, choiceState);
    

    Since BooleanChoiceTransition is a QState, this can even be nested easily:

    auto outerChoiceState = new BooleanChoiceTransition([this](){ return _someOtherConditionFullfilled; }, s4, choiceState);