WebGL Unity error when hot loading scripts

Background

We’re working on a game running on the browser via Unity WebGL. Because of specific browser features, we needed to create a custom web interface and inject messages into the game via the React Unity WebGL library.

To make our testing environment easier (since testing on the fly for WebGL games is challenging without knowing Node.js), we have a system where you can upload any builds, and it will automatically show up on the page and be loaded to the custom web interface (live demo: play.witchone.io).

The logic is simple: I store a list of build metadata (URLs to the framework.js, loader.js, .wasm, data, streaming assets folder, etc.) and load them to the component when the player selects one from the drop-down option.

The Issue

An error message pops up when I load a game binary and switch to another remote one. In the live environment, players can ignore this error and start playing without issues (though this isn’t a solution). However, the Uncaught Runtime Error overlay blocks the preview during development, so it’s hard to test certain things.

My guess is that the error happened because the framework.js couldn’t unload gracefully and caused the page to have a dangling reference. But I don’t know how to fix it.

Error Logs

This error will occur when you switch the build after the game loads.

TypeError: canvas is null
    _JS_DOM_MapViewportCoordinateToElementLocalCoordinate http://localhost:3000/unity/Build/witchone.framework.js:1781
    createExportWrapper http://localhost:3000/unity/Build/witchone.framework.js:1052
    mouseEventHandlerFunc http://localhost:3000/unity/Build/witchone.framework.js:10471
    mouseEventHandlerFunc http://localhost:3000/unity/Build/witchone.framework.js:10472
    jsEventHandler http://localhost:3000/unity/Build/witchone.framework.js:9292
lockdown-install.js:1:94626

After the first error, the two errors will follow:

TypeError: mWorkletNode is null
    onmessage http://localhost:3000/unity/Build/witchone.framework.js line 10278 > eval:1
    OutputAudioWorklet_resumeAudio http://localhost:3000/unity/Build/witchone.framework.js line 10278 > eval:1
    promise callback*OutputAudioWorklet_resumeAudio/< http://localhost:3000/unity/Build/witchone.framework.js line 10278 > eval:1
    promise callback*OutputAudioWorklet_resumeAudio http://localhost:3000/unity/Build/witchone.framework.js line 10278 > eval:1
    7005703 http://localhost:3000/unity/Build/witchone.framework.js:1306
    _emscripten_asm_const_int http://localhost:3000/unity/Build/witchone.framework.js:8471
    createExportWrapper http://localhost:3000/unity/Build/witchone.framework.js:1052
    invoke_iii http://localhost:3000/unity/Build/witchone.framework.js:15916
    createExportWrapper http://localhost:3000/unity/Build/witchone.framework.js:1052
    invoke_viii http://localhost:3000/unity/Build/witchone.framework.js:15905
    createExportWrapper http://localhost:3000/unity/Build/witchone.framework.js:1052
    invoke_iiii http://localhost:3000/unity/Build/witchone.framework.js:15938
lockdown-install.js:1:94626
Invoking error handler due to
TypeError: mWorkletNode is null
OutputAudioWorklet_resumeAudio/</</mWorkletNode.port.onmessage@http://localhost:3000/unity/Build/witchone.framework.js line 10278 > eval:1:937
EventHandlerNonNull*OutputAudioWorklet_resumeAudio/</<@http://localhost:3000/unity/Build/witchone.framework.js line 10278 > eval:1:858
promise callback*OutputAudioWorklet_resumeAudio/<@http://localhost:3000/unity/Build/witchone.framework.js line 10278 > eval:1:467
promise callback*OutputAudioWorklet_resumeAudio@http://localhost:3000/unity/Build/witchone.framework.js line 10278 > eval:1:412
EventListener.handleEvent*7005703@http://localhost:3000/unity/Build/witchone.framework.js:1306:10
_emscripten_asm_const_int@http://localhost:3000/unity/Build/witchone.framework.js:8471:26
FMODZ::OutputAudioWorklet::initCallback(FMOD_OUTPUT_STATE*, int, unsigned int, int*, FMOD_SPEAKERMODE*, int*, FMOD_SOUND_FORMAT*, int, int*, int*, void*)@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[7773]:0x24465b
FMODZ::System::init(int, unsigned int, void*)@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[9574]:0x2eca3f
System_FMOD_Studio_System_Initialize_mFB0456B8271844C4E6661D8557F6E1C4D180E903@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[14086]:0x3c3e47
System_initialize_m2E2BF9789F2BE742D58A97A61B9E0DCEBC839D0C@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[14085]:0x3c3d02
RuntimeManager_Initialize_mFEE7DA41F7459614BDAA9C41ED1603AD878EFBF4@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[42598]:0xa7eb57
dynCall_iii@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[138016]:0x23e3ebb
unityFramework/createExportWrapper/<@http://localhost:3000/unity/Build/witchone.framework.js:1052:20
invoke_iii@http://localhost:3000/unity/Build/witchone.framework.js:15916:10
RuntimeManager_get_Instance_mA38324D3D54A4EB27BDED775364C07DA8C3EAD12@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[42596]:0xa7e4f2
RuntimeManager_PathToGUID_mCB35BA0F40882F6BCA66094F31FC11E608967316@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[42658]:0xa827a7
dynCall_viii@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[138028]:0x23e3f6a
unityFramework/createExportWrapper/<@http://localhost:3000/unity/Build/witchone.framework.js:1052:20
invoke_viii@http://localhost:3000/unity/Build/witchone.framework.js:15905:15
RuntimeManager_CreateInstance_mB5613913B946498BBF3B847EEDAD1AE121BFA04E@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[42665]:0xa82f23
MainMusic_SpawnMusic_mFE4807DD6027739606D49BA469BB3262ACDFD21F@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[51196]:0xc5e1ba
MainMusic_Awake_m1F0989727ABA74B75AA97204666733AFCF6DA3DA@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[51195]:0xc5e146
RuntimeInvoker_TrueVoid_t4861ACF8F4594C3437BB48B6E56783494B843915(void (*)(), MethodInfo const*, void*, void**, void*)@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[114805]:0x18db5d5
il2cpp::vm::Runtime::InvokeWithThrow(MethodInfo const*, void*, void**)@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[117468]:0x190f22b
dynCall_iiii@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[138019]:0x23e3eea
unityFramework/createExportWrapper/<@http://localhost:3000/unity/Build/witchone.framework.js:1052:20
invoke_iiii@http://localhost:3000/unity/Build/witchone.framework.js:15938:10
il2cpp::vm::Runtime::Invoke(MethodInfo const*, void*, void**, Il2CppException**)@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[117446]:0x190e743
il2cpp_runtime_invoke@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[117504]:0x1910605
ScriptingInvocation::Invoke(ScriptingExceptionPtr*, bool)@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[135080]:0x22fed68
ScriptingInvocation::InvokeChecked(ScriptingExceptionPtr*)@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[135085]:0x22fef6c
SerializableManagedRef::CallMethod(Object&, ScriptingMethodPtr)@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[120070]:0x19933b7
MonoBehaviour::CallAwake()@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[135038]:0x22fb8cd
MonoBehaviour::AddToManager()@http://localhost:3000/unity/Build/witchone.wasm:wasm-function[135040]:0x22fbd8a witchone.loader.js:304:13

Any help would be much appreciated.

Found a quick solution (need more testing to see if it’s the full solution).

Basically, React Unity WebGL has a unload function that you can call before switching the build. However, you should NOT remove the canvas ref when doing this. Instead, it’s better to have a custom UI behavior to indicate that the game was unloaded and then do the build switch.