Pexの内部構成の簡単な解説
Pex(https://github.com/PexJS/PexJS)がオープンソースになりました。 前身のExGameの時代から @tkihiraと一緒にずっと開発してきたプロダクトなので、大変感慨深いです。今はプロジェクトを離れ別なプロダクトの仕事をしているのですが、まだ私の書いたコードが多少残っているようです(糞コードがあれば何割かぐらいの確率で私の責任です)。
出力されるpex.jsは約9000行で、一人で全体を把握できる規模のプロダクトだと思います。しかし、クラス名がなかなか独特だったりして、初見では全体像がなかなか掴みづらいと思いますので、簡単に全体の構成を解説してみたいと思います。
前提知識
最低限、SWFファイルのバイナリフォーマットを理解していることが必要です。知らない方は、こちら http://labs.gree.jp/blog/2011/01/2353/ の「SWFバイナリ編集のススメ」シリーズがわかりやすいので目を通しておくことをお勧めします。
また、WF勉強会#2(http://gihyo.jp/news/report/2012/09/2801)での @tkihira の講演はPexの実装を念頭においたものなので、こちらもソースを理解する助けになるかと思います。ちょっと長いですがgihyo記事中からUStreamへのリンクがあります。
ビルド方法
README.mdにある通り、jakeを実行すると、output/pex.js に出力されます。このpex.jsは、build/jsset/normal.txt に書かれているファイルを、その順番通りに連結したものになっています。#で始まる行はコメントなので、src以下に存在するいくつかのファイルは利用されません。
注)ちなみに、このjssetを切り替えることでモジュールを差し替えることができます。例えば jake jsset=hogeとして実行すると build/jsset/hoge.txt が読み込まれるようになります。
各ファイル/クラスの役割
build/jsset/normal.txtに書かれている順に、ファイルに含まれる内容をみていきます。
header.js
全体のヘッダーとログ出力関数などの定義
option.js
ランタイムに渡すオプションのデフォルト値の定義
define.js
SWFバイナリ中で使われている定数値の定義
util.js
各種ユーティリティ
master.js
Masterクラスの定義。1つのHTMLページで複数のSWFを同時に再生できるのですが、1つ1つの再生用インスタンスがEngine(次項)で、それら全体を管理するためのものがMasterです。
SWFバイナリのparse処理はMasterから呼び出します。仮に同じSWFを複数再生する場合はここでparse結果をキャッシュしています。各Engineの同期をとるため、タイマーのループはMasterクラス中に含まれています。
engine.js
Engineクラスの定義。1つの再生用インスタンスを表します。各ムービークリップの状態を1フレーム進めるためのtick関数があり、前半でオブジェクトの位置や大きさを更新、後半でその結果をもとに描画ルーチン(後述のRenderer)を呼び出します。
analyzer.js
Analyzerクラスの定義。SWFバイナリをparseした結果は、単純にSWFバイナリをJSのオブジェクトに転写しただけに近い状態で、そのままでは実行できません。parse結果を解析しどのフレームでどのムービークリップが生成・消滅されるかなどの解析を行います。
movieclip.js
MovieClipクラスの定義。ムービークリップのインスタンスを表します。
movieclipinfo.js
MovieClipInfoクラスの定義。ムービークリップの定義を表します。Analyzerの解析結果がここに格納されています。この情報をもとにMovieClipが生成されます。(MovieClipInfoとMovieClipの関係は、クラスとそのインスタンスのようなものです)
vm.js
VMクラスの定義。ActionScriptのVMです。
api.js
APIクラスの定義。外部から実行を操作するためのAPIです。
profile.js
開発用のプロファイラ。ちなみに if(develop) { … } で囲まれた箇所はminifyをかけると消えるようになっています。
touch.js
Touch クラスの定義。タッチ操作用のユーティリティです。
renderer/
rendererには描画用のクラスが含まれます
renderer/util_render.js
描画用ユーティリティです。行列計算や色の加算・乗算用の関数などが定義されています。
renderer/cache_controller.js
CacheControllerクラスの定義。描画結果をキャッシュするためのクラスです。
renderer/general_renderer.js
Rendererクラスの定義。デフォルトの描画ルーチンで、愚直に描画します。ここでは、ムービークリップなどのオブジェクトツリーのレイヤーを処理しており、末端のオブジェクトの描画はdraw_function_factory.jsで生成された描画関数を呼び出します。
renderer/dirtyrect_renderer.js
DirtyRectRendererクラスの定義。初期化オプションを指定することでRendererと差し替えて利用します。各オブジェクトの重なりと変化を計算し、再描画の必要のある箇所だけを描画します。一般的にはこちらの方が高速に動作しますが、全画面が再描画されるような動きの激しいSWFの場合はオーバーヘッドの分Rendererのほうが速いこともあります。
renderer/draw_function_factory.js
シェイプなどの一連の描画命令を1つのFunctionとしてコンパイルするための関数。使っているテクニックについては http://www.slideshare.net/takuokihira/html5-conference-2013 プレビュー の P21参照。
parser/
SWFのバイナリをJSのデータ構造に落とし込むためのクラス群が含まれます。
parser/utils_binary.js
ビット演算やSWFのデータ構造を扱うためのユーティリティ関数
parser/utils_tag.js
タグの中身を遅延評価させるためのユーティリティ関数
parser/utils_sjis.js
SJIS処理用のユーティリティ関数
parser/utils_support.js
たぶんデバッグ用の関数
parser/loader.js
SWFを外のURLから取得するための関数
parser/parser.js
Parserクラス
parser/tag_*.js
SWFのタグを表すクラス。
parser/image_manager.js
バイナリで格納されているラスター画像を展開するためのクラス。
parser/inflate.min.js
@imayaさん作zlibライブラリ。lossless画像展開用。
footer.js
フッター。header.jsで中括弧を開きっぱなしにしているので、ここで閉じる。
理解するのが難しいのはMasterとEngineの関係、MovieClipとMovieClipInfoの関係、rendererの役割分担あたりでしょうか。クラス名を見て役割がぱっと分かればいいんですが…。そのへんの命名がいけてないのは @tkihira 先生に苦情を!
(もしかしたら続く)