Kent
Qt
Labs
Posted by Kent
 in Qt, Labs
 on Friday, January 30, 2009 @ 18:05

Hopefully there won’t be a public outcry when I claim that most Qt applications can be described as “a set of objects that interact with eachother over a period of time”. It’s the job of Qt to provide APIs that support this process. Today I’d like to focus on the “over a period of time” part.

Whereas class diagrams describe structure, state diagrams describe behavior. This is what a state diagram for a simple checkbox might look like:

In an object-oriented language like C++, there’s usually a one-to-one mapping between the elements of a class diagram and the code — classes are a fundamental part of the language, after all. With state diagrams there isn’t any such direct correspondence, though; they’re foreign tongue.

For “trivial” things (like a checkbox) you can quickly code something that works, of course, without using any concepts from state diagrams directly. However, because of the nature of event-driven programs (most Qt applications are), it’s very easy to end up with a bunch of if- and switch-statements that try to determine what the application should do at each point. Because the state of the application is implicit (just a bunch of variables), it soon gets difficult to understand what’s going on, which cases you’ve covered and which you haven’t. And don’t ever try to change or add something because that’ll just break everything.

The alternative to the ad hoc implementations of state machines described above is to rely on a framework to help you out. To this end, we’re working on a State Machine Framework that makes Qt speak the language of state diagrams; it lets you construct state graphs and execute them. The goal is to enable applications to have a cleaner separation between control flow and actual useful work. It’s pretty much an event loop with a control flow mechanism on top.

The framework integrates deeply with Qt’s event system, signals and slots, and property system. By making it a core technology in Qt, the threshold for starting to use it should be very low, so that as many applications as possible can benefit from it. Inside Qt this framework is being used in conjunction with the new Animations Framework to provide animated transitions. Since the State Machine framework implements the algorithm defined by SCXML, we also aim to make it the backend of Qt SCXML.

OK, time to implement our first state machine. Consider the following state diagram:

Every time the button button is clicked, the state machine transitions to another state, and button’s text property is changed to reflect which state the machine is now in. Here’s how it can be implemented using the Qt State Machine API:

#include <QtGui>

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QPushButton button;

    QStateMachine machine;

    QState *s1 = new QState(machine.rootState());
    s1->setPropertyOnEntry(&button, “text”, “In s1″);

    QState *s2 = new QState(machine.rootState());
    s2->setPropertyOnEntry(&button, “text”, “In s2″);

    QState *s3 = new QState(machine.rootState());
    s3->setPropertyOnEntry(&button, “text”, “In s3″);

    s1->addTransition(&button, SIGNAL(clicked()), s2);
    s2->addTransition(&button, SIGNAL(clicked()), s3);
    s3->addTransition(&button, SIGNAL(clicked()), s1);

    button.resize(200, 200);
    button.show();

    machine.setInitialState(s1);
    machine.start();

    return app.exec();
}

You can have a slot get called whenever a state is entered; or you can subclass Q(Abstract)State and do whatever you like. There’s a mechanism for having transitions based on events (e.g. mousePress). You can have composite (nested) states, parallel states… It’s pretty powerful stuff. Oh yeah, in case I forgot to mention, I’m pretty excited about this! :D If I had to pick, this is the one thing I’ve felt was missing since I started using Qt. Death to spaghetti code, long live control abstractions.

The State Machine Framework is part of the Qt Kinetic project. You’ll find it in the kinetic-animations branch of the Qt Kinetic repository. Have fun, and let us know what you think.

29 Responses to “Qt State Machine Framework”

» Posted by Scorp1us
 on Friday, January 30, 2009 @ 18:31

Undo?

Is there a way to move backwards or conditionally forwards through the state machine?

I think there is a missing concept in programming of the “anti-operation”. This anti-op is the inverse of an operation. For instance, getchar/ungetchar, multiply/divide, add/substract. While not every operation has a functional inverse (send data to socket), at some point there is a business-logic inverse (POST/DELETE, INSERT/DELETE UPDATE/RESTORE) that should allow actions to automatically back out. Or better, take a collection of actions and undto them. For instance serialization.

Anyway I am rambling now. But being able to go backwards and conditionally forwards seems needed. Does it do that?

» Posted by Peterix
 on Friday, January 30, 2009 @ 19:15

What I think is that state machines aren’t really specific to animations. I can see this used for network communication, language parsers and other things.

» Posted by Jos
 on Friday, January 30, 2009 @ 19:28

So will this allow us to prove that livelocks and deadlocks are not possible?

http://public.kitware.com/IGSTKWIKI/images/4/4e/Kickoff.SMValidation.ppt

» Posted by Ian Monroe
 on Friday, January 30, 2009 @ 20:18

We’ve been thinking about moving to a state system for Amarok’s startup sequence.

Cool stuff. :)

» Posted by Rich
 on Friday, January 30, 2009 @ 20:48

Maybe there should be qt/kinetic documentation being generated on doc.trolltech.com as well as the existing main-snapshot docs.

» Posted by Tau
 on Friday, January 30, 2009 @ 21:16

Cool. Otherwise state machines (i.e. DFAs) are much limited than Qt programs in general since Qt/C++ is for sure Turing-complete.

» Posted by ris
 on Saturday, January 31, 2009 @ 02:52

Excellent. I’ve always hated GUI programming and this is the sort of thing that I feel we should have been using for years. One step towards de-ghetto-ifying GUI programming.

» Posted by Scorp1us
 on Saturday, January 31, 2009 @ 03:00

Gee, I wish I could try this, but I’ve not been able to compile kintetic for MONTHS. This is my latest error:

..\lib\uilib\abstractformbuilder.cpp: In function `void storeItemProps(QFormInternal::QAbstractFormBuilder*, const T*, QList*)’:
..\lib\uilib\abstractformbuilder.cpp:1716: error: cannot convert `const QMetaObject QFormInternal::QAbstractFormBuilderGadget::*’ to `const QMetaObject*’ for argument `2′ to `QFormInternal::DomProperty* QFormInternal::variantToDomProperty(QFormInternal::QAbstractFormBuilder*, const QMetaObject*, const QString&, const Q
Variant&)’

This is Qt on mingw32

» Posted by Wysota
 on Saturday, January 31, 2009 @ 12:50

Am I wrong or is it going to make implementing network servers and all kinds of parsers a veery easy task now?

» Posted by Andreas
 on Sunday, February 01, 2009 @ 22:51

Hey, I’ve been pondering the same idea. It should have some potential to simplify networking code; the state space there (even of basic TCP/IP) is somewhat large.

» Posted by Anssi
 on Monday, February 02, 2009 @ 07:37

I am not sure that I like that api style,
would it be more convenient if it would be something like..

QPushButton *button = QPushButton();
QState *s1 = new QState(), s2= new QState():
QState *subsState = new QState(s2);
button->setPropertyOnEntry( s1, “text”, “In s1″);
button->setPropertyOnExit( s1, “text”, “Out s1″);
button->sendSignalOnExit( s1, “text”, “Out s1″);
button->addTransition( s1, , SIGNAL(clicked()), s2 );
s1->addTransition(&button, SIGNAL(clicked()), s2);

» Posted by eskil
 on Monday, February 02, 2009 @ 08:50

Anssi: In your case it would not be clear to which state machine the top level states belong. As for putting the setPropertyOnEntry() function in QObject… I think for me your code example looks less readable than the one in the article, and without any added convenience, really. The API isn’t necessarily set in stone at this point, but I think we’ve found a nice balance of convenience, readability and consistency in the current revision =)

» Reply from Kent
 on Monday, February 02, 2009 @ 09:54
Kent

Peterix, Wysota, Andreas: Yes, the state machine framework has a broader scope than just animations; there’s nothing in it that ties it do that. The animation-specific “states and transitions” API is a layer on top of the general-purpose SM API. Network programming is indeed another interesting use case.

» Posted by Peter
 on Monday, February 02, 2009 @ 12:34

Hello, very good idea to introduce a generic statemaschine.

But I think the class name ‘QState’ is too generic because it
is a QUiState. Assume, lateron you wanna introduce a state with
a more network related interface then such a hierarchy looks
much better:

QAbstractState
|- QUiState
|- QNetworkState

» Posted by eskil
 on Monday, February 02, 2009 @ 13:27

Peter: QState currently lives in QtCore and is not specifically a UI state. It is basically a QAbstractState with a convenience API on top which allows you to specify behavior without subclassing. In most cases, it is the class you will use to make new states.

» Posted by Peter
 on Monday, February 02, 2009 @ 14:28

OK, read the outdated QtSoution doc where QState has much more ui relevent functions:
http://doc.trolltech.com/solutions/4/qtanimationframework/qtstate.html

But QState still has ‘addAnimatedTransition’ which sounds like a ui-spezific function.

» Posted by Peter
 on Monday, February 02, 2009 @ 14:51

And what I really miss is the possibility to add functions which are not signals/slots.
This way errors will pop up at compile time and not at runtime. I know this
is Qt with the policy “we don’t need type-checked connections” but it would
very comfortable when it is possible to use functions and memberfunctions of
non-QObject classes.

This means you would have to use templates, for instance:

template
void QAbstractState::addTransition(const T *sender, F function,
QAbstractState *target);

But I assume you don’t wanna go this long C++ way.

» Posted by Peter
 on Monday, February 02, 2009 @ 15:08

“const QObject *” doesn’t look Qt like -> “const Qbject&” or “QObject*”

» Posted by MH
 on Monday, February 02, 2009 @ 16:49

Great Work Guys!

This is exactly what we need!

Is there any chance to have the framework as a separate component (like the Qt Animation Framework)?
We’d like to use it with Qt 4.4.3.

» Posted by Scorp1us
 on Monday, February 02, 2009 @ 18:13

Wysota and Andreas,

The state for a network service (over HTTP) is traditionally held in a server-based cookie. But this is risky, because you are not assured of a single “thread” per machine. In fact, I regularly open multiple tabs at the same site. This would produce a very confusing sequence of operations… The user wants these states in parallel, but your server code will attempt to treat all as one.

However, I still think it is a good idea. If we had a good REST-based server, and we combine this state machine, we could make it easy to come up with Java, Qt, or Web client applications, using business logic in Qt on the back end.

» Posted by markot
 on Monday, February 02, 2009 @ 18:54

Hi,

Great work!

Any chance to have the framework as a separate component (like the Qt Animation Framework)?
We’d like to use it with Qt 4.4.3.

» Posted by eskil
 on Monday, February 02, 2009 @ 19:53

Peter: No templates, sorry. The framework is however written with user extensions in mind. The “const QObject *” signature does look Qt-like to me, and proof is here: http://doc.trolltech.com/4.4/qobject.html#connect

:-)

» Posted by Marko
 on Tuesday, February 03, 2009 @ 10:04

Hi,

Any chance to have the framework as a separate component (like the Qt Animation Framework)?
We’d like to use it with Qt 4.4.3.

» Reply from Kent
 on Wednesday, February 04, 2009 @ 11:51
Kent

Marko, we’d like to make a stand-alone Qt Solutions release. Stay tuned.

» Posted by Marko
 on Friday, February 06, 2009 @ 07:23

Hi,

Yesterday, I took the latest snapshot and made some experiments. I have one question.
QTransition allows adding actions via addAction(), setPropertyOnTransition(), and invokeMethodOnTransition() methods. QTransition triggers on QEvent. This works fine. The question is that why this interface of adding actions is not possible in the case of QSignalTransition? This would be very useful.

» Posted by Philipp
 on Tuesday, February 10, 2009 @ 12:27

Hi,
Will there be hierarchical states? (Like http://www.state-machine.com/resources/glossary.htm#HSM) That would be a great plus indeed!

» Posted by Anssi
 on Tuesday, February 10, 2009 @ 20:17

eskil, kent: I think that you should read one document http://www.accu-usa.org/Slides/samek0311.pdf
i don’t say that your design is any less capable, but there is some benefits in hsm.

» Posted by eskil
 on Wednesday, February 11, 2009 @ 14:46

Philipp: Yes, hierarchical states are possible, as are parallel states.
Anssi: From a quick look at that, it is a different implementation of the same UML state chart concepts. Am I missing something?

» Posted by mirosamek
 on Friday, February 13, 2009 @ 15:10

I’m really glad to see that Qt now offers hierarchical state machines (UML statecharts) as an option!

As I’m the developer of another state machine framework called QP (available from www.state-machine.com), I’d like to compare QP with the new Qt State Machine Framework.

So yes, QP could be considered “yet another implementation of the same UML state chart concepts”. Although, I would turn this around a bit, because the QP state machines predated the new Qt state machines by almost a decade. Also, while QP tries to be as “UML-compliant” as possible, the Qt state machines seem to be based on the SCXML specification, which is different than UML. (Why use yet another state machine specification?)

The QP framework, or actually a family of three frameworks QP/C, QP/C++, and QP-nano, is designed for real-time embedded (RTE) systems. For all intents and purposes, you can view QP as a modern event-driven operating system that replaces or augments traditional OS or RTOS, so QP is clearly much more than just state machines. QP was designed for efficiency, determinism, and very small footprint–especially in RAM. All QP frameworks can run standalone (on “bare metal” microprocessors, microcontrollers, or DSPs) and ports are provided for ARM7/ARM9/Cortex, AVR, MSP430, M16C/R8C, H8, PIC, 8051, ColdFire, HC08, etc. The QP/C and QP/C++ frameworks can also work with a conventional OS/RTOS, and ports exist for Linux/BSD, Win32, and many RTOSs such as VxWorks, ThreadX, eCos, uC/OS-II, and FreeRTOS.org.

The part of the QP framework responsible for executing HSMs is called the QEP event processor. The QEP itself is less than 1KB of code (typically just 600 bytes) and implements state machines very differently than the Qt State Machine Framework. The biggest difference is that hierarchical state machines for QEP are defined as pure *code* (each state is a separate state-handler function). In contrast, Qt’s state machines are represented as *data* (state objects, transition objects, etc.) Here is an example of the state machine example implemented in QEP:

#include “qp_port.h”

class TestHSM : public QHsm {
private:
// add any extended-state variables
public:
TestHsm() : QHsm(&TestHSM::initial) {} // ctor

protected:
static QState initial(TestHsm *me, QEvent const *e);
static QState s1(TestHsm *me, QEvent const *e);
static QState s2(TestHsm *me, QEvent const *e);
static QState s3(TestHsm *me, QEvent const *e);
};

QState TestHSM::initial(TestHsm *me, QEvent const *e) {
return Q_TRAN(&TestHSM::s1); // top-most initial tran
}

QState TestHSM::s1(TestHsm *me, QEvent const *e) {
switch (e->sig) {
case Q_ENTRY_SIG: {
putString(”In s1″);
return Q_HANDLED();
}
case BTN_CLICKED_SIG: {
return Q_TRAN(&TestHSM::s2);
}
}
return Q_SUPER(&QHsm::top); // superstate
}

QState TestHSM::s2(TestHsm *me, QEvent const *e) {
switch (e->sig) {
case Q_ENTRY_SIG: {
putString(”In s2″);
return Q_HANDLED();
}
case BTN_CLICKED_SIG: {
return Q_TRAN(&TestHSM::s3);
}
}
return Q_SUPER(&QHsm::top); // superstate
}

QState TestHSM::s3(TestHsm *me, QEvent const *e) {
switch (e->sig) {
case Q_ENTRY_SIG: {
putString(”In s3″);
return Q_HANDLED();
}
case BTN_CLICKED_SIG: {
return Q_TRAN(&TestHSM::s1);
}
}
return Q_SUPER(&QHsm::top); // superstate
}

Miro Samek
www.state-machine.com



© 2008 Nokia Corporation and/or its subsidiaries. Nokia, Qt and their respective logos are trademarks of Nokia Corporation in Finland and/or other countries worldwide.
All other trademarks are property of their respective owners.