WebAssemblyをブラウザで実行するときにハマった
2017/11/15 01:55 JST
せっかくRustちまちま書けるようになってきたのでWebAssemblyを触り始めた.
基本的にChromeで実行していて気づかなかったのだけど, Firefoxで実行しようとしたとき, 以下の初期化コードでは動かなかった.
fetch('wasm/hello.wasm')
.then((response) => response.arrayBuffer())
.then((buffer) => {
Module.wasmBinary = buffer
const scriptElem = document.createElement("script");
scriptElem.src = "wasm/hello.js";
scriptElem.addEventListener('load', (e) => {
const mainScriptElem = document.createElement('script');
mainScriptElem.src = 'main.js';
document.body.appendChild(mainScriptElem);
});
document.body.appendChild(scriptElem);
})
それで, このコードの
...
scriptElem.addEventListener('load', (e) => {
const mainScriptElem = document.createElement('script');
mainScriptElem.src = 'main.js';
document.body.appendChild(mainScriptElem);
});
...
が良くなかった.
何が良くないかというとwasm/hello.js
の読み込みが終わっただけで, WebAssemblyのランタイムの準備が整ったわけではなく, このあとmain.js
がロードされてもwasm
の初期化がされていないのでエラーになることがある.
この問題, Chromeのパースがはやいのか気づかなかった. Firefoxでいざ実行してみたらwasmが準備できてないのに実行するな
みたいなエラーが出た.
Assertion failed: you need to wait for the runtime to be ready (e.g. wait for main() to be called)
はい. しっかりとライフサイクルに従いましょう!
wasmがパースされた後にmain.jsをロード
するようにすれば解決できるっということは理解できているので, そのように振る舞ってくれるAPIを利用すれば良いのです.
今回の場合, main.js
をロードさせるところから始めるので Module.onRuntimeInitialized
が該当するはずです.
他にもModule._main
やModule.addOnInit
があると思いますが, WebAssemblyランタイムが初期化されて初めて使えるものなので利用できません.
以下が正しく動作するコードです.
var Module = {}
Module.onRuntimeInitialized = () => {
const mainScriptElem = document.createElement("script");
mainScriptElem.src = "main.js";
document.body.appendChild(mainScriptElem);
};
fetch('wasm/hello.wasm')
.then((response) => response.arrayBuffer())
.then((buffer) => {
Module.wasmBinary = buffer
const scriptElem = document.createElement("script");
scriptElem.src = "wasm/hello.js";
document.body.appendChild(scriptElem);
})
※Module
はEmscriptenが用意しているものでWebAssembly規格標準のものではありません
おまけ:
Firefoxでライフサイクルを確認するコード
fetch('wasm/hello.wasm')
.then((response) => response.arrayBuffer())
.then((buffer) => {
Module.wasmBinary = buffer
const scriptElem = document.createElement("script");
scriptElem.src = "wasm/hello.js";
scriptElem.addEventListener('load', (e) => {
Module.addOnPreRun(() => {
console.log('addOnPreRun() has been called!')
});
Module.addOnPostRun(() => {
console.log('addOnPostRun() has been called!')
});
Module.addOnExit(() => {
console.log('addOnExit() has been called!')
});
Module.addOnInit(() => {
console.log('addOnInit() has been called!')
});
});
document.body.appendChild(scriptElem);
})