Основополагающий механизм в 'yield return www' игрового движка Unity3D

14

В игровом движке Unity3D общая последовательность кода для получения удаленных данных такова:

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

Каков основной механизм здесь?

Я знаю, что мы используем механизм yield для обработки следующего кадра во время завершения загрузки. Но что происходит под капотом, когда мы делаем yield return www?

Какой метод вызывается (если есть, в классе WWW)? Unity использует потоки? Получает ли «верхний» слой Unity экземпляр www и что-то делает?

РЕДАКТИРОВАТЬ:

  • Этот вопрос конкретно о внутренностях Unity3D. Меня не интересуют объяснения того, как yieldоператор работает в C #. Вместо этого я ищу внутреннее представление о том, как Unity работает с этими конструкциями, чтобы позволить, например, WWW загружать фрагмент данных распределенным образом по нескольким кадрам.
thyandrecardoso
источник
1
Обратите внимание, что использование yield returnдля асинхронных операций является взломом. В «настоящей» C # -программе вы бы использовали Taskдля этого. Unity, вероятно, не использует их, потому что он был создан до .Net 4.0, когда он Taskбыл представлен.
BlueRaja - Дэнни Пфлюгофт

Ответы:

8

Это ключевое слово yield C # в действии - оно не делает ничего особенного с wwwобъектом, скорее оно означает что-то особенное для метода, в котором он содержится. В частности, это ключевое слово может использоваться только в методе, который возвращает IEnumerable(или IEnumerator), и используется чтобы указать, какой объект будет «возвращен» перечислителем при вызове MoveNext .

Это работает, потому что компилятор преобразует весь метод в отдельный класс, который реализует IEnumerable(или IEnumerator) с использованием конечного автомата - в результате получается, что тело самого метода не выполняется до тех пор, пока кто-то не перечислит через возвращаемое значение. Это будет работать с любым типом, в этом нет абсолютно ничего особенного WWW, скорее это особый содержащий метод.

Взгляните за кулисы ключевого слова C # yield для более глубокого понимания того, какой код генерирует компилятор C #, или просто экспериментируйте и проверяйте код самостоятельно, используя что-то вроде IL Spy.


Обновление: уточнить

  • Когда Unity вызывает сопрограмму, содержащую yield returnинструкцию, все, что происходит, - это то, что возвращается перечислитель - в этот момент тело метода не выполняется
  • Чтобы получить тело метода для выполнения Unity необходимо вызвать MoveNext итератор, чтобы получить первое значение в последовательности. Это заставляет метод выполняться до первого yeild returnоператора, после чего вызывающая сторона возобновляет работу (и, по-видимому, Unity продолжает визуализацию остальной части кадра)
  • Как я понимаю, Unity обычно затем вызывает MoveNextметод на итераторе один раз в каждом последующем кадре, в результате чего метод выполняется снова до следующегоyield return оператора после каждого кадра, пока не yield breakбудет достигнут конец метода или оператора (указывая конец последовательности)

Единственный специальный бит здесь (и в нескольких из других случаев ) является то , что Unity не продвигает эту конкретную итератор следующего кадра, вместо этого он продвигает только итератор (вызвавший метод для продолжения выполнения) , когда загрузка завершена. Хотя, похоже, есть базовая YieldInstruction класс который, предположительно, содержит общий механизм для сигнализации Unity, когда итератор должен быть расширен, WWWкласс не наследуется от этого класса, поэтому я могу только предположить, что существует особый случай для этот класс в движке Unity.

Просто чтобы прояснить: yieldключевое слово не делает ничего особенного для WWWкласса, скорее его специальная обработка, которую Unity предоставляет членам возвращаемого перечисления, вызывает такое поведение.


Обновите второе: что касается механизма, который WWWиспользует для загрузки веб-страниц асинхронно, он, вероятно, использует либо метод HttpWebRequest.BeginGetResponse, который будет внутренне использовать асинхронный ввод-вывод, либо, альтернативно, он мог бы использовать потоки (либо создавая выделенный поток, либо используя пул потоков).

Джастин
источник
5
На самом деле, в Unity происходит нечто особенное с WWWобъектом, когда он получен, см. WWWСсылку .
Эрик
9

yieldкажется, в основном используется в Unity в сопрограммном контексте. Чтобы узнать больше о сопрограммах и почему они используют C #, yieldя рекомендую эту статью в блоге: сопрограммы Unity3D подробно . Большинство исследований в этом ответе происходит из этой статьи.

Сопрограммы в Unity используются для инкапсуляции задач, которые:

  1. Может потребоваться больше времени, чем для визуализации кадра (что приводит к замедлению), и
  2. Может выполняться отдельно от игрового цикла (поскольку результат не обязательно должен быть доступен для текущего кадра).

Примерами таких задач являются поиск пути (пере) вычисления или, как в вашем вопросе, получение данных с веб-сайта.

Чтобы ответить на ваши подвопросы (в слегка измененном порядке):

Какой метод вызывается (если есть, в классе WWW)? Получает ли «верхний» слой Unity экземпляр www и что-то делает?

WWWКласс Unity предназначен для получения сопрограмм. Согласно комментариям к статье в блоге, на которую ссылаются выше, спекулятивный блок кода («верхний» уровень) о YieldInstructions на самом деле содержит переключатель, который также проверяет наличие WWWs. Затем этот код обеспечивает автоматическое завершение сопрограммы после завершения загрузки, как описано в WWWсправочном руководстве .

Unity использует потоки?

В этом случае для загрузки данных «без блокировки остальной части игры»: да, скорее всего. (И потоки определенно используются для распаковки загруженных данных, о чем свидетельствует WWW.threadPriority.)

Эрик
источник
красивый! Я видел комментарий altdevblogaday.com/2011/07/07/unity3d-coroutines-in-detail/… и кажется, что WWW лечится специально. Итак, я хотел бы знать, как это сделать, чтобы загрузка осуществлялась в нескольких кадрах без использования потоков?
thyandrecardoso
Хорошая точка зрения! Я полагаю, что на этом уровне, в конце концов, должно быть какое-то продвижение, я отредактирую свой ответ, чтобы отразить это.
Эрик
1
@thyandrecardoso Я думаю, он использует HttpWebRequest.BeginGetResponse или аналогичный, однако вы можете декомпилировать сборку, чтобы подтвердить это, если это действительно имеет значение для вас.
Джастин
на данный момент я действительно жду только «лучшего объяснения» ... было бы здорово, если бы кто-то из команды разработчиков Unity дал «правильный ответ» 8 -) ... в конечном счете, я думаю, что настоящая реализация не будет далеко от тех, что уже приведены здесь ... У меня нет реальной необходимости декомпилировать сборку и все это точно знаю. Но я мог бы попробовать это позже :)
thyandrecardoso
7

К сожалению, WWW реализован внутри как нативный код, что означает, что мы не можем смотреть на код. Из экспериментов могу сказать, что

  1. WWWне является производным от YieldInstruction, поэтому, что бы ни происходило с вами, yieldоно должно обрабатываться кодом особого случая.
  2. Я никогда не замечал никакой разницы между

    yield return www;

    и

    while(!www.isDone)
        yield return null;

    Я думаю, что это самый логичный способ реализовать это, и, скорее всего , это то, что происходит под капотом. Но я не знаю точно.

  3. Unity не запускает новый поток для загрузки, по крайней мере, на некоторых платформах (iOS, веб-плеер). Или, если это так, он устанавливает WWW.isDoneна основной поток. Я знаю это, потому что этот код:

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

    не работает

Я не думаю, что у вас могут быть более конкретные ответы, если кто-то, имеющий доступ к исходному коду Unity3d, не придет сюда.

Ничего
источник
Ага. Действительно хорошие идеи! Благодарность! Даже если не запускать поток как таковой, наиболее вероятная вещь, которая может произойти, это WWW (или слой движка), использующий HttpWebRequest.BeginGetResponse (или что-то подобное) ... верно? В любом случае должно произойти что-то полностью асинхронное ... загрузка не может быть приостановлена.
thyandrecardoso
** я не имею в виду "паузу" между кадрами.
Thyandrecardoso
2

Поскольку Unity3D использует C # в качестве механизма сценариев, я предполагаю, что это стандартное ключевое слово yield , встроенное в C #. По сути, это означает, что он уже возвращает значение www, так что вы можете продолжить, пока на следующей итерации он вернет следующее значение и т. Д. ... Выход в основном создает конечный автомат и итератор в фоновом режиме.

Рой Т.
источник
Да, я думаю, что у меня есть основные понятия о ключевом слове yield. Однако что происходит во время конструктора WWW, который учитывает этот «конечный автомат»? Я имею в виду, что «yield return www», по-видимому, не вызывает ничего внутри класса WWW ... Когда вы говорите «это означает, что он уже возвращает значение www», каково значение экземпляра www? Что этот экземпляр будет делать на следующей «итерации»?
thyandrecardoso
1
В сопрограммах Unity выход WWW является особым случаем, см. WWWСсылку . Кроме того, я не уверен, что yieldсоздает что-либо. Контекст итератора создается путем его реализации IEnumerableили использования в качестве возвращаемого типа. «Конечный автомат» тоже кажется выключенным. Конечно, есть государство, но одного этого недостаточно, верно? Может быть, вы могли бы уточнить это.
Эрик
1
Вот хорошая ссылка на нормальное поведение C #. shadowcoding.blogspot.nl/2009/01/yield-and-c-state-machine.html . Выход генерирует много кода, чтобы отслеживать, где он находится в итераторе. Насчет особого случая Unity, приводящего к WWW, я не знал, но, согласно документам, он не имеет ничего общего с обычным ключевым словом C #, это очень запутанно, они могли бы просто сделать его асинхронным методом.
Рой Т.