Aperçu de l'article : IL2CPP Full Generic Sharing dans Unity 2022.1 beta

Le partage générique intégral vous permet d'écrire un code plus expressif et plus facile à tester. Non seulement elle élimine toute une série d'erreurs de script détectables au moment de l'exécution, mais elle garantit également un comportement plus prévisible du code sur des plates-formes telles que les appareils mobiles et les consoles. Lisez la suite pour savoir comment faire.
Les générateurs sont des fonctionnalités puissantes de C#. Ils permettent au code d'exprimer des comportements indépendamment des types. En tant que développeurs, nous nous attendons à ce que List<string> se comporte comme List<int> ou List<T>, où T est un type quelconque.
Depuis des années, l'IL2CPP utilise le partage générique dans les cas où T est un type de référence (chaîne de caractères, objet, etc.). Cela fonctionne bien parce que les types de référence en Objective-C sont toujours représentés par un pointeur, de sorte que la taille et l'implémentation de List<string> correspondront à la taille et à l'implémentation de List<object>. Mais que se passe-t-il si T est un int (quatre octets) sur un système 64 bits (où les pointeurs sont de huit octets) ? IL2CPP doit générer un code spécial pour List<int>, List<double>, List<MyValueType>, etc.
C'est pourquoi dans Unity 2022.1, IL2CPP génère déjà un code spécial qui peut gérer List<T> pour n'importe quel type T, référence ou valeur. Cette technologie s'appelle le partage générique intégral.
Alors que les méthodes virtuelles génériques sont des caractéristiques expressives de C# qui fonctionnent bien avec la compilation juste à temps (JIT), elles sont difficiles à mettre en œuvre pour les cas de compilation en avance sur le temps (AOT), tels que IL2CPP. C'est là qu'intervient le partage générique intégral.
Examinons un exemple de méthode virtuelle générique tiré du manuel Unity:
using UnityEngine;
using System;
public class AOTProblemExample : MonoBehaviour, IReceiver
{
public enum AnyEnum
{
Zero,
One,
}
void Start()
{
// Subtle trigger: The type of manager *must* be
// IManager, not Manager, to trigger the AOT problem.
IManager manager = new Manager();
manager.SendMessage(this, AnyEnum.Zero);
}
public void OnMessage<T>(T value)
{
Debug.LogFormat("Message value: {0}", value);
}
}
public class Manager : IManager
{
public void SendMessage<T>(IReceiver target, T value) {
target.OnMessage(value);
}
}
public interface IReceiver
{
void OnMessage<T>(T value);
}
public interface IManager
{
void SendMessage<T>(IReceiver target, T value);
}
Ce code démontre l'expressivité des méthodes virtuelles génériques. En d'autres termes, nous pouvons envoyer des données de n'importe quel type (le "message") depuis n'importe quelle classe implémentant l'interface IManager vers n'importe quelle classe implémentant l'interface IReceiver. Avec IL2CPP dans Unity 2021.2, ce code apparemment simple ne fonctionne pas. Lors de l'exécution, l'erreur suivante apparaît dans le journal du lecteur :
ExecutionEngineException: Attempting to call method 'Test::OnMessage<Test+AnyEnum>' for which no ahead of time (AOT) code was generated. Consider increasing the --generic-virtual-method-iterations=1 argument
at Manager.SendMessage[T] (IReceiver target, T value) [0x00000] in <00000000000000000000000000000000>:0
at Test.Start () [0x00000] in <00000000000000000000000000000000>:0
Décortiquons cette erreur.
Étant donné que l'appel à Send Message dans la méthode Start se fait par l'intermédiaire d'une interface (IManager, c'est-à-dire la partie "virtuelle" de generic virtual), IL2CPP ne détecte pas quelle méthode sera appelée au moment de l'exécution lorsque le code est compilé.
Vous vous posez peut-être la question : Pourquoi l'IL2CPP n'y arrive-t-il pas ? Eh bien, c'est possible ! Il est possible pour IL2CPP de rechercher tout le code disponible au moment de la compilation et de déterminer les endroits où cet appel peut se retrouver. Mais cette recherche est coûteuse ; elle prend un temps précieux pendant que vous attendez que le projet se construise, et elle peut amener IL2CPP à générer du code supplémentaire qui ne sera jamais appelé, augmentant ainsi la taille finale de l'exécutable.
L'argument --generic-virtual-method-iterations (mentionné dans le message d'erreur) vous permet d'indiquer à IL2CPP le temps qu'il doit consacrer à la recherche. Pour un compilateur JIT, ce type d'appel de méthode virtuelle générique est très simple. Il peut "voir" la méthode cible au moment de l'exécution et faire ce qu'il faut. Dans Unity 2022.1, IL2CPP a appris la même astuce. Il génère désormais une nouvelle version spéciale de SendMessage - la version entièrement partagée.
Cela fonctionnera quel que soit le type T, référence ou valeur, ce qui signifie que si IL2CPP ne peut pas voir quelle devrait être la méthode cible au moment de la compilation, il appellera cette version entièrement partagée à la place. Le code C# est tout aussi expressif, fonctionne à l'exécution et se compile aussi rapidement.
La technologie du partage générique intégral est incroyablement utile, car elle permet au code des plates-formes AOT de se comporter beaucoup plus comme le code des plates-formes JIT. Cela permet de réduire les surprises au moment de l'exécution.
Il s'avère que ces erreurs ExecutionEngineException apparaissent également dans d'autres cas. Cette erreur peut se produire lorsque IL2CPP ne parvient pas à déterminer le code à exécuter. Nous voyons souvent cela dans les sérialiseurs, lorsque de nouvelles données sérialisées sont désérialisées vers un type que IL2CPP ne peut pas deviner. Mais dans Unity 2022.1, IL2CPP ne produit plus d'ExecutionEngineException, éliminant ainsi toute une classe d'erreurs difficiles à rectifier.
Tenez également compte du fait que certains codes utilisent des types génériques récursifs imbriqués. Comme IL2CPP peut continuer à traiter ces types à l'infini au moment de la compilation, nous devons imposer une limite au temps que doit prendre le processus de construction.
IL2CPP produisait l'erreur suivante lorsque votre code avait besoin de certains de ces types profondément imbriqués au moment de l'exécution : "IL2CPP a rencontré un type géré qu'il ne peut pas convertir à l'avance. Le type utilise des types génériques ou des types de tableaux, qui sont imbriqués au-delà de la profondeur maximale pouvant être convertie". Aujourd'hui, le partage générique intégral permet à IL2CPP d'utiliser une implémentation qui n'échoue jamais, de sorte que vous ne rencontrerez plus ce message d'erreur.
Imaginez que vous ayez un projet que vous souhaitez redimensionner et rendre aussi petit que possible. Si vous disposez d'un code exécutable pour List<int>, List<double> et List<string>, vous devriez peut-être aussi réfléchir à la possibilité d'équilibrer autant d'implémentations différentes.
Ne serait-il pas formidable d'avoir une seule implémentation générique entièrement partagée pour tout List<T> ? Vérifiez l'option de génération de code IL2CPP "Constructions plus rapides (plus petites)" dans les paramètres du lecteur. Il s'appuie sur le partage générique intégral pour vous donner le code exécutable le plus petit possible avec le temps de construction le plus court possible - sans parler des constructions incrémentielles rapides. Si vous décidez d'utiliser List<DateTime> (ou tout autre T) dans le projet, IL2CPP n'a plus besoin de générer ou de compiler un nouveau code pour cette implémentation.
Si vous souhaitez commencer à écrire du code qui exploite le partage générique complet IL2CPP, il vous suffit de télécharger Unity 2022.1 beta depuis le Unity Hub ou sur notre page de téléchargement. N'oubliez pas que la version bêta n'est pas destinée à être utilisée dans des projets en phase de production ; veillez donc à sauvegarder vos projets existants.
Cela dit, nous aimerions savoir comment Unity 2022.1 fonctionne pour vous. N'hésitez pas à visiter le forum de la version bêta pour nous faire part de vos commentaires. Nous vous serions reconnaissants de nous faire part de vos commentaires sur le partage générique intégral ou sur toute autre fonctionnalité avec laquelle vous travaillez actuellement. En prime de votre participation, chaque rapport de bogue original et reproductible augmentera vos chances de gagner l'un de nos prix de loterie. Vous trouverez tous les détails dans l'article de blog sur la version bêta.