Stopping a QStateMachine with unconditional transitions



  • I am trying to model a process with QStateMachine which I am able to interrupt using some kind of 'stop'-Signal. For testing purpose I build this simple StateMachine:

    StateMachineWrapper::StateMachineWrapper(QObject *parent) : QObject(parent)
    {
        sm = new QStateMachine(this);
    
        QState *running = new QState(sm);
        QState *stopped = new QState(sm);
    
        QState* stateA = new QState(running);
        QState* stateB = new QState(running);
        QState* stateC = new QState(running);
    
        stateA->addTransition(stateB);
        stateB->addTransition(stateC);
        stateC->addTransition(stateA);
        running->addTransition(this, &StateMachineWrapper::stopSM, stopped);
    
        connect(stateA, &QState::entered, this, &StateMachineWrapper::onA);
        connect(stateB, &QState::entered, this, &StateMachineWrapper::onB);
        connect(stateC, &QState::entered, this, &StateMachineWrapper::onC);
        connect(stopped, &QState::entered, this, &StateMachineWrapper::onStopped);
    
        sm->setInitialState(running);
        running->setInitialState(stateA);
    }
    
    void StateMachineWrapper::onA()
    {
        qDebug() << "State A";
    }
    void StateMachineWrapper::onB()
    {
        qDebug() << "State B";
    }
    void StateMachineWrapper::onC()
    {
        qDebug() << "State C";
    }
    
    void StateMachineWrapper::onStopped()
    {
        qDebug() << "State Stopped";
    }
    
    void StateMachineWrapper::onStartSM()
    {
        sm->start();
    }
    
    void StateMachineWrapper::onStopSM()
    {
        qDebug() << "Trying to Stop SM";
        emit stopSM();
    }
    

    Basically the StateMachine has a loop consisting of the three states A, B and C. These are nested in a parent "Running"-QState. In a real-world application this might be some control loop or just some process containing many states.

    I want to be able to stop this process using a signal. To achieve that I created a "Stopped"-state with a transition that should trigger on a specific signal (here: stopSM).

    However this does not work because the QStateMachine does not seem to return to the EventLoop I guess. I know I could achieve that If I would create an event for every existing transition in that StateMachine and would not use unconditional transitions. But this would also mean a big overhead If my StateMachine consists of > 100 States.

    Is there any way of making my StateMachine react to external signals like the 'stopped'-Signal in the above example?


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    What do you mean by it doesn't return control to the event loop ?



  • Well, I wrote a minimal sample-application like this:

    void MainWindow::on_pushButton_clicked()
    {
        wrapper = new StateMachineWrapper();
        QThread* thread = new QThread();
        connect(thread, &QThread::started, wrapper, &StateMachineWrapper::onStartSM);
        connect(this, &MainWindow::stop, wrapper, &StateMachineWrapper::onStopSM);
        wrapper->moveToThread(thread);
        thread->start();
    }
    
    void MainWindow::on_pushButton_2_clicked()
    {
        emit stop();
    }
    
    void MainWindow::on_pushButton_3_clicked()
    {
        emit stop();
    }
    

    The SM starts without any problems and outputs A,B,C,A,B,C,... as expected.
    But when I want to stop the StateMachine by emitting the 'stopSM' signal, it does not work. I would expect the StateMachine to transition to the "stopped" state because I added that transition on the 'running' state.


  • Lifetime Qt Champion

    What version of Qt are you using ?



  • I am using Qt 5.11.2 and MinGW 5.3.0

    Another question I have is: Can Transitions somehow be priorized? So for example I want a SignalTransition to be of an higher priority as unconditional transitions.
    Background: I have processes with a lot of steps, so I would like to be able to use unconditional transitions for this. However, to be able to react to external signals or even be able to stop this process (like in my above example) I need those unconditional transitions to be of a lower priority.



  • @kain

    void StateMachineWrapper::onStopSM()
    {
    qDebug() << "Trying to Stop SM";
    emit stopSM();
    }

    I'm not sure why in this method onStopSM() you don't directly stop the state machine with

    sm->stop()
    

    the same way you start the state machine in method onStartSM() (that you even know works!). The only thing missing I see in the StateMachineWrapper class is to connect the state machine stopped() signal to do the final tasks as you need



  • I don't want to stop the whole statemachine. I got 2 parent states: 'running' and 'stopped'. The states A,B and C are substates of 'running'.
    In a real-world application this might be a "paused" or "error" state or whatever process I want to be able to interact with.

    Edit: I just tried to stop the StateMachine as you suggested. It does not work either.


  • Lifetime Qt Champion

    Would you mind providing your test application as a complete project ? This would allow to test it locally in the same conditions as you.



  • Sure. I Attached the whole TestApp
    (I could not upload here due to missing priviliges, so I instead uploaded it here:
    https://ufile.io/u3kbd )



  • @kain said in Stopping a QStateMachine with unconditional transitions:

    However this does not work because the QStateMachine does not seem to return to the EventLoop I guess. I know I could achieve that If I would create an event for every existing transition in that StateMachine and would not use unconditional transitions. But this would also mean a big overhead If my StateMachine consists of > 100 States.

    Looking at your code, you're probably right about never making it to the event loop. Why do you need to have a different event for each state if it is unconditional anyways?

    Create one signal in your wrapper to swap states, and emit it in each on entry slot you have.

    StateMachineWrapper::StateMachineWrapper(QObject *parent) : QObject(parent)
    {
        sm = new QStateMachine(this);
    
        QState *running = new QState(sm);
        QState *stopped = new QState(sm);
    
        QState* stateA = new QState(running);
        QState* stateB = new QState(running);
        QState* stateC = new QState(running);
    
        stateA->addTransition(this, &StateMachineWrapper::sig_nextState, stateB);
        stateB->addTransition(this, &StateMachineWrapper::sig_nextState, stateC);
        stateC->addTransition(this, &StateMachineWrapper::sig_nextState, stateA);
        running->addTransition(this, &StateMachineWrapper::stopSM, stopped);
    
        connect(stateA, &QState::entered, this, &StateMachineWrapper::onA);
        connect(stateB, &QState::entered, this, &StateMachineWrapper::onB);
        connect(stateC, &QState::entered, this, &StateMachineWrapper::onC);
        connect(stopped, &QState::entered, this, &StateMachineWrapper::onStopped);
    
        sm->setInitialState(running);
        running->setInitialState(stateA);
    }
    
    void StateMachineWrapper::onA()
    {
        qDebug() << "State A";
        emit sig_nextState();
    }
    void StateMachineWrapper::onB()
    {
        qDebug() << "State B";
        emit sig_nextState();
    }
    void StateMachineWrapper::onC()
    {
        qDebug() << "State C";
        emit sig_nextState();
    }
    
    void StateMachineWrapper::onStopped()
    {
        qDebug() << "State Stopped";
    }
    
    void StateMachineWrapper::onStartSM()
    {
        sm->start();
    }
    
    void StateMachineWrapper::onStopSM()
    {
        qDebug() << "Trying to Stop SM";
        emit stopSM();
    }
    


  • Thank you, I solved it this way already. I was just curios, if I could use QStateMachine in a more 'implicit' way.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.