Hatena::ブログ(Diary)

tomoTakaの日記

2015-12-13

Server Event Client Sample

no titleの13日目です。
昨日は[twitter:@skrb]さんのJavaFX と Project Jigsaw - JavaFX in the Boxでした。
明日は[twitter:@Yucchi_jp]さんです。
前回Server Sent Event のサーバー処理についてちょっと実装を試したJava EEエヴァンジェリストによる!EE 8最新動向!に行ってきました! - tomoTakaの日記のですが、クライアントJavaで実装できると紹介されていたので、JavaFXサーバーから受信したレスポンスをアニメーションで表現してみるのに挑戦しました。
ですが、そもそもアニメーションがよくわかっていなくて、、、
2パターンの実装は、ここno titleを参考にしました。

画面

リクエスト送信ボタンをクリックした後、指定したタスク数の「Task0〜5の結果」ラベルが左(サーバー)から右(クライアント)へタスク処理時間で指定した時間の間隔で移動します。
「リクエスト送信(非同期)」ボタンの処理は思ってようにアニメーションしますが、「リスエスト送信(同期)」ボタンの処理は、アニメーションで複数のラベルが同時になってしまいます???

  • 初期表示時

f:id:tomoTaka:20151212153445p:image:w350

  • リスエスト送信(非同期)ボタンクリック後

f:id:tomoTaka:20151212153517p:image:w370

実装

  • 非同期処理

アニメーションの部分はコメントいただいて修正しています。

    @FXML
    private void requestAction2(ActionEvent event){
        listeningProperty.setValue(Boolean.TRUE);
        disableProperty.setValue(Boolean.FALSE);
        int taskCntVal = taskCnt.getValue();
        int taskIntervalVal = taskInterval.getValue();
        String url = "http://localhost:8080/event/" 
                + taskCntVal + "/" + taskIntervalVal;
        Client client = ClientBuilder.newBuilder().register(SseFeature.class).build();
        WebTarget target = client.target(url);
        eventSource = EventSource.target(target).build();
        eventSource.register(inboundEvent ->{
            String data = inboundEvent.readData(String.class);
            int id = Integer.parseInt(inboundEvent.getId());
            Label label = labels[id];
            TranslateTransition t = transitions[id]; // **サーバーより受信したIDで実行するアニメーションを設定
            Platform.runLater(()->{
                label.setText(data); // **ラベルにサーバーより受信した結果を設定
                new Timeline(new KeyFrame(Duration.millis(500), e-> t.play())).play(); / **アニメーション開始
            });
        }, "message-client");
        eventSource.open();
    }

修正後

            Platform.runLater(()->{
                 label.setText(data);
                TranslateTransition t = new TranslateTransition(Duration.millis(300), label);
                t.setFromX(0);
                t.setToX(-200);
                t.play();  // **アニメーション開始
            });
  • 同期処理
    @FXML
    private void requestAction1(ActionEvent event) {
        listeningProperty.setValue(Boolean.TRUE);
        disableProperty.setValue(Boolean.FALSE);
        Arrays.stream(labels).forEach(l -> l.setText(null));
        int taskCntVal = taskCnt.getValue();
        int taskIntervalVal = taskInterval.getValue();
        String url = "http://localhost:8080/event/" 
                + taskCntVal + "/" + taskIntervalVal;
        Client client = ClientBuilder.newBuilder().register(SseFeature.class).build();
        WebTarget target = client.target(url);
        EventInput eventInput = target.request().get(EventInput.class);
        while (!eventInput.isClosed()){
            InboundEvent inboundEvent = eventInput.read();
            if (inboundEvent == null){
                break;
            }
            int id = Integer.parseInt(inboundEvent.getId());
            Label label = labels[id];
            String data = inboundEvent.readData(String.class);
            label.setText(data);                
            TranslateTransition t = transitions[id];
            new Timeline(new KeyFrame(Duration.millis(500), e-> t.play())).play();
        }
    }
  • 初期処理

アニメーションの初期設定など

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        labels = new Label[] {label1, label2, label3, label4, label5};
        Arrays.stream(labels).forEach(l -> l.setText(null));
        transitions = new TranslateTransition[]{
            new TranslateTransition(Duration.millis(500), label1)
           ,new TranslateTransition(Duration.millis(500), label2)
           ,new TranslateTransition(Duration.millis(500), label3)
           ,new TranslateTransition(Duration.millis(500), label4)
           ,new TranslateTransition(Duration.millis(500), label5)
        };
        Arrays.stream(transitions).forEach(t -> {
            t.setFromX(0);
            t.setToX(-200);
        });
        // Buttonの使用制御
        listeningProperty = new SimpleBooleanProperty(false);
        startBtn1.disableProperty().bind(listeningProperty);
        startBtn2.disableProperty().bind(listeningProperty);
        disableProperty = new SimpleBooleanProperty(true);        
        closeBtn.disableProperty().bind(disableProperty);
    }    

実装はここno titleにアップしています。
JavaFXの問題ではないのですが、非同期処理を実行するとサーバーへ何度もリクエスト送信されてしまいます?(汗)
すごく中途半端な状態で申し訳ないですが、アドバイスいただければ幸いです、、、
アニメーション難しいです、まだまだ勉強不足です、、、

aoe-tkaoe-tk 2015/12/15 23:05 再現環境を作った訳では無く、ソースを見ただけですが、気付いた点をコメントしますね。

>「リスエスト送信(同期)」ボタンの処理は、アニメーションで複数のラベルが同時になってしまいます???

まずこちらについてですが、 Animation#play() は非同期で実行され、呼び出した瞬間に即実行される訳ではありません。
アニメーション処理がアプリケーションスレッドをブロックしてはいけないので、次の pulse で実行されるよう一旦キューイングされます。
で、この同期処理のパターンでは、ボタンのイベントハンドラの中で、同期的にレスポンスを受け取るメソッドを呼び出しているので、全てのレスポンスが返ってくるまで、ボタンのイベントハンドラの処理が終わりません。その間ずっとアプリケーションスレッドを占有していることになります。
で、play() メソッドでキューイングされたアニメーション処理は、このボタンのイベントハンドラの処理が終了し、アプリケーションスレッドが解放されたタイミングで一斉に実行されます。なので、同時に実行されたかのように見えるのです。
(全てのレスポンスが返ってきたタイミングで一斉に実行されるはずです)

このように、長時間スレッドをブロックするような処理を呼び出すときはアプリケーションスレッドとは別スレッドで実行する必要があります (そういう意味でも非同期で処理するメソッドを使うのが正解です) 。

続いて、アニメーション処理の記述ですが、TranslateTransition のインスタンスを作って、それをさらにタイムラインに載せて実行させていますが、単純に移動のアニメーションを実行させたいのならば、TranslateTransition#play() を実行するだけで OK です。
Timeline クラスは KeyFrame と組み合わせてより複雑なアニメーションを実現させたいとき (実行間隔を調整したいとか、複数の動きを同時に流したいとか) に使います。
(Timeline も Transition も Animation を継承しています)

tomoTakatomoTaka 2015/12/16 07:45 コメントありがとうございます。早速修正しました。

トラックバック - http://d.hatena.ne.jp/tomoTaka/20151213/1449956731