Pepperくすぐりアプリを昔のPythonベースからAndroid/Javaへ移植しようとしてた時、非同期的にSayアクションの処理の実装にあたって複数の問題に直面しました。
Futureとは、ある処理を非同期的に包むオブジェクトであり、利用すると非同期的処理を手続き型に表せるようになります。
Pepper APIの公式ドキュメンテーションだと、複数のFutureを一つのメソッドに利用すればアクションをチェーンすることができるという例はたくさんありますが、pepper-tickleの場合だと、同じFutureを複数のメソッドに利用せざるをえなくて、Atomic変数を使用することにしました。
先ほど書いた通り、普段だと一つのFutureを複数のメソッドで呼び出すことは不可能です。この場合、並行的な問題を処理してくれるAtomic変数を利用するのが一番です。
Pepper APIだとAtomic変数の使用に関して一切ドキュメントされていませんが、幸いに、Future変数をAtomicレファレンスにキャストすればFuture変数を複数の非同期メソッドで共有することが可能になります。
慣習的に、あるアクションのためのFutureの定義はこのようになります:
Future<Void> sayFuture;
ですが、このやり方ですとそのFutureを非同期メソッドに呼ぼうとしたらエラーが返されます。使用可能にするには、このようにAtomicレファレンスにキャストします:
AtomicReference<Future<Void>> sayFuture;
pepper-tickleのユースケースだと、一つのSay関数を使用して色んなフレーズを言わせることが必要だったため、実行する度にキャンセルすることができるようにするのが必要でした。
そのため、まずフレーズを引数としてこの関数を書きました。新しいスレッドにSayアクションが実行されるようになっています。
public Future<Void> speakNow(QiContext qiContext, Phrase phrase) {
Future<Say> say = SayBuilder.with(qiContext).withPhrase(phrase).buildAsync();
Future<Void> sayFuture = null;
try {
sayFuture = say.get().async().run();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
return sayFuture;
}
次に、この関数を実際のコードベースに実装します。頭のタッチセンサーと連携します。
headTouchSensor.addOnStateChangedListener(touchState -> {
Log.i("TAG", "Sensor " + (touchState.getTouched() ? "touched" : "released") + " at " + touchState.getTime());
if (touchState.getTouched()) {
String randomHand = whichHand();
gameRunning.set(true);
sayFuture.get().cancel(true);
sayFuture.set(speakNow(qiContext, phraseStartGame));
Log.i("TAG", "Game is now running, correct hand is " + randomHand);
}
});
ここでは、また一つAtomic変数AtomicReference<Boolean> gameRunning
を使ってくすぐりゲームが実行中かどうかを確認します。sayFuture.get().cancel(true);
を実行してまず実行中のSayアクションをキャンセルし、sayFuture.set(speakNow(qiContext, phraseStartGame));
を実行してSayアクションをFutureにパスして非同期で実行します。