ぶれすとつーる

だいたいjavascript

canvasのlazyな描画

ご無沙汰してます、nazomikanです。

最近、趣味でフラクタルとかで遊ぶことが多く、フラクタル特有な計算量の多さに日々ブラウザが悲鳴をあげています。

そんなこともあってUI-Blockをさせないようにあれこれやった手法を紹介


まず最初に何も考えずに描画するコード


描画コード (github)

ディレクトリ構成 (github)


結果(ブラウザ落ちるかもしれないので新しいウィンドウで見ることをおすすめします)
http://nazomikan.com/archive/20121007/001/


まぁ当然大きな画像で、かつ1pxごとに計算して色を決めてるわけなので940*940ともなると死ぬほど処理がブロックされてブラウザ固まります。


残念ですね、非常に残念ですね、もはやブラクラです。



昔から大きな処理をするときはasyncにしなさいというのでlazyLoop(非同期なループ処理)を適用させたいと思います。


ちなみにlazyLoopってこんなん

/*
 * asyncなループ
 * @param {number} count ループのカウンター
 * @param {number} ループ回数
 */
function lazyLoop(count, max) {
    // 毎ループごとの処理
    // do something..

    // 終了条件
    if (count === max) {
        return;
    }

    // 非同期な再帰的処理
    setTimeout(function () {
        lazyLoop(++count, max);
    }, 10);
}
lazyLoop(0, 100);


上記のlazyLoopと同様にsetTimeoutを使ってasyncにする

描画コード (github)

ディレクトリ構成 (github)

結果
http://nazomikan.com/archive/20121007/002/





遅い・・・。




まぁ、遅い・・・・。




ノンブロッキングになってブラウザが固まってるような印象はなくなりましたが、これでは描画が完了するまで待ってたらおじいさんになっちゃいます。



で、描画をある程度の単位でlazyにしたいなーと思うわけです。



というわけで描画の範囲をある程度の大きさに分割して描画していく方法


描画処理をキューにして、待ちキューをasyncに実行していきます。


二次元的な連続処理をキューに変換するコードをかいたのがこちら

(function () {
    var queue;

    // 100*100の二次元処理を25*25の処理ブロックに細分化
    queue = createQueue(0, 0, 100, 100, 25, function (xMin, yMin, xMax, yMax) {
        var x, y;
        for (x = xMin; x < xMax; x++) {
            for (y = yMin; y < yMax; y++) {
                // do someThing
            }
        }
    });

    // lazy output
    (function lazyLoop(i) {
        var task = queue[i++];
        task();
        if (i < queue.length) {
            setTimeout(function () {
                lazyLoop(i);
            }, 50);
        }
    }(0));

    function createQueue(startW, startH, endW, endH, delta, callback) {
        var queue = [], startX, startY, endX, endY, xz, yz;
        for (startY = startH, endY = endH; startY < endY; startY) {
            for (startX = startW, endX = endW; startX < endX; startX) {
                // ここはjavascript>1.7ならlet使ったほうがいい
                xz = ((endW < startX + delta) ? endW : startX + delta);
                yz = ((endH < startY + delta) ? endH : startY + delta);

                //タスクを詰めたキューを作成している
                queue.push(function (x, y, xz, yz) {
                    return function () {
                        callback(x, y, xz, yz);
                    };
                }(startX, startY, xz, yz));
                startX = xz;
            }
            startY = yz;
        }

        return queue;

        /* 返り値の中身
        return [
            function () {
                callback(0, 0, 25, 25);
            },
            function () {
                callback(25, 0, 50, 25);
            },
            function () {
                callback(50, 0, 75, 25);
            },
            function () {
                callback(75, 0, 100, 25);
            },
            function () {
                callback(0, 25, 25, 50);
            },
            function () {
                callback(25, 25, 50, 50);
            },
            function () {
                callback(50, 25, 75, 50);
            },
            function () {
                callback(75, 25, 100, 50);
            },
            function () {
                callback(0, 50, 25, 75);
            },
            function () {
                callback(25, 50, 50, 75);
            },
            function () {
                callback(50, 50, 75, 75);
            },
            function () {
                callback(75, 50, 100, 75);
            },
            function () {
                callback(0, 75, 25, 100);
            },
            function () {
                callback(25, 75, 50, 100);
            },
            function () {
                callback(50, 75, 75, 100);
            },
            function () {
                callback(75, 75, 100, 100);
            }
        ];
        */
    }
}());

これを応用して描画を細分化します。


描画コード (github)


ディレクトリ構成 (github)

結果
http://nazomikan.com/archive/20121007/003/


処理が100*100のブロックずつ描画されるようになったため、大規模なブロッキングも発生せずにサクサクと描画ができるようになりました。


こんな感じで、大きな描画などを発生させるときはlazyな描画でUI-blockから逃げましょう。


これは別にcsjsのcanvas描画のみに使えるというわけではなくbsjs(both side javascript)で二次元配列に再帰的処理を行いたいときとかにも使えます。


むしろssjsでのケースの方が再帰処理してる間、スレッドをブロックしてしまうと別ユーザーからのリクエストさばけないとかやばいことになりやすいのでこういうのが便利になってきます。


タスク分解されてるとスケールもしやすいですしね。





おわり


リポジトリ (github)







はてな入りたい!