前回はステートマシーンフレームワークの経緯を説明し、
ステートマシンフレームワークの使用手順
ステートマシンフレームワークの基本的な使用手順は次のようになります。
- ①状態遷移機械の生成
QStateMachineのインスタンスを生成します。
- ②状態の生成
QStateのインスタンスが状態を表し、
コンストラクタにはQStateMachineまたはQStateのインスタンスを指定します。通常の状態では、 QStateMachineを渡してQState のインスタンスを生成します。複合状態や並列状態を生成する場合には、 QState のインスタンスを渡します。 - ③遷移の生成
オブジェクトのシグナル送信によって引き起される遷移の場合には、
QSignalTransitionのインスタンスを生成し、 マウスとキーによる場合には、 QMouseEventTransitionやQKeyEventTransitionのインスタンスを生成して、 遷移を生成し、 QState::addTransition() で、 状態に遷移を設定します。 以下のような、
簡単にトランジションを設定できるメソッドも用意されています。 QState::addTransition( QObject* sender, const char* signal, QAbstractState* target )
QAbstractTransition::addAnimation()で必要に応じてアニメーションを付加します。
- ④初期状態の設定
QStateMachine::setInitialState()で、
初期状態となるQStateのインスタンスを設定します。複合状態や並列状態の場合には、 入れ子になった各状態に対して、 QState::setInitialState()で初期状態を設定します。 - ⑤状態遷移機械の起動
QStateMachineのインスタンスに対してstart() を呼び出します。
基本的なステートマシンフレームワークの適用例
ステートマシンフレームワークのサンプルプログラムがQtに付属しています。まず、
このサンプルプログラムを動かすと、
![図1 “Two-way Button Example”の動作 図1 “Two-way Button Example”の動作](/assets/images/dev/serial/01/qt-2010/0005/001.jpg)
状態遷移図は図2のようになります。前回のサンプルプログラムの状態遷移図と本質的には同じです。
![図2 “Two-way Button Example”の状態遷移 図2 “Two-way Button Example”の状態遷移](/assets/images/dev/serial/01/qt-2010/0005/002.jpg)
サンプルプログラムを見ていきましょう。
1: #include <QtGui>
2:
3: int main(int argc, char **argv)
4: {
5: QApplication app(argc, argv);
6: QPushButton button;
7: QStateMachine machine;
「状態遷移機械の生成」
9: QState *off = new QState();
10: off->assignProperty(&button, "text", "Off");
11: off->setObjectName("off");
12:
13: QState *on = new QState();
14: on>setObjectName("on");
15: on->assignProperty(&button, "text", "On");
「状態の生成」
17: off->addTransition(&button, SIGNAL(clicked()), on);
18: on->addTransition(&button, SIGNAL(clicked()), off);
状態に、
- void addTransition(QAbstractTransition* transition)
QSignalTransition、
QMouseEventTransitionやQKeyEventTransitionのインスタンスを生成してから設定します。リスト1の17~18行は、 以下のように書き換えられます。 QSignalTransition *fromOfftoOnTransition = new QSignalTransition(&button, SIGNAL(clicked()), off); fromOfftoOnTransition->setTargetState(on); QSignalTransition *fromOntoOffTransition = new QSignalTransition(&button, SIGNAL(clicked()), on); fromOntoOffTransition->setTargetState(off);
QSignalTransitionのインスタンスの所有者は、
offまたはonのQStateインスタンスになることに注意しましょう。 - QSignalTransition *addTransition(QObject *sender, const char *signal, QAbstractState *target)
このサンプルプログラムで使われているメソッドです。signalにはQObject::connect()と同様に、
マクロSIGNAL()によって正規化したシグナルのシグネチャ文字列を渡します。 - QAbstractTransition *addTransition(QAbstractState *target)
無条件遷移で、
遷移のためのトリガーはなく、 元状態からtargetに指定した状態にすぐに遷移します。
20: machine.addState(off);
21: machine.addState(on);
「状態の生成」
-
9: QState *off = new QState(&machine); 13: QState *on = new QState(&machine);
23: machine.setInitialState(off);
「初期状態の設定」
24: machine.start();
「状態遷移機械の起動」
26: button.resize(100, 50);
27: button.show();
28: return app.exec();
29: }
複合状態を持つステートマシンフレームワークの適用例
次に、
Traffic Lightでは簡略化した信号機の動作を状態遷移機械で表現していて、
![図3 Traffic Lightの動作 図3 Traffic Lightの動作](/assets/images/dev/serial/01/qt-2010/0005/thumb/TH800_003.jpg)
信号機の動作仕様を文章で書き下すと、
- ① 信号機には3つの電球があり、
赤、 黄、 青の3色である。 - ② 各電球は、
ある一時点で一色のみが表示される。 - ③ 各電球は、
点灯して一定時間経つと消える。 - ④ 電球の点灯時間は、
赤と青は 3 秒、 黄は 1 秒である。 - ⑤ ある電球が点灯し、
その点灯時間が経つと、 別の電球が点灯する。点灯する順番は、 赤、 黄、 青、 黄、 赤…と繰り返される。 - ⑥ 最初に点灯する電球は赤である。
この仕様に従った信号機の動作をするプログラムを作成するのですが、
文章よりもはっきりと形式化して、
各電球の状態遷移図
Traffic Lightには2つ状態遷移図が描かれています。最初の図は、
1つ目の図LightStateは、
![図4 各電球の状態遷移図 図4 各電球の状態遷移図](/assets/images/dev/serial/01/qt-2010/0005/thumb/TH800_004.jpg)
LightState状態は、
- ①点灯中を表すtiming状態を持つ。
- ②黒丸が指している先は、
LightStateの最初の状態。つまりLightStateという状態に遷移すると、 内部状態 timing に遷移し、 電球が点いて、 タイマーが起動されます。 - ③タイムアウトすると電球が消えて、
終了状態になります。丸で囲まれた黒丸が終了状態を表します。
図4を使うと文章で書いた仕様の③が満たされていることが確認できます。
信号機の状態遷移図
2つ目の図は、
LightState | 意味付け |
---|---|
redGoingYellow | 赤の点灯制御のための状態 |
yellowGoingGreen | 次の点灯が青の場合の黄の点灯制御のための状態 |
greenGoingYellow | 緑の点灯制御のための状態 |
yellowGoingRed | 次の点灯が赤の場合の黄の点灯制御のための状態 |
![図5 信号機の状態遷移図 図5 信号機の状態遷移図](/assets/images/dev/serial/01/qt-2010/0005/thumb/TH800_005.jpg)
この状態遷移機械が表現しているのは以下の6つです。
- ①黒丸が指している先のredGoingYellowが最初の状態。つまり、
最初に赤が点灯する。 - ②redGoingYellow.
finished、 つまり、 redGoingYellow状態の内部状態が終了状態になると yellowGoingGreen 状態に遷移する。 - ③yellowGoingGreen状態の内部状態が終了状態になるとgreenGoingYellow状態に遷移する。
- ④greenGoingYellow状態の内部状態が終了状態になるとyellowGoingRed状態に遷移する。
- ⑤yelloowGoingRed状態の内部状態が終了状態になるとredGoingYellow状態に遷移する。つまり、
最初の状態に戻る。 - ⑥終了状態はなく、
永久に前述の動作を繰返す。
図5を使うと、
このようにしてステートマシンで形式化した仕様を機械的にコードに書き換えることができれば、
ここまでの説明を念頭にして、
1: #include <QtGui>
2:
3: class LightWidget : public QWidget
4: {
5: Q_OBJECT
6: Q_PROPERTY(bool on READ isOn WRITE setOn)
電球にonプロパティを用意し、
7: public:
8: LightWidget(const QColor &color, QWidget *parent = 0)
9: : QWidget(parent), m_color(color), m_on(false) {}
10:
11: bool isOn() const
12: { return m_on; }
13: void setOn(bool on)
14: {
15: if (on == m_on)
16: return;
17: m_on = on;
18: update();
19: }
onプロパティのゲッターとセッターのうちセッターでは電球のオン/
21: public slots:
22: void turnOff() { setOn(false); }
23: void turnOn() { setOn(true); }
遷移が起きたときに呼び出すためのスロットです。
25: protected:
26: virtual void paintEvent(QPaintEvent *)
27: {
28: if (!m_on)
29: return;
30: QPainter painter(this);
31: painter.setRenderHint(QPainter::Antialiasing);
32: painter.setBrush(m_color);
33: painter.drawEllipse(0, 0, width(), height());
34: }
電球の点灯状態に合わせて、
36: private:
37: QColor m_color;
38: bool m_on;
39: };
40:
41: class TrafficLightWidget : public QWidget
42: {
43: public:
44: TrafficLightWidget(QWidget *parent = 0)
45: : QWidget(parent)
46: {
47: QVBoxLayout *vbox = new QVBoxLayout(this);
48: m_red = new LightWidget(Qt::red);
49: vbox->addWidget(m_red);
50: m_yellow = new LightWidget(Qt::yellow);
51: vbox->addWidget(m_yellow);
52: m_green = new LightWidget(Qt::green);
53: vbox->addWidget(m_green);
54: QPalette pal = palette();
55: pal.setColor(QPalette::Background, Qt::black);
56: setPalette(pal);
57: setAutoFillBackground(true);
58: }
三色の電球を配置し、
60: LightWidget *redLight() const
61: { return m_red; }
62: LightWidget *yellowLight() const
63: { return m_yellow; }
64: LightWidget *greenLight() const
65: { return m_green; }
各電球のインスタンスへの参照アクセッサーです。
67: private:
68: LightWidget *m_red;
69: LightWidget *m_yellow;
70: LightWidget *m_green;
71: };
72:
73: QState *createLightState(LightWidget *light, int duration, QState *parent = 0)
各電球の内部状態遷移機械を持つ状態を生成するためのメソッドです。
74: {
75: QState *lightState = new QState(parent);
76: QTimer *timer = new QTimer(lightState);
77: timer->setInterval(duration);
78: timer->setSingleShot(true);
79: QState *timing = new QState(lightState);
lightStateは生成する状態で、
80: QObject::connect(timing, SIGNAL(entered()), light, SLOT(turnOn()));
81: QObject::connect(timing, SIGNAL(entered()), timer, SLOT(start()));
82: QObject::connect(timing, SIGNAL(exited()), light, SLOT(turnOff()));
シグナルentered()と exited() はQAbstractStateクラスで定義されていて、
83: QFinalState *done = new QFinalState(lightState);
84: timing->addTransition(timer, SIGNAL(timeout()), done);
終了状態を生成し、
85: lightState->setInitialState(timing);
内部状態遷移機械の初期状態を設定が必要なことに注意しましょう。
86: return lightState;
87: }
88:
89: class TrafficLight : public QWidget
90: {
91: public:
92: TrafficLight(QWidget *parent = 0)
93: : QWidget(parent)
94: {
95: QVBoxLayout *vbox = new QVBoxLayout(this);
96: TrafficLightWidget *widget = new TrafficLightWidget();
97: vbox->addWidget(widget);
98: vbox->setMargin(0);
99:
100: QStateMachine *machine = new QStateMachine(this);
101: QState *redGoingYellow = createLightState(widget->redLight(), 3000);
102: redGoingYellow->setObjectName("redGoingYellow");
103: QState *yellowGoingGreen = createLightState(widget->yellowLight(), 1000);
104: yellowGoingGreen->setObjectName("yellowGoingGreen");
105: redGoingYellow->addTransition(redGoingYellow, SIGNAL(finished()), yellowGoingGreen);
106: QState *greenGoingYellow = createLightState(widget->greenLight(), 3000);
107: greenGoingYellow->setObjectName("greenGoingYellow");
108: yellowGoingGreen->addTransition(yellowGoingGreen, SIGNAL(finished()), greenGoingYellow);
109: QState *yellowGoingRed = createLightState(widget->yellowLight(), 1000);
110: yellowGoingRed->setObjectName("yellowGoingRed");
111: greenGoingYellow->addTransition(greenGoingYellow, SIGNAL(finished()), yellowGoingRed);
112: yellowGoingRed->addTransition(yellowGoingRed, SIGNAL(finished()), redGoingYellow);
113:
114: machine->addState(redGoingYellow);
115: machine->addState(yellowGoingGreen);
116: machine->addState(greenGoingYellow);
117: machine>addState(yellowGoingRed);
118: machine->setInitialState(redGoingYellow);
119: machine->start();
今までに出てきたものと同じようにして、
120: }
121: };
122:
123: int main(int argc, char **argv)
124: {
125: QApplication app(argc, argv);
126:
127: TrafficLight widget;
128: widget.resize(110, 300);
129: widget.show();
130:
131: return app.exec();
132: }
133:
134: #include "main.moc"
ステートマシンフレームワークを適用したサンプルコード
Qtには、
- State Machine Examples
- Event Transitions
- Factorial States
- Ping Pong States
- Rogue
- Traffic Light
- Two-way Button
- Animation Framework Examples
- Animated Tiles
- Application Chooser
- Move Blocks
- States
- Stick man
- Sub-Attaq
- ほとんどはGUIでの動作をするものですが、
ステートマシーンフレームワークは、 Qtの非GUI機能の基本モジュールQtCoreに含まれていることからわかるように、 GUIを持たないプログラムにも適用でき、 Factorial Statesと Ping Pong StatesはGUI機能を使わずに書かれています。
ステートマシーンフレームワーク適用の注意
経験的に、
①低動作レベルな場合には適用しない。
ステートマシンフレームワークは、
②Qtアプリケーションの全動作をステートマシーンフレームワークだけで実装ししない。
適宜いろいろな手段で状態遷移を記述するべきで、
③状態遷移図または状態遷移表を最初に記述する
いきなりステートマシンフレームワークを使ってコードを記述すると、
おわりに
次回はステートマシンフレームワークの最後として、