colyseus-unity-sdk
colyseus-unity-sdk copied to clipboard
JsonSerializationException when receiving a message from server in WebGL export
When exporting my Unity project as a WebGL application, the following exception is thrown during runtime:
JsonSerializationException: Unable to deserialize instance of MyMessage because there is no parameterless constructor is defined on type.
This prevents any data sent by the server through messages to be deserialized and used on the client, which is a major problem.
After investigating on the issue with my local fork, I found that it originates from the TryGetConstructor
method, at line 200:
if (AotRuntime || type.IsAbstract || type.IsInterface)
return false;
It seems that AotRuntime
is set to true for WebGL and iOS platforms, as shown in line 43 of the same file.
#if ((UNITY_WEBGL || UNITY_IOS || ENABLE_IL2CPP) && !UNITY_EDITOR)
AotRuntime = true;
#else
I don't really know what this parameter corresponds to, but it seems to cause the issue I describe here. My quick and dirty fix was to comment the AotRuntime
part of the condition at line 200, but I can't tell if it was there for a reason in the first place. What should be done to fix this?
Interesting, thanks for the report @sticmac. If you can could you share the version of Unity you're using?
EDIT: Also, are you possibly using "Managed Code Stripping"? If enabled your issue could be related to that (https://github.com/colyseus/colyseus-unity-sdk/issues/135)
Thank you for your quick answer @endel. I'm using Unity 2021.3.2f1 LTS and the managed code stripping option is activated and set to minimal level.
Hi @sticmac, would you mind providing the full stack trace for us? And perhaps a small reproduction scenario where we can check this error happening?
The problem seems to happen while receiving any message containing a custom object in its payload while running the client as a WebGL application. In my case, I have this script for managing players connections (spawning characters for players already connected and for newly connected players).
public class PlayersConnection : MonoBehaviour
{
[Header("Components")]
[SerializeField] PlayerSpawner _playerSpawner;
[Header("Models")]
[SerializeField] IntObservableModel _playersCounter;
private class ConnectedPlayersMessage {
public Player[] players;
}
private void OnEnable() {
StartCoroutine(OnEnableCoroutine());
}
private IEnumerator OnEnableCoroutine() {
// Basically my method to wait until the client is connected to a room on the colyseus server
yield return new WaitUntil(() => ColyseusManager.Instance.CurrentRoom != null);
_playersCounter.Value = 1; // reset player counter (counting the player themselves)
ColyseusRoom<GameRoomState> currentRoom = ColyseusManager.Instance.CurrentRoom;
// Fetching already connected players to instantiate them
currentRoom
.OnMessage<ConnectedPlayersMessage>("connectedPlayers", (message) => {
_playersCounter.Value = 1; // reset player counter (counting the player themselves)
Player[] players = message.players;
foreach (Player p in players) {
PlayerJoin(p);
}
});
// Instantiates a newly joining player
currentRoom.OnMessage<Player>("playerJoin", (p) => PlayerJoin(p));
// and removes the ones leaving
currentRoom.OnMessage<Player>("playerLeave", (p) => PlayerLeave(p));
// We tell the server that we are ready to recieve already connected players
currentRoom.Send("playerReady");
}
/// <summary>
/// Increases player counter and make the player spawn if they're in the room
/// </summary>
/// <param name="p">The model of the player who joined</param>
private void PlayerJoin(Player p) {
if (p.position.room == SceneManager.GetActiveScene().buildIndex) {
_playerSpawner.InstantiatePlayer(p);
}
_playersCounter.Value++; // increases player counter after adding joining player
}
/// <summary>
/// Decreases player counter and dispawn the player if they're in the room
/// </summary>
/// <param name="p">The model of the player who joined</param>
private void PlayerLeave(Player p) {
if (p.position.room == SceneManager.GetActiveScene().buildIndex) {
_playerSpawner.RemovePlayer(p);
}
_playersCounter.Value--; // decreases player counter after removing leaving player
}
}
My schema classes are generated with schema-codegen
:
public partial class Player : Schema {
[Type(0, "string")]
public string id = default(string);
[Type(1, "string")]
public string username = default(string);
[Type(2, "ref", typeof(Position))]
public Position position = new Position();
}
public partial class Position : Schema {
[Type(0, "number")]
public float room = default(float);
[Type(1, "number")]
public float x = default(float);
[Type(2, "number")]
public float y = default(float);
[Type(3, "number")]
public float z = default(float);
}
The above PlayersConnection
scripts works perfectly fine when running from the editor. However, when building a WebGL application, exposing it through a local HTTP server on 8080 port and running it in my browser, I get this error in the browser console (development build):
JsonSerializationException: Unable to deserialize instance of 'PlayersConnection+ConnectedPlayersMessage' because there is no parameterless constructor is defined on type.
at GameDevWare.Serialization.Metadata.TypeDescription.CreateInstance () [0x00000] in <00000000000000000000000000000000>:0
--- End of stack trace from previous location where exception was thrown ---
WebGLClient.framework.js:1798:11
_JS_Log_Dump http://127.0.0.1:8080/Build/WebGLClient.framework.js:1798
WebGLPrintfConsolev(LogType, char const*, void*) http://127.0.0.1:8080/Build/WebGLClient.wasm:21852580
InternalErrorConsole(char const*, ...) http://127.0.0.1:8080/Build/WebGLClient.wasm:21856477
DebugStringToFilePostprocessedStacktrace(DebugStringToFileData const&) http://127.0.0.1:8080/Build/WebGLClient.wasm:21855962
DebugStringToFile(DebugStringToFileData const&) http://127.0.0.1:8080/Build/WebGLClient.wasm:21853157
Scripting::LogExceptionFromManaged(ScriptingExceptionPtr, int, char const*, bool, Scripting::LogExceptionFromMangedSettings const*) http://127.0.0.1:8080/Build/WebGLClient.wasm:21196712
ScriptingInvocation::Invoke(ScriptingExceptionPtr*, bool) http://127.0.0.1:8080/Build/WebGLClient.wasm:21200125
void ScriptingInvocation::Invoke<void>(ScriptingExceptionPtr*, bool) http://127.0.0.1:8080/Build/WebGLClient.wasm:21200627
InitPlayerLoopCallbacks()::UpdateScriptRunDelayedTasksRegistrator::Forward() http://127.0.0.1:8080/Build/WebGLClient.wasm:21034722
ExecutePlayerLoop(NativePlayerLoopSystem*) http://127.0.0.1:8080/Build/WebGLClient.wasm:11521830
ExecutePlayerLoop(NativePlayerLoopSystem*) http://127.0.0.1:8080/Build/WebGLClient.wasm:11521986
MainLoop() http://127.0.0.1:8080/Build/WebGLClient.wasm:19732962
MainLoopUpdateFromBackground(void*) http://127.0.0.1:8080/Build/WebGLClient.wasm:19711104
dynCall_vi http://127.0.0.1:8080/Build/WebGLClient.wasm:21972628
createExportWrapper http://127.0.0.1:8080/Build/WebGLClient.framework.js:1036
_emscripten_set_interval http://127.0.0.1:8080/Build/WebGLClient.framework.js:9974
_emscripten_set_interval http://127.0.0.1:8080/Build/WebGLClient.framework.js:9975
(Asynchrone : setInterval handler)
_emscripten_set_interval http://127.0.0.1:8080/Build/WebGLClient.framework.js:9972
main http://127.0.0.1:8080/Build/WebGLClient.wasm:19738445
createExportWrapper http://127.0.0.1:8080/Build/WebGLClient.framework.js:1036
callMain http://127.0.0.1:8080/Build/WebGLClient.framework.js:16994
doRun http://127.0.0.1:8080/Build/WebGLClient.framework.js:17037
run http://127.0.0.1:8080/Build/WebGLClient.framework.js:17049
runCaller http://127.0.0.1:8080/Build/WebGLClient.framework.js:16977
removeRunDependency http://127.0.0.1:8080/Build/WebGLClient.framework.js:991
unityFileSystemInit http://127.0.0.1:8080/Build/WebGLClient.framework.js:37
doCallback http://127.0.0.1:8080/Build/WebGLClient.framework.js:4865
done http://127.0.0.1:8080/Build/WebGLClient.framework.js:4876
oncomplete http://127.0.0.1:8080/Build/WebGLClient.framework.js:4232
(Asynchrone : EventHandlerNonNull)
reconcile http://127.0.0.1:8080/Build/WebGLClient.framework.js:4230
syncfs http://127.0.0.1:8080/Build/WebGLClient.framework.js:4003
<anonyme> http://127.0.0.1:8080/Build/WebGLClient.framework.js:4092
(Asynchrone : EventHandlerNonNull)
getRemoteSet http://127.0.0.1:8080/Build/WebGLClient.framework.js:4089
onsuccess http://127.0.0.1:8080/Build/WebGLClient.framework.js:4039
(Asynchrone : EventHandlerNonNull)
getDB http://127.0.0.1:8080/Build/WebGLClient.framework.js:4036
getRemoteSet http://127.0.0.1:8080/Build/WebGLClient.framework.js:4079
syncfs http://127.0.0.1:8080/Build/WebGLClient.framework.js:3999
getLocalSet http://127.0.0.1:8080/Build/WebGLClient.framework.js:4072
syncfs http://127.0.0.1:8080/Build/WebGLClient.framework.js:3997
syncfs http://127.0.0.1:8080/Build/WebGLClient.framework.js:4883
syncfs http://127.0.0.1:8080/Build/WebGLClient.framework.js:4879
unityFileSystemInit http://127.0.0.1:8080/Build/WebGLClient.framework.js:35
unityFramework http://127.0.0.1:8080/Build/WebGLClient.framework.js:40
callRuntimeCallbacks http://127.0.0.1:8080/Build/WebGLClient.framework.js:1186
preRun http://127.0.0.1:8080/Build/WebGLClient.framework.js:867
run http://127.0.0.1:8080/Build/WebGLClient.framework.js:17025
runCaller http://127.0.0.1:8080/Build/WebGLClient.framework.js:16977
removeRunDependency http://127.0.0.1:8080/Build/WebGLClient.framework.js:991
receiveInstance http://127.0.0.1:8080/Build/WebGLClient.framework.js:1103
receiveInstantiationResult http://127.0.0.1:8080/Build/WebGLClient.framework.js:1110
(Asynchrone : promise callback)
instantiateAsync http://127.0.0.1:8080/Build/WebGLClient.framework.js:1130
(Asynchrone : promise callback)
instantiateAsync http://127.0.0.1:8080/Build/WebGLClient.framework.js:1128
createWasm http://127.0.0.1:8080/Build/WebGLClient.framework.js:1149
unityFramework http://127.0.0.1:8080/Build/WebGLClient.framework.js:14006
loadBuild http://127.0.0.1:8080/Build/WebGLClient.loader.js:1052
(Asynchrone : promise callback)
loadBuild http://127.0.0.1:8080/Build/WebGLClient.loader.js:1051
createUnityInstance http://127.0.0.1:8080/Build/WebGLClient.loader.js:1095
createUnityInstance http://127.0.0.1:8080/Build/WebGLClient.loader.js:1080
onload http://127.0.0.1:8080/:71
(Asynchrone : EventHandlerNonNull)
<anonyme> http://127.0.0.1:8080/:70
So the problem seems to happen while deserializing my connectedPlayers
message (which is, indeed, the first message I shall receive).
The quick and dirty fix I described in the original post was to change line 200 of Assets/Colyseus/Runtime/GameDevWare.Serialization/Metadata/MetadataReflection.cs
file to this:
if (/*AotRuntime ||*/ type.IsAbstract || type.IsInterface)
return false;
By doing so, the application now works on a WebGL platform.
Hope it helps! :) Ping me if you need any more details, I wrote this very quickly
I like dirty fix :) And yes , it's works for me thanks @sticmac
Hi everyone, I'm having the same problem.
if (/*AotRuntime ||*/ type.IsAbstract || type.IsInterface)
return false;
Doing so can fix the problem of WEBGL in Ios. But it still doesn't work in Mac system (safari).
if you can target sub .net version it can fix your issue too.
I'm having the same issue, and i juste downgrade .net to 2.1 (but keep il2cpp enabled)
i'm using package manager to import colyseus, then i cannot change the MetadataReflection.cs file.
Same issue here, colyseus onMessage room method not supporting custom class with il2cpp. Workaround used : Send your message as string (use JSON.stringify) and deserialize it manually in the onMessage (with JsonUtility.FromJson). No modification needed in colyseus package.
workaround c# client side code
public static void OnStringMessage<MessageType, S>(this ColyseusRoom<S> room, string type, Action<MessageType> handler) where S : Schema
{
room.OnMessage<string>(type, (messageString) =>
{
var message = JsonUtility.FromJson<MessageType>(messageString);
handler(message);
});
}
// Usage
room.OnStringMessage<MessageType, State>("message", callback);
Look like assembly issue because i can use Colyseus classes as custom message.
Here an exemple with lobby room message
room.OnMessage<List<ColyseusRoomAvailable>>("rooms", onRooms);
Sorry for pinging @endel but this issue is still tagged as Missing Minimal Reproduction and hasn't seen real progress for months as far as I know. Do you need more information than what I've provided in my third message?
Hi @sticmac. Did you tried solutions from #135?
Hi @deniszykov, @endel already mentioned them indeed. But as stated in a previous message, at the time I raised this issue, I was using Unity 2021.3.2f1 LTS and the managed code stripping option was activated and set to minimal level.
The only working fix I've been using since then was to comment AotRuntime
as stated in my second message here.
Yep it is a bug, AotRuntime
check should be few lines down. I will make PR.
Thanks to @deniszykov's PR (https://github.com/colyseus/colyseus-unity-sdk/pull/210) this has been finally fixed on latest version (0.14.21)