[C++] イベントドリブンなステートマシンライブラリを作った



イベントドリブンなステートマシンライブラリを作った。
https://github.com/maueki/seedsm

特徴は以下の通り

* C++11以降
* libev上で動作
* １ファイルをインクルードするだけ
* [Qt5のステートマシン](http://doc.qt.io/qt-5/statemachine-api.html)ライクなイベントドリブンステートマシン
* 最低１クラス定義すれば使用可能

# 使用例

使用例としてQt5ステートマシンのドキュメントにある以下のステートマシンを実装してみる。

![statemachine](http://doc.qt.io/qt-5/images/statemachine-button.png)

## ポリシークラスの作成

まずはポリシークラスを作成。
状態を`STATE`で、イベントを`EVENT`で定義する。
イベントは`DEFINE_EVNET()`マクロを呼んでおく必要もある。

```cpp
struct Policy {
    enum STATE { s1, s2, s3 };
    enum EVENT { clicked };
};

DEFINE_EVENT(Policy::clicked);
```

内部でログ出力に使うため`to_string()`を定義しておく

```cpp
std::string to_string(Policy::STATE st) {
    return "s" + std::to_string(static_cast<int>(st) + 1);
}

std::string to_string(Policy::EVENT ev) {
    return "clicked";
}
```

## 状態と遷移の作成

ステートマシンクラスを作成し、状態と遷移を定義する。

```cpp
using ST = Policy::STATE;
using EV = Policy::EVENT;

int main(int argc, char *argv[]) {
    ev::dynamic_loop main_loop;

    seedsm::StateMachine<Policy> sm("Root", main_loop);

    // 状態作成                                                                                                                                                                                                    
    sm.create_states({ST::s1, ST::s2, ST::s3});

    // 遷移作成                                                                                                                                                                                                    
    sm.add_transition<EV::clicked>(ST::s1, ST::s2);
    sm.add_transition<EV::clicked>(ST::s2, ST::s3);
    sm.add_transition<EV::clicked>(ST::s3, ST::s1);

// 以下後述
```

これで図と同じステートマシンが定義できた。

## 実行

状態遷移を開始して別スレッドから１秒毎にclickedイベントを送信する

```cpp

    sm.start();

    auto f = std::async(std::launch::async, [&sm] {
            using namespace std::literals;

            for (int i=0; i<5; ++i) {
                std::this_thread::sleep_for(1s);
                sm.send<EV::clicked>();                                                                                                                                                                                                       
            }
        });

    main_loop.run(0);

    return 0;
}
```

ビルドして実行してみる

```bash
$ g++ -o seedsm_sample main.cpp -lev -lpthread -std=gnu++14
$ ./seedsm_sample
initialize                                                 
enter state: Root                                          
enter state: s1                                            
exit state: s1                                             
enter state: s2                                            
exit state: s2                                             
enter state: s3                                            
exit state: s3                                             
enter state: s1                                            
exit state: s1                                             
enter state: s2 
exit state: s2
enter state: s3
# press Ctrl+C
```

コードの全体は以下に置いた

https://gist.github.com/maueki/aa7645152c0a46dcfee74ed4a551e1c8

# その他の機能

* `on_state_entered()`, `on_state_exited()`, `on_transition()`で遷移に対するコールバック処理の登録
* `send_high()`で高優先イベント送信
* `set_parallel(true)`で子状態を並行状態にする
* イベント定義に`DEFINE_EVENT_WITH_DATA()`マクロを使うことでイベント送信時にデータを付加