canvasをより速く(Flashもサポート)-Take3
latest log の続きです
Flashモードのレンダリング速度をさらに改善しました。fpsが半分ぐらいに落ち込む問題も解決できたようです。
なにをしたか
ExternalInterface のjs側のコードを要約し CallFunction() だけにする事で、最適化していた(つもりだった)のですが、
- ExternalInterface は AS側の戻り値を js側に戻す際に try-catch が入る
- 戻り値を返す都合上、同期処理になっている
といった点がネックになっているようなので、速度優先なら flashVars を直接叩き、それ以外なら ExternalInterface を使う方法に切り替えました。
修正前
ExternalInterface(CallFunction) だけで通信しています。
var _stack = []; // Flashに送信するコマンド文字列を格納している var _lockState = 0; // ctx.lock(), ctx.unlock() 用 var _readyState = 0; // 初期化済で1 function send(fg) { // @param String: fragment, "{COMMAND}\t{ARG1}\t..." if (fg) { this._stock.push(fg); } if (!this._lockState && this._readyState) { this._view.CallFunction(send._prefix + this._stock.join("\t") + send._suffix); this._stock = []; // clear } } send._prefix = '<invoke name="send" returntype="javascript"><arguments><string>'; send._suffix = '</string></arguments></invoke>';
Flash側のコードです。
public function Canvas() { ExternalInterface.addCallback("send", recv); } private function recv(msg:String):void { var ary:Array = msg.split("\t"); var i:int = -1; var iz:int = ary.length; var v:String; while (++i < iz) { switch (ary[i]) { // {COMMAND} case "in": init(+ary[++i], +ary[++i]); break; }
修正後
flashVars に連続でコマンドを設定すると、Flash側には最後のコマンドしか転送されないため、setTimeout(0)でコマンドを溜め、非同期に送信するようにしています。
var _stack = []; var _lockState = 0; var _readyState = 0; // js側初期化済で1, Flash側初期化済で2 function send(fg) { // @param String: fragment, "{COMMAND}\t{ARG1}\t..." if (fg) { this._stock.push(fg); } if (!this._lockState && this._readyState) { if (this._readyState === 1) { if (this._view) { this._view.CallFunction(send._prefix + "in\t" + this.canvas.width + "\t" + this.canvas.height + send._suffix); this._readyState = 2; } } if (this._readyState === 2) { var ctx = this; // <param name="flashVars" param="t={time}&c={cmd}" /> setTimeout(function() { if (ctx._stock.length) { ctx._view.flashVars = "t=" + (+new Date) + "&c=" + ctx._stock.join("\t"); ctx._stock = []; // clear } }, 0); } } }
Flash側に、onEnterFrame イベントハンドラを追加しています。
以前送信したコマンドを再度実行しないように、ガード「js側でタイムスタンプを渡し、AS側で最後に処理したタイムスタンプを比較。タイムスタンプが同じなら何もしない」を入れています。
public function Canvas() { ExternalInterface.addCallback("send", recv); } // このメソッドを追加 private function onEnterFrame(evt:Event):void { var cmd:Object = stage.loaderInfo.parameters; if (cmd.t && lastRecvTime !== cmd.t) { // タイムスタンプを比較 //trace(cmd.t + ":" + cmd.c); lastRecvTime = cmd.t; // 更新 recv(cmd.c); } } private function recv(msg:String):void { var ary:Array = msg.split("\t"); var i:int = -1; var iz:int = ary.length; var v:String; while (++i < iz) { switch (ary[i]) { // {COMMAND} case "in": init(+ary[++i], +ary[++i]); addEventListener("enterFrame", onEnterFrame); // ここも追加 break; }
デモ
- AutoDetect
- Silverlight → Flash → VML 順にバックエンドを探索
- Flash 優先
- Flash → Silverlight → VML 順にバックエンドを探索
- Silverlight 優先
- Silverlight → Flash → VML 順にバックエンドを探索
- VML のみ
- ExplorerCanvas
速度はいい感じになったので、ぼちぼち中身を実装していきます。