O mecanismo subjacente no 'retorno de retorno www' do Unity3D Game Engine

14

No mecanismo do jogo Unity3D, uma sequência de código comum para obter dados remotos é a seguinte:

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

Qual é o mecanismo subjacente aqui?

Sei que usamos o mecanismo de rendimento para permitir o processamento do próximo quadro, enquanto o download está sendo concluído. Mas o que está acontecendo sob o capô quando fazemos o yield return www?

Qual método está sendo chamado (se houver, na classe WWW)? O Unity está usando threads? A camada "superior" do Unity está se apossando da instância www e está fazendo alguma coisa?

EDITAR:

  • Esta pergunta é especificamente sobre os componentes internos do Unity3D. Não estou interessado em explicações sobre como a yieldinstrução funciona em c #. Em vez disso, estou procurando uma visão interna de como o Unity lida com essas construções, para permitir, por exemplo, à WWW baixar um pedaço de dados de maneira distribuída em vários quadros.
thyandrecardoso
fonte
1
Observe que o uso yield returnpara operações assíncronas é um hack. Em um programa C # "real", você usaria um Taskpara isso. O Unity provavelmente não os está usando porque foi criado antes do .Net 4.0, quando Taskfoi introduzido.
BlueRaja - Danny Pflughoeft

Respostas:

8

Esta é a palavra-chave C # yield em ação - não está fazendo nada de especial com o wwwobjeto, mas significa algo especial para o método em que está contido. Especificamente, essa palavra-chave pode ser usada apenas em um método que retorna um IEnumerable(ou IEnumerator) e é usado para indicar qual objeto será "retornado" pelo enumerador quando MoveNext for chamado.

Funciona porque o compilador converte o método inteiro em uma classe separada que implementa IEnumerable(ou IEnumerator) usando uma máquina de estado - o resultado líquido é que o corpo do método em si não é executado até que alguém enumere por meio do valor de retorno. Isso funcionará com qualquer tipo, não há absolutamente nada de especial WWW, é o método que o contém, que é especial.

Dê uma olhada nos bastidores da palavra-chave C # yield para obter mais informações sobre que tipo de código o compilador C # gera, ou apenas experimente e inspecione você mesmo usando algo como IL Spy


Atualização: para esclarecer

  • Quando o Unity chama uma corotina que contém uma yield returndeclaração, tudo o que acontece é que um enumerador é retornado - nenhum corpo do método é executado neste momento
  • Para que o corpo do método execute o Unity, é necessário chamar MoveNexto iterador para obter o primeiro valor na sequência. Isso faz com que o método execute até a primeira yeild returninstrução, momento em que o chamador é reiniciado (e presumivelmente o Unity continua a renderizar o restante do quadro)
  • Pelo que entendi, o Unity normalmente passa a chamar o MoveNextmétodo no iterador uma vez a cada quadro subseqüente, fazendo com que o método seja executado novamente até a próxima yield returninstrução uma vez a cada quadro, até que seja yield breakatingido o final do método ou de uma instrução (indicando final da sequência)

O bit somente especial aqui (e em um par de outros casos ) é que a unidade não avança este iterador determinado o próximo quadro, em vez ela só avança o iterador (fazendo com que o método para continuar a execução) quando o download for concluído. Embora pareça haver uma classe YieldInstruction básica que, presumivelmente, contém um mecanismo genérico para sinalizar para o Unity quando um iterador deve ser avançado, a WWWclasse não parece herdar dessa classe, portanto, posso apenas assumir que há um caso especial para essa classe no mecanismo do Unity.

Só para esclarecer: a yieldpalavra - chave não faz nada de especial para a WWWclasse, é o tratamento especial que o Unity oferece aos membros da enumeração retornada que causa esse comportamento.


Atualize o segundo: quanto ao mecanismo WWWusado para baixar páginas da Web de forma assíncrona, provavelmente ele usa o método HttpWebRequest.BeginGetResponse, que usará internamente a IO assíncrona ou, alternativamente, poderia usar threads (criando um thread dedicado ou usando um pool de threads).

Justin
fonte
5
Na verdade, no Unity, algo especial acontece com um WWWobjeto quando é produzido, veja WWWa referência .
Eric
9

yieldparece ser usado principalmente no Unity em um contexto de rotina. Para ler mais sobre as corotinas e por que elas usam C #, yieldrecomendo este artigo do blog: Corotinas do Unity3D em detalhes . A maioria das pesquisas nesta resposta vem desse artigo.

As corotinas no Unity são usadas para encapsular tarefas que:

  1. Pode levar mais tempo do que um quadro a ser renderizado (causando lentidão) e
  2. Pode ser executado separadamente do loop do jogo (porque o resultado não precisa estar disponível para o quadro atual).

Exemplos desses tipos de tarefas são (re) cálculos de localização de caminhos ou, como é o caso da sua pergunta, obter dados de um site.

Para responder às suas subquestões (em uma ordem ligeiramente modificada):

Qual método está sendo chamado (se houver, na classe WWW)? A camada "superior" do Unity está se apossando da instância www e está fazendo alguma coisa?

A WWWclasse da Unity é projetada para ser produzida a partir de uma rotina. De acordo com os comentários no artigo do blog vinculado acima, o bloco de código especulativo (a camada "superior") sobre YieldInstructions na verdade contém uma opção que também verifica se há WWWs produzidos . Esse código garante que a corotina termine automaticamente quando o download for concluído, conforme descrito na WWWreferência .

O Unity está usando threads?

Nesse caso, para baixar os dados "sem bloquear o resto do jogo": sim, muito provavelmente. (E o encadeamento é definitivamente usado para descompactar os dados baixados, conforme evidenciado por WWW.threadPriority.)

Eric
fonte
legais! Eu vi o comentário altdevblogaday.com/2011/07/07/unity3d-coroutines-in-detail/… e parece que a WWW é tratada especialmente. Então, eu gostaria de saber como isso é feito, para permitir que o download seja feito em vários quadros, sem o uso de threads?
thyandrecardoso
Bom ponto! Suponho que deve haver alguma discussão nesse nível, afinal, editarei minha resposta para refletir isso.
Eric
1
@thyandrecardoso Eu acho que ele usa HttpWebRequest.BeginGetResponse ou similar, no entanto, você pode descompilar o assembly para confirmar isso, se isso realmente importa para você.
Justin
por enquanto, estou apenas esperando a "melhor explicação" ... seria ótimo se alguém da equipe de desenvolvimento do Unity fornecesse a "resposta correta" 8 -) ... em última análise, acho que a implementação real não será longe das que já foram fornecidas aqui ... Não tenho uma necessidade real de descompilar a montagem e sei tudo isso com certeza. Mas eu poderia tentar mais tarde :)
thyandrecardoso
7

Infelizmente, o WWW é implementado internamente como código nativo, o que significa que não podemos ver o código. Por experimentação, posso dizer que

  1. WWWnão é derivado YieldInstruction, portanto, aconteça o que acontecer quando você yieldprecisar ser tratado pelo código de caso especial.
  2. Eu nunca observei nenhuma diferença entre

    yield return www;

    e

    while(!www.isDone)
        yield return null;

    Eu acho que é a maneira mais lógica de implementá-lo e, provavelmente, é o que está acontecendo sob o capô. Mas não tenho certeza.

  3. O Unity não inicia um novo segmento para download, pelo menos em algumas plataformas (iOS, webplayer). Ou, se o fizer, ele define WWW.isDoneno thread principal. Eu sei disso porque este código:

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

    não funciona

Eu não acho que você possa ter respostas mais específicas, a menos que alguém com acesso ao código fonte do Unity3d venha aqui.

deixa pra lá
fonte
Sim. Informações realmente agradáveis! Obrigado! Embora não inicie um encadeamento por si só, a coisa mais provável que pode estar acontecendo é a WWW (ou a camada do mecanismo) usando HttpWebRequest.BeginGetResponse (ou algo parecido) ... certo? Algo completamente assíncrono deve ocorrer de qualquer maneira ... o download não pode ser "pausado".
thyandrecardoso
** não pode ser "pausado" entre os quadros, quero dizer.
thyandrecardoso
2

Como o Unity3D usa o C # como seu mecanismo de script, suponho que seja a palavra-chave yield padrão que está embutida no C #. Basicamente, o que isso significa é que ele já retorna o valor de www para que você possa continuar enquanto a próxima iteração retornará o próximo valor, etc ... O rendimento cria basicamente uma máquina de estado e um iterador em segundo plano.

Roy T.
fonte
Sim, acho que tenho as noções básicas sobre a palavra-chave yield. No entanto, o que está acontecendo durante o construtor da WWW que permite essa "máquina de estado"? Quero dizer, "yield return www" não parece estar chamando nada dentro da classe WWW ... Quando você diz "significa que já retorna o valor de www", qual é o valor da instância www? O que essa instância fará na próxima "iteração"?
thyandrecardoso
1
Nas rotinas da Unity, produzir WWW é um caso especial, consulte WWWa referência . Além disso, não tenho certeza se yieldcria alguma coisa. O contexto do iterador é criado implementando IEnumerableou usando-o como um tipo de retorno. "Máquina de estado" parece desligada também. Claro, existe estado, mas isso por si só não é uma propriedade suficiente, certo? Talvez você possa elaborar sobre isso.
Eric
1
Aqui está uma boa referência sobre o comportamento normal de C #. shadowcoding.blogspot.nl/2009/01/yield-and-c-state-machine.html . O rendimento gera muito código para acompanhar onde está no iterador. Sobre o caso especial do Unity com WWW, eu não sabia, mas de acordo com os documentos, ele não tem nada a ver com a palavra-chave C # normal, isso é muito confuso, eles poderiam apenas torná-lo um método assíncrono.
Roy T.