Dynamic number of parallel states in a state machine
-
Hi all,
I'm trying to dev my first desktop application using Qt. I used to work with statecharts on previous projects, so I'm trying to do the same with Qt. But I'm facing a design issue I don't know how to work around.
The application is developped with PySide2 (Python). It will be managed by a state machine, created with QScxmlStateMachine.fromFile(). To simplify, there is 2 states [Login, Operate(Parallel)]. In the Operate state, the user can create multiple tabs. Each tab must be managed by a/the statemachine. This is were I'm stuck. The Operate state mode must be "parallel", so that each tab has its own state and run in parallel. But, as the number of tabs are dynamic, I cannot create them in advance. And from my understanding, all states of a statemachine must be created before the statemachine is started. So, I tried to workaround by creating one statemachine with QScxmlStateMachine.fromFile() per tab. But I end up in weird situation: the "child" statemachine does not start, except if I raise an Exception (I feel it could be somehow related to the event loop, but I'm don't know Qt enough yet).
I think I miss a design pattern here. Any help/tips/lessons are welcomed :)
Thx for your help.
-
By writing a small demo to complete my post, I found the issue. As it might be helpful to somebody else, here it is:
demo.scxml:
<?xml version="1.0" encoding="UTF-8"?> <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" binding="early" xmlns:qt="http://www.qt.io/2015/02/scxml-ext" name="plop.scxml" qt:editorversion="4.7.1"> <state id="State_1"> <qt:editorinfo scenegeometry="144.50;162.05;84.50;112.05;120;100" geometry="144.50;162.05;-60;-50;120;100"/> </state> </scxml>
#!/usr/bin/env python import sys from PySide2.QtCore import QObject, QCoreApplication, SLOT, Slot from PySide2.QtScxml import QScxmlStateMachine class Backend_1(QObject): def __init__(self, machine): super(Backend_1, self).__init__() self.machine = machine self.machine.connectToState("State_1", self, SLOT("s1_active(bool)")) @Slot(bool) def s1_active(self, active): if active: print('Backend_1::State_1: enter') self.submachine = QScxmlStateMachine.fromFile('demo.scxml') b = Backend_2(self.submachine) self.submachine.start() else: print('Backend_1::State_1: exit') class Backend_2(QObject): def __init__(self, machine): super(Backend_2, self).__init__() self.machine = machine self.machine.connectToState("State_1", self, SLOT("s1_active(bool)")) @Slot(bool) def s1_active(self, active): if active: print('Backend_2::State_1: enter') else: print('Backend_2::State_1: exit') if __name__ == '__main__': app = QCoreApplication(sys.argv) machine = QScxmlStateMachine.fromFile('demo.scxml') b = Backend_1(machine) machine.start() sys.exit(app.exec_())
If you run this, you expect to see:
Backend_1::State_1: enter
Backend_2::State_1: enterBut, the second statemachine does not seem to start. To fix it, you need to store the instance of Backend_2 (eg:
self.b = Backend_2(self.submachine)
). I guess it was garbage collected, so all the states/events connections were broken.Meanwhile, if you think my design patter is bad, I'm willing to learn :)