Subscribe to Eventボックスにちゃんとunsubscribeさせる

先日イベントで「Subscribe to EventボックスがonStop入力後もイベントを出力してしまう」みたいな指摘があったのですが、[Record Videoを直してみた](http://qiita.com/UMasaU/items/d8e0861d603179661008)みたいな投稿にもある通り、Choregrapheに添付されたボックスライブラリ群は簡単にAPIの機能などにアクセスできる一方、少々扱いが面倒な部分があります。

今回は、Subscribe to Eventボックスの現在の動きの確認と、所望の動きをする(onStopされたらunsubscribeされる)ボックスへとどのように修正すべきかを個人的に考えてみたのでそのメモ。


# Subscribe to Eventボックスの現在の動き

動作確認用ビヘイビアを作りました。動作確認は Choregraphe 2.3.1.25 とバーチャルロボットでおこなっています。
また、今回作成したChoregrapheプロジェクトは、GitHub https://github.com/yacchin1205/pepper-subscribe-to-event に置いてあります。

動作確認用プロジェクトは **test** で、ビヘイビアの構成はこんな感じ。

![root.png](https://qiita-image-store.s3.amazonaws.com/0/28770/a5163a77-6ba0-7bd2-2be8-dfd0226d11c9.png)

Testボックス内でSubscribe to Eventボックスを実行しています。

![Test.png](https://qiita-image-store.s3.amazonaws.com/0/28770/3f8fac53-e333-09a0-2b83-fbddefc7b8fb.png)

Subscribe to Eventボックスはテスト用のイベント `TestKey1` を購読するよう設定しています。
Raise Eventボックスにより、以下の3つのタイミングで `TestKey1` のイベントを発火し、それぞれにSubscribe to Eventボックスがどう反応するかを見ています。

なお、Subscribe to EventボックスがどのRaise Eventボックスに反応したかをわかりやすくするため、それぞれタイミングによりイベントに与える値を変えています。

1. Subscribe to EventボックスにonStartが入力された後 - Raise Eventにより`Expected` を発火
2. Subscribe to EventボックスにonStopを入力された後(停止状態) - `Unexpected` を発火
3. Subscribe to Eventボックスの親ボックスが停止し、ボックスがアンロードされた後 - `Unload` を発火

挙動の確認はログビューアにより行っています。Subscribe to Eventボックスには以下のような接続、修正をおこなっています。

- Subscribe to EventボックスにLogボックスを接続(Message textは `Test`)
- Subscribe to Eventボックスの`onEventCallback` に以下の行を追加
   
    ```python
    def onEventCallback(self, key, value, message):
        # テストのため追加-ここから
        self.logger.info('Called: key=%s value=%s' % (key, value))
        # テストのため追加-ここまで
        self.onEvent(value)
    ```

このように、動作確認した内容もビヘイビアとして残しておくことで、テストした内容を残しておくことができ、「以前はどんな動きしてたんだっけ」といったことを確認しやすくなります。

## 出力されたログ

バーチャルロボットで実行したところ、以下のようなログが得られました。

```
[INFO ] behavior.box :onInput_message:26 ...__Test_9__Log_1: Test: Expected
[INFO ] behavior.box :onEventCallback:21 ...__Test_9__SubscribetoEvent_7: Called: key=TestKey1 value=Expected
[INFO ] behavior.box :onInput_message:26 ...__Test_9__Log_1: Test: Unexpected
[INFO ] behavior.box :onEventCallback:21 ...__Test_9__SubscribetoEvent_7: Called: key=TestKey1 value=Unexpected
[INFO ] behavior.box :onUnload:10 ...__Test_9__Log_1: Unloaded: Test box
[INFO ] behavior.box :onEventCallback:21 ...__Test_9__SubscribetoEvent_7: Called: key=TestKey1 value=Unloaded
```

簡単にまとめるとこんな感じ。

| 状況       | Subscribe to Eventに続くボックスの反応 | onEventCallbackの反応
|:-----------|:--------------------------|:---------------------| 
| onStartが入力された後 | あり | あり |
| onStopが入力された後  | あり | あり |
| アンロードされた後    | なし | あり |

Testボックス内のLogボックスはアンロードされていることをログから確認しているので、Subscribe to Eventボックスがアンロードされなかったということは考えにくそう。
ということは、アンロードされた後も `onEventCallback` が呼ばれているような・・・

## 現在のSubscribe to Eventボックスの停止・アンロード処理における課題

以上からわかった挙動と、Subscribe to Eventボックスのコードを考慮すると、以下の2つの課題がありそうです。

1. Subscribe to EventボックスはonStop入力がされても、onStopped出力を発火するだけで、特に購読の停止処理をおこなうことはない
2. Subscribe to Eventボックスはアンロードされても購読の停止処理をおこなわず、onEventCallbackの呼び出し自体は継続される

実際の利用上は、Subscribe to Eventボックスがアンロードされるのに合わせて、onEvent出力の出力先のボックスもアンロードされるため、購読自体はアンロードにともない停止されたように見える。ということなのかなと思います。

# Subscribe to Eventボックスの修正例

Subscribe to Eventボックスは、現在の実装でも、回避策(適切に他のボックスをアンロードする)をとればまあ利用できないわけではないわけですが、アプリ中での予期しない挙動を防ぐためにも、単純に、

- onStart入力がされると、イベント発生に応じてonEvent出力がされる(開始状態)
- onStop入力がされると、イベントが発生してもonEvent出力がなされなくなる(停止状態)

みたいにしておきたいよね、となるのが開発者の心情です。

そこで、以下のような修正をすることにします。

1. onStop入力がされた際にアンロード相当の処理を実行する

    ```python
    def onInput_onStop(self):
        # 修正: onStop時にonUnloadを呼び出す
        self.onUnload()
        self.onStopped()    
    ```

2. onUnload時に `ALMemory.unsubscribeToEvent()` を実行する

    ```python
    def onUnload(self):
        # 修正: unsubscribeToEventを実行する
        if self.memory is not None:
            self.memory.unsubscribeToEvent(self.getParameter("key"), self.getName())
        self.memory = None
    ```

まあ、新規作成直後のPythonボックスの `onStop` の記述にも `self.onUnload() #it is recommended to reuse the clean-up as the box is stopped` って書いてあることだし・・・ということと、ハンドラを追加したら、適切に削除しましょう。ということで。

## 修正例の動作確認

testプロジェクトのうち、Subscribe to Eventボックスを上記のような修正をしたバージョンに置き換えたのが、GitHubにおいたコード中の **fixed** プロジェクトです。

これを実行すると、以下のように期待した動作(Unexpected, Unloadイベントは無視)をするようになります。

```
[INFO ] behavior.box :onEventCallback:26 ...__Test_9__SubscribetoEvent_7: Called: key=TestKey1 value=Expected
[INFO ] behavior.box :onInput_message:26 ...__Test_9__Log_1: Test: Expected
[INFO ] behavior.box :onUnload:10 ...__Test_9__Log_1: Unloaded: Test box
```

このように修正したボックスを、 [独自ボックスライブラリの作成と管理](http://qiita.com/Atelier-Akihabara/items/db907ed319b8af0984db) などを参考に管理していくと、よりボックスでのアプリ作成が楽になるのでは・・・と思います。

ちなみに、このようなテストや、問題発生時の修正は、独自ボックスでも同じように必要になってくる作業です。他にも個人的におこなっているトライアルがあるので、キリがいいところで投稿していこうと考え中・・・