HTML5 Canvas with JSX
JSXを触ってみる
Canvasのリッチな機能を使って描画するのはid:xaicronさんが既にやっていたので、私はピクセルをゴリゴリ描画する低レイヤーな部分を触ってみる。
あんまり面白いテーマが思いつかなかったので、とりあえずマンデルブロ集合を描画することに。どの程度最適化されるのかが気になったので、あまり手でコードは崩さずに安直で素直な実装を心がけた。
ちなみに、同じくJSXを使ってWebGLを触るのをid:santarhくんがやっていたので、こっちも見ると良いかもしれない。
デモ
ソースはデモページに併記してある。Canvasの高さを基準に虚数軸をiから-iまで計算しているので、幅を高さの二倍程度に設定すると綺麗に全体が描画されるはず。
初期値だと1秒程度で描画されるが、あまりに大きな数字を設定するとブラウザが死ぬので注意*1。
思ったこと
私はJavaScript弱者だけど、JSXは寧ろJavaに近い感じでサクサク書けたので、習得コストは低めで手軽という印象。極力JavaScriptは書きたくないので今後お世話になるかも。
Optimization
面白かった部分は複素数の演算周り。今回はオーバーヘッドを気にせず、素直に複素数型を実装して演算をメソッドとして定義したのだけれども、JavaScriptへコンパイルした際に全てインライン展開されてメソッド呼び出しがゴッソリ消えていた。この辺のオプティマイズは良くできてるなーという印象。
JSX:
for (var i = 0; i < this.count; ++i) { k = k.mul(k).plus(new Complex(x, y)); if (k.abs() > 2) { return i; } }
Compiled JavaScript:
for (i = 0; i < this.count; ++ i) { this$0 = new Complex$NN(k.real * k.real - k.im * k.im, k.im * k.real + k.real * k.im); c$0 = new Complex$NN(x, y); k = new Complex$NN(this$0.real + c$0.real, this$0.im + c$0.im); if (Math.sqrt(Math.pow(k.real, 2) + Math.pow(k.im, 2)) > 2) { return i; } }
JS側から呼ぶ
逆に面倒だった部分は、JSXで記述された関数をJavaScript側から呼ぼうとするときに名前が分からないこと。JSXでは関数のオーバーロードができるので、コンパイルされたJavaScriptの関数は名前にポストフィクスを付けて型ごとの実装を区別しているようだ*2。途中で方針を変えて_Main.main()の引数を増やしたら唐突に動かなくなって焦った。これはコンパイルされたJavaScriptを読まないと分からないような気がするけど、実は私が知らないだけでスマートなやり方が用意されているのかもしれない。
JSXレベルの関数名を引数に渡したら、適合する型の実装をディスパッチしてくれるような機能があれば便利な気がする。
型宣言
これは仕方ないことかもしれないけど、高階関数を定義するときの型宣言があまりに煩雑に成ってる感じが。具体的にどう書けるようになれば嬉しいかは分からないけど、現行のはちょっとなぁ、という風に感じた。
/* 引数にMandelbrot型を受け取り、返り値として 「引数にint型を受け取り、返り値としてint型の配列を返す関数」 を返す関数 */ static function schema (m: Mandelbrot) : function(:int) : Array.<int> { ... }
最後に
これはJSXが優秀なのかブラウザが優秀なのかわからないけど、思ったよりCanvasが高速で描画してくれるのが面白い。スマートフォンでもちゃんと動くし。
むしろデモページ作るためのJavaScript書くのが一番大変だった気がする。別ファイルのソースコードを引っ張ってくる方法がよくわからなくて、結局ググってXHRで書いた。JavaScript難しい…
とりあえずCanvasに絵が書けたので、次はアニメーションに挑戦してみよう。