デスクトップマスコットをブラウザ移植してみた 利用者視点のEmscriptenとその周辺
うかがか伺かon Browserデスクトップマスコットをブラウザ移植してみた利用者視点のEmscriptenとその周辺
View Slide
自己紹介佐藤隆人(な ら ざ か奈良阪)Twitter: @narazakaGithub: @Narazakanpm: @narazakaCPAN: NARAZAKAドリコム2015新卒(2年目)サーバーサイドRuby書いてますPerl / JavaScript / Ruby / C#あたりが好きその他:伺か/漫画読み描き/ OPアニメ愛好家/鉄
※注意 1から10まで趣味の話ですプロダクションレベルの知見ではありません。試行錯誤の途中なのでアドバイスなどいただけると幸いです。
近年やってる趣味
うかがか伺か2000年頃に一部で流行ったデスクトップマスコット
デスクトップマスコットのなかま?たちポストペットペルソナウェアデスクトップのメイドさんいもうとデスクトップまいんど・ふぉ~かすお座りマルチ
かわいい
懐かしい
なんかすごい
キャラクター表現プラットフォーム16年目の現在もたくさんのキャラの更新がある
しかし……世はスマホ全盛
「キャラクターの居場所デスクトップ」の消滅
……スマホでも使いたいブラウザにのせて動かせないか?
「伺かon Browserイカガカ」プロジェクトコードhttps://github.com/Ikagakaデモhttp://ikagaka.github.io/Ikagaka.demo/
ブラウザに載せるにあたって……うかがか伺か はどういうプログラム?
うかがか伺か(materia.exe)キャラクターデータを差し替えられるデスクトップマスコットランタイム→クローズドソースなのでJavaScriptで新規開発キャラクターの人格は任意のWindows用アンマネージドDLLをそのまま動作させる仕様(めっちゃ自由)extern "C" ̲̲declspec(dllexport) BOOL ̲̲cdecl load(HGLOBAL h, long len);extern "C" ̲̲declspec(dllexport) BOOL ̲̲cdecl unload();extern "C" ̲̲declspec(dllexport) HGLOBAL ̲̲cdecl request(HGLOBAL h, long*len);→OSSのC++製人格(台本)記述用言語DLL(.so)がいくつか存在
各人格記述用言語DLL (with Hello, World!)shiori.dll (華和梨8) / shiori.dll (華和梨7)System.Callback.OnGET: Hello, World!satori.dll (里々)*Hello, World!yaya.dll (YAYA) / aya5.dll (文5)request{"Hello World!"}
_人人人人人人人_> Emscripten < ̄Y^Y^Y^Y^Y^Y ̄
Linuxで依存なくコンパイルできるC++だった(一部boostはずした)既存のコードほぼ変更なし
Makefileもフラグ追加とg++ ‑> em++rem emsdk1.25.0 on Windowsemcmdprompt.batD:cd kawarimake
各人格記述用言語DLL割とすんなり変換できたshiori.dll (華和梨8) ‑> kawari.jsshiori.dll (華和梨7) ‑> kawari7.jssatori.dll (里々) ‑> libsatori.jsyaya.dll (YAYA) ‑> yaya.jsaya5.dll (文5) ‑> aya5.js
_人人人人人人人_> Emscripten < ̄Y^Y^Y^Y^Y^Y ̄‑完‑
!ここからが問題!~Emscriptenとその周辺~
今回の最重要要件=互換動作キャラデータの変更なしにデスクトップと同じ挙動を保証したいキャラデータは創作畑の人たちが作っていて、形式変換などが望めるものではない
1.落ちるキャラデータがあるメモリ関係のエラーで落ちる任意のキャラデータを突っ込んで動作させたい……。
1.落ちるキャラデータがある(解決)em++のデフォルトでは最大メモリサイズが固定値だった。メモリが足りなくなったら自動的に拡張してくれる‐s ALLOW_MEMORY_GROWTH=1をつけてコンパイル。(実行速度遅くなるよという警告が出る)
2.複数キャラをたちあげたい同じ人格DLL.jsを色々なキャラデータに適用したい。しかし→Emscriptenはスクリプトをロードした時点で、グローバルの Moduleとかに色々入れてそのまま単一インスタンスとして実行してしまう。
2.複数キャラをたちあげたい(解決)関数で囲ってnewする ‐‐pre‐js em‐pre.js ‐‐post‐js em‐post.js(どう見ても正攻法ではない気がする…… →現在はWorkerに隔離)var Kawari = function(){ // em‐pre.js(****Emscriptenでコンパイルされたコード...****)this['Module'] = Module;this['FS'] = FS;this['PATH'] = PATH;this['ERRNO_CODES'] = ERRNO_CODES;this['NODEFS'] = NODEFS;this['IDBFS'] = IDBFS;this['WORKERFS'] = WORKERFS;}; // em‐post.jsvar kawari = new Kawari();console.log(kawari.ERRNO_CODES);
3.ファイルシステムの取り扱いユーザーが投げる任意のキャラデータ(ファイルサイズ無制限のZIP)を展開してFSに配置し、中に入っているスクリプトを動かしたい。DLLがFSに書き込むセーブファイル等を保持したい。追加の前提ブラウザではローカルのファイルシステムは読めない。WorkerでIndexedDBにアクセスできないブラウザ(IE)がある。
3.ファイルシステムの取り扱い(解決)npm install browserfs1. ZIPを展開し(FS APIをエミュレートする)BrowserFSでIndexedDBに保存して保持2. (IndexedDBは非同期APIしかないので同期APIを使う)EmscriptenFSにメモリロードしてからDLL実行3.実行終了後にFSの中身をIndexedDBに書き戻す数MBのZIP複数扱う時点でIndexedDB以外容量足りませんEmscripten付属のIndexedDBの扱いも大して変わらないめちゃくちゃ泥臭いので何かいい方法ないでしょうかね……
3.ファイルシステムの取り扱い(願い)Workerで使えるIndexedDBの同期版API復活しないかなあmountみたいにFSの一部だけロードできないかなあIEがサポート切れますように(♪)
4. Shift̲JIS文字列を渡す人格用言語DLLが2000年代初頭製なのでSJIS前提ユーザーから投げられるキャラデータのソースもSJIS。互換性のため文字コード変換かましたコンパイルはしたくない。EmscriptenデフォルトのPointer̲stringifyやintArrayFromStringはUnicode前提なのか正しく動かなかった。
4. Shift̲JIS文字列を渡す(解決)npm install encoding‐japanesevar _request = Module.cwrap( // DLLのrequest()関数'request', 'number', ['number', 'number']);var requestStr = "こんにちは";// intArrayFromStringではダメだったvar unicodeReq = Encoding.stringToCode(requestStr);var sjisReq = Encoding.convert(unicodeReq, 'SJIS', 'UNICODE');var req = allocString(sjisReq); // 文字列メモリ確保var len = allocLong(req.size); // 文字列長メモリ確保var resPtr = _request(req.ptr, len.ptr); // request()!var resHeap = new Uint8Array( // 応答文字列のヒープの参照Module.HEAPU8.buffer, resPtr, len.heap[0]);// intArrayToStringはOKvar sjisResStr = Module.intArrayToString(resHeap);Module._free(len.ptr); Module._free(resPtr); // freeするvar sjisRes = Encoding.stringToCode(sjisResStr);var unicodeRes = Encoding.convert(sjisRes, 'UNICODE', 'SJIS');var response = Encoding.codeToString(unicodeRes);
4. Shift̲JIS文字列を渡す(解決)詳しくはhttps://github.com/Narazaka/nativeshiori↓実際のコード
5.人格用言語DLLから別のDLLを呼ぶ機能の少ない人格用言語DLLから別のDLLを呼んで機能を補完するSAORIというインターフェース規格が流通しているextern "C" ̲̲declspec(dllexport) HGLOBAL ̲̲cdecl request(HGLOBAL h, long*len);extern "C" ̲̲declspec(dllexport) BOOL ̲̲cdecl load(HGLOBAL h, long len);extern "C" ̲̲declspec(dllexport) BOOL ̲̲cdecl unload();任意のDLL(.so)呼びたい
5.人格用言語DLLから別のDLLを呼ぶ(未解決)https://github.com/kripken/emscripten/wiki/Linking2015年から対応された模様?(イカガカ初期開発時の2014年当時はうやむやだった)素人が「ホームページ」に気軽における程度に簡単な対応にしたいが、まだよく調べていません。
(要件によっては)コンパイル後の対処が大変今回の要件=デスクトップアプリとの互換動作1.全部C++ではなく、JavaScriptで書かれたメインルーチンに任意個数のコンポーネントDLLを連携させて動作させる。2.任意のスクリプトを言語ランタイムに食わせてデスクトップと互換の動作・ファイル操作をする。3.入出力はSJIS。4. DLLから別の任意DLLを呼ぶ(未解決)。めぼしい知見がなかったので全部無理矢理解決
知見はまだまだ足りないエミュレーターで動かしているようなものなのでC++やJavaScript単体と異なった事情が出てくる。「全部C++で書いてそのまま実行する」以外のシナリオ例があまりない。
まとめブラウザでデスクトップマスコット「伺か」を動かしてみたDLLがあったのでEmscriptenでJSにした要求仕様に沿ってDLLを扱うにあたって、複数起動、ファイルシステム、文字コード、動的リンクの問題があったこの様な(ニッチな)需要にみあう知見は(当然)充実していない
[PR]イカガカ開発チームドリコムはエンジニアを募集しています!