Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What is going on with this article?
@yminabe

Pepper SDK実践(1) Futureを使いこなそう!

More than 1 year has passed since last update.

今回はFutureについて少しだけ解説しておこうと思います。「Pepper SDK入門(8) アクションの連結」にある内容については、理解している前提で書いていきます。

Future自体は使わなくてもPepper向けのアプリは作れるけど、いろんな割込みとかを考慮して製品としてのアプリを作ろうとすると結局Futureのような機能を自前で作ることになるので、素直にFutureを使ってアプリを作るのがお勧めです。

1.とりあえず発話

「はろーペッパーえすでぃーけー」と発話させる方法です。

Say say = SayBuilder.with(qiContext).withText("はろーペッパーえすでぃーけー").build();
say.run();

同期実行なのでUIスレッドでやるとエラーが発生するので注意が必要なヤツです。これをFutureを使って非同期にしてみます。

Future<Void> futureSay = SayBuilder.with(qiContext).withText("はろーペッパーえすでぃーけー").buildAsync() // Sayアクションを非同期でビルド
        .andThenCompose(new Function<Say, Future<Void>>() {
            @Override
            public Future<Void> execute(Say say) throws Throwable {  // 引数としてビルドされたSayアクションのインスタンスが渡される
                return say.async().run();   // 非同期で実行
            }
        });

ぱっと見行数も増えて難しくなっちゃいましたね。でも、非同期であればUIスレッドから呼べますし、アクションを途中でキャンセルできますし、ものにもよりますがアクションを複数同時に実行できて非常に助かります。実際のアプリ開発のシーンで同期実行を使うケースはたぶんありません。あと、andThenで繋いでいるのは、Sayアクションのビルドが失敗、キャンセルされたら、Sayアクションの実行も当然できないので後続の処理を呼ばれなくするためです。

2.一応、アニメーションもやっとこう

今度はアニメーションさせる方法です。
デフォでついてる犬のモノマネをするアニメーション、dog_a001というqianimファイルを「R.raw.dog_a001」にインポートします。
で、イメージしやすいように一旦、同期実行でコードを書くとこんな感じ。

Animation animation = AnimationBuilder.with(qiContext).withResources(R.raw.dog_a001).build();
Animate animate = AnimateBuilder.with(qiContext).withAnimation(animation).build();
animate.run();

アニメーションはAnimationリソースをビルドして、Aniamteアクションをビルドして実行と3ステップになります。これをFutureを使って非同期にしてみます。

Future<Void> futureAnimate =  AnimationBuilder.with(qiContext).withResources(R.raw.dog_a001).buildAsync()
        .andThenCompose(new Function<Animation, Future<Animate>>() {
            @Override
            public Future<Animate> execute(Animation animation) throws Throwable {
                return AnimateBuilder.with(qiContext).withAnimation(animation).buildAsync();
            }
        }).andThenCompose(new Function<Animate, Future<Void>>() {
            @Override
            public Future<Void> execute(Animate animate) throws Throwable {
                return animate.async().run();
            }
        });

やっぱり同期のほうがシンプルに見えますね、、lambdaを使えば見栄えはだいぶ良くなりますので、ここだけlambdaでコードを綺麗にしたものも載せておきます。

Future<Void> futureAnimate =  AnimationBuilder.with(qiContext).withResources(R.raw.dog_a001).buildAsync()
        .andThenCompose(animation1 -> AnimateBuilder.with(qiContext).withAnimation(animation1).buildAsync())
        .andThenCompose(animate1 -> animate1.async().run());

一気に見通しが良くなりました。見やすさとバグの少なさは相関がある気もするので、こういったコードの見栄えは割と重要だと考えています。

3.そろそろ本題、Futureの待ち合わせ

Pepper向けのアプリを作っていると発話とアニメーションが両方終わったら次の何かをさせたいようなシーンが良くあります。
そんな時はFuture.waitAllを使うとことが簡単です。

Future<Void> futureBoth = Future.waitAll(futureSay, futureAnimate);
futureBoth.thenConsume(new Consumer<Future<Void>>() {
    @Override
    public void consume(Future<Void> voidFuture) throws Throwable {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                getSupportFragmentManager().beginTransaction()
                        .replace(R.id.main_content, NankanoFragment.newInstance())
                        .commit();
            }
        });
    }
});

前の説明で作っておいたfutureSayとfutureAnimateをFuture.waitAllを使って連結してfutureBothを作っています。さらにfutureBothに対して、thenConsumeを使ってFragmentを操作するような処理を入れてみました。Choregraphe的なイメージだとこんな感じですね。

レイヤー 1.png

これで、発話とアニメーションが両方終了した後に、thenConsumeで渡したConsumerインスタンスのconsumeメソッドが呼ばれ、Fragmentの変更などが行えます。途中でボタン操作などがあり発話やアニメーションをキャンセルしたい場合は、futureBothのrequestCancellation()でキャンセルすれば発話とアニメーションの両方がキャンセルされます。今回は、Fragmentの変更処理をthenConsumeで繋いでいるので、キャンセルされた場合でもconsumeメソッドが呼ばれ、Fragmentの変更処理は実行されます。

ほとんど余談になりますが、future内はすべてバックグラウンドスレッドで呼ばれますので、画面操作を行いたい場合などにはUIスレッド上で実行しなければならないことを忘れないでください。ここではrunOnUiThreadにRunnableインスタンを渡して実現していますが、Qi.onUiThreadを挟むことでUIスレッド上でコールバックさせることもできます。

Future<Void> futureBoth = Future.waitAll(futureSay, futureAnimate);
futureBoth.thenConsume(Qi.onUiThread(new Consumer<Future<Void>>() {
    @Override
    public void consume(Future<Void> voidFuture) throws Throwable {
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.main_content, NankanoFragment.newInstance())
                .commit();
    }
}));

コードはきれいになりますが、consumeメソッドは必ずバックグラウンドスレッドで呼ばれるというシンプルさを失うので注意も必要です。

以上、Futureの使い方についてざざざーーっとまとめてみました。

1マイクロでも役に立った方は、ぜひコメントやいいね!をよろしくお願いします!
あと、面白いお仕事にお声がけくださーい笑

1
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
happyhack
スマホやWebはもちろん、ロボット、IOT、AIなどの技術を駆使して、ユーザに新しい体験を届けることをモチベーションとする開発会社です。特にPepper関連の実績が豊富で、公式アプリを含む多数のロボアプリ開発を行ってまいりました。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
1
Help us understand the problem. What is going on with this article?