El mecanismo subyacente en 'return return www' de Unity3D Game Engine

14

En el motor de juego Unity3D, una secuencia de código común para obtener datos remotos es la siguiente:

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

¿Cuál es el mecanismo subyacente aquí?

Sé que utilizamos el mecanismo de rendimiento para permitir que se procese el siguiente fotograma, mientras se completa la descarga. ¿Pero qué sucede debajo del capó cuando hacemos el yield return www?

¿Qué método se llama (si corresponde, en la clase WWW)? ¿Está Unity usando hilos? ¿La capa de Unidad "superior" se está apoderando de la instancia de www y está haciendo algo?

EDITAR:

  • Esta pregunta es específicamente sobre los componentes internos de Unity3D. No me interesan las explicaciones de cómo yieldfunciona la declaración en C #. En cambio, estoy buscando una visión interna de cómo Unity trata con estas construcciones, para permitir, por ejemplo, a WWW descargar una pieza de datos de manera distribuida en varios marcos.
thyandrecardoso
fuente
1
Tenga en cuenta que usar yield returnpara operaciones asincrónicas es un hack. En un programa "real" de C #, usaría un Taskpara esto. Es probable que Unity no los use porque se creó antes de .Net 4.0, cuando Taskse introdujo.
BlueRaja - Danny Pflughoeft

Respuestas:

8

Esta es la palabra clave de rendimiento de C # en acción: no está haciendo nada especial con el wwwobjeto, sino que significa algo especial para el método que contiene. Específicamente, esta palabra clave solo se puede usar en un método que devuelve un IEnumerable(o IEnumerator), y se usa para indicar qué objeto será "devuelto" por el enumerador cuando se invoca MoveNext .

Funciona porque el compilador convierte todo el método en una clase separada que implementa IEnumerable(o IEnumerator) usando una máquina de estado; el resultado neto es que el cuerpo del método en sí no se ejecuta hasta que alguien enumera a través del valor de retorno. Esto funcionará con cualquier tipo, no tiene absolutamente nada de especial WWW, sino que es el método que lo contiene lo que es especial.

Eche un vistazo a Detrás de escena de la palabra clave de rendimiento de C # para obtener más información sobre qué tipo de código genera el compilador de C #, o simplemente experimente e inspeccione el código usted mismo usando algo como IL Spy


Actualización: para aclarar

  • Cuando Unity llama a una rutina que contiene una yield returndeclaración, todo lo que sucede es que se devuelve un enumerador; en este punto, no se ejecuta ninguno de los cuerpos del método
  • Para que el cuerpo del método se ejecute, Unity debe llamar MoveNextal iterador para obtener el primer valor en la secuencia. Esto hace que el método se ejecute hasta la primera yeild returninstrucción, momento en el que se reanuda la llamada (y presumiblemente Unity continúa para representar el resto del marco)
  • Según tengo entendido, Unity normalmente llama al MoveNextmétodo en el iterador una vez por cada cuadro posterior, lo que hace que el método se ejecute nuevamente hasta la siguiente yield returndeclaración una vez por cada cuadro, hasta yield breakque se alcanza el final del método o una declaración (lo que indica el final de la secuencia)

El único bit especial aquí (y en un par de otros casos ) es que Unity no avanza este iterador en particular el siguiente fotograma, sino que solo avanza el iterador (haciendo que el método continúe ejecutándose) cuando se completa la descarga. Aunque parece haber una clase YieldInstruction base que presumiblemente contiene un mecanismo genérico para indicar a Unity cuándo se debe avanzar un iterador, la WWWclase no parece heredar de esta clase, por lo que solo puedo suponer que hay un caso especial para Esta clase en el motor de Unity.

Para ser claros, la yieldpalabra clave no hace nada especial para la WWWclase, sino que es el manejo especial que Unity le da a los miembros de la enumeración devuelta lo que causa este comportamiento.


Actualiza el segundo: cuanto al mecanismo que WWWusa para descargar páginas web de forma asincrónica, probablemente use el método HttpWebRequest.BeginGetResponse que usará internamente IO asincrónico o, alternativamente, podría usar hilos (ya sea creando un hilo dedicado o usando un grupo de hilos).

Justin
fuente
55
En realidad, en Unity, algo especial le sucede a un WWWobjeto cuando se entrega, vea WWWla referencia .
Eric
9

yieldparece ser utilizado principalmente en Unity en un contexto de rutina. Para leer más acerca de las corutinas y por qué usan C #, yieldrecomiendo este artículo de blog: las corutinas Unity3D en detalle . La mayor parte de la investigación en esta respuesta proviene de ese artículo.

Las rutinas en Unity se utilizan para encapsular tareas que:

  1. Puede tomar más tiempo que un marco para ser renderizado (causando ralentizaciones), y
  2. Se puede realizar por separado del bucle del juego (porque el resultado no necesita estar disponible para el marco actual).

Ejemplos de este tipo de tareas son los (re) cálculos de búsqueda de ruta o, como es el caso en su pregunta, obtener datos de un sitio web.

Para responder a sus preguntas (en un orden ligeramente modificado):

¿Qué método se llama (si corresponde, en la clase WWW)? ¿La capa de Unidad "superior" se está apoderando de la instancia de www y está haciendo algo?

La WWWclase de Unity está diseñada para ser producida a partir de una rutina. De acuerdo con los comentarios en el artículo del blog vinculado anteriormente, el bloque de código especulativo (la capa "superior") sobre YieldInstructions en realidad contiene un interruptor que también verifica si hay WWWs producidos . Este código luego se asegura de que la rutina termine automáticamente cuando se realiza la descarga, como se describe en WWWla referencia .

¿Está Unity usando hilos?

En este caso, descargar los datos "sin bloquear el resto del juego": sí, lo más probable. (Y el enhebrado definitivamente se usa para descomprimir los datos descargados, como lo demuestra WWW.threadPriority).

Eric
fuente
¡bonito! Vi el comentario altdevblogaday.com/2011/07/07/unity3d-coroutines-in-detail/… y parece que WWW recibe un trato especial. Entonces, me gustaría saber cómo se logra esto, para permitir que la descarga se realice en varios cuadros, sin usar hilos.
thyandrecardoso
¡Buen punto! Supongo que después de todo debe haber algún subproceso en ese nivel, editaré mi respuesta para reflejar eso.
Eric
1
@thyandrecardoso Supongo que usa HttpWebRequest.BeginGetResponse o similar, sin embargo, podría descompilar el ensamblaje para confirmar esto si realmente le importa.
Justin
por ahora, realmente solo estoy esperando la "mejor explicación" ... sería genial si alguien del equipo de desarrollo de Unity diera la "respuesta correcta" 8 -) ... en última instancia, creo que la implementación real no será lejos de los que se dan aquí ... No tengo la necesidad real de descompilar el ensamblado y sé todo esto con seguridad. Pero podría intentarlo más tarde :)
thyandrecardoso
7

Desafortunadamente, WWW se implementa internamente como código nativo, lo que significa que no podemos ver el código. Por experimentación puedo decir que

  1. WWWse no se deriva de YieldInstruction, así que lo que sucede cuando usted yielddebe ser manejada por el código-caso especial.
  2. Nunca he observado ninguna diferencia entre

    yield return www;

    y

    while(!www.isDone)
        yield return null;

    Creo que esa es la forma más lógica de implementarlo, y muy probablemente eso es lo que está sucediendo debajo del capó. Pero no estoy seguro.

  3. Unity no inicia un nuevo hilo para descargar, al menos en algunas plataformas (iOS, jugador web). O si lo hace, se establece WWW.isDoneen el hilo principal. Lo sé porque este código:

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

    no funciona

No creo que pueda tener respuestas más específicas a menos que alguien con acceso al código fuente de Unity3d venga aquí.

No importa
fuente
Sip. Realmente buenas ideas! ¡Gracias! A pesar de no lanzar un hilo per se, lo más probable que podría estar sucediendo es WWW (o la capa del motor) usando HttpWebRequest.BeginGetResponse (o algo así) ... ¿verdad? Algo completamente asíncrono debe ocurrir de cualquier manera ... la descarga no se puede "pausar".
thyandrecardoso
** no se puede "pausar" entre cuadros, quiero decir.
thyandrecardoso
2

Dado que Unity3D usa C # como su motor de secuencias de comandos, supongo que es la palabra clave de rendimiento estándar que está integrada en C #. Básicamente, lo que significa es que ya devuelve el valor de www para que pueda continuar mientras que en la próxima iteración devolverá el siguiente valor, etc. El rendimiento básicamente crea una máquina de estado y un iterador en segundo plano.

Roy T.
fuente
Sí, creo que tengo las nociones básicas sobre la palabra clave de rendimiento. Sin embargo, ¿qué está sucediendo durante el constructor de WWW que permite esa "máquina de estado"? Quiero decir, "return return www" no parece estar llamando a nada dentro de la clase WWW ... Cuando dices "significa que ya devuelve el valor de www", ¿cuál es el valor de instancia de www? ¿Qué hará esa instancia en la próxima "iteración"?
thyandrecardoso
1
En las corutinas de Unity, producir WWW es un caso especial, consulte WWWla referencia . Además, no estoy seguro yieldcrea nada. El contexto del iterador se crea al implementarlo IEnumerableo usarlo como un tipo de retorno. La "máquina de estado" también parece estar apagada. Claro, hay estado, pero eso por sí solo no es una propiedad suficiente, ¿verdad? Tal vez podrías dar más detalles sobre eso.
Eric
1
Aquí hay una buena referencia sobre el comportamiento normal de C #. shadowcoding.blogspot.nl/2009/01/yield-and-c-state-machine.html . El rendimiento genera una gran cantidad de código para realizar un seguimiento de dónde se encuentra en el iterador. Sobre el caso especial de Unity que produce WWW, no lo sabía, pero según los documentos no tiene nada que hacer con la palabra clave normal de C #, eso es muy confuso, podrían simplemente convertirlo en un método asíncrono.
Roy T.