W jaki sposób oddzielna logika gry i rendering? Wiem, że wydaje się, że są tutaj pytania, które dokładnie o to pytają, ale odpowiedzi nie są dla mnie zadowalające.
Z tego, co do tej pory rozumiem, rozdzielenie ich na różne wątki polega na tym, aby logika gry mogła natychmiast uruchomić kolejny tik zamiast czekać na następny vsync, w którym renderowanie w końcu powraca z wywołania swapbuffera, które blokuje.
Ale konkretnie, jakie struktury danych są używane, aby zapobiec warunkom wyścigu między wątkiem logiki gry a wątkiem renderowania. Prawdopodobnie wątek renderujący potrzebuje dostępu do różnych zmiennych, aby dowiedzieć się, co narysować, ale logika gry może aktualizować te same zmienne.
Czy istnieje de facto standardowa technika rozwiązania tego problemu? Może jak kopiowanie danych potrzebnych do renderowania po każdym wykonaniu logiki gry. Jakim rozwiązaniem będzie narzut związany z synchronizacją, czy czymś mniejszym niż tylko uruchamianie wszystkiego w jednym wątku?
źródło
Odpowiedzi:
Pracowałem nad tym samym. Dodatkowym problemem jest to, że OpenGL (i, o ile mi wiadomo, OpenAL) oraz szereg innych interfejsów sprzętowych, to skutecznie maszyny stanu, które nie radzą sobie dobrze z byciem wywoływanym przez wiele wątków. Nie sądzę, że ich zachowanie jest nawet zdefiniowane, a dla LWJGL (być może również JOGL) często rzuca wyjątek.
Skończyło się na tym, że stworzyłem sekwencję wątków implementujących określony interfejs i ładowałem je na stos obiektu kontrolnego. Gdy ten obiekt otrzyma sygnał do zamknięcia gry, będzie przebiegał przez każdy wątek, wywoływał zaimplementowaną metodę ceaseOperations () i czekał na ich zamknięcie przed zamknięciem. Uniwersalne dane, które mogą być istotne przy renderowaniu dźwięku, grafiki lub innych danych, są przechowywane w sekwencji obiektów, które są ulotne lub powszechnie dostępne dla wszystkich wątków, ale nigdy nie są przechowywane w pamięci wątków. Jest tam niewielka kara za wydajność, ale właściwie zastosowana, pozwoliła mi elastycznie przypisać dźwięk do jednego wątku, grafikę do drugiego, fizykę do innego i tak dalej, bez wiązania ich z tradycyjną (i przerażającą) „pętlą gry”.
Tak więc z reguły wszystkie wywołania OpenGL przechodzą przez wątek Graphics, wszystkie OpenAL przez wątek Audio, wszystkie dane wejściowe przez wątek Input i wszystko, o co powinien martwić się organizacyjny wątek kontrolny, to zarządzanie wątkiem. Stan gry odbywa się w klasie GameState, na którą wszyscy mogą przyjrzeć się, gdy tego potrzebują. Jeśli kiedykolwiek zdecyduję, że powiedzmy, że JOAL ma datę i chcę zamiast tego użyć nowej edycji JavaSound, po prostu zaimplementuję inny wątek dla Audio.
Mam nadzieję, że rozumiecie, co mówię, mam już kilka tysięcy wierszy na temat tego projektu. Jeśli chcesz, żebym spróbował zeskrobać próbkę, zobaczę, co mogę zrobić.
źródło
Zwykle logika zajmująca się renderowaniem grafiki przebiega osobno (i ich harmonogram, a kiedy mają się uruchomić itp.) Obsługiwana jest przez osobny wątek. Jednak ten wątek jest już zaimplementowany (uruchomiony i uruchomiony) przez platformę, której używasz do rozwijania pętli gry (i gry).
Aby więc uzyskać pętlę gry, w której logika gry aktualizuje się niezależnie od harmonogramu odświeżania grafiki, nie trzeba tworzyć dodatkowych wątków, wystarczy skorzystać z już istniejącego wątku dla wspomnianych aktualizacji grafiki.
To zależy od używanej platformy. Na przykład:
jeśli robisz to na większości platform związanych z Open GL ( GLUT dla C / C ++ , JOLG dla Java , akcja związana z OpenGL ES dla Androida ), zwykle dają ci metodę / funkcję, która jest okresowo wywoływana przez wątek renderujący i którą może zintegrować się z twoją pętlą gry (bez uzależniania iteracji gameloopa od tego, kiedy wywoływana jest ta metoda). Dla GLUT za pomocą C, robisz coś takiego:
glutDisplayFunc (myFunctionForGraphicsDrawing);
glutIdleFunc (myFunctionForUpdatingState);
w JavaScript możesz używać Web Workers,
ponieważ nie ma wielowątkowości (które możesz osiągnąć programowo), możesz także użyć mechanizmu "requestAnimationFrame", aby otrzymywać powiadomienia o planowanym renderowaniu grafiki i odpowiednio aktualizować stan gry .Zasadniczo to, czego chcesz, to mieszana pętla gry: masz kod, który aktualizuje stan gry i który jest wywoływany w głównym wątku twojej gry, a także chcesz okresowo korzystać z (lub zostać oddzwonionym) już istniejący wątek renderowania grafiki dla heads up, kiedy nadszedł czas na odświeżenie grafiki.
źródło
W Javie jest słowo kluczowe „synchronizowane”, które blokuje przekazywane do niego zmienne, aby były bezpieczne dla wątków. W C ++ możesz to samo osiągnąć przy pomocy Mutex. Na przykład:
Jawa:
C ++:
Blokowanie zmiennych zapewnia, że nie zmieniają się podczas uruchamiania następującego po nim kodu, więc zmienne nie są zmieniane przez wątek aktualizujący podczas ich renderowania (w rzeczywistości zmieniają się, ale z punktu widzenia wątku renderującego nie zmieniają się) t). Musisz jednak uważać na zsynchronizowane słowo kluczowe w Javie, ponieważ tylko upewnia się, że wskaźnik do zmiennej / Object się nie zmieni. Atrybuty mogą się nadal zmieniać bez zmiany wskaźnika. Aby to rozważyć, możesz samodzielnie skopiować obiekt lub zsynchronizować wszystkie atrybuty obiektu, którego nie chcesz zmieniać.
źródło
To, co ogólnie widziałem w obsłudze komunikacji wątków logicznych / renderujących, to potrójne buforowanie danych. W ten sposób wątek renderujący mówi, że wiadro 0 odczytuje. Wątek logiczny używa segmentu 1 jako źródła danych wejściowych dla następnej ramki i zapisuje dane ramki w segmencie 2.
W punktach synchronizacji indeksy każdego z trzech segmentów są zamieniane, dzięki czemu dane następnej ramki są przekazywane do wątku renderowania i wątek logiczny może kontynuować.
Ale niekoniecznie jest powód, aby podzielić renderowanie i logikę na odpowiednie wątki. W rzeczywistości można utrzymać szeregową pętlę gry i oddzielić liczbę klatek renderowania od kroku logicznego za pomocą interpolacji. Aby skorzystać z procesorów wielordzeniowych korzystających z tego rodzaju konfiguracji, potrzebna jest pula wątków, która działa na grupach zadań. Zadania te mogą polegać na tym, że zamiast iterować listę obiektów od 0 do 100, iterujesz listę w 5 segmentach po 20 w 5 wątkach, skutecznie zwiększając wydajność, ale nie komplikując głównej pętli.
źródło
To jest stary post, ale wciąż się pojawia, więc chciałem tu dodać moje 2 centy.
Pierwsza lista danych, które powinny być przechowywane w interfejsie użytkownika / wątku wyświetlania vs wątku logicznym. W wątku interfejsu użytkownika możesz dołączyć siatkę 3D, tekstury, informacje o świetle oraz kopię danych pozycji / obrotu / kierunku.
W wątku logiki gry może być potrzebny rozmiar obiektu gry w 3D, obwiednia prymitywów (kula, sześcian), uproszczone dane siatki 3d (na przykład szczegółowe kolizje), wszystkie atrybuty wpływające na ruch / zachowanie, takie jak prędkość obiektu, współczynnik obrotu itp., a także dane pozycji / obrotu / kierunku.
Jeśli porównasz dwie listy, zobaczysz, że tylko kopia danych pozycji / obrotu / kierunku musi zostać przekazana z logiki do wątku interfejsu użytkownika. Możesz także potrzebować pewnego rodzaju identyfikatora korelacji, aby ustalić, do którego obiektu gry należą te dane.
To, jak to zrobisz, zależy od języka, z którym pracujesz. W Scali możesz używać Software Transactional Memory, w Javie / C ++ pewien rodzaj blokowania / synchronizacji. Lubię niezmienne dane, więc zwracam nowy niezmienny obiekt dla każdej aktualizacji. Jest to trochę marnowania pamięci, ale w przypadku nowoczesnych komputerów nie jest to taka wielka sprawa. Jednak jeśli chcesz zablokować udostępnione struktury danych, możesz to zrobić. Sprawdź klasę Exchanger w Javie, użycie dwóch lub więcej buforów może przyspieszyć proces.
Zanim zaczniesz udostępniać dane między wątkami, sprawdź, ile danych faktycznie musisz przekazać. Jeśli masz oktodę dzielącą przestrzeń 3d i możesz zobaczyć 5 obiektów z 10 obiektów, nawet jeśli twoja logika wymaga aktualizacji wszystkich 10, musisz przerysować tylko 5, które widzisz. Więcej informacji można znaleźć na tym blogu: http://gameprogrammingpatterns.com/game-loop.html Nie chodzi tu o synchronizację, ale pokazuje ona, w jaki sposób logika gry jest oddzielona od wyświetlania i jakie wyzwania należy pokonać (FPS). Mam nadzieję że to pomoże,
znak
źródło