PDA

View Full Version : QStataMachine: transition guards



nick85
26th September 2013, 09:23
Hi all,

I basically have a QStateMachine with 3 states: A, B and C.
I want to model state machine transitions like:
1) A -> B [event e1]
2) A -> C [event e1]
3) B -> C [event e2]

And I want to add exclusive guard on transitions #1 and #2 like:
1) if (myModel.v)
3) if (!myModel.v)

The final goal is to manage locking of access from A to C by inserting password:
when I am in A and fire e1 (for example by clicking on a button in my GUI):
- if "myModel.v" is not set (i.e. C is still locked by password) go to state B (to insert password)
- if "myModel.v" is set (that is C was unlocked) go to state C.

What is the best way to add these guards on transitions?
Can you give me a small example?

Thank you,

Nicola

nick85
26th September 2013, 14:52
I've tried to follow the documentation http://qt-project.org/doc/qt-4.8/statemachine-api.html#events-transitions-and-guards
but it does not seem to fully contemplate my case.

Here is part of code:

The state machine:


StateMachine::StateMachine(bool debugMode, QObject *parent)
: QStateMachine(parent)
{
_a = new State();
_b = new State();
_c = new State();

addState(_a);
addState(_b);
addState(_c);

addConditionalTransition(_a, true, _b);
addConditionalTransition(_a, true, _c);
addCustomTransition(_b, this, SIGNAL(goToCSigna()),_c);
}

QSignalTransition* StateMachine::addCustomTransition(State* source,
const char* signal,
QAbstractState* target)
{
QSignalTransition* transition = source->addTransition(sender,signal,target);
connect(transition,SIGNAL(triggered()),this,SLOT(_ onStateChanged()));
return transition;
}

ConditionalTransition *StateMachine::addConditionalTransition(State *source,
bool condition,
QAbstractState *target)
{
ConditionalTransition * transition = new ConditionalTransition(condition);
transition->setTargetState(target);
source->addTransition(transition);
connect(transition,SIGNAL(triggered()),this,SLOT(_ onStateChanged()));
return transition;
}

// SLOT of state machine used for debug
void StateMachine::_onStateChanged()
{
if(QSignalTransition* transition = qobject_cast<QSignalTransition*>(this->sender()))
{
qDebug() << "Changing State" <<
"from:" << transition->sourceState()->objectName() <<
"to:" << transition->targetState()->objectName();
}
}

// Q_INVOKABLE method to use state machine postEvent
void StateMachine::invokablePostEvent(bool b)
{
this->postEvent(new ConditionalEvent(b));
}


The custom transition:

class ConditionalTransition : public QAbstractTransition
{
public:
ConditionalTransition(bool condition);

protected:
virtual bool eventTest(QEvent *e);
virtual void onTransition(QEvent *) {}

bool _condition;
};

// implementation ------------------------------------
ConditionalTransition::ConditionalTransition(bool condition) :
_condition(condition)
{
}

bool ConditionalTransition::eventTest(QEvent *e)
{
// Test if this event is a ConditionalEvent
if (e->type() != QEvent::Type(QEvent::User+1))
return false;

ConditionalEvent *se = static_cast<ConditionalEvent*>(e);

return (se->value == _condition);
}


The custom event:

struct ConditionalEvent : public QEvent
{
ConditionalEvent(bool val);

bool value;
};

// implementation ------------------------------------
ConditionalEvent::ConditionalEvent(bool val) :
QEvent(QEvent::Type(QEvent::User+1)),
value(val)
{
}


And finally, into a button of QML GUI, the caller:


MyButton
{
id: myButton
[...]
onClicked: stateMachine.invokablePostEvent(dataModel.val)
}



CustomTransitions have no problem.
ConditionalTransition seems not works... when I'm in state _a and click on myButton, the transition is not triggered (I do not see output of lost _onStateChanged).

What is wrong?
Or, otherwise, what is the way to check the condition?

Added after 33 minutes:

I found the problem of the trigger:
the cast had to be to QAbstractTransition instead of QSignalTransition.

Now the statemachine works but what I wonder is whether it is necessary to write all this code to add a simple guard of the transition.
Or rather, if it is not possible to execute a "conditional transition" when is rised a signal like "addCustomTransition"s.

anda_skoa
27th September 2013, 08:24
well, you wouldn't need to subclass QStateMachine but maybe you want to do that for a different reason.

Since you are triggering this from QML and on a button click:

- create a context propery object that has a slot or Q_INVOKABLE method that the button triggers
- in that slot you check the condition and emit one of two different signals
- you have two signal transition on your state, one for each signal

Such a signal source is a very common way to interface a QStateMachine from QML. QML code triggers signal emits on the object, the signals trigger transitions.
The object can also hold properties that are set by state machine states, in case that the QML code wants to do things in certain states, like hiding/showing certain elements.

Cheers,
_

nick85
27th September 2013, 09:50
Ok this is right and it certainly works.. but doing it the state machine control (i.e. the valutation of the condition) is moved inside the QML and I think this may be not conceptually correct.

anda_skoa
27th September 2013, 19:15
I am not sure what you mean.

The state machine is part of the application code, independent of the UI.
The UI, well the user through the UI, generates changes, some of which make the state machine change states.
The UI reacts to state changes when appropriate, independent of how the state change was triggered.

In that model the UI doesn't know anything about the state machine and the state machine doesn't know anything about the UI.

Cheers,
_

manuelschneid3r
12th April 2024, 20:21
More modern termplate based version:



template<typename T>
class GuardedTransition : public T
{
public:
static_assert(std::is_convertible<T*, QAbstractTransition*>::value,
"GuardedTransition must inherit QAbstractTransition");

template <class... Args>
GuardedTransition(function<bool()> g, Args&&... args)
: T(std::forward<Args>(args)...), guard(g) {}

bool eventTest(QEvent *e) override
{ return T::eventTest(e) && guard(); }

private:
function<bool()> guard;
};


Did not find out how to use the std::index stuff to make the guard the last parameter yet. However using this state transitions are finally somewhat readable:





static void addEventTransition(QState &src, QState &tgt,
QObject *object, QEvent::Type type,
function<bool()> guard = nullptr)
{
QAbstractTransition *t;
if (guard)
t = new GuardedTransition<QEventTransition>(guard, object, type, &src);
else
t = new QEventTransition(object, type, &src);
t->setTargetState(&tgt);
}

template <typename Func>
static void addSignalTransition(QState &src, QState &tgt,
const typename QtPrivate::FunctionPointer<Func>::Object *sender,
Func signal,
function<bool()> guard = nullptr)
{
QAbstractTransition *t;
if (guard)
t = new GuardedTransition<QSignalTransition>(guard, sender, signal, &src);
else
t = new QSignalTransition(sender, signal, &src);
t->setTargetState(&tgt);
}




addSignalTransition(
settings_button_highlight, settings_button_visibile,
window, &Window::queryChanged,
[=] { window->input_frame.underMouse(); });