TypeScript zipファイルとIndexedDBを使ったインストール(ファイルキャッシュ)システムの作成

ブラウザで動作するゲームなどが利用するデータ(画像、ポリゴンデータなど)をIndexedDBに保存する(インストール)することで、読み込み処理の高速化、サーバー負荷軽減が行えます。

使い方

zipファイル内のファイルをIndexedDBに保存、fetchなどサーバーにアクセスする前にDBを検索、あればそのデータを使いなければサーバーから取得
zipが更新されていればDBの再構築

    const nameDB = "ZipFileCacheSample";
    // db削除
    // IdxDBFileCache.deleteDatabase(nameDB)
    
    // DBオープン なければ作成
    // zipファイルが更新されていたら再構築
    let fileCache = await IdxDBFileCache.openDB(nameDB, "./test.zip");

    // キャッシュにあればIndexedBDから取得
    // なければfetchで取得
    let blob = await fileCache.loadFile("./test/test.dat");
    console.log(blob);

IndexedDB?

ブラウザが管理するオブジェクト指向データベース
ローカルな記憶領域(装置)にデータが保存されます。
詳しくは検索で

ソースコード(有料)

サイズが大きく更新頻度が低いデータ(画像、ポリゴンデータとか)を利用するゲームでの利用が効果的です
ただし
実際に使用するには、エラー処理やデータの整合性チェックなどが必要になります。基本的な機能のみの実装です。

利用API
IndexedDB
DecompressionStream
TextDecoder

動作確認
Windows10 Edge  (2023/11/9)

ソースコードの一部
実装部分を除いたソースコードです

export class IdxDBFileCache{
    iddb : IDBDatabase;
    readonly nameStore : string;
    topURL : string;
    private constructor(iddb : IDBDatabase, topURL : string, nameStore : string){
        this.topURL = topURL;
        this.iddb = iddb;
        this.nameStore = nameStore;
    }

    // DBオープン なければ作成
    // zipファイルが更新されていたら再構築
    static async openDB(nameDB : string, zipURL : string){
        // remove ".zip"
        let topURL = zipURL.slice(0,-4);

        // zipファイルの更新日時
        let resp = await fetch(zipURL,{method:"HEAD"});
        let dateStr = resp.headers.get("Last-Modified");
        if(!dateStr){
            throw "IdxDBFileCache zipURL " + zipURL;
        }

        // 更新日時(ms)をバージョンとして使用
        let date = new Date(dateStr);
        const version = date.getTime();
        const nameStore = "FileCache";

        let rebuild = false;
        let open = indexedDB.open(nameDB, version);
        let iddb = await new Promise<IDBDatabase>((rs,rj)=>{
            open.onupgradeneeded = (event : IDBVersionChangeEvent)=>{
                let db = open.result;
                if(event.oldVersion != 0 && event.oldVersion != event.newVersion){
                    // 削除
                    // 効率化:更新があったファイルだけ更新とか
                    db.deleteObjectStore(nameStore);
                }
                console.log("db build");
                db.createObjectStore(nameStore);
                rebuild = true;
            };

            open.onsuccess = ()=>{
                console.log("db open success " + nameDB);
                rs(open.result);
            };

            open.onerror = (event)=>{
                rj(event);
            };
        });

        // ファイルキャッシュDB構築
        if(rebuild){
            // zipファイルをメモリに
            // 大きい場合は分割、ファイルの部分アクセスなどで省メモリ化
            let blobZip = await (await fetch(zipURL)).blob();
           
            // ファイル情報取得
            let files = await parsePkZip(blobZip);
           
            // ファイルの登録
            // DB容量節約 : 圧縮した状態で保存、読み込み時に展開 
            for(let file of files){
                let key = topURL+"/"+file.fileName;
                let buff = blobZip.slice(file.filePtr, file.filePtr+file.compSize);
                //console.log(key)
                IdxDBFileCache.putObj(iddb, nameStore, key, {header:file, data : buff});
            }
        }
        return new IdxDBFileCache(iddb, topURL, nameStore);
    }

    // ファイル読み込み
    // キャッシュにあればIndexedBDから取得
    // なければfetchで取得
    async loadFile(url : string){
        let obj = await IdxDBFileCache.getObj(this.iddb, this.nameStore, url);
        if(obj){
            if(obj.header.compType == COMP_DEFLATE){
                let blob = await decompressPkZipDeflate(obj.data);
                let crc32 = new CRC32().crc32( new Uint8Array( await blob.arrayBuffer()));
                if(crc32 != obj.header.crc32){
                    throw "IdxDBFileCache loadFile crc";
                }
                return blob;
            }else{
                return obj.data;
            }
        }else{
            //console.log("fetch " + url)
            let resp = await fetch(url);
            if(!resp.ok || resp.status != 200){
                return undefined;
            }
            return resp.blob();
        }
    }

}


ソースコード(フル)

ここから先は

12,748字

¥ 300

この記事が役に立ったという方は、サポートお願いします。今後の製作の励みになります。