DevDays Intermezzo: State of The Machine Address

I had the pleasure of talking to some fellow state machine enthusiasts at DevDays in Munich. Here are some of the topics that were brought up, in randomized order:

Verification. How can you check that your state machine is sane, or rather, how can you disprove that it's criminally insane? Currently there isn't a way to do that. We tried to design the API so that many (most?) semantic errors will be caught at compile time. But that doesn't get us all the way, unfortunately; what happens if you forget to specify the initial state of a compound state, for example? The QStateMachine::error() signal is there to report such cases; if the state machine detects a semantic error at any stage of executing its algorithm, it emits the error() signal. But maybe it would be nice to have a function that can run a set of standard sanity checks before the machine is started?

Performance. We currently implement the SCXML algorithm more or less exactly as it's specified. I don't yet know how well it scales wrt number of states and transitions; well, I have one benchmark where the running time grows exponentially, but there are many different configurations that need to be investigated (flat vs nested state machines, sequential vs parallel states, custom events and transitions vs built-in ones, etc.). Is anyone out there creating state machines with thousands of states, and if so, are you seeing performance issues?

Lazy population of states. For large compound states, it can make sense to only create the contents of the state when (if) the state is actually entered. To do that, you need only create an initial state, and reimplement QState::onEntry(). You need to create the initial state because the state machine will request it _before_ the compound state is entered (and QState::initialState() is not virtual...). In your onEntry() implementation, use a flag to check whether the state is being entered for the first time, and if so, create the remaining child states and add transition(s) to them from the initial state, as appropriate. The state machine actually does similar kinds of lazy initialization internally; the signal associated with a QSignalTransition is only connected when the transition's source state is entered, for example.

Debugging. There's no straightforward way to debug state machines in Qt 4.6. What's needed first is a "debugger client" interface that the state machine can use to deliver notification of e.g. state changes. This interface would then serve as the basis of debugging tools. It would be nice to have a visual representation of the machine (tree view?) highlighting the current state, be able to "step" the state machine execution, set breakpoints on transitions, inspect the event queues, and so on. P.S.: If you're building Qt from sources, there's a QT_STATEMACHINE_DEBUG define that you can enable in qstatemachine.cpp; this will cause the state machine to spew out a log of everything it's doing. This output can be difficult to parse humanely, though, especially when your machine has nested states.

Why QtCore? Couldn't the state machine framework be in a library of its own? Yes, it could, but we chose to put it in QtCore so it can easily be used anywhere, without introducing new dependencies. The QtCore library grows by about 5% (150K) due to the state machine classes. Note that it's still possible to remove the framework from compilation by using the qconfig tool, just like you can with the animation framework (also part of QtCore) and several other features in Qt.

Multithreading. How do you use state machines with threads? Well, there are a few things you should know. Firstly, the state machine classes are reentrant, meaning you can have multiple state machines running at the same time (one per thread, say). Secondly, the QStateMachine::postEvent() function is _not_ thread-safe, meaning that it's not safe to call postEvent() on the _same_ machine from different threads; you'll have to manage the synchronization yourself if you want to do so (but maybe QStateMachine::postEvent() should be made thread-safe, like QCoreApplication::postEvent()?). Thirdly, if you're using signal transitions, there is no restriction on whether the object emitting the signal lives in the same thread as the state machine or not; the state machine will always create a connection of type Qt::AutoConnection, meaning that the connection will be queued if the object lives in a different thread. This means that the state machine can be processing events in its own thread concurrently with the signal being emitted in another thread, without you having to worry about synchronization.

See you in San Francisco, maybe?

Blog Topics: