JSでObject Poolを頻繁に利用するのでまとめた

はじめに

私はフロントエンジニアですが、無限カルーセル(*1)や大量のパーティクルなどの見た目部分を作ることが少なくありません。
言うまでもなく、無限カルーセルやパーティクルのオブジェクトを無限にnewしていたのでは、メモリに負荷をかけて、ユーザーに不快な思いを与えてしまいます。

Object Pool

そこで大量に作る予定のものは、あらかじめ作っておき使い回す、
Object Pool(オブジェクトプール)という仕組みを作っておきます。

イメージとしては、
箱(プール)の中にあらかじめ利用するものを入れておき、
使う時になったら箱から取り出し、
使い終わったら箱に戻してあげる
そんなシンプルな機構です。

Javascript

Object PoolをJavascriptで書いてみました。

export class ObjectPool {
    
    constructor(onRequire, onDestroy, initCount, progressCount) {
        this.onRequire = onRequire;
        this.onDestroy = onDestroy;
        this.initCount = initCount;
        this.progressCount = progressCount;
        this.index = 0;
        this.items = [];

        this.init();
    }

    init() {
        for (let i = 0; i < this.initCount; i += 1) {
            this.items.push(this.onRequire());
        }
        this.index = this.initCount;
    }

    clear() {
        for (let i = 0; i < this.items.length; i += 1) {
            this.onDestroy(this.items.shift());
        }
        this.items = [];
    }

    getItem() {
        if (this.index > 0) {
            return this.items[--this.index];
        }
        else {
            for (let i = 0; i < this.progressCount; i += 1) {
                this.items.unshift(this.onRequire());
            }
            this.index = this.progressCount;
            return this.getItem();
        }
    }

    returnItem(item) {
        this.items[this.index++] = item;
    }

    destroy() {
        this.onRequire = null;
        this.onDestroy = null;
        this.index = 0;
        this.items = null;
    }

    reduce() {
        for (let i = 0; i < this.index; i += 1) {
            this.onDestroy(this.items.shift());
        }
        this.index = 0;
    }
}

onRequireでは、箱(プール)に入れるアイテムをインスタンス化する処理を、 onDestroyでは、箱(プール)に戻すアイテムにする初期化処理のコールバック関数を渡します。
こうしておくことで、プールで利用するクラスに依存しないので汎用性が高まります。

initCountは最初に箱に入れるアイテムの数を、
progressCountは箱が空になった際に生み出すアイテムの数を指定します。
indexは利用できるアイテムのインデックスを位置を表していています。

*Typescriptだったらジェネリクス使えるんでもっといい感じに書ける

利用例

下記サイトの右から左に流れる無限カルーセル(1*)では、
あらかじめ箱のオブジェクトは、箱の数だけ生成しておき(initCount)、 もっとも左にある箱が画面から消えたらプールにアイテムを返し(ObjectPool.returnItem)、 もっとも右にある箱が箱一個分画面の内側に入ったらプールにアイテムをもらう(ObjectPool.getItem) といった仕組みでできています。 bodoge.blue-puddle.com

余談

意外にもこういった無限カルーセル(*1)は、 keyframesアニメーションで背景画像をリピートさせたり、 リピートできる位置に到達したら位置をリセットするといった仕組みでできているものが多いようでした。

私も最初のうちはそれでも良かったのですが、スピードを減速させたり、タッチスクロールに対応させなければいけないケースなどでは、
Object Poolを利用するものが汎用性が高く良いという結論に達しました。(というか教わりました)

おわり

大量に生成する系のUIや表現(テーブルビューやパーティクル)などに出会ったら、
迷うことなく利用するお便利クラスの紹介というか振り返りでした。

*1 無限カルーセルと私が呼んでいるものは、下記サイトで箱が動いている部分のUIを指しています。 bodoge.blue-puddle.com