UnityのWebGL出力で気づいたことをまとめていきます。
主に WebAssembly でのビルドについて書いています。
後で追記する予定です。
この記事の内容で、動かなかったところがあればコメント欄で教えて下さい。
一緒に解決しましょう。
解決策が残っていたら、他の人の役にも立つかもしれません。
※ Unity のバージョンによって動作が異なる場合もあるかもしれません。
その点についても情報を頂けると助かります。
Unity側
サンプルファイルの場所
Unity Hub 導入時
- Windows: C:\Program Files\Unity\Hub\Editor\20xx.x.xxx\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\lib\*.js
- Mac: /Applications/Unity/Hub/Editor/20xx.x.xxx/PlaybackEngines/WebGLSupport/BuildTools/lib/*.js
Unity Hub 未導入時
- 分かる方が居たら教えてください。
JS側の関数を宣言する時のテンプレート
using System.Runtime.InteropServices;
public class Hoge {
[DllImport("__Internal")]
static extern int JS_Hoge_Create(string url);
}
JSから呼び出せるC#メソッドの宣言テンプレート
using AOT;
using System;
public class Hoge {
// static でないと呼び出せない
[MonoPInvokeCallback(typeof(Action<int>))]
static void HogeCallback(int fuga) {
UnityEngine.Debug.Log(fuga);
}
}
ビルドを速くする
結論: どうやっても速くならない
ここに一応、速くする方法は書かれています。
これらの手順を踏まえても、それほど速くできません。
上の URL に書かれていない手順を紹介します。
次の状態でいったん全てをビルド後、
- Development Build をチェック
- Scripts Only Build のチェックを外す
2回目のビルドは同じ出力場所に、
- Scripts Only Build をチェック
でビルドすると、スクリプト以外のところはビルドを省略できるため、少し速くなるようです。
ほぼコンテンツなしで WebGL だけのコードを書いている時は速くなった気がしません。
初回のビルドで Scripts Only Build をチェックしてしまうと、ビルドに失敗します。
そもそもなぜ遅いのかという話はここで色々書かれています。
引用すると、
- Compile C# to IL (C#をILにコンパイル。monoのビルドツールが実行される ?)
- Strip assemblies (ILのアセンブリから不要なコードを削除)
- Convert IL to C++ (ILをC++に変換。IL2CPPが実行される ?)
- Compile C++ into WASM. (C++をWebAssemblyにコンパイル。Emscriptenのビルドツールが実行される ?)
これだけの手順でビルドするため、遅くなるようです。
IL2CPP ではなく、 IL2WASM のようなツールが登場すれば改善されるかもしれません。
スタックオーバーフロー等でも同じように困っている人がいるので、おそらく世界中の人がビルドの遅さに困っているのではないかと思います。 "unity webgl build slow" 等で検索すると色んなページが出てきます。
Addressable Asset System を使用する
コメント欄で @usamino さんに教えて頂きました。
アセットのビルドをコードのビルドと分離できるようになるみたいです。気になるかたは試してみてください。
- 【Unity】Addressable Asset System 公式マニュアルの和訳 Part1 | ドクロモエ
- Unity Addressables Migration: Music to My Ears - The Gamedev Guru
InputField で日本語入力できるようにする
ブラウザ側
コンソールでメモリの中身をチェック
WebInspector の Console 画面で次のように入力すると、 Emscripten 内で確保されたメモリを確認できます。
unityInstance.Module.HEAPU8[0]
HEAPU8の部分には、次のような変数も指定することができます。
名前 | 型 | 説明 |
---|---|---|
HEAP8 | Int8Array | 符号付き 8bit 整数 |
HEAPU8 | Uint8Array | 符号なし 8bit 整数 |
HEAP16 | Int16Array | 符号付き 16bit 整数 |
HEAPU16 | Uint16Array | 符号なし 16bit 整数 |
HEAP32 | Int32Array | 符号付き 32bit 整数 |
HEAPU32 | Uint32Array | 符号なし 32bit 整数 |
HEAPF32 | Float32Array | 32bit 浮動小数点数 |
HEAPF64 | Float64Array | 64bit 浮動小数点数 |
上記の型は、 TypedArray から派生されていますので、 subarray() 等のメソッドを呼び出すことができます。
メモリ使用量のチェック
Console で次のように入力すると、 Emscripten で確保されたメモリの使用量が分かります。
unityInstance.Module.asmLibraryArg.getTotalMemory()
HEAPxx.buffer.slice() vs HEAPxx.subarray()
どちらも同様に、配列の一部分を配列で抜き出す操作ができますが、速度に差があります。
subarray() のほうが速そうです。
参考URL
unityInstance のメンバ
プロパティ
名前 | 型 | 説明 |
---|---|---|
Module | Object | Emscripten のモジュール |
container | HTMLDivElement | 表示領域の div 要素 |
logo | HTMLDivElement | ロゴ画像の div 要素 |
progress | HTMLDivElement | プログレスバーの div 要素? |
url | string | 設定用 json ファイルの URL (Build/xxxx.json) |
container、 logo、 progress は TemplateData/UnityProgress.js で使用されています。
メソッド
名前 | 説明 |
---|---|
Quit(onQuit) | Unity の実行環境を終了? |
SendMessage(objectName, methodName, ...) | Unity のメソッドを呼び出す |
SetFullscreen(fullscreen) | true: フルスクリーン, false: 解除 |
compatibilityCheck(onsuccess, onerror) | 互換性チェック? |
popup(message, callbacks) | 用途不明 |
SendMessage() は便利に使えるかもしれません。
objectName 引数には、ヒエラルキービューの場所を文字列で入れるようです。
unityInstance.Module のメンバ
Emscripten のドキュメントは次の場所にあります。
Unity で追加されたメンバ (?)
プロパティ
名前 | 型 | 説明 |
---|---|---|
companyName | string | 会社名 |
productName | string | 製品名 |
productVersion | string | 製品バージョン |
unityVersion | Object | Unity のバージョン情報 |
developmentBuild | bool | 開発用ビルドかどうか (?) |
graphicsAPI | Array | WebGL1, 2のどちらを使うか (?) |
splashScreenStyle | string | スプラッシュ画面のスタイル |
useWasm | bool | WebAssemblyを使っているか (?) |
usingWasm | bool | WebAssemblyを使っているか (?) |
canvas | HTMLCanvasElement | Unity の描画領域の canvas タグ |
ctx | WebGL2RenderingContext | Unity の描画領域の操作用 (?) |
メソッド
名前 | 説明 |
---|---|
pauseMainLoop() | メインループの停止 |
resumeMainLoop() | メインループの再開 |
resolveBuildUrl(buildUrl) | Buildフォルダ以下の場所を返す |
setCanvasSize(width, height, noUpdates) | 描画領域のサイズ変更 |
setWindowTitle(title) | ウインドウのタイトルを変更 |
streamingAssetsUrl() | ストリーミングアセットのURL (?) |
Console で色々試す時に、 pauseMainLoop() を事前に呼び出しておくと負荷が下がって便利です。
設定用 JSON ファイルの内容
{
"companyName": "DefaultCompany",
"productName": "ProductName",
"productVersion": "0.1",
"dataUrl": "xxxx.data.unityweb",
"wasmCodeUrl": "xxxx.wasm.code.unityweb",
"wasmFrameworkUrl": "xxxx.wasm.framework.unityweb",
"graphicsAPI": ["WebGL 2.0","WebGL 1.0"],
"webglContextAttributes": {"preserveDrawingBuffer": false},
"splashScreenStyle": "Dark",
"backgroundColor": "#231F20",
"cacheControl": {"default": "must-revalidate"},
"developmentBuild": true,
"multithreading": false,
"unityVersion": "2019.2.2f1"
}
xxxx のところには、出力時に設定したファイルの名前が入ります。
それぞれの項目に設定可能な値は今のところ不明です。
Unityのビルド時に生成されたメインスクリプトの中身を見る
次のコードを入力することで、メインスクリプトの内容が見れるようです。
今のところ詳しくは調べていません。
unityInstance.Module.mainScriptUrlOrBlob.text().then((s) => {console.log(s)})
"mainScriptUrlOrBlob" とあるように、実際には URL が返されるケースがあるのかもしれません。
Blobが返された場合は、上のコードで動きます。
Console 画面の、赤で囲んだところをクリックしてもメインスクリプトを見ることができます。表示されたら、全選択してエディタにコピペすると見やすいです。約 25,000 行ほどあります。
メインスクリプトの内容について
システムコール
___syscall から始まる関数で OS のシステムコールのような機能が実装されています。番号が振られていますが、どの番号がどの機能かまでは今のところ調べきれていません。
OS のシステムコールとはどのようなものかというと、例えば WebAssembly 内から擬似的にファイルシステムのような機能を使う仕組みがあります。具体的には fopen() のような C 言語の関数を実行できるようになっています。
WebAssembly 内に実装された関数からシステムコールを呼び出す時は、適宜 JavaScript で実装された外部関数を呼び出すようになっていて、 JavaScript で擬似的に OS の仕組みが動いているかのように見えるようになっています。なぜそのようになっているかというと、実質的に WebAssembly は基本的な (32 bit または 64 bit の整数、浮動小数点数のような) 型ごとの演算とメモリの操作ができるだけで、モニタに画像を表示したり、キーボードの入力を受け取ったり、システム時間を取得したりするような機能が実行できないようになっているからです。基本的な演算とメモリ操作以外のほぼ全ての機能の実行には、 JavaScript で実装された外部関数呼び出しが必要になります。そのような関数類が、 Emscripten によって ___syscallxxx という関数で提供されています。最近の WebAssembly では WASI という共通の基盤で OS の機能に相当する部分を実装しようという動きがあります。
参考URL
メモリ
メモリは JS で宣言された次の変数の場所に定義されています。
名前 | 用途 |
---|---|
STACK_BASE | スタックの開始位置 |
DYNAMIC_BASE | ヒープの開始位置。STACK_MAX から開始 |
メモリ関連の他の変数には、次のようなものがあります。
名前 | 用途 |
---|---|
TOTAL_STACK | スタックの最大サイズ |
STACK_MAX | STACK_BASE + TOTAL_STACK |
STACKTOP | STACK_BASE と同じ |
DYNAMICTOP_PTR | DYNAMIC_BASE の 4 バイト値を保存する場所 |
DYNAMIC_BASE の値は、初期化時に DYNAMICTOP_PTR が指すポインタの位置に保存されます。
// HEAP32 のインデックス値はポインタを 2 bit 分、右シフトすることで得られる
// アラインメイントが揃っている必要あり
HEAP32[DYNAMICTOP_PTR >> 2] = DYNAMIC_BASE;
DYNAMIC_BASE の値は _malloc() 等で使われているんだろうと思います (未確認)。
メインスクリプトで HEAP32[DYNAMICTOP_PTR >> 2] を検索すると、使われている関数が出てきます。
lengthBytesUTF8の中身
function lengthBytesUTF8(str) {
var len = 0;
for (var i = 0; i < str.length; ++i) {
var u = str.charCodeAt(i);
if (u >= 55296 && u <= 57343) u = 65536 + ((u & 1023) << 10) | str.charCodeAt(++i) & 1023;
if (u <= 127) {
++len;
} else if (u <= 2047) {
len += 2;
} else if (u <= 65535) {
len += 3;
} else if (u <= 2097151) {
len += 4;
} else if (u <= 67108863) {
len += 5;
} else {
len += 6;
}
}
return len;
}
Pointer_stringifyの中身
function Pointer_stringify(ptr, length) {
if (length === 0 || !ptr) return "";
var hasUtf = 0;
var t;
var i = 0;
while (1) {
assert(ptr + i < TOTAL_MEMORY);
t = HEAPU8[ptr + i >> 0];
hasUtf |= t;
if (t == 0 && !length) break;
i++;
if (length && i == length) break;
}
if (!length) length = i;
var ret = "";
if (hasUtf < 128) {
var MAX_CHUNK = 1024;
var curr;
while (length > 0) {
curr = String.fromCharCode.apply(String, HEAPU8.subarray(ptr, ptr + Math.min(length, MAX_CHUNK)));
ret = ret ? ret + curr : curr;
ptr += MAX_CHUNK;
length -= MAX_CHUNK;
}
return ret;
}
return UTF8ToString(ptr);
}
_mallocの中身
_malloc は wasm ファイルの中に実装されています。
ファイルの場所は、 Build/xxxx.wasm.code.unityweb です。
拡張子が違いますが、中身は wasm 形式です。
ツールを使って wasm の内容を wat 形式 (S 式) に戻すと、 6,058,780 行目あたりに見つかります。5,000 行弱ありそうです。おそらく元のコードは Emscripten の中に C で書かれていると思うのですが、リポジトリの場所はまだ調べていません。
(func $_malloc (type $t2) (param $p0 i32) (result i32)
.
.
.
export もされていました。
(export "_malloc" (func $_malloc))
dynCallの中身
dynCall() は JS 側から WebAssembly の関数を呼び出す時に使用します。
function dynCall(sig, ptr, args) {
if (args && args.length) {
assert(args.length == sig.length - 1);
assert("dynCall_" + sig in Module, "bad function pointer type - no table for sig '" + sig + "'");
return Module["dynCall_" + sig].apply(null, [ ptr ].concat(args));
} else {
assert(sig.length == 1);
assert("dynCall_" + sig in Module, "bad function pointer type - no table for sig '" + sig + "'");
return Module["dynCall_" + sig].call(null, ptr);
}
}
実際の使い方は次のようになります。
// 引数
// 0: 関数ポインタの型
// 1: C# メソッドのポインタ (数値)
// 2: C# メソッドに渡す引数の配列
dynCall("vii", callback, [ arg0, arg1 ]);
関数ポインタの型は初めて見た時に面くらいますが、次のような構造になっています。
- 最初の文字: 戻り値の型。上の例では "v"
- 2文字目以降: メソッドに渡す引数の型を順番に書く。上の例では "ii"
1 文字で 1 つの型を表します。
文字 | C#の型 | WebAssemblyの型 |
---|---|---|
v | void | void |
i | int (32-bit) | i32 |
j | long (64-bit) | i64 |
f | float (32-bit) | f32 |
d | double (64-bit) | f64 |
dynCall() の実装を見ると、 Module["dynCall_" + sig] というように関数名を参照しています。実際には、上の例だと "dynCall_vii" のようになります。
なぜこのような戻り値・引数ごとに別の関数になっているかというと、 WebAssembly で実装された、外部に公開する関数は型が明確である必要があるためです。そのため、型ごとの関数が公開されています。実際に Emscripten によって生成された wasm ファイルを S 式に戻したコードの中身を見てみると、 export の項目は次のようになっています。呼び出し可能な戻り値・引数の組を全て公開しているため、かなりの数の関数が export に書かれています。
(export "dynCall_dd" (func $dynCall_dd))
(export "dynCall_ddd" (func $dynCall_ddd))
(export "dynCall_dddi" (func $dynCall_dddi))
(export "dynCall_ddi" (func $dynCall_ddi))
(export "dynCall_dfi" (func $legalstub$dynCall_dfi))
(export "dynCall_di" (func $dynCall_di))
(export "dynCall_diddi" (func $dynCall_diddi))
(export "dynCall_didi" (func $dynCall_didi))
(export "dynCall_dii" (func $dynCall_dii))
(export "dynCall_diii" (func $dynCall_diii))
(export "dynCall_diiii" (func $dynCall_diiii))
(export "dynCall_dji" (func $legalstub$dynCall_dji))
(export "dynCall_f" (func $legalstub$dynCall_f))
.
.
.
注: JavaScript では long 型 (64 ビット) の整数を扱うことはできません。
JS 内での単体の数値は基本的に Number 型になるのですが、扱える範囲が 2^53 - 1 なので、 64 ビットの整数値は 53 ビット分のみ使うことができます。 long を扱う時には注意する必要がありそうです。
WebAssembly の中では 64 ビット整数用の型があります。 JS と値をやり取りする時に問題が生じます。
追記
BigInt を使うことで、 JS でも 64 ビットの整数を WebAssembly とやり取りできるようになる (なった ?) みたいです。
実際に現行のブラウザへ実装されているかは試していないです。
wasm 内での実装
wasm ファイル内で、 例えば dynCall_v は次のような実装になっています。
(func $dynCall_v (type $t1) (param $p0 i32)
local.get $p0
i32.const 2047
i32.and
i32.const 23135
i32.add
call_indirect (type $t18) $env.table)
$p0 には C# メソッドのポインタが渡されます。
ポインタの場所を関数テーブルのインデックス値に変換して呼び出しているようです。
call_indirect は、関数テーブルに登録された関数を、インデックス番号と関数の型で呼び出す命令です。
その他
- HEAPU8[2] と HEAPU8[3] はエンディアンの判定用。
それぞれ 115、 99 である必要がある。
jslib ファイルのテンプレート
var LibraryHogeWebGL = {
$Hoge: {
instances: []
},
JS_Hoge_Hello: function () {
window.alert("Hello, world!");
},
JS_Hoge_Create: function (url) {
try {
url = Pointer_stringify(url);
var socket = new WebSocket(url);
var instance = Hoge.instances.push(socket) - 1;
} catch (e) {
console.error(e);
return -1;
}
return instance;
},
JS_Hoge_Release: function (instance) {
Hoge.instances[instance] = null;
}
};
autoAddDeps(LibraryHogeWebGL, '$Hoge');
mergeInto(LibraryManager.library, LibraryHogeWebGL);
参考URL
- WebGL: ブラウザースクリプトとの相互作用 - Unity マニュアル
- Unity(WebGL)でC#の関数からブラウザー側のJavaScript関数を呼び出すまたはその逆(JS⇒C#)に関する知見(プラグイン形式[.jslib]) - Qiita
jslib に書いたコードの場所
Console で次のように書くと直接呼び出すことができます。
関数名の先頭にアンダーバーが追加されています。
unityInstance.Module.asmLibraryArg._JSxxxx()
Unity側、ブラウザ側の両方に関連すること
C#からJSにバイト配列を渡すテンプレート
using System.Runtime.InteropServices;
public class Hoge {
[DllImport("__Internal")]
static extern void JS_Hoge_TestByteArray(byte[] data);
public void TestByteArray() {
// 配列を渡したつもりだけど、実際には HEAPxx 上のポインタが渡される。
byte[] data = new byte[] { 1, 2, 3, 4 };
JS_Hoge_TestByteArray(data);
}
}
JS_Hoge_TestByteArray: function (dataPtr) {
console.log(HEAPU8[dataPtr]);
console.log(HEAPU8[dataPtr + 1]);
console.log(HEAPU8[dataPtr + 2]);
console.log(HEAPU8[dataPtr + 3]);
}
C#からJSに文字列を渡すテンプレート
using System.Runtime.InteropServices;
public class Hoge {
[DllImport("__Internal")]
static extern void JS_Hoge_TestString(string str);
public void TestString() {
// 文字列を渡したつもりだけど、実際には HEAPxx 上のポインタが渡される。
string str = "test";
JS_Hoge_TestString(str);
}
}
// Pointer_stringify() にポインタを渡すと文字列になる
JS_Hoge_TestString: function (strPtr) {
var str = Pointer_stringify(strPtr);
console.log(str);
}
JSからC#に文字列を返すテンプレート
var str = 'result';
var length = lengthBytesUTF8(str) + 1;
var buffer = _malloc(length);
stringToUTF8(str, buffer, length);
return buffer;
lengthBytesUTF8(), _malloc(), stringToUTF8() は Emscripten の関数です。
stringToUTF8() については、次のURLに説明があります。
lengthBytesUTF8(), _malloc() は公式のドキュメントが見つけられませんでした。
JSからC#に配列を返すテンプレート
下のコードをそのまま書いて試したわけではないので、動かなかった、もしくは適切な書き方が分かるかたはコメント欄で教えて頂けると助かります。
(実際には WebSocket の onmessage コールバックで動作することを確認しました。)
using AOT;
using System;
using System.Runtime.InteropServices;
public class Hoge {
[DllImport("__Internal")]
static extern void JS_Hoge_Callback(Action<IntPtr, int> callback);
public void Test() {
JS_Hoge_Callback(HogeCallback);
}
// static でないと呼び出せない
[MonoPInvokeCallback(typeof(Action<IntPtr, int>))]
static void HogeCallback(IntPtr ptr, int length) {
byte[] data = new byte[length];
Marshal.Copy(ptr, data, 0, length);
for (int i = 0; i < data.Length; i++) {
UnityEngine.Debug.LogFormat("{0}: {1}", i, data[i]);
}
}
}
JS_Hoge_Callback: function (callback) {
var data = new ArrayBuffer(8);
var buffer = _malloc(data.byteLength);
writeArrayToMemory(new Uint8Array(data), buffer);
dynCall('vii', callback, [ buffer, data.byteLength ]);
// 注: _free()を呼び出したほうがいいかも
}
byte[] ではなく int[] 等を返す場合は、 length 引数に data.byteLength を渡す代わりに TypedArray.prototype.length を渡す必要がありそうです。
(次のコードは実際に動かして確認したことがないので、動かなかった場合、もしくはより良い書き方、解決策をご存知のかたはコメントを頂けると助かります。)
// int[] が返せるであろうというコード (未確認)
// data のバイトサイズが 4 の倍数でないと Int32Array を作成する時にエラーが出る
// Uncaught RangeError: byte length of Int32Array should be a multiple of 4
var data = new ArrayBuffer(8);
var buffer = _malloc(data.byteLength);
var array = new Int32Array(data);
// アラインメントが揃っているのか疑問
HEAP32.set(array, buffer / 4);
dynCall('vii', callback, [ buffer, array.length ]);
Marshal.Copy の説明は次の場所に書かれています。
ArrayBuffer の説明は次の場所に書かれています。
writeArrayToMemory() の説明は次の場所に書かれています。
C#でインスタンス管理をする時のテンプレート
JSから呼び出される、C#のコールバックメソッドはstaticである必要があるため、C#側でもインスタンス番号と実際のインスタンスを紐付けるリストを持つ必要が出てきます。JSからC#のコールバックメソッドを呼び出すタイミングで、JS内で管理されているインスタンス番号を一緒に渡すと、C#側でもインスタンスを特定することができます (すみません、自分でも読みにくい文になってしまいました)。
コールバックを扱う時は多くのケースで、このサンプルに近いものが必要になると思います。
using AOT;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
public class Hoge {
[DllImport("__Internal")]
static extern int JS_Hoge_Create();
[DllImport("__Internal")]
static extern void JS_Hoge_Release(int instance);
[DllImport("__Internal")]
static extern void JS_Hoge_RegisterCallback1(int instance, Action<int> callback);
[DllImport("__Internal")]
static extern void JS_Hoge_Call1(int instance);
// インスタンス管理用
static Dictionary<int, Hoge> _instances;
// インスタンスの番号
readonly int _instance;
// static コンストラクタ
// プログラム開始時に一度だけ実行される
static Hoge() {
_instances = new Dictionary<int, Hoge>();
}
// コンストラクタ
public Hoge() {
_instance = JS_Hoge_Create();
if (_instance < 0) {
UnityEngine.Debug.LogError("error 1: new Hoge()");
return;
}
if (_instances.ContainsKey(_instance)) {
UnityEngine.Debug.LogError("error 2: new Hoge()");
return;
}
_instances.Add(_instance, this);
// ここでコールバックメソッドを JS 側に登録しておく
JS_Hoge_RegisterCallback1(_instance, Callback1);
}
// 後処理用
public void Dispose() {
JS_Hoge_Release(_instance);
if (_instances.ContainsKey(_instance)) {
_instances.Remove(_instance);
}
}
// コールバックの呼び出しサンプル
public void Call1() {
JS_Hoge_Call1(_instance);
}
[MonoPInvokeCallback(typeof(Action<int>))]
static void Callback1(int instance) {
if (_instances.ContainsKey(instance) == false) {
UnityEngine.Debug.LogError("error 1: Callback1()");
return;
}
var hoge = _instances[instance];
if (hoge == null) {
UnityEngine.Debug.LogError("error 2: Callback1()");
return;
}
UnityEngine.Debug.Log("Callback1() called");
}
}
// 例: WebSocket を扱う JS
var LibraryHogeWebGL = {
$Hoge: {
instances: [],
callbacks: {}
},
JS_Hoge_Create: function () {
try {
var socket = new WebSocket('ws://localhost');
var instance = Hoge.instances.push(socket) - 1;
// コールバックのリストを初期化
Hoge.callbacks[instance] = {
callback1: null
};
} catch (e) {
console.error(e);
return -1;
}
return instance;
},
JS_Hoge_Release: function (instance) {
Hoge.instances[instance] = null;
Hoge.callbacks[instance] = null;
},
JS_Hoge_RegisterCallback1: function (instance, callback) {
// コールバックを登録する処理のサンプル
var callbacks = Hoge.callbacks[instance];
if (callbacks == null) {
console.error('error: JS_Hoge_RegisterCallback1()');
return;
}
callbacks.callback1 = callback;
},
JS_Hoge_Call1: function (instance) {
// コールバックの呼び出しサンプル
var callbacks = Hoge.callbacks[instance];
if (callbacks == null) {
console.error('error: JS_Hoge_Call1()');
return;
}
dynCall('vi', callbacks.callback1, [ instance ]);
}
};
autoAddDeps(LibraryHogeWebGL, '$Hoge');
mergeInto(LibraryManager.library, LibraryHogeWebGL);
このサンプルも改善の余地があると思いますので、改善方法をご存知のかたはコメント欄で教えて頂けると助かります。