1

I've created a class that inherits from List to be used on my Unity-firebase project porpouses.

using System.Collections.Generic;

public class FirestoreList<FirestoreData, PlainData> : List<FirestoreData>, IConvertToPlainData<List<PlainData>>
    where FirestoreData : IConvertToPlainData<PlainData>
    where PlainData : IConvertToFirestore<FirestoreData>
{
    public FirestoreList() : base() { }
    public FirestoreList(List<PlainData> plainDataList) <-- this is the 8th line
    {
        this.Clear();
        foreach (PlainData plainData in plainDataList)
        {
            this.Add(plainData.ToFirestoreData());
        }
    }

    public List<PlainData> ToPlainData()
    {
        List<PlainData> convertedPlainDataList = new List<PlainData>();
        if (this.Count > 0)
        {
            foreach (FirestoreData firestoreData in this)
            {
                convertedPlainDataList.Add(firestoreData.ToPlainData());
            }
        }

        return convertedPlainDataList;
    }
}

The interfaces are the following ones (basically functioning as a Serialize/Deserialize):

public interface IConvertToPlainData<T>
{
    T ToPlainData();
}

public interface IConvertToFirestore<T>
{
    T ToFirestoreData();
}

This code works on the Editor, on runtime etc, but when I try to build using IL2CPP (cause I'm targeting mobile stores) the following error on the build appears:

IL2CPP error for method 'System.Void FirestoreList`2::.ctor(System.Collections.Generic.List`1<PlainData>)' in FirestoreList.cs:8
Additional information: Unable to retrieve the runtime generic context for 'System.Collections.Generic.List`1<PlainData>'.

I don't understand the error message, and I didn't find any similar references on google, all that I have is this link about Unity and IL2CPP ScriptingRestrictions :(

Can someone help me or at least point me to the answer? Thanks!

EDIT: An example of the classes that I use to show the generic parameter that I use to instantiate FirestoreList:

[Serializable]
public class User : IConvertToFirestore<User_FirestoreData>
{
  [SerializeField] public List<User> usersList = new List<User>();

  public User_FirestoreData ToFirestoreData()
  {
    User_FirestoreData userFire = new User_FirestoreData()
    {
      usersList = new FirestoreList<User_FirestoreData, User>(this.usersList)
    };

    return userFire;
  }

}

[FirestoreData]
public class User_FirestoreData : IConvertToPlainData<User>
{
  [FirestoreProperty] public FirestoreList<User_FirestoreData, User> usersList { get; set; }

  public User ToPlainData()
  {
    User user = new User()
    {
      usersList = this.usersList.ToPlainData(),
    };
    return user;
  }

}

EDIT2: My thoughs are that when I'm using this.usersList.ToPlainData() there's no specification of what is FirestoreData and PlainData, so the compiler can't know what cast to do (or something similar).

For the moment the only solution have been to remove the FirestoreList class and implement a couple of extension methods for List like these ones:

public static List<PlainData> ToPlainData<PlainData, FirestoreData>(this List<FirestoreData> list) where FirestoreData : IConvertToPlainData<PlainData> where PlainData : IConvertToFirestore<FirestoreData>
{
    List<PlainData> convertedPlainDataList = new List<PlainData>();
    if (list.Count > 0)
    {
        foreach (FirestoreData firestoreData in list)
        {
            convertedPlainDataList.Add(firestoreData.ToPlainData());
        }
    }

    return convertedPlainDataList;
}

public static List<FirestoreData> ToFirestoreData<FirestoreData, PlainData>(this List<PlainData> list) where FirestoreData : IConvertToPlainData<PlainData> where PlainData : IConvertToFirestore<FirestoreData>
{
    List<FirestoreData> convertedFirestoreDataList = new List<FirestoreData>();
    if (list.Count > 0)
    {
        foreach (PlainData plainData in list)
        {
            convertedFirestoreDataList.Add(plainData.ToFirestoreData());
        }
    }

    return convertedFirestoreDataList;
}

As you are forced to call it like this: this.charactersList.ToPlainData<User, User_FirestoreData>() seems like there is no problem with the JIT or AOT.

Lotan
  • 4,078
  • 1
  • 12
  • 30
  • With what generic parameter did you instantiate FirestoreList? – TinglePan Nov 24 '21 at 09:40
  • By the way, dynamic does not work on il2cpp. I personally encontered a lot of weird problems using dynamic when I did not know this. – TinglePan Nov 24 '21 at 09:44
  • @TinglePan I've updated the question, hope it answers your first comment! – Lotan Nov 24 '21 at 10:04
  • I think the [Serializable] tag of User class is suspecious. The error message seems to indicate that User class not fully loaded at the time you instantiate your FirestoreList. The link you post mentioned something related to code generation with types in the "Serialzation" section. I'm not sure though. – TinglePan Nov 25 '21 at 03:12
  • @TinglePan removed the [Serializable] property, and still won't work :( – Lotan Nov 25 '21 at 07:40
  • I found this [link](https://stackoverflow.com/questions/37238197/how-can-i-generate-any-generic-type-at-runtime-on-aot-platforms). Does that help? – TinglePan Nov 26 '21 at 06:32

1 Answers1

1

Generics used like this are not going to work in Unity on all platforms. From Microsoft documentation:

When a generic type is first constructed with a value type as a parameter, the runtime creates a specialized generic type with the supplied parameter or parameters substituted in the appropriate locations in the MSIL. Specialized generic types are created one time for each unique value type that is used as a parameter.

This is called Just In Time (JIT) compilation, and is not supported in Unity using IL2CPP (only Mono). From Unity's documentation:

The IL2CPP backend converts MSIL (Microsoft Intermediate Language) code (for example, C# code in scripts) into C++ code, then uses the C++ code to create a native binary file (for example, .exe, .apk, or .xap) for your chosen platform. This type of compilation, in which Unity compiles code specifically for a target platform when it builds the native binary, is called ahead-of-time (AOT) compilation.

In other words, when you compile your app, it creates a native binary that does not have the JIT functionality in C#/.NET.

Additionally:

Some platforms don’t support AOT compilation, so the IL2CPP backend doesn’t work on every platform. Other platforms support AOT and IL2CPP, but don’t allow JIT compilation, and so can’t support the Mono backend.

This is why it works on some platforms, but not others.

Chris
  • 444
  • 2
  • 12
  • But usually when the error is related with this, It prints something about AOT, this is not the case of my error :( – Lotan Nov 25 '21 at 07:39