Oddzielanie logiki / aktualizacji od kodu renderowania / rysowania w jednym wątku za pomocą trybu uśpienia

9

Czytałem, że szybkość obiektów w grze nie powinna być utrudniana przez FPS, ale zamiast tego powinna być oparta na czasie. Jak mogę oddzielić kod aktualizacji / rysowania, aby zmaksymalizować wydajność bez ograniczania szybkości rysowania i zapewnić stałą logiczną szybkość aktualizacji na podstawie czasu?

Mój obecny pseudo kod jest następujący

loop
{
    draw();
    if (ticksElapsed() > 100)
    {
        update();
        ticks+= ticksElapsed();
    }        
}

Problem polega na tym, że kod rysunkowy utrudnia wydajność aktualizacji (). I zużywa 100% procesora, ponieważ jeśli zostanie uśpiony, to wyłączy obie funkcje rysowania / logiki.

Używam również SDL i wydaje się, że nie ma opcji vsync. Słyszałem również o terminach ustalonych i zmiennych krokach czasowych, jednak nie jestem pewien, jak można to zrobić za pomocą funkcji sleep ()

Oskenso Kashi
źródło
1
Nie musisz marnować 100% mocy procesora tylko na czekanie, uśpienie (0) na końcu pętli while, jeśli ticksElapsed () <100. System operacyjny natychmiast powróci do wątku, jeśli nie będzie innego wątku, który chce biec. Ale nie marnujesz już 100% mocy procesora.
Maik Semder,
Jednak najlepszym rozwiązaniem dla takiej konfiguracji 1 wątku jest użycie VSYNC, jeśli cant Vsync, a następnie połączenia snu (0) w pętli, aż osiągnie docelową liczbę klatek na sekundę, a następnie aktualizować i rysować
Maik Semder

Odpowiedzi:

3

We fragmencie kodu wygląda to tak, jakbyś próbował uruchomić grę w trybie krokowym w ustalonym czasie, czekając na zajęcie, jeśli rysunek i aktualizacja zajęły mniej niż 15 ms (60 klatek na sekundę). Jest to możliwe i dobrze zgadłeś, że nie można tego zrobić za pomocą połączenia snu, ponieważ nie wiesz dokładnie, jak długo będziesz spać. Pętla oczekiwania na zajęty jest dobrym rozwiązaniem.

Jednak rozważ przypadek, w którym aktualizacja i rysunek przekraczają 15 ms, teraz gra jest rysowana i aktualizowana w zwolnionym tempie. Możesz teraz zrobić dwie rzeczy: wykryć ten stan i upuścić klatki (pomiń rysowanie i przejdź od razu do aktualizacji, dopóki nie zsynchronizujesz ponownie), ale jeśli komputer jest po prostu spowolniony, nigdy go nie dogoni.

Innym rozwiązaniem jest uniezależnienie logiki aktualizacji od czasu. Nie potrzebujesz do tego osobnego wątku, musisz tylko określić, jak szybko powinno się poruszać. Zamiast 5 pikseli na tik należy użyć 50 pikseli na sekundę. Aby to osiągnąć, potrzebujesz precyzyjnego timera, a cała logika aktualizacji powinna mieć dostęp do timera, aby zobaczyć, ile czasu minęło od ostatniej aktualizacji.

Zasadniczo odchodzisz od:

void UpdatePlayer()
 player.x += 10;

Do

void UpdatePlayer(float elapsedSeconds) //the total seconds elapsed since last update
 player.x += walkspeed * elapsedSeconds;
Roy T.
źródło
Więc mój silnik zawsze zużywa 100% i nic nie mogę na to poradzić?
Oskenso Kashi
1
Zużywa tyle cykli, ile pozwala na to harmonogram, ale nie jest to tak naprawdę problemem, ponieważ nie można tak naprawdę robić wielu innych rzeczy podczas gry :).
Roy T.,
@Oskenso Problemem jest jednak, jeśli używasz więcej niż jednego wątku, wtedy główny wątek nie pozwoli, aby pozostałe działały tak dużo, jak mogłyby, marnując dużo mocy obliczeniowej w pętli while, naprawdę powinieneś rozważyć sen
Maik Semder,
@ Maik Semder: czy masz rozwiązanie dla snu (x), które nie jest dokładne? Po upływie okresu uśpienia wątek jest gotowy do uruchomienia. Ale gotowy wątek nie gwarantuje natychmiastowego uruchomienia. To zależy od harmonogramu. Kiedy używasz dwóch wątków, istnieją inne rozwiązania, do tego doskonały artykuł: altdevblogaday.com/2011/07/03/threading-and-your-game-loop
Roy T.
1
@Roy sleep (0) jest rozwiązaniem. Zwraca natychmiast, jeśli nie ma innego wątku, który chciałby uruchomić ( Sleep WinAPI ) i daje innym wątkom szansę na uruchomienie. Jeśli drugi wątek nie da szansy na uruchomienie głównego wątku w zamian, masz problem z wątkami, ale blokowanie wszystkiego innego, nie wywoływanie uśpienia w pierwszej kolejności, czyni go jeszcze gorszym i nie jest rozwiązaniem. Kluczem jest wywołanie uśpienia (0) i przetestowanie upływającego czasu, aż osiągniesz docelową stałą liczbę klatek, abyś nie marnował 100% CPU tylko na czekanie.
Maik Semder