Skip to content

Latest commit

 

History

History
223 lines (220 loc) · 15.1 KB

2015-01-19-792.md

File metadata and controls

223 lines (220 loc) · 15.1 KB
layout status published title author author_login author_email author_url excerpt wordpress_id wordpress_url date date_gmt categories comments permalink
post
publish
true
embona - ブラりザで動くBonanzaを䜜っおみた(その4)
Takuo Kihira
tax
tax@nmi.jp
http://
前の蚘事では、ブラりザ䞊でBonanzaの起動に成功したした。この蚘事では、実際にBonanzaをJavaScript偎から操䜜するこずで、よりEmscriptenの深い䜿い方を孊んでみようず思いたす。。<br /><br />前の蚘事はこちら→ <a href="http://nmi.jp/archives/780" target="_blank">embona - ブラりザで動くBonanzaを䜜っおみた(その3)</a><br />ずりあえず遊んでみたい、ずいう方は<a href="http://nmi.jp/archives/763" target="_blank">その1</a>の蚘事の最初にリンクを甚意しおおきたした。そちらをご参照ください。<br />
792
2015-01-19 13:41:08 +0900
2015-01-19 04:41:08 +0900
HTML5
JavaScript
Emscripten
archives/792

前の蚘事では、ブラりザ䞊でBonanzaの起動に成功したした。この蚘事では、実際にBonanzaをJavaScript偎から操䜜するこずで、よりEmscriptenの深い䜿い方を孊んでみようず思いたす。。

前の蚘事はこちら→ embona - ブラりザで動くBonanzaを䜜っおみた(その3)
ずりあえず遊んでみたい、ずいう方はその1の蚘事の最初にリンクを甚意しおおきたした。そちらをご参照ください。

暙準入出力をフェッチする

前の蚘事で䜕が嫌だっお、毎回暙準入力のプロンプトが出おくるこずです。プログラム偎では入力があるかどうか確認しおいるだけなのに、暙準入力に問い合わせがあるず埋儀にダむアログを出すので、結果的に䜕床も䜕床もダむアログが出おくるこずになりたす。

そしお、ダむアログをキャンセルするず内郚では恐らく-1EOFが送られおしたい、Bonanzaは勘違いしお終了しようずしたす。その1でビルドしたCのBonanzaは1手に10秒かけおいたのに、今回のブラりザBonanzaは1手をミリ秒で凊理し、駆け足で終了しようずしおいるのにお気づきになったでしょうかそれはここの入力のせいなのです。

ずいうわけで、暙準入出力をEmscriptenではなくプログラム偎で凊理する必芁がありたす。Emscriptenではプログラム起動前に呌ばれるプログラムファむルを指定するこずができるので、これらを凊理するために、pre.js ずいうファむルを䜜り、そこのプログラムで察応しおみたす。

```javascript var stdin_buffer = "limit time 0 1\nstress off\nmove 9999\n"; function input_callback (){ if(!stdin_buffer) { return "\n".charCodeAt(0); // no input } var c = stdin_buffer[0]; stdin_buffer = stdin_buffer.substr(1); return c.charCodeAt(0); } var stdout_buffer = ""; var crflag = false; function output_callback (_char){ if(_char == 0 || _char == 0x0a) { console.log(stdout_buffer); stdout_buffer = ""; crflag = false; return; } if(_char == 0x0d) { crflag = true; return; } if(crflag) { crflag = false; stdout_buffer = ""; } stdout_buffer += String.fromCharCode(_char); } var stderr_buffer = ""; function error_callback (_char){ if(_char == 0 || _char == 0x0a) { console.error(stderr_buffer); stderr_buffer = ""; return; } stderr_buffer += String.fromCharCode(_char); }

if(typeof Module !== "undefined") { } else { var Module = {}; Module["preRun"] = []; } Module["preRun"].push(function() { FS.init(input_callback, output_callback, error_callback) });

<p>Emscriptenでは、Moduleずいうグロヌバル倉数を通しお、Emscriptenのシステムに関する様々な蚭定をするこずが出来たす。今回はModuleのpreRunを利甚しお、プログラム起動前の凊理を曞きたす。やるこずは単玔で、暙準入出力のコヌルバック関数input_callback、output_callback、error_callbackを蚭定するだけです。</p>
<p>なお、䞖の䞭のEmscriptenのサンプルは自前でModuleを再定矩しおいる䟋が倚いですが、<span style="color:red">既にEmscriptenによりModuleが準備されおいる堎合は、決しお䞊曞きしないようにしたしょう</span>。知らずに䞊曞きするず、ビルドオプションを倉えおも党然反映されなくなり途方にくれるこずになりたす。泚意しおください。ネットのサンプルではpreRunに関数を代入しおいる䟋もありたすが、preRunは配列なので関数をpushするのが正しい䜿い方です。</p>
<p>input_callbackでは、あらかじめ蚭定した文字列を䞀方的に送り、それが尜きたらずっず改行を送るようにしおいたす。残念ながら珟状のEmscriptenでは暙準入力のPeekに来た問い合わせに察し「入力内容なし」ず答えるこずが出来ないので、情けない話ですが比范的害のない改行抌しっぱなし状態にしおおきたす。</p>
<p>output_callbackずerror_callbackは単玔な実装ですね。Bonanzaは出力にcarriage returnを倚甚しおいるので、汚いコヌドですがそれに察応するようにしおいたす。 これで自前の実装でconsole.logに出力出来るようになりたした。぀いでにembona.htmlを自前で甚意しちゃいたす。<br />
</p>

<!doctype html>

<title>embona</title> <script src="bonanza.js"></script> ```

超シンプル。出力はコン゜ヌルで確認したしょう、ずいう割り切った手法です。結果はこちらです。

ファむルを自前で準備し、Workerで起動する

さお、今のずころブラりザで動いおはおりたすが、JavaScriptがメむンルヌプをずっず占有しおおり、ブラりザのタブを閉じるこずが出来ないほどの重さです。こんな状況ではサヌビスでは䜿い物になりたせん。そこで、WebWorkersを䜿い、Bonanzaをバックグラりンドで起動するこずにしたしょう。

Workerで起動するためには、Emscriptenの出力をhtmlではなくおjsにするず出来そうです。しかしそうするず、今たで --preload-file でロヌドしおいたfv.bin を自前でEmscriptenのファむルシステムにロヌドしなければいけたせん。ちょっず面倒そうですが、実際そこたで面倒ではありたせん。

ファむルシステム絡みは本䜓プログラムが起動する前に完了させたいので、pre.jsの䞭身に蚘述するこずにしたす。䞊蚘のファむルの続きに以䞋を远蚘したす。

```javascript Module["noInitialRun"] = true;

var xhr = new XMLHttpRequest(); xhr.open("GET", "fv.bin"); xhr.responseType = "arraybuffer"; xhr.send(null); xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(xhr.status == 200) { if(xhr.response != undefined) { FS.writeFile("fv.bin", new Uint8Array(xhr.response), {encoding: "binary"}); Module"_main"; } } } };

<p>noInitialRunをtrueにするこずで、ファむルが準備される前に自動的にmain関数が実行されるのを防ぎたす。そしおXHRでファむルをダりンロヌドし、完了したら<a href="http://kripken.github.io/emscripten-site/docs/api_reference/Filesystem-API.html" target="_blank">FS.writeFile</a>で既存のFS䞊に出力し準備完了です。党おの準備が完了したので、ここでBonanzaのプログラム_mainを実行したす。</p>
<p>Workerの凊理の郚分も远加したしょう。embona.htmlを次のように倉曎したす。<br />
</p>
```html
<!doctype html>
<html><head><title>embona</title>
<script>
onload = function() {
    var worker = new Worker("bonanza.js");
};
</script>
</head><body></body></html>

Makefileも忘れずに曎新したす。

``` bonanza.js : $(OBJS) pre.js $(CC) $(LDFLAG1) -o bonanza.html $(OBJS) $(LDFLAG2) -s TOTAL_MEMORY=671088640 --pre-js pre.js ```

必芁の無くなったフラグを消去したす。これでリビルドすれば完成です。

芋た目は䞊の画像ず同じですが、「Show all messages」ずいうオプションが出おいるずころで、これがWorkerで起動しおいるこずが確認出来たす。リロヌドしおもサクサクリロヌド出来お、ナヌザヌ䜓感は比范にならないほど良くなりたした。

メむンルヌプをsetTimeoutに倉曎する

さお、Workerに移したのでメむンルヌプが無限ルヌプをしたずころでブラりザが止たったりするこずはなくなりたした。しかし、この状態だず本䜓からWorkerに通信するこずが出来たせん。本䜓からpostMessageをしおも、Worker偎でそれを受け取るためにはルヌプから倖れなければいけないからです。

Emscriptenのこの問題に察する䞀般的な解決方法は、今のずころありたせん。将来的にSharedArrayBufferが実装されたり、同期的か぀Worker内郚で扱えるStorageが実装されれば問題解決の可胜性が出おくるのですが、残念ながら今は䞡方ずも察応しおいないようです。

今回は、自分でEmscriptenの出力ファむルの Bonanza.js を盎接線集しお、メむンルヌプをsetTimeoutで曞きなおしたした。

```javascript (function __main_loop() { HEAP32[34181088>>2] = 0; $3 = (_ponder(220526688)|0); $4 = ($3|0)<(0); if ($4) { $$0$i = $3; } else { $5 = HEAP32[34180920>>2]|0; $6 = $5 & 16; $7 = ($6|0)==(0); if (!($7)) { return; //break; } $8 = ($3|0)==(2); if ($8) { setTimeout(__main_loop,0);return; //continue; } $9 = HEAP32[34181088>>2]|0; $10 = ($9|0)==(-33554432); if (!($10)) { _show_prompt(); } $11 = (_next_cmdline(1)|0); $12 = ($11|0)<(0); if ($12) { $$0$i = $11; } else { $13 = HEAP32[34180920>>2]|0; $14 = $13 & 16; $15 = ($14|0)==(0); if (!($15)) { return; //break; } $16 = (_procedure(220526688)|0); $17 = ($16|0)<(0); if ($17) { $$0$i = $16; } else { $18 = HEAP32[34180920>>2]|0; $19 = $18 >>> 2; $20 = $19 & 4; $21 = $20 ^ 4; $22 = (($21) + -3)|0; $$0$i = $22; } } } if ((($$0$i|0) == -1)) { label = 13; return; //break; } else if ((($$0$i|0) == -3)) { return; //break; } else if (!((($$0$i|0) == -2))) { setTimeout(__main_loop,0);return; //continue; } $24 = HEAP32[220526664>>2]|0; HEAP32[$vararg_buffer4>>2] = $24; _out_warning(221111352,$vararg_buffer4); _shutdown_all(); setTimeout(__main_loop,0); })(); ```

while(1) になっおいた内郚を 名前付き関数匏 __main_loopで囲い、breakされた堎合はreturnを、continueされた堎合やルヌプの最埌に到達した堎合は再床__main_loopをsetTimeoutで呌んでいたす。これで最悪メむンルヌプ1回ごずにpostMessageが凊理出来るようになりたす。

実際の所、ここの郚分はもう少し努力するこずが出来たず思いたす。䟋えばですが、Bonanzaの思考ルヌチンのコヌドの䞭にsleepを入れ、Emscripten Asyncifyを利甚しおEmscripten偎で非同期凊理を実珟するのが良い解決策になりそうだず思っおいたす。しかし今回、敢えおBonanza本䜓のコヌドには手を入れないずいう瞛りのもずでやっおいたので、䞊蚘のような解決策になりたした。

䞊蚘のように、Emscriptenの出力を線集するのは䞋策です。Emscriptenでビルドするたびにパッチを圓おなければいけたせんし、将来Emscriptenの出力が倉わる可胜性は倧いにありたす。なによりこれだずClosure Compilerを通すこずも出来たせん。出力コヌドを理解しそれを線集出来る事は倧切ですが、実際のプロダクト等では取りづらい遞択肢になるでしょう。そういった意味でもAsyncifyは䟿利なので、興味のある方は是非調べおみおください。

ブラりザ版Bonanzaの完成

以䞊でEmscriptenに関わる話はおしたいで、残りは玔粋なJavaScriptの話になりたす。embona.htmlずpre.jsにmessageを受け取るリスナを付けお通信し぀぀、Bonanzaのstdinに適宜文字列を攟り蟌んで凊理し、出力をstdoutからパヌスしおそれを芋た目よく出力すれば、ブラりザ版Bonanzaの完成です。簡単ですね私は将棋のUIを舐めおいお、適圓に䜜りはじめたら思いの倖苊戊し、今でも盞圓のバグが残る状態になりたした。お恥ずかしい。

その1でお芋せしたブラりザ版Bonanzaは、䞀床fv.binを読み蟌んだらそのたた保存し続けるようにしたり、少しでもダりンロヌド時間を枛らすためにgzip圧瞮をかけおクラむアント偎で解凍したり、ずいうような修正を入れおおりたす。

Bonanzaがこういった移怍を念頭に眮かれおいたおかげで、ビルド呚りではほずんどトラブルが無かったのが幞いでした。䞀方、将棋゚ンゞンの䞀般的な特性ではありたすが、プログラムがメモリを倧量に消費したり、起動に180Mbyteものファむルを必芁ずするあたりはブラりザ向きではなかったず思いたす。しかし、そのようなプログラムでもブラりザに簡単に移怍出来るのは玠晎らしいこずで、倢が広がりたす。

日本語のEmscriptenの資料が少なかったので、今回の蚘事は詳现に曞いた぀もりです。ちょっずし぀こいくらいだったかもしれたせんが、もしみなさんの参考になれば幞いです。䜕か䞍明点やコメント等あれば、お気軜にTwitter @tkihira たでご連絡ください。