Since there have been no responses thus far, I'll submit my own answer to the question of whether it's possible to use the DSMF in non-graphical applications. It is! I'll present the small example program I wrote to satisfy my curiosity. First, here's a C++ class meant to simulate a 'domain object' that knows how to poke the world to achieve some desired side effect—in this case, connecting to a network:
// File: NetworkManager.h
#ifndef NETWORK_MANAGER_H
#define NETWORK_MANAGER_H
#include <QObject>
#include <QTimer>
#include <iostream>
class NetworkManager : public QObject
{
Q_OBJECT
Q_PROPERTY(bool timedOut MEMBER timedOut_ READ getTimedOut)
public:
NetworkManager() : timedOut_(false) { }
~NetworkManager() { }
Q_INVOKABLE void connectAsync()
{
std::cout << "NetworkManager: In connectAsync(), connecting...\n";
// Simulate a successful connection within 1 to 5 seconds...
QTimer::singleShot(
(1 + qrand() % 5)*1000,
[this]() {
std::cout << "NetworkManager: Connected!\n";
emit this->connected();
// ... and a random disconnect 5 to 15 seconds later.
QTimer::singleShot(
(5 + qrand() % 11)*1000,
[this]() {
std::cout << "NetworkManager: Lost connection!\n";
emit this->disconnected();
}
);
}
);
}
bool getTimedOut() const { return timedOut_; }
signals:
void connected();
void disconnected();
private:
bool timedOut_;
};
#endif // NETWORK_MANAGER_H
And here's the QML file defining the state logic. Note how it delegates the 'real' work to the domain object:
// File: NetworkManager.qml
import QtQuick 2.0
import QtQml.StateMachine 1.0
StateMachine {
id: networkFsm
initialState: sConnecting
running: true
State {
id: sConnecting
SignalTransition {
targetState: sConnected
signal: network_.connected
guard: {
network_.timedOut == false;
}
}
onEntered: {
console.log("NetworkFsm: sConnecting entered");
network_.connectAsync();
}
onExited: {
console.log("NetworkFsm: sConnecting exited");
}
}
State {
id: sConnected
SignalTransition {
targetState: sConnecting
signal: network_.disconnected
}
onEntered: {
console.log("NetworkFsm: sConnected entered");
}
onExited: {
console.log("NetworkFsm: sConnected exited");
}
}
}
Finally, here's the main.cpp
file that binds the two together, and a CMakeLists.txt
file to build everything:
// File: main.cpp
#include <QCoreApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <iostream>
#include "NetworkManager.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
NetworkManager network;
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("network_", &network);
engine.load(QUrl("./NetworkManagerFsm.qml"));
return app.exec();
}
# File: CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(qmlfsm)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt5Core REQUIRED)
find_package(Qt5Qml REQUIRED)
add_executable(qmlfsm main.cpp NetworkManager.h)
target_link_libraries(qmlfsm Qt5::Core Qt5::Qml)
Here's what it looks like when I run it:
qml: NetworkFsm: sConnecting entered
NetworkManager: In connectAsync(), connecting...
NetworkManager: Connected!
qml: NetworkFsm: sConnecting exited
qml: NetworkFsm: sConnected entered
NetworkManager: Lost connection!
qml: NetworkFsm: sConnected exited
qml: NetworkFsm: sConnecting entered
NetworkManager: In connectAsync(), connecting...
NetworkManager: Connected!
qml: NetworkFsm: sConnecting exited
qml: NetworkFsm: sConnected entered
^C
So, yes... it's possible. Whether or not it's useful remains to be seen. My motivation for asking the question in the first place was to see if I could make writing state machines in Qt more similar to my experience using SMC—a tool that has served me well for several years now. I love the succinctness of SMC's .sm
file format, and the fact it isn't littered with <qt:editorinfo>
tags and other XML bloat. The DSMF seems to hit most of those same sweet spots. Time will tell, but... I think I'll prefer it to SCXML—at least, for less complex FSMs.
NOTE
Please understand that the FSM posted above is just a 'toy' example cobbled together to demonstrate that the framework can be used in non-graphical apps. In particular, note that the timedOut
property of the NetworkManager
class is only present to highlight the fact that properties of QObject
classes exported to QML can be used in guard conditions. (In a real implementation, timedOut
would make more sense as a separate SignalTransition
.)
Parameters passed to the signal that causes the transition can also be used in guard conditions, but I personally feel that the FSM 'reads better' when properties/methods of the domain object are accessed explicitly. Otherwise, one wonders where, e.g., the timedOut
parameter has come from, and must open the header file for the domain object's class in order to be certain.