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?
-
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. -
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. -
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?
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.
-
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(); }