HTML5ゲームのできるまで ~起動の高速化(2)~

image1.jpg

 

こんにちは。システム本部プラットフォーム部の坊野です。

 

今回は前回お話したHTML5ゲーム起動時の高速化手法の詳細をサンプルプログラムを用いて説明しようと思います。

 

前回の振り返り

前回の記事で起動の高速化のためにキャッシュを作成したと書きましたが、一般的にロードキャッシュの実装は通常のロードの実装に比べてかなり複雑になります。今回はこのロードキャッシュの実装をサンプルアプリケーションを用いて説明しようと思います。

なお、このアプリケーションはあくまでもサンプルであり、ロードキャッシュが本来実装すべき機能のうち、キャッシュの削除などの機能は実装されていません。

 

サンプルアプリケーションの構成

まずこのサンプルアプリケーションの構成を説明します。このサンプルプログラムは1個のHTMLファイル1個のJavaScriptファイルで構成されています。とはいえ、このHTMLファイルは画像読み込みや操作用のUIのみで構成されており、画像のキャッシュや画像処理はすべてJavaScriptファイルの中で実装されています。ですから、以後はJavaScriptファイルの内部を中心に説明します。

 

JavaScriptファイルとCoding Style

さて、このJavaScriptファイルですが、多くの読者の方々にとってこの書き方はあまりなじみのないものだと思われます。この書き方はGoogle JavaScript Styleと呼ばれるもので、Closure LibraryなどGoogleのJavaScriptアプリケーションを書く際に用いられているものです。

ご存知のとおりGoogleは様々なJavaScriptアプリケーションを開発しています。これらの開発の際にJavaScriptの書き方についても読みやすさや開発効率、速度など複数の観点から検討し、その結果をCoding Styleとして提供しています。

つまり、JavaScriptの内部について深い理解をもたなくても高品質なコードを書けるように書き方を決めているということです。このCoding Styleの詳細の説明は省略しますが、時間があれば一読してなぜこのような書き方をするのか考えていただければと思います。

(なお、既存のJavaScriptライブラリの中にはこの書き方との相性があまりよくないものがありますし、それらのライブラリを用いたほうが高品質なアプリケーションを作成できる場合も多いので、みなさんに強制するつもりはありません。)

 

アプリケーションの起動

サンプルアプリケーションの説明に話を戻しますと、このアプリケーションは図1のようにHTMLページとJavaScriptファイルの読み込み後、HTMLのUIとJavaScriptアプリケーションとの対応関係を設定して、画像の読み込みを開始します。

図1 アプリケーションの起動

図1 アプリケーションの起動

この際test.Applicationクラスtest.Loaderクラスのインスタンスを作成するのですが、今回必要なのは図2で作成されるtest.Loaderクラスのみなので以後はtest.Loaderクラスについて説明していきます。

image2.png

図2 test.Loaderインスタンス作成

 

画像のロード

まずtest.Loaderクラスがどのようにして画像のロードを行っているのか説明します。このtest.LoaderクラスはIndexed Database APIを用いて実装されたキャッシュに指定されたURLの画像が存在するかどうかをチェックし、存在すればその画像データをData URIで返します。また、画像が存在しない場合はXMLHttpRequest Level 2を用いて画像のバイナリデータを取得し、それをData URIに変換して返すと同時にキャッシュに書き込みます。

残念ながら、このような処理をJavaScriptで実装する場合、複数のコールバック関数を用いる必要があり、可読性がよくないという問題があります。このため、今回はまず図3の状態遷移図を用いて、おおまかな動作を説明します。(この状態遷移図はあくまで説明用のものであり、厳密な意味での状態遷移図とは異なる所があります。)

図3 画像のロード処理

図3 画像のロード処理

この状態遷移図とtest.Loaderクラスとの関係を説明しますと、表1のように図3の各状態はtest.Loaderクラスのメソッド (状態関数) に対応しています。また、これらの各メソッドはその入力に応じて必要な処理を行い、次の状態に遷移するという構造になります。

表1 状態およびJavaScript関数

表1 状態およびJavaScript関数

たとえばIndexed Database API対応ブラウザで最初にキャッシュにない画像をロードした場合には図4の赤線のように状態遷移してサーバーから画像をロードしてキャッシュに書き込みます。

図4 Indexed Database API対応ブラウザで初回ロード時

図4 Indexed Database API対応ブラウザで初回ロード時

またIndexed Database API対応ブラウザでキャッシュされている画像をロードする際には図5の赤線のように状態遷移してキャッシュからのロードを行います。

図5 Indexed Database API対応ブラウザでキャッシュ済画像読込

図5 Indexed Database API対応ブラウザでキャッシュ済画像読込

このように状態遷移図を用いて複雑な処理を状態関数と処理の組み合わせに分解することにより、個々の処理を簡潔にすることができます。以下の各節で個々の状態関数についてどのような処理を行っているのか説明します。

 

ロード開始

まずtest.Loaderクラスはロード開始時に以下の処理をおこなっています。

  1. キャッシュに用いているIndexed Database APIが利用可能かどうか

  2. キャッシュの初期化が終了しているかどうか

また、これらの処理結果に応じて以下の処理をおこないます。

  • キャッシュが利用不可の場合
    ネットワークから画像をロード

  • キャッシュが利用可能だが初期化が終了していない場合
    キャッシュを初期化

  • キャッシュが利用可能で初期化も終了している場合
    キャッシュから画像の取得

この部分のコードは図6のようになっているのですが、ここで注意する必要があることとしてこのメソッドはIndexed Database APIのメソッドを直接利用せず、図7にあるtest.Loader.Cacheインターフェースを経由して利用していることが分かります。

図6 画像のロード開始

6 画像のロード開始

図7 test.Cacheインターフェース

7 test.Cacheインターフェース

一般的に高速化という意味においてこのような抽象化インターフェースを用いると速度が低下することが多いのですが、抽象化インターフェースを用いるとWeb Storage APIやWebSQL APIなど他のHTML5 APIへの対応が簡単になります。またtest.Cacheインターフェースを用いてIndexed Database API関連の処理を分離すると、図6のようにtest.Loader側の処理が非常に簡潔に書けるという利点もあります。

 

キャッシュ初期化終了

キャッシュの初期化が終了するとtest.Cacheインターフェースは図8にあるhandleOpen()メソッドを呼びだします。このメソッドでは初期化に成功した場合は指定された画像をキャッシュから取得します。他方、失敗した場合にはサーバーからの画像のロードを開始します。

図8 キャッシュ初期化終了

8 キャッシュ初期化終了

 

キャッシュ読込終了

キャッシュ初期化終了時と同様にキャッシュから画像の読み込みが終了した場合test.CacheインターフェースはhandleGet()メソッドを呼び出します。このメソッドはhandleOpen()メソッドと同様にキャッシュからの画像読み込みに失敗した場合はサーバーからの画像ロードを開始します。他方、成功した場合はコールバック関数handleLoadData() を呼び出してその画像をアプリケーションに渡します。

図9 キャッシュ読み込み終了

図9 キャッシュ読み込み終了

 

ネットワークロード開始

上記の状態においてキャッシュから画像がロードできなかった場合はサーバーから画像をロードします。画像のロード方法としては以下のようにHTMLImageElementを作成してそのsrcプロパティにURLを代入するという方法があります。

var image = document.createElement('img');
image.src = url;

この方法は非常に便利なのですが、残念ながらロードされた画像データそのものを読むことができないという問題があります。

このため、今回のサンプルのように読み込んだ画像データをキャッシュに書き込むという用途には適していません。今回のサンプルのような用途では一度画像データを取得してから、その画像データをData URIに変換してHTMLImageElementのsrcプロパティに代入するという処理が必要になります。

XMLHttpRequest Level 2は図10のようにresponseTypeプロパティに “arraybuffer” を代入することに画像などのバイナリデータを直接読み込むことが可能になっており、今回のサンプルではこの機能を利用しています。

また、DOMのaddEventListener()メソッドは関数だけでなくEventListenerインターフェースを実装した(つまりhandleEvent()メソッドをもつ) オブジェクトをコールバックとして利用することができます。

今回のtest.LoaderクラスはこのEventListenerインターフェースを実装することによってFunctionオブジェクトを作成せずに直接ロード終了イベントを受信しています。(Google JavaScript Style Guideにも書いてありますがFunctionオブジェクトの作成には細心の注意を払う必要があり、作成せずに済むのであればそれに越したことはないです。)

図10 XMLHttpRequest送信

10 XMLHttpRequest送信

 

ネットワークロード終了

最後にサーバーからの画像のロードが終了した時、図11のtest.LoaderクラスのhandleEvent()メソッドが呼びだされます。このメソッドは以下のような処理を行っています。

  • ロードイベントの受信を停止する
    これを忘れるとコールバック関数で利用されているオブジェクトの削除ができなくなる場合があります。

  • 受信した画像データをData URIに変換する
    詳細の説明は省略しますが、XMLHttpRequestで取得したバイナリデータ (ArrayBuffer) をData URIの文字列に変換します。

  • 変換後のData URI文字列をアプリケーションに渡す

  • キャッシュが利用可能であれば変換したData URIをキャッシュに書き込む
    これによって次回同じファイルを読み込む際にはキャッシュから読み込めるようになります。

図11 サーバーからの画像ロード終了

図11 サーバーからの画像ロード終了

 

まとめ

今回はサンプルアプリケーションを用いて画像キャッシュの基本構造について説明しました。残念ながら都合により今回は画像キャッシュの構造の説明のみに終わってしまい、このキャッシュがIndexed Database APIをどのように用いて実装されているのか説明できませんでした。次回は、このあたりの説明およびSVGを用いた画像処理について説明したいと思います。