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;
        }

デモ


速度はいい感じになったので、ぼちぼち中身を実装していきます。