2011年8月22日月曜日

JavaScript-GWTでのメッセージング

JavaScript-GWTでのメッセージング


概要

GWTで記述された非同期(同期は制限付きで可能)なメッセージング機構ライブラリ

MessengerGWT の紹介。



構造は単純で、

・HTML5(一応)規格内に含まれている、Window.postMessage を使用。

・GWTMessengerImplementとGWTMessengerInterface のクラスで構成

・相手先の名前と、コマンド(Stringによる識別子)でもって、オブジェクト間でのメッセージングが可能。



DL&ScreenCast

DL:

https://gitorious.org/messengersystem-gwt/


ScreenCast:

    インストール

http://www.screenr.com/Dsgs

    応用

http://www.screenr.com/LSgs



コード

送付側:

messenger = new MessengerGWTImplement("master", this);

messenger.call("anotherObject", "command", messenger.tagValue("タグ", "値"));



受付側:

@Override

public void receiveCenter(String message) {

String exec = messenger.getCommand(message);

JSONObject tagValue = messenger.getJSONObjetFromMessage(message);

}



これだけで、"master" から "anotherObject"へ、メッセージ送信と受信ができる。

この例は非同期。callメソッドの代わりにsCallを使うと同期になる。




使用条件

・InterfaceとしてGWTMessengerInterfaceをimplementsする

implements MessengerGWTInterface




・GWTMessengerInterfaceで定義されているメソッドを@Overrideする。

public void receiveCenter(String message) {


// TODO Auto-generated method stub


}


・MessengerGWTImplement クラスのインスタンスを持つ。

MessengerGWTImplement messenger;




使用のための概略

メッセージングに「参加」するオブジェクトは、すべからく

            MessengerGWTInterface をimplementsしており、

    MessengerGWTImplementクラスのインスタンス messenger を持っている

という前提で動かす。


messengerインスタンスから送り先を指定して、

messagingを送る(call)事で、

もし対象が存在していれば、メッセージ対象の receiveCenter メソッドに届く。




仕組み

送信に、Window.postMessage APIを使用している。


GWTでJSON形式のコンテナを用意し、

相手名、コマンド(メッセージにつける名前)、その他の情報などを

Stringとして渡し、受け取ってからJSON化している。



そのため、値の受け渡しがメインになっており、値の参照などは受け渡せない。

JavaメソッドをJSObject化して送る、などができるのかもしれないが、

Stringからメソッドに変えての実行は嫌な予感がするのでおすすめしない。



極力GWT独自の仕組みを使わない事で、今後他のJSCompilerが出てきても、

対応できるように(あるいは共存できるように!)する目論み。





工夫ポイント

メッセージの送受信が可能な条件として、親子関係が確立されている、という条件を持たせている。

オブジェクトA Bがあったとき、オブジェクトAからBへとメッセージを送る/受け取るには、

A、Bの間に 親子関係 がある必要がある。


A.messener.inputParent("Bのmessengerの名前"); か、 

        B.messener.inputParent("Aのmessengerの名前");


こうすると、

A/Bから他方へとinputParent という”自分、君の子ですよ”コールが行き、

        条件を満たせば子供に加わる事ができる。


こうする事で、メッセージを送る側、受ける側の関係に最低でも親子関係というコードが必要になり、

親子関係部分のコードを見れば誰と通信する可能性があるのか限定できる


この機構はObjective-C版のMessengerSystemと同様。

https://gitorious.org/messengersystem-obj-c



で、親子関係があるので、子から親の呼び出しには

messenger.callParent(command, tagValue);

を使い、メッセージ送付先の名前を省く事ができる。


自分自身は、

messenger.callMyself(command, tagValue);

で呼べる。




搬送物について

tagValue には、色々なものを詰められる。

messenger.callMyself("command"

                            messenger.tagValue("tagString", "String"),    //字列

    messenger.tagValue("tagDouble", 100.0),                   //Double

    messenger.tagValue("tagInt", 100),                           //Int

    messenger.tagValue("tagJSONArray", new JSONArray()),       //JSONArray

    messenger.tagValue("tagJSONObject", new JSONObject()),    //JSONObject

    messenger.tagValue("tagJSONObject[]", JSONObjects...)      //JSONObject[]

);

     などなど。

で、しれっと書いたけれど、

tagValue パラメータは可変長のJSONObjectになっているので、複数のオブジェクトをMap的に

messenger.callMyself(command

messenger.tagValue("tag", "value"),

messenger.tagValue("tag2", "value2")

);

入れることが可能。


また、省略して

messenger.callMyself(command);

と書くことも可能。




受ける側は、

String value = messenger.getValueForTag("tag", message);

でメッセージに含まれるTagValueを取得できる。



ただ、パースしてるだけなので、一個ずつ要素をパースするのはかなりパフォーマンス案配が悪い。

なので、

JSONObject rootObject =  messenger.getJSONObjetFromMessage(message);

で、JSONObjectにしてから、独自に rootObject.get("タグ名"); などが使用可能。


その場合は自身で、Stringなら

String value = rootObject.get("タグ名").isString().stringValue();

などの処理が必要。




既存の実装上の欠点

・受け側の仕組みにGWTのイベントを使用している箇所がある

なんとかして無くしたかったが、GWTからはWindow.postMessageがstaticメソッドでないと

メソッドのロード順の問題を突破できず機能しないため、苦肉の策。

GWT以外であれば、受け側の仕組みを変えるだけで移植可能なはず。




・値の出し元と受け取り先に、参照ポインタ的な接続が無い

これは、値を受け渡す際に、文字列を飛ばしているだけなので、

                その分のメモリをもれなくがっつり、コピーしているのと同じコストを払っていることを意味する。

Objective-Cであればポインタ渡しにできる所を、値をコピーして渡す事しかできない。

なので、あんまりでっかいものを送付すると、まんまコピーになるので、その後GCが大忙しになる。


このあたりは、methodを送る、という事が荒技でできるので、

そのあたりを利用すればいいかもしれない。

メッセージでメソッド自体をinvoker文字列として送りつけ、

相手側でinvokeメソッド化して値ゲット、という荒技。

試した事無い。が、できる、、んじゃないかなーうん。



・早さがまちまち

最速で0.01秒程度で送受信が完了する。ブラウザのメモリ依存のようだが、1sかかった事もある。

                未達ケースは未体験。(まあロストしたら使ってないのだけれど)



        ・大体のIEで動かない

                デスクトップ用の対外のIEはモダンブラウザではない(ぉ ので、問題点かどうかは怪しいが、

         Safari,Chrome,FireFox3x~で確認済み。IE9はWP7 mangoのみ確認。(モバイル対応が優先されてるため。)

                より具体的に範囲を言うと、Google+でもWindow.postMessage APIを使っているため、

                Google+が対応を表明しているブラウザでは動く。



・遅延実行(withDelay)、実行ロック(withLock)が無い

Obj-C版にはある機能である、遅延実行と実行ロックがない。

遅延実行は「~秒後にメッセージ送付」。

普通に積めると思う。面倒で書いていない。


実行ロックは、同じmessengerにロックで指定したcommandが送られた場合、

ロックを解除していき、全てのロックが解除されたタイミングで元のメッセージを発行する、というもの。

値のクロージャなどが(ただ単に面倒で)ネックになって、実装されていない。




・実ブラウザでないと動作しない(= テストがガチテストになる)

GWTの標準で用意されているHTMLUnitはWindow.postMessageメソッド非対応。

のため、テストに使えない。

オプションの設定などを駆使して、実機環境と同じ環境でテストを組む必要がある。

        



・全部非同期で書くとテストがきっつい

まあきっつい。通信と同じ内容になる。




・同期がインチキ

callメソッドなどには、sで始まるsync系のメソッドが併せて用意してある。

現在は残念な事に、GWTのイベントで記述されている。


yieldがJavaScriptにあるのはFFだけなので、それ以外のブラウザで動くような同期が書けない。

そもGWTもその辺知ってて、yieldがそもそもコンパイルされない。

JSNIの中ですら怒られるレベル。


処理Aの途中でmessagingと同時にyieldし、相手にmessageが届き、

その返信を受け取ってyieldから処理Aを再開、という流れがとりたい訳だが、

FFにしかないので頑張らない。


どうせFFはもう、、、


WebWorkerでできるかもしれない。が、うーん、、どうなんだ。。



以上





0 件のコメント:

コメントを投稿

フォロワー