Podstawowy mechanizm „zwrotu z zysków www” silnika gry Unity3D

14

W silniku gry Unity3D powszechna sekwencja kodów do uzyskiwania zdalnych danych jest następująca:

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

Jaki jest tutaj mechanizm podstawowy?

Wiem, że używamy mechanizmu wydajności, aby umożliwić przetwarzanie następnej klatki podczas pobierania. Ale co się dzieje pod maską, kiedy to robimy yield return www?

Jaka metoda jest wywoływana (jeśli istnieje) w klasie WWW? Czy Unity używa wątków? Czy „górna” warstwa jedności bierze instancję www i coś robi?

EDYTOWAĆ:

  • To pytanie dotyczy w szczególności elementów wewnętrznych Unity3D. Nie interesują mnie wyjaśnienia dotyczące działania yieldinstrukcji w języku C #. Zamiast tego szukam wewnętrznego spojrzenia na to, jak Unity radzi sobie z tymi konstrukcjami, aby na przykład umożliwić WWW pobieranie fragmentu danych w sposób rozproszony w kilku ramkach.
thyandrecardoso
źródło
1
Pamiętaj, że używanie yield returndo operacji asynchronicznych to włamanie. W „prawdziwym” programie C # użyłbyś Taskdo tego celu. Unity prawdopodobnie ich nie używa, ponieważ został stworzony przed .Net 4.0, kiedy Taskzostał wprowadzony.
BlueRaja - Danny Pflughoeft

Odpowiedzi:

8

Jest to słowo kluczowe C # w działaniu - nie robi nic specjalnego z wwwobiektem, a raczej oznacza coś specjalnego dla metody w nim zawartej. W szczególności tego słowa kluczowego można użyć tylko w metodzie, która zwraca IEnumerable(lub IEnumerator) i jest używana wskazujący, który obiekt zostanie „zwrócony” przez moduł wyliczający, gdy zostanie wywołane MoveNext .

Działa, ponieważ kompilator konwertuje całą metodę na osobną klasę, która implementuje IEnumerable(lub IEnumerator) za pomocą automatu stanów - wynik netto jest taki, że ciało samej metody nie jest wykonywane, dopóki ktoś nie wyliczy wartości zwracanej. Działa to z każdym typem, nie ma w tym absolutnie nic specjalnego WWW, a raczej metoda zawierająca, która jest wyjątkowa.

Rzuć okiem za kulisy słowa kluczowego wydajności C #, aby uzyskać więcej informacji na temat tego, jaki rodzaj kodu generuje kompilator C #, lub po prostu eksperymentuj i sprawdź kod samodzielnie, używając czegoś takiego jak IL Spy


Aktualizacja: w celu wyjaśnienia

  • Gdy Unity wywołuje coroutine, która zawiera yield returninstrukcję, dzieje się tak, że zwracany jest moduł wyliczający - w tym momencie nie jest wykonywana żadna treść metody
  • Aby uzyskać treść metody do wykonania, Unity musi wywołać MoveNextiterator w celu uzyskania pierwszej wartości w sekwencji. Powoduje to, że metoda wykonuje się do pierwszej yeild returninstrukcji, w którym to momencie osoba dzwoniąca wznawia (i prawdopodobnie Unity renderuje resztę ramki)
  • Jak rozumiem, Unity normalnie następnie wywołuje MoveNextmetodę na iteratorze co każdą kolejną ramkę, powodując, że metoda wykonuje się ponownie aż do następnej yield returninstrukcji po każdej ramce, aż do osiągnięcia końca metody lub yield breakinstrukcji (wskazując koniec sekwencji)

Jedynym specjalny bit tutaj (oraz w kilku z pozostałych przypadkach ) jest to, że Unity nie posuwa tej konkretnej iterator następną ramkę, zamiast tylko przesuwa iterator (powodując metodę, aby kontynuować wykonywanie) po zakończeniu pobierania. Chociaż wydaje się, że istnieje podstawowa klasa YieldInstruction, która prawdopodobnie zawiera ogólny mechanizm sygnalizowania Unity, kiedy iterator powinien być zaawansowany, WWWklasa nie wydaje się dziedziczyć po tej klasie, więc mogę jedynie założyć, że istnieje specjalny przypadek ta klasa w silniku Unity.

Żeby było jasne - yieldsłowo kluczowe nie robi nic specjalnego dla WWWklasy, a raczej specjalną obsługę, jaką Unity zapewnia członkom zwróconego wyliczenia, co powoduje takie zachowanie.


Zaktualizuj drugi: Jeśli chodzi o mechanizm, który WWWużywa do asynchronicznego pobierania stron internetowych, prawdopodobnie używa albo metody HttpWebRequest.BeginGetResponse, która wewnętrznie użyje asynchronicznego We / Wy lub alternatywnie może użyć wątków (albo tworząc dedykowany wątek, albo używając puli wątków).

Justin
źródło
5
W rzeczywistości, w Unity, coś specjalnego dzieje się z WWWprzedmiotem, gdy jest on poddawany, patrz WWWodnośnik .
Eric
9

yieldwydaje się być stosowany głównie w Unity w kontekście korporacyjnym. Aby przeczytać więcej o coroutines i dlaczego używają C # yieldpolecam ten artykuł na blogu: Coroutines Unity3D szczegółowo . Większość badań w tej odpowiedzi pochodzi z tego artykułu.

Coroutines in Unity służą do enkapsulacji zadań, które:

  1. Renderowanie może potrwać dłużej niż klatka (powodując w ten sposób spowolnienie), oraz
  2. Można wykonać oddzielnie od pętli gry (ponieważ wynik nie musi być dostępny dla bieżącej klatki).

Przykładami tego rodzaju zadań są obliczenia (ponowne) odnajdywania ścieżek lub, jak w przypadku twojego pytania, pobieranie danych ze strony internetowej.

Aby odpowiedzieć na pytania (w nieco zmodyfikowanej kolejności):

Jaka metoda jest wywoływana (jeśli istnieje) w klasie WWW? Czy „górna” warstwa jedności bierze instancję www i coś robi?

WWWKlasa Unity została zaprojektowana w taki sposób, aby pochodzić z koroutyn. Zgodnie z komentarzami do powyższego artykułu na blogu, spekulatywny blok kodu („górna” warstwa) o YieldInstructions zawiera przełącznik, który również sprawdza, czy uzyskano WWWs. Ten kod gwarantuje, że coroutine zakończy się automatycznie po zakończeniu pobierania, jak opisano w WWWodnośniku .

Czy Unity używa wątków?

W takim przypadku, aby pobrać dane „bez blokowania reszty gry”: tak, najprawdopodobniej. (A wątkowanie jest zdecydowanie używane do dekompresji pobranych danych, o czym świadczy WWW.threadPriority.)

Eric
źródło
ładny! Widziałem komentarz altdevblogaday.com/2011/07/07/unity3d-coroutines-in-detail/... i wydaje się, że WWW jest traktowane specjalnie. Chciałbym więc wiedzieć, jak to osiągnąć, aby umożliwić pobieranie z kilku ramek bez użycia wątków?
thyandrecardoso
Słuszna uwaga! Podejrzewam, że na tym poziomie musi być trochę wątków, zredaguję moją odpowiedź, aby to odzwierciedlić.
Eric
1
@ thyandrecardoso Domyślam się, że używa HttpWebRequest.BeginGetResponse lub podobnego, jednak można zdekompilować zestaw, aby potwierdzić to, jeśli to naprawdę miało dla Ciebie znaczenie.
Justin
na razie naprawdę czekam tylko na „najlepsze wytłumaczenie” ... byłoby świetnie, gdyby ktokolwiek z zespołu deweloperów Unity udzielił „poprawnej odpowiedzi” 8 -) ... ostatecznie myślę, że prawdziwa implementacja nie będzie daleko od tych, które już tu podano ... Nie mam rzeczywistej potrzeby dekompilacji zestawu i na pewno to wszystko wiem. Ale mógłbym spróbować później :)
thyandrecardoso
7

Niestety WWW jest implementowane wewnętrznie jako kod macierzysty, co oznacza, że ​​nie możemy na niego patrzeć. Z eksperymentów mogę to powiedzieć

  1. WWWnie jest pochodną YieldInstruction, więc niezależnie od tego , co się dzieje, gdy trzeba yieldto zrobić, należy użyć specjalnego kodu.
  2. Nigdy nie zauważyłem żadnej różnicy między

    yield return www;

    i

    while(!www.isDone)
        yield return null;

    Myślę, że jest to najbardziej logiczny sposób na jego wdrożenie i najprawdopodobniej dzieje się to pod maską. Ale nie wiem na pewno.

  3. Unity nie rozpoczyna nowego wątku do pobrania, przynajmniej na niektórych platformach (iOS, webplayer). A jeśli tak, ustawia WWW.isDonesię na głównym wątku. Wiem to, ponieważ ten kod:

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

    nie działa

Nie sądzę, abyś mógł uzyskać bardziej szczegółowe odpowiedzi, chyba że przyjedzie ktoś z dostępem do kodu źródłowego Unity3d.

Nieważne
źródło
Tak. Naprawdę fajne spostrzeżenia! Dzięki! Nawet jeśli nie uruchamia wątku per se, najbardziej prawdopodobną rzeczą, która może się zdarzyć, jest WWW (lub warstwa silnika) przy użyciu HttpWebRequest.BeginGetResponse (lub coś podobnego) ... prawda? Tak czy inaczej, musi wystąpić coś całkowicie asynchronicznego ... pobieranie nie może zostać „wstrzymane”.
thyandrecardoso
** Mam na myśli, że nie można „wstrzymać” między ramkami.
thyandrecardoso
2

Ponieważ Unity3D używa C # jako silnika skryptowego, przypuszczam, że jest to standardowe słowo kluczowe wydajności, które jest wbudowane w C #. Zasadniczo oznacza to, że zwraca już wartość www, aby można było kontynuować, podczas gdy następna iteracja zwróci następną wartość itp. Yield zasadniczo tworzy maszynę stanu i iterator w tle.

Roy T.
źródło
Tak, myślę, że mam podstawowe pojęcia na temat słowa kluczowego wydajności. Co jednak dzieje się podczas konstruktora WWW, który pozwala na tę „maszynę państwową”? Mam na myśli, że „return return www” nie wydaje się wywoływać czegokolwiek w klasie WWW ... Kiedy mówisz „oznacza to, że zwraca już wartość www”, jaka jest wartość instancji www? Co ta instancja zrobi podczas następnej „iteracji”?
thyandrecardoso
1
W koronach Unity pozyskiwanie WWW jest szczególnym przypadkiem, patrz WWWodnośnik . Co więcej, nie jestem pewien, czy yieldcoś tworzy. Kontekst iteratora jest tworzony przez implementację IEnumerablelub użycie go jako typu zwracanego. „Maszyna państwowa” też wydaje się wyłączona. Jasne, istnieje państwo, ale samo to nie jest wystarczającą własnością, prawda? Może mógłbyś to rozwinąć.
Eric
1
Oto dobre odniesienie do normalnego zachowania w języku C #. shadowcoding.blogspot.nl/2009/01/yield-and-c-state-machine.html . Wydajność generuje dużo kodu, aby śledzić, gdzie jest w iteratorze. O specjalnym przypadku Unity generującym WWW, nie wiedziałem, ale zgodnie z dokumentami nie ma nic do zrobienia ze zwykłym słowem kluczowym C #, co jest bardzo mylące, mogliby to zrobić po prostu metoda asynchroniczna.
Roy T.