Reputation: 15631
There is a state machine (called the outer). This machine has two states - first and the final. First state is custom implemented. Inside the first state there is created another state machine (called the inner) which in this example does nothing.
Outer state machine with two states:
#include <QDebug>
#include <QCoreApplication>
#include <QTimer>
#include <custom_outer_state.hpp>
#include <QFinalState>
#include "a.hpp"
class Functor
{
public:
void operator()()
{
// just emits a signal
TestObject outerTestObject;
// create outer state machine with all states
QStateMachine outerStateMachine;
CustomOuterState *state1 = new CustomOuterState();
QFinalState *state2 = new QFinalState();
state1->addTransition(&outerTestObject, SIGNAL(testObjectSignal()), state2);
outerStateMachine.addState(state1);
outerStateMachine.addState(state2);
outerStateMachine.setInitialState(state1);
outerStateMachine.start();
// process state machine transitions
QCoreApplication::processEvents();
qDebug() << &outerStateMachine << ": Outer state machine first state " << outerStateMachine.configuration();
outerTestObject.testObjectSignal();
QCoreApplication::processEvents();
qDebug() << &outerStateMachine << ": Outer state machine second state " << outerStateMachine.configuration();
}
};
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QTimer::singleShot(0, Functor());
return QCoreApplication::exec();
}
And custom state:
#ifndef CUSTOM_OUTER_STATE_H
#define CUSTOM_OUTER_STATE_H
#include <QState>
#include <QStateMachine>
class CustomOuterState : public QState
{
Q_OBJECT
public:
virtual void onEntry(QEvent * event)
{
// create inner state machine
machine = new QStateMachine();
/*
* some operations with the machine
*/
}
private:
QStateMachine* machine;
};
#endif
And test object which just emits a signal:
#ifndef A_H
#define A_H
#include <QObject>
class TestObject : public QObject
{
Q_OBJECT
signals:
void testObjectSignal();
};
#endif
So this code works as expected - the outer state machine goes from the first state to the final:
QStateMachine(0x7fffc00f0a20) : Outer state machine first state QSet(CustomOuterState(0xe0a380) )
QStateMachine(0x7fffc00f0a20) : Outer state machine second state QSet(QFinalState(0xe0a460) )
But with a little change inside the custom state - passing this
(which is a subclass of QState
) to the inner state machine constructor
- machine = new QStateMachine();
+ machine = new QStateMachine(this);
results that the outer state machine doesn't want to make a transition - it stays in the first state although the transition signal was sent
QStateMachine(0x7fff219edcb0) : Outer state machine first state QSet(CustomOuterState(0x1fc4380) )
QStateMachine(0x7fff219edcb0) : Outer state machine second state QSet(CustomOuterState(0x1fc4380) )
The solution is simple - just delete the inner state machine and everything works. But the question is why the bad thing happens.
So, why adding this
which is a subclass of QState
to the inner state machine constructor results so that the outer state machine doesn't want to make a transition?
Upvotes: 2
Views: 1505
Reputation: 98505
You're trying to modify a state machine's statechart from a state's event handler. That's not supported by the state machine. Since QStateMachine
is-a QState
, by adding it as a child you're modifying the statechart.
When onEntry
is called, the statechart looks as follows:
During onEntry
, you change it to something resembling the following. The machine
is not an initial state, it's just a dangling, useless state. Even if it weren't dangling, it'd still be unusable as it was added during a state transition.
Since QStateMachine
is a QState
, when you make it a direct child of a state it becomes a substate of that state. If all you want is to use the state as a container for the state machine, you can insert an intervening non-state object between the parent and the state.
// https://github.com/KubaO/stackoverflown/tree/master/questions/statemachine-nested-32619103
#include <QtCore>
struct OuterState : QState
{
QStateMachine * machine { nullptr };
virtual void onEntry(QEvent *) Q_DECL_OVERRIDE
{
// through an intervening container
auto container = new QObject(this);
machine = new QStateMachine(container);
}
OuterState(QState * parent = 0) : QState(parent) {}
};
Finally, the pseudo-synchronous code that invokes processEvents
is unnecessary. You can do things when states are entered, etc. Recall that since a signal is an invokable method just like a slot is, you can connect signals to signals. In fact, since all you want to achieve is an unconditional transition, you might as well use one.
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// create outer state machine with all states
QStateMachine outerStateMachine;
OuterState state1 { &outerStateMachine };
QFinalState state2 { &outerStateMachine };
state1.addTransition(&state2);
outerStateMachine.setInitialState(&state1);
outerStateMachine.start();
a.connect(&state1, &QState::entered, []{ qDebug() << "state1 entered"; });
a.connect(&state2, &QState::entered, []{ qDebug() << "state2 entered"; });
a.connect(&state2, &QState::entered, qApp, &QCoreApplication::quit);
return a.exec();
}
The above is, more or less, how self-contained test cases should look: single file, no fluff and verbosity that detracts from the issue at hand.
Upvotes: 4