Il meccanismo sottostante in "rendimento ritorno www" di Unity3D Game Engine


14

Nel motore di gioco Unity3D, una sequenza di codice comune per ottenere dati remoti è questa:

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

Qual è il meccanismo sottostante qui?

So che utilizziamo il meccanismo di rendimento per consentire l'elaborazione del fotogramma successivo, mentre il download è in fase di completamento. Ma cosa succede sotto il cofano quando facciamo il yield return www?

Quale metodo viene chiamato (se presente, nella classe WWW)? Unity sta usando i thread? Il livello Unity "superiore" si sta impossessando dell'istanza www e sta facendo qualcosa?

MODIFICARE:

  • Questa domanda riguarda in particolare gli interni di Unity3D. Non mi interessano le spiegazioni sul funzionamento yielddell'istruzione in C #. Invece, sto cercando una visione interna di come Unity gestisce queste costruzioni, per consentire, ad esempio, al WWW di scaricare una porzione di dati in modo distribuito su più frame.

1
Si noti che l'utilizzo yield returnper operazioni asincrone è un hack. In un "vero" programma C #, useresti a Taskper questo. Probabilmente Unity non li utilizza perché è stato creato prima di .Net 4.0, quando è Taskstato introdotto.
BlueRaja - Danny Pflughoeft,

Risposte:


8

Questa è la parola chiave C # in azione - non sta facendo nulla di speciale con l' wwwoggetto, piuttosto significa qualcosa di speciale per il metodo in cui è contenuta. In particolare questa parola chiave può essere utilizzata solo in un metodo che restituisce un IEnumerable(o IEnumerator) e viene utilizzata per indicare quale oggetto verrà "restituito" dall'enumeratore quando viene chiamato MoveNext .

Funziona perché il compilatore converte l'intero metodo in una classe separata che implementa IEnumerable(o IEnumerator) usando una macchina a stati - il risultato netto è che il corpo del metodo stesso non viene eseguito fino a quando qualcuno non viene enumerato attraverso il valore restituito. Funzionerà con qualsiasi tipo, non c'è assolutamente nulla di speciale WWW, piuttosto è il metodo di contenimento che è speciale.

Dai un'occhiata a Dietro le quinte della parola chiave yield di C # per avere qualche informazione in più sul tipo di codice generato dal compilatore C #, o semplicemente sperimenta e ispeziona il codice tu stesso usando qualcosa come IL Spy


Aggiornamento: per chiarire

  • Quando Unity chiama una coroutine che contiene yield returnun'istruzione tutto ciò che accade è che viene restituito un enumeratore - nessuno del corpo del metodo viene eseguito a questo punto
  • Per ottenere il corpo del metodo da eseguire, Unity deve chiamare MoveNextl'iteratore per ottenere il primo valore nella sequenza. Questo fa sì che il metodo venga eseguito fino alla prima yeild returnistruzione, a quel punto il chiamante riprende (e presumibilmente Unity continua a rendere il resto del frame)
  • A quanto ho capito, Unity normalmente continua a chiamare il MoveNextmetodo sull'iteratore una volta per ogni fotogramma successivo, facendo sì che il metodo venga eseguito nuovamente fino yield returnall'istruzione successiva una volta per ogni fotogramma, fino a raggiungere la fine del metodo o yield breakun'istruzione (che indica la fine della sequenza)

L'unico bit speciale qui (e in un paio di altri casi ) è che Unity non fa avanzare questo particolare iteratore al fotogramma successivo, invece fa avanzare l'iteratore (facendo continuare l'esecuzione del metodo) al termine del download. Sebbene sembra esserci una classe YieldInstruction di base che presumibilmente contiene un meccanismo generico per segnalare a Unity quando un iteratore dovrebbe essere avanzato, la WWWclasse non sembra ereditare da questa classe, quindi posso solo supporre che ci sia un caso speciale per questa classe nel motore Unity.

Giusto per essere chiari: la yieldparola chiave non fa nulla di speciale per la WWWclasse, piuttosto è la gestione speciale che Unity dà ai membri dell'enumerazione restituita che causa questo comportamento.


Aggiorna il secondo: per quanto riguarda il meccanismo che WWWutilizza per scaricare le pagine Web in modo asincrono, probabilmente utilizza il metodo HttpWebRequest.BeginGetResponse che utilizzerà internamente IO asincrono o in alternativa potrebbe utilizzare i thread (creando un thread dedicato o utilizzando un pool di thread).


5
In realtà, in Unity, qualcosa di speciale accade ad un WWWoggetto quando viene ceduto, vedi WWWil riferimento .
Eric

9

yieldsembra essere utilizzato principalmente in Unity in un contesto coroutine. Per saperne di più sulle coroutine e sul perché usano C #, yieldraccomando questo articolo del blog: coroutine Unity3D in dettaglio . La maggior parte della ricerca in questa risposta proviene da quell'articolo.

Le coroutine in Unity vengono utilizzate per incapsulare attività che:

  1. Può richiedere più tempo rispetto al rendering di un fotogramma (causando in tal modo rallentamenti) e
  2. Può essere eseguito separatamente dal loop di gioco (perché il risultato non deve essere disponibile per il frame corrente).

Esempi di questo tipo di attività sono i (ri) calcoli di pathfinding o, come nel caso della tua domanda, ottenere dati da un sito Web.

Per rispondere alle tue domande (in un ordine leggermente modificato):

Quale metodo viene chiamato (se presente, nella classe WWW)? Il livello Unity "superiore" si sta impossessando dell'istanza www e sta facendo qualcosa?

La WWWclasse di Unity è progettata per essere ceduta da un coroutine. Secondo i commenti sull'articolo del blog linkato sopra, il blocco speculativo di codice (il "livello" superiore ") su YieldInstructions in realtà contiene un interruttore che controlla anche la presenza di WWWs. Questo codice assicura quindi che la procedura terminerà automaticamente al termine del download, come descritto nel WWWriferimento .

Unity sta usando i thread?

In questo caso, per scaricare i dati "senza bloccare il resto del gioco": sì, molto probabilmente. (E il threading è sicuramente usato per decomprimere i dati scaricati, come evidenziato da WWW.threadPriority.)


simpatico! Ho visto il commento altdevblogaday.com/2011/07/07/unity3d-coroutines-in-detail/… e sembra che il WWW sia trattato in modo speciale. Quindi mi piacerebbe sapere come questo viene realizzato, al fine di consentire il download su più frame, senza utilizzare i thread?
thyandrecardoso,

Buon punto! Suppongo che dopo tutto ci debba essere qualche threading a quel livello, modificherò la mia risposta per riflettere questo.
Eric

1
@thyandrecardoso Immagino che usi HttpWebRequest.BeginGetResponse o simili, tuttavia potresti decompilare l'assembly per confermare questo se davvero ti interessasse.
Giustino,

per ora, sto davvero aspettando solo la "migliore spiegazione" ... sarebbe fantastico se qualcuno del team di sviluppo di Unity fornisse la "risposta corretta" 8 -) ... alla fine, penso che la vera implementazione non sarà lontano da quelli già indicati qui ... Non ho davvero bisogno di decompilare l'assemblaggio e sapere tutto questo sicuramente. Ma potrei provarlo più tardi :)
thyandrecardoso

7

Sfortunatamente, il WWW è implementato internamente come codice nativo, il che significa che non possiamo guardare il codice. Dalla sperimentazione posso dirlo

  1. WWWè non è derivato da YieldInstruction, quindi, qualsiasi cosa succede quando si yielddeve essere gestita dal codice speciale per caso.
  2. Non ho mai notato alcuna differenza tra

    yield return www;

    e

    while(!www.isDone)
        yield return null;

    Penso che sia il modo più logico per implementarlo, e molto probabilmente è quello che sta succedendo sotto il cofano. Ma non lo so per certo.

  3. Unity non avvia un nuovo thread per il download, almeno su alcune piattaforme (iOS, webplayer). O se lo fa, imposta WWW.isDonesul thread principale. Lo so perché questo codice:

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

    non funziona

Non credo che tu possa avere risposte più specifiche a meno che qualcuno con accesso al codice sorgente di Unity3d venga qui.


Sì. Intuizioni davvero belle! Grazie! Anche se non si avvia un thread di per sé, la cosa più probabile che potrebbe accadere è il WWW (o il livello motore) utilizzando HttpWebRequest.BeginGetResponse (o qualcosa del genere) ... giusto? Qualcosa di completamente asincrono deve avvenire in entrambi i casi ... il download non può essere "messo in pausa".
thyandrecardoso,

** non posso essere "messo in pausa" tra i frame, intendo.
thyandrecardoso,

2

Poiché Unity3D utilizza C # come motore di scripting, suppongo sia la parola chiave di rendimento standard incorporata in C #. Fondamentalmente ciò che significa è che restituisce già il valore di www in modo da poter continuare mentre la prossima iterazione restituirà il valore successivo, ecc. Yield fondamentalmente crea una macchina a stati e un iteratore in background.


Sì, penso di avere le nozioni di base sulla parola chiave yield. Tuttavia, cosa sta succedendo durante il costruttore del WWW che consente quella "macchina a stati"? Voglio dire, "yield return www" non sembra chiamare nulla all'interno della classe WWW ... Quando dici "significa che restituisce già il valore di www", qual è il valore dell'istanza www? Cosa farà quell'istanza nella prossima "iterazione"?
thyandrecardoso,

1
Nelle coroutine di Unity, cedere il WWW è un caso speciale, vedi WWWil riferimento . Inoltre, non sono sicuro che yieldcrei qualcosa. Il contesto iteratore viene creato implementandolo IEnumerableo utilizzandolo come tipo restituito. Anche "State machine" sembra spento. Certo, c'è stato, ma da solo non è una proprietà sufficiente, giusto? Forse potresti approfondire questo.
Eric

1
Ecco un buon riferimento al normale comportamento in C #. shadowcoding.blogspot.nl/2009/01/yield-and-c-state-machine.html . Rendimento genera molto codice per tenere traccia di dove si trova nell'iteratore. A proposito del caso speciale di Unity che ha prodotto il WWW, non lo sapevo, ma secondo i documenti non ha nulla da fare con la normale parola chiave C #, è molto confuso, potrebbero semplicemente renderlo un metodo asincrono.
Roy T.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.