colyseus-unity-sdk icon indicating copy to clipboard operation
colyseus-unity-sdk copied to clipboard

JsonSerializationException when receiving a message from server in WebGL export

Open sticmac opened this issue 2 years ago • 5 comments

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?

sticmac avatar Jul 11 '22 07:07 sticmac

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)

endel avatar Jul 11 '22 12:07 endel

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.

sticmac avatar Jul 11 '22 13:07 sticmac

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?

endel avatar Jul 20 '22 12:07 endel

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

sticmac avatar Jul 20 '22 18:07 sticmac

I like dirty fix :) And yes , it's works for me thanks @sticmac

xhacker5000 avatar Sep 28 '22 10:09 xhacker5000

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).

yty avatar Nov 18 '22 02:11 yty

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) image

i'm using package manager to import colyseus, then i cannot change the MetadataReflection.cs file.

etherny avatar Nov 27 '22 02:11 etherny

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);

etherny avatar Dec 29 '22 22:12 etherny

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);

etherny avatar Dec 29 '22 22:12 etherny

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?

sticmac avatar Dec 30 '22 21:12 sticmac

Hi @sticmac. Did you tried solutions from #135?

deniszykov avatar Jan 21 '23 12:01 deniszykov

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.

sticmac avatar Jan 21 '23 14:01 sticmac

Yep it is a bug, AotRuntime check should be few lines down. I will make PR.

deniszykov avatar Jan 21 '23 14:01 deniszykov

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)

endel avatar Jan 24 '23 17:01 endel