Search Unity

Resolved Is there any limitations to deserializing json on webgl?

Discussion in 'Web' started by jmgek, Mar 9, 2022.

  1. jmgek

    jmgek

    Joined:
    Dec 9, 2012
    Posts:
    177
    2020.3.22f
    I'm using the newtonsoft nugat (forced library on us) package to deseralize to my object over a http request, I send out one request get an ID then fire off another to get that info. I hit the first endpoint and that returns and deserializes fine on the editor play and webgl build:


    Code (CSharp):
    1. public struct CurrentNode{
    2.     public uint id { get; set; }
    3.     public uint start_time{ get; set; }
    4. }
    5.  
    6. public struct CurrentBattleData{
    7.     public uint timestamp{ get; set; }
    8.     public CurrentNode previous{ get; set; }
    9.     public CurrentNode next { get; set; }
    10. }

    But as soon as I build out for webgl I get the issue with trying to deserialize the below struct: `System.TypeInitializationException: The type initializer for 'System.Collections.Generic.List<ChampionData>'`

    Code (CSharp):
    1. public struct Match
    2. {
    3.     public ChampionData[] champions { get; set; }
    4.     public BattleData[] battles { get; set; }
    5.     public InfoData info { get; set; }
    6. }

    That goes further as champions have properties and those properties have properties, so I have a funny feeling I'm just hitting some sort of limit of the deserializer. If I only keep the `info` it works and it deseralizes properly.

    I'm kind of lost here so any help would be appreciated. I have a feeling it's the newtonsoft package but can't switch to another package to test, since there seems to be no way I can remove the package from my package dist.


    Code (CSharp):
    1.         try
    2.         {
    3.             using (var www = UnityEngine.Networking.UnityWebRequest.Get(url))
    4.             {
    5.                 www.SetRequestHeader("Content-Type", "application/json");
    6.  
    7.                 var operation = www.SendWebRequest();
    8.  
    9.                 while (!operation.isDone)
    10.                 {
    11.                     await Task.Yield();
    12.                 }
    13.  
    14.                 if (www.result != UnityEngine.Networking.UnityWebRequest.Result.Success)
    15.                 {
    16.                     Debug.Log("Not successful");
    17.                 }
    18.  
    19.                 var result = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(www.downloadHandler.text);
    20.                 return result;
    21.  
    22.             }
    23.         }
    24.         catch (System.Exception ex)
    25.         {
    26.               Debug.Log(ex);
    27.             return default;
    28.         }
     
    Last edited: Mar 10, 2022
  2. De-Panther

    De-Panther

    Joined:
    Dec 27, 2009
    Posts:
    589
    Google newtonsoft unity AOT.

    There's code stripping that might remove the init of ChampionData
     
  3. jmgek

    jmgek

    Joined:
    Dec 9, 2012
    Posts:
    177
    Thanks, I Installed 3.0 and the same errors, here's the full stack:


    Code (CSharp):
    1. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.TypeInitializationException: The type initializer for 'System.Collections.Generic.List<ChampionData>' threw an exception. ---> System.ExecutionEngineException: Attempting to call method 'System.Collections.Generic.List`1[[ChampionData, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]::.cctor' for which no ahead of time (AOT) code was generated.
    2.  
    3.    --- End of inner exception stack trace ---
    4.  
    5.    --- End of inner exception stack trace ---
    6.   at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0
     
  4. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,931
    The fundamental problem is that IL2CPP must know about every value type T that is used in the generic type List<T>. The serialization code uses List<ChampionData>, but not explicitly, so IL2CPP does not know to generate code for it.

    You have a few options here. First, you can create some unused static method that uses the type List<ChampionData>. That will tell IL2CPP to generate for for that type, so that this error will be avoided. However, you may hit other AOT errors for similar types. You would need to explicitly write code to use all of them.

    Another option is to use Unity 2021.2 or later. There is a new Build Settings option named "IL2CPP Code Generation". Choosing the option "Smaller code" there will work around this issue by generating one implementation for any T in List<T>.

    Finally, you can use Unity 2022.1 or later, where this general List<T> code is always generated, and will be used if there is not specific code for a given T.
     
    GraemeS, Coderious, OceanX000 and 2 others like this.
  5. jmgek

    jmgek

    Joined:
    Dec 9, 2012
    Posts:
    177
    Thank you Josh, updating to 2021 solved my issues.
     
    JoshPeterson likes this.
  6. R1PFake

    R1PFake

    Joined:
    Aug 7, 2015
    Posts:
    540
    I think I have a similar issue, I use reflection to create a generic type (with the MakeGenericType method) and then use the Activator to create the instance, so the IL2CPP system never "sees" the actual generic type usage by just scanning the code.

    I im using Unity 2021.3 LTS but I can't see the "smaller code" option, only "faster runtime" or "faster build" and neither of these work for my use case. Was this setting removed or am I missing something?

    I have many of these cases for serialization / modding support, so creating a "dummy" type for every possible combination would be a pain.

    I wanted to use the C# backend, but now im forced to use IL2CPP (otherwise my app will not be accepted to the store, the C# version works fine, but they ignore my request to allow it)

    Update: It looks like the option "faster build" works, I selected the wrong folder before.
    I see new IL2CPP entries for the generic types which are created with MakeGenericType and the runtime error is gone, for example:

    Code (CSharp):
    1. // ConditionFactory`2<Unity.IL2CPP.Metadata.__Il2CppFullySharedGenericType,Unity.IL2CPP.Metadata.__Il2CppFullySharedGenericType>
    2. struct ConditionFactory_2_t7370ACCCC9FBC9BEE165E4172D785BCA640EBA67;
     
    Last edited: Jun 10, 2022
  7. Gluttonium

    Gluttonium

    Joined:
    Aug 19, 2021
    Posts:
    31
    Just throwing it out there but I noticed that when trying to deserialize to certain objects, that you can't use properties. If converting these properties to straight fields the build won't complain.

    And if the custom types used in collections are stripped because they're not used elsewhere, you could use the AotHelper class to ensure its inclusion. e.g.

    Code (CSharp):
    1. AotHelper.Ensure(() =>
    2.         {
    3.             _ = new Queue<string>(Array.Empty<string>());
    4.         });
    Dependant on the used types ofcourse. And it needs to be invoked, for example by putting it in the awake of new a class inheriting from monobehaviour
     
    Last edited: Jun 10, 2022
  8. jamie_xr

    jamie_xr

    Joined:
    Feb 28, 2020
    Posts:
    67
    Hey, I've just ran into this AOT issue @JoshPeterson . on 2021.3, I noticed that IL2CPP code generation build setting has 2 options "Faster Runtime" and "Faster (smaller) builds" rather than "Smaller code". Of course I want faster runtime. It can't be slowing down the runtime that much to include some extra types can it?
     
  9. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,931
    Another name for the "Faster (smaller) builds" option could be "Smaller code".

    The performance difference really depends on your use case. If your code has hot paths in generic methods with value types, then you will see a performance degradation. But if those methods are not hot paths, then likely the fact they are are slower will not be noticeable.

    The situation is a bit better in Unity 2022 - you might want to try that if possible. In that version "Faster Runtime" will fallback to the slower code path only when necessary. So in Unity 2022 I like this think of the options like this:
    • "Faster Runtime" = optimize for run time performance
    • "Faster (smaller) builds" = optimize for code size
     
  10. jamie_xr

    jamie_xr

    Joined:
    Feb 28, 2020
    Posts:
    67
    Ok i'll explain my use case a bit:

    I construct a generic type using type.MakeGenericType and instantiate it via Activator. Once I have the object I then cast it to a more abstract (non generic) type, and hit it's functions from there.

    The class itself is a serializer, and since it's a networked game I use it quite a lot. Technically I only use the generics to instantiate it? Will this cause the degredation you talk of?

    The long term plan is to upgrade to 2022, but if that's just a fallback, it will still be slower right?

    Perhaps a better workaround for me is to instantiate the serializers with the generic value types I know about manually without reflection, and pool them somewhere, then rely on reflection for the others. I can probably catch 95% of use cases with standard built-in primitives, there are just a few user defined types that I won't know about at library level. I can also probably provide an API that project specific game code can hit to add their own typed serializers to the pool
     
  11. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,931
    Yes, I think that it will. Since you are using reflection to create the generic type, IL2CPP cannot know at compile time how to generic the code. So It must generate a general section of code that can work for any generic type argument, and looses out on opportunities the C++ compiler has to evaluate that code.

    This sounds like a good work around.
     
    jamie_xr likes this.
  12. jamie_xr

    jamie_xr

    Joined:
    Feb 28, 2020
    Posts:
    67
    Thanks for the explanation. How does it work with reference types, is there still the same performance hit?
     
  13. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,931
    There is no performance hit for reference types. The generated generic code can be shared across all of them, since all reference types are the same size (the size of a pointer).
     
    jamie_xr likes this.
  14. GRXkiller

    GRXkiller

    Joined:
    Oct 16, 2017
    Posts:
    1
    @JoshPeterson

    Hi Josh, I have a similar problem in my project.
    It works fine when running in the editor but when I compile into webgl and run it on my webpage, I get the following error:

    upload_2023-10-13_0-20-14.png

    I'm using Unity 2020.2.7f1 and also the newtonsoft library to convert a json text into a Unity class.

    It crashes here:
    upload_2023-10-13_0-21-23.png

    And this is the GameData object:

    upload_2023-10-13_0-22-1.png


    As I menctioned, this error only happens on webgl build.

    Any idea on how could i fix this?

    Thank you in advance...
     
  15. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,931
    I'm not sure why this error occurs. If possible, can you build and run this project in a standalone player with the IL2CPP scripting backend (an option that is set in the Player Settings)? That will run similar code to the WebGL build, but might be easier to debug.
     
  16. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
    I'd like to add to this conversation. I get the error

    Unreachable code should not be executed (evaluating 'dynCall_iiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9)')

    on Unity 2021, and on Unity 2023,

    JsonSerializationException: Unable to find a constructor to use for type mytype

    To fix the issue, I have to set the stripping level to minimal. I let code optimization runtime speed and IL2CPP Faster runtime. Using smaller code had no effect.

    upload_2024-1-6_5-8-31.png


    On Unity 2022 I could not fix the problem though.