Der zugrunde liegende Mechanismus in 'Yield Return WWW' von Unity3D Game Engine

14

In der Unity3D-Game-Engine lautet eine übliche Codesequenz zum Abrufen von Remote-Daten wie folgt:

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

Was ist der zugrunde liegende Mechanismus hier?

Ich weiß, dass wir den Ertragsmechanismus verwenden, um die Verarbeitung des nächsten Frames zu ermöglichen, während der Download abgeschlossen ist. Aber was ist unter der Haube los, wenn wir das machen?yield return www ?

Welche Methode wird aufgerufen (falls vorhanden in der WWW-Klasse)? Verwendet Unity Threads? Bekommt die "obere" Unity-Ebene eine WWW-Instanz und tut sie etwas?

BEARBEITEN:

  • Diese Frage bezieht sich speziell auf Unity3D-Interna. Ich bin nicht an Erklärungen interessiert, wie yieldAnweisungen in C # funktionieren. Stattdessen suche ich einen Einblick in den Umgang von Unity mit diesen Konstruktionen, um beispielsweise dem WWW das verteilte Herunterladen von Daten über mehrere Frames zu ermöglichen.
thyandrecardoso
quelle
1
Beachten Sie, dass die Verwendung yield returnfür asynchrone Vorgänge ein Hack ist. In einem "echten" C # -Programm würden Sie Taskhierfür a verwenden. Unity verwendet sie wahrscheinlich nicht, da sie vor der TaskEinführung von .Net 4.0 erstellt wurden .
BlueRaja - Danny Pflughoeft

Antworten:

8

Dies ist das C # yield-Schlüsselwort in Aktion - es führt keine besonderen Aktionen mit dem wwwObjekt aus, sondern bedeutet etwas Besonderes für die Methode, in der es enthalten ist. Insbesondere kann dieses Schlüsselwort nur in einer Methode verwendet werden, die ein IEnumerable(oder IEnumerator) zurückgibt und verwendet wird um anzugeben, welches Objekt vom Enumerator "zurückgegeben" wird, wenn MoveNext aufgerufen wird.

Dies funktioniert, weil der Compiler die gesamte Methode in eine separate Klasse konvertiert, die mithilfe einer Zustandsmaschine implementiert IEnumerable(oder IEnumerator). Das Nettoergebnis ist, dass der Hauptteil der Methode selbst erst ausgeführt wird, wenn jemand den Rückgabewert durchführt. Dies funktioniert mit jedem Typ, es ist absolut nichts Besonderes WWW, sondern die Containing-Methode, die speziell ist.

Werfen Sie einen Blick hinter die Kulissen des Schlüsselworts C # yield, um mehr darüber zu erfahren, welche Art von Code der C # -Compiler generiert, oder testen Sie den Code einfach selbst mit etwas wie IL Spy


Update: Zur Verdeutlichung

  • Wenn Unity eine Coroutine aufruft, die eine yield returnAnweisung enthält , wird lediglich ein Enumerator zurückgegeben. Zu diesem Zeitpunkt wird kein Methodentext ausgeführt
  • Damit der Methodentext ausgeführt werden kann, muss Unity MoveNextden Iterator aufrufen , um den ersten Wert in der Sequenz abzurufen. Dies führt dazu, dass die Methode bis zur ersten yeild returnAnweisung ausgeführt wird. An diesem Punkt setzt der Aufrufer die Ausführung fort (und vermutlich wird der Rest des Frames von Unity wiedergegeben).
  • So wie ich es verstehe, ruft Unity normalerweise die MoveNextMethode für den Iterator bei jedem folgenden Frame erneut auf, wodurch die Methode bis zur nächsten yield returnAnweisung bei jedem Frame erneut ausgeführt wird, bis entweder das Ende der Methode oder eine yield breakAnweisung erreicht ist (was anzeigt) das Ende der Sequenz)

Die einzige spezielle Bit hier (und in einem Paar von anderen Fällen ) ist , dass die Einheit nicht diese besondere Iterator es nur den nächsten Frame, stattdessen geht nicht voran den Iterator (die Methode verursacht weiterhin ausgeführt wird ) , wenn der Download abgeschlossen ist. Obwohl es anscheinend eine Basis- YieldInstruction- Klasse gibt, die vermutlich einen generischen Mechanismus für die Signalisierung an Unity enthält, wenn ein Iterator weiterentwickelt werden soll, WWWscheint die Klasse nicht von dieser Klasse zu erben, sodass ich nur davon ausgehen kann, dass es einen Sonderfall für gibt diese Klasse in der Unity-Engine.

Nur um klar zu sein: Das yieldSchlüsselwort hat keine besonderen Auswirkungen auf die WWWKlasse, sondern auf die spezielle Behandlung, die Unity den Mitgliedern der zurückgegebenen Aufzählung gibt, die dieses Verhalten verursacht.


Aktualisieren Sie die zweite: Für den Mechanismus, WWWmit dem Webseiten asynchron heruntergeladen werden, wird wahrscheinlich entweder die HttpWebRequest.BeginGetResponse-Methode verwendet, die intern asynchrone E / A verwendet, oder es können Threads verwendet werden (entweder ein dedizierter Thread erstellt oder ein Threadpool verwendet werden).

Justin
quelle
5
Tatsächlich passiert in Unity etwas Besonderes mit einem WWWObjekt, wenn es abgegeben wird, siehe WWWReferenz .
Eric
9

yieldscheint hauptsächlich in Unity im Coroutine-Kontext verwendet zu werden. Um mehr über Coroutinen zu erfahren und warum sie C # verwenden, yieldempfehle ich diesen Blog-Artikel: Unity3D-Coroutinen im Detail . Der größte Teil der Forschung in dieser Antwort stammt aus diesem Artikel.

Coroutines in Unity werden verwendet, um Aufgaben zu kapseln, die:

  1. Das Rendern kann länger dauern als ein Frame (was zu Verlangsamungen führt)
  2. Kann separat von der Spielrunde durchgeführt werden (da das Ergebnis für den aktuellen Frame nicht verfügbar sein muss).

Beispiele für solche Aufgaben sind Pfadfindungsberechnungen oder das Abrufen von Daten von einer Website, wie es in Ihrer Frage der Fall ist.

So beantworten Sie Ihre Unterfragen (in einer leicht geänderten Reihenfolge):

Welche Methode wird aufgerufen (falls vorhanden in der WWW-Klasse)? Bekommt die "obere" Unity-Ebene eine WWW-Instanz und tut sie etwas?

Die WWWKlasse von Unity ist so konzipiert, dass sie sich aus einer Koroutine ergibt. Gemäß den Kommentaren zu dem Blog-Artikel, der oben verlinkt wurde, enthält der spekulative Codeblock (die "obere" Schicht) über YieldInstructions tatsächlich einen Schalter, der auch nach ausgegebenen WWWs sucht . Dieser Code stellt dann sicher, dass die Coroutine automatisch beendet wird, wenn der Download abgeschlossen ist, wie in WWWder Referenz beschrieben .

Verwendet Unity Threads?

In diesem Fall zum Herunterladen der Daten "ohne den Rest des Spiels zu blockieren": Ja, höchstwahrscheinlich. (Und Threading wird definitiv verwendet, um die heruntergeladenen Daten zu dekomprimieren WWW.threadPriority.)

Eric
quelle
nett! Ich habe den Kommentar altdevblogaday.com/2011/07/07/unity3d-coroutines-in-detail/… gesehen und es scheint, dass das WWW speziell behandelt wird. Ich möchte also wissen, wie dies erreicht wird, damit der Download über mehrere Frames hinweg ohne Verwendung von Threads erfolgen kann.
thyandrecardoso
Guter Punkt! Ich nehme an, auf dieser Ebene muss doch ein Threading stattfinden. Ich werde meine Antwort bearbeiten, um dies widerzuspiegeln.
Eric
1
@thyandrecardoso Ich schätze, es wird HttpWebRequest.BeginGetResponse oder ähnliches verwendet. Sie können jedoch die Assembly dekompilieren, um dies zu bestätigen, wenn es Ihnen wirklich wichtig ist.
Justin
Im Moment warte ich wirklich nur auf die "beste Erklärung". Wäre großartig, wenn jemand vom Unity-Entwicklerteam die "richtige Antwort" 8 geben würde weit weg von den hier bereits gegebenen ... Ich habe keine Notwendigkeit, die Assembly zu dekompilieren und weiß das alles sicher. Aber ich könnte es später versuchen :)
thyandrecardoso
7

Leider ist das WWW intern als systemeigener Code implementiert, sodass wir den Code nicht anzeigen können. Aus Experimenten kann ich das sagen

  1. WWWwird nicht von abgeleitet YieldInstruction, was auch immer passiert, wenn Sie yieldes von Sonderfall-Code behandelt werden müssen.
  2. Ich habe nie einen Unterschied zwischen gesehen

    yield return www;

    und

    while(!www.isDone)
        yield return null;

    Ich denke, das ist der logischste Weg, es umzusetzen, und höchstwahrscheinlich ist es das, was unter der Haube passiert. Aber ich weiß es nicht genau.

  3. Unity ist nicht einen neuen Thread zum Download starten, zumindest auf einigen Plattformen (iOS, Webplayer). Oder wenn doch, setzt es WWW.isDoneauf den Hauptthread. Ich weiß das, weil dieser Code:

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

    funktioniert nicht

Ich denke nicht, dass Sie spezifischere Antworten haben können, es sei denn, jemand, der Zugriff auf den Quellcode von Unity3d hat, kommt hierher.

Keine Ursache
quelle
Jep. Wirklich schöne Einblicke! Vielen Dank! Auch wenn kein Thread per se gestartet wird, ist das wahrscheinlichste, was passieren kann, WWW (oder die Engine-Ebene) mit HttpWebRequest.BeginGetResponse (oder so ähnlich) ... richtig? In beiden Fällen muss etwas völlig Asynchrones auftreten. Der Download kann nicht "angehalten" werden.
thyandrecardoso
** Kann nicht zwischen Frames "angehalten" werden, meine ich.
thyandrecardoso
2

Da Unity3D C # als Scripting-Engine verwendet, nehme ich an, dass es das Standard- Yield-Schlüsselwort ist, das in C # integriert ist. Grundsätzlich bedeutet dies, dass der Wert von www bereits zurückgegeben wird, so dass Sie fortfahren können, während die nächste Iteration den nächsten Wert zurückgibt usw. Yield erstellt im Hintergrund im Grunde eine Zustandsmaschine und einen Iterator.

Roy T.
quelle
Ja, ich glaube, ich habe die Grundbegriffe zum Yield-Keyword. Was passiert jedoch während des Konstruktors von WWW, der diese "Zustandsmaschine" zulässt? Ich meine, "yield return www" scheint nichts innerhalb der WWW-Klasse aufzurufen ... Wenn Sie sagen "es bedeutet, dass es den Wert von www bereits zurückgibt", was ist der Wert der www-Instanz? Was wird diese Instanz bei der nächsten "Iteration" tun?
thyandrecardoso
1
In Unity-Coroutinen ist das Ausgeben von WWW ein Sonderfall, siehe WWWReferenz . Außerdem bin ich mir nicht sicher, ob ich yieldetwas erschaffe. Der Iteratorkontext wird erstellt, indem er implementiert IEnumerableoder als Rückgabetyp verwendet wird. "State Machine" scheint auch ausgeschaltet zu sein. Sicher, es gibt einen Staat, aber das allein ist kein ausreichendes Eigentum, oder? Vielleicht könnten Sie das näher erläutern.
Eric
1
Hier finden Sie eine gute Referenz zum normalen C # -Verhalten. shadowcoding.blogspot.nl/2009/01/yield-and-c-state-machine.html . Yield generiert eine Menge Code, um zu verfolgen, wo er sich im Iterator befindet. Über Unitys Spezialfall, der zu WWW führt, wusste ich nichts, aber laut Dokumentation hat es nichts mit dem normalen C # -Schlüsselwort zu tun, das ist sehr verwirrend, sie könnten es einfach zu einer asynchronen Methode machen.
Roy T.