Le mécanisme sous-jacent du «return return www» d'Unity3D Game Engine

14

Dans le moteur de jeu Unity3D, une séquence de code commune pour obtenir des données à distance est la suivante:

WWW www = new WWW("http://remote.com/data/location/with/texture.png");
yield return www;

Quel est le mécanisme sous-jacent ici?

Je sais que nous utilisons le mécanisme de rendement afin de permettre le traitement de la prochaine image pendant le téléchargement. Mais que se passe-t-il sous le capot lorsque nous faisons le yield return www?

Quelle méthode est appelée (le cas échéant, sur la classe WWW)? Unity utilise-t-il des threads? La couche Unity "supérieure" s'empare-t-elle de l'instance www et fait-elle quelque chose?

ÉDITER:

  • Cette question concerne spécifiquement les composants internes d'Unity3D. Je ne suis pas intéressé par les explications sur le fonctionnement de l' yieldinstruction en C #. Au lieu de cela, je cherche une vue intérieure de la façon dont Unity traite ces constructions, pour permettre, par exemple, à WWW de télécharger une donnée de manière distribuée sur plusieurs trames.
thyandrecardoso
la source
1
Notez que l'utilisation yield returnpour les opérations asynchrones est un hack. Dans un "vrai" programme C #, vous utiliseriez un Taskpour cela. Unity ne les utilise probablement pas car il a été créé avant .Net 4.0, lors de Taskson introduction.
BlueRaja - Danny Pflughoeft

Réponses:

8

Il s'agit du mot-clé C # yield en action - il ne fait rien de spécial avec l' wwwobjet, il signifie plutôt quelque chose de spécial pour la méthode dans laquelle il est contenu. Plus précisément, ce mot-clé ne peut être utilisé que dans une méthode qui renvoie un IEnumerable(ou IEnumerator), et est utilisé pour indiquer quel objet sera "renvoyé" par l'énumérateur lorsque MoveNext est appelé.

Cela fonctionne parce que le compilateur convertit la méthode entière en une classe distincte qui implémente IEnumerable(ou IEnumerator) à l'aide d'une machine d'état - le résultat net est que le corps de la méthode elle-même n'est pas exécuté jusqu'à ce que quelqu'un énumère via la valeur de retour. Cela fonctionnera avec n'importe quel type, il n'y a absolument rien de spécial WWW, c'est plutôt la méthode conteneur qui est spéciale.

Jetez un œil aux coulisses du mot-clé de rendement C # pour en savoir plus sur le type de code généré par le compilateur C #, ou expérimentez et inspectez le code vous-même en utilisant quelque chose comme IL Spy


Mise à jour: pour clarifier

  • Lorsque Unity appelle une coroutine contenant une yield returninstruction, tout ce qui se passe est qu'un énumérateur est renvoyé - aucun des corps de méthode n'est exécuté à ce stade
  • Pour que le corps de la méthode s'exécute, Unity doit appeler MoveNextl'itérateur afin d'obtenir la première valeur de la séquence. Cela provoque l'exécution de la méthode jusqu'à la première yeild returninstruction, moment auquel l'appelant reprend (et vraisemblablement Unity continue de rendre le reste de la trame)
  • Si je comprends bien, Unity continue ensuite d'appeler la MoveNextméthode sur l'itérateur une fois par trame suivante, ce qui fait que la méthode s'exécute à nouveau jusqu'à l' yield returninstruction suivante une fois par trame, jusqu'à ce que la fin de la méthode ou une yield breakinstruction soit atteinte (indiquant la fin de la séquence)

Le bit spécial seulement ici (et dans deux ou trois d' autres cas ) est que l' unité ne fait pas avancer ce iterator particulier la trame suivante, au lieu , il avance que la iterator ( ce qui provoque la méthode pour poursuivre l' exécution) lorsque le téléchargement est terminé. Bien qu'il semble y avoir une classe YieldInstruction de base qui contient vraisemblablement un mécanisme générique pour signaler à Unity quand un itérateur doit être avancé, la WWWclasse ne semble pas hériter de cette classe, donc je ne peux que supposer qu'il existe un cas spécial pour cette classe dans le moteur Unity.

Juste pour être clair - le yieldmot-clé ne fait rien de spécial pour la WWWclasse, c'est plutôt le traitement spécial que Unity donne aux membres de l'énumération retournée qui provoque ce comportement.


Mettez à jour la seconde: en ce qui concerne le mécanisme qui WWWutilise pour télécharger les pages Web de manière asynchrone, il utilise probablement la méthode HttpWebRequest.BeginGetResponse qui utilisera en interne des E / S asynchrones ou bien il pourrait utiliser des threads (soit en créant un thread dédié, soit en utilisant un pool de threads).

Justin
la source
5
En fait, dans Unity, quelque chose de spécial arrive à un WWWobjet quand il est cédé, voir WWWla référence de .
Eric
9

yieldsemble être principalement utilisé dans Unity dans un contexte de coroutine. Pour en savoir plus sur les coroutines et pourquoi ils utilisent les C #, yieldje recommande cet article de blog: les coroutines Unity3D en détail . La plupart des recherches dans cette réponse proviennent de cet article.

Les coroutines dans Unity sont utilisées pour encapsuler des tâches qui:

  1. Le rendu d'une image peut prendre plus de temps (entraînant ainsi des ralentissements), et
  2. Peut être effectué séparément de la boucle de jeu (car le résultat n'a pas besoin d'être disponible pour l'image actuelle).

Des exemples de ces types de tâches sont les (re) calculs d'orientation ou, comme c'est le cas dans votre question, l'obtention de données à partir d'un site Web.

Pour répondre à vos sous-questions (dans un ordre légèrement modifié):

Quelle méthode est appelée (le cas échéant, sur la classe WWW)? La couche Unity "supérieure" s'empare-t-elle de l'instance www et fait-elle quelque chose?

La WWWclasse d'Unity est conçue pour être produite à partir d'une coroutine. Selon les commentaires sur l'article de blog lié ci-dessus, le bloc de code spéculatif (la couche "" supérieure ") sur YieldInstructions contient en fait un commutateur qui vérifie également les WWWs produits. Ce code s'assure alors que la coroutine se terminera automatiquement une fois le téléchargement terminé, comme décrit dans WWWla référence de .

Unity utilise-t-il des threads?

Dans ce cas, pour télécharger les données "sans bloquer le reste du jeu": oui, très probablement. (Et le filetage est définitivement utilisé pour décompresser les données téléchargées, comme en témoigne WWW.threadPriority.)

Eric
la source
agréable! J'ai vu le commentaire altdevblogaday.com/2011/07/07/unity3d-coroutines-in-detail/… et il semble que WWW soit traité spécialement. J'aimerais donc savoir comment cela se fait, afin de permettre le téléchargement sur plusieurs frames, sans utiliser de threads?
thyandrecardoso
Bon point! Je suppose qu'il doit y avoir un filetage à ce niveau après tout, je vais modifier ma réponse pour refléter cela.
Eric
1
@thyandrecardoso Je suppose qu'il utilise HttpWebRequest.BeginGetResponse ou similaire, mais vous pouvez décompiler l'assembly pour le confirmer si cela vous importe vraiment.
Justin
pour l'instant, j'attends vraiment la "meilleure explication" ... ce serait génial si quelqu'un de l'équipe de développement Unity donnait la "bonne réponse" 8 -) ... au final, je pense que la vraie implémentation ne sera pas loin de ceux déjà donnés ici ... Je n'ai pas vraiment besoin de décompiler l'assemblage et je sais tout cela avec certitude. Mais je pourrais essayer plus tard :)
thyandrecardoso
7

Malheureusement, WWW est implémenté en interne en tant que code natif, ce qui signifie que nous ne pouvons pas regarder le code. Par expérimentation, je peux dire que

  1. WWWn'est pas dérivé de YieldInstruction, donc quoi qu'il arrive quand vous yielddevez le gérer par un code de cas spécial.
  2. Je n'ai jamais observé de différence entre

    yield return www;

    et

    while(!www.isDone)
        yield return null;

    Je pense que c'est la façon la plus logique de le mettre en œuvre, et c'est probablement ce qui se passe sous le capot. Mais je n'en suis pas sûr.

  3. Unity ne démarre pas de nouveau thread à télécharger, du moins sur certaines plateformes (iOS, webplayer). Ou si c'est le cas, il se place WWW.isDonesur le thread principal. Je le sais parce que ce code:

    while(!www.isDone)
        Thread.Sleep(500);

    ne fonctionne pas.

Je ne pense pas que vous puissiez avoir des réponses plus spécifiques à moins qu'une personne ayant accès au code source d'Unity3d vienne ici.

Ça ne fait rien
la source
Ouaip. Des idées vraiment sympas! Merci! Même si vous ne lancez pas un thread en soi, la chose la plus probable qui pourrait se produire est WWW (ou la couche moteur) en utilisant HttpWebRequest.BeginGetResponse (ou quelque chose comme ça) ... non? Quelque chose de complètement asynchrone doit se produire de toute façon ... le téléchargement ne peut pas être "suspendu".
thyandrecardoso
** ne peut pas être "suspendu" entre les images, je veux dire.
thyandrecardoso
2

Comme Unity3D utilise C # comme moteur de script, je suppose que c'est le mot-clé de rendement standard intégré à C #. Fondamentalement, cela signifie qu'il renvoie déjà la valeur de www afin que vous puissiez continuer tandis que la prochaine itération, il retournera la valeur suivante, etc ... Le rendement crée essentiellement une machine à états et un itérateur en arrière-plan.

Roy T.
la source
Oui, je pense avoir les notions de base sur le mot-clé yield. Cependant, que se passe-t-il pendant le constructeur de WWW qui permet cette "machine d'état"? Je veux dire, "yield return www" ne semble pas appeler quoi que ce soit à l'intérieur de la classe WWW ... Lorsque vous dites "cela signifie qu'il renvoie déjà la valeur de www", quelle est la valeur d'instance www? Que fera cette instance lors de la prochaine "itération"?
thyandrecardoso
1
Dans les coroutines Unity, la production de WWW est un cas particulier, voir WWWréférence . De plus, je ne suis pas sûr de yieldcréer quoi que ce soit. Le contexte d'itérateur est créé en l'implémentant IEnumerableou en l'utilisant comme type de retour. "State machine" semble également éteint. Bien sûr, il y a un État, mais cela seul n'est pas une propriété suffisante, non? Peut-être pourriez-vous nous en dire plus.
Eric
1
Voici une bonne référence sur le comportement normal de C #. shadowcoding.blogspot.nl/2009/01/yield-and-c-state-machine.html . Yield génère beaucoup de code pour garder une trace de sa position dans l'itérateur. À propos du cas spécial d'Unity donnant WWW, je ne le savais pas, mais selon les documents, il n'a rien à faire avec le mot clé C # normal, c'est très déroutant, ils pourraient simplement en faire une méthode asynchrone.
Roy T.