Czy jest coś złego w niekontrolowanym uruchomieniu głównej pętli gry?


19

Zastanawiałem się, czy istnieje jakakolwiek szkoda, gdy moja pętla gry działa tak szybko, jak pozwala na to system?

Obecnie mam pętlę, która mierząc upływ czasu w nanosekundach, uruchamia logikę gry i renderuje logikę z predefiniowanymi prędkościami bez problemu. W rzeczywistości każda logika, którą wykonuję w pętli, jest taktowana na określoną liczbę wywołań co sekundę.

Sama pętla działa jednak tak szybko, jak chce, a na mojej maszynie dochodzi do około 11,7 miliona pętli na sekundę.

Pętla (prosty pseudokod):

while(!isGameOver){

 if(canPollInputs){
    pollInputs()
 }

 while(canStepLogic){
    stepLogic()
 }

 if(canRender){
    render()
 }
}

Moje pytanie jest w zasadzie, czy ta prosta pętla, jeśli nie działa z kontrolowaną prędkością, może wyrządzić szkodę systemowi?

Edycja: Oznacza to, że moja logika działa 30 razy na sekundę (30 tps), mój renderer działa przy 60 fps, odpytuję dane wejściowe 100 razy na sekundę, a także jest trochę logiki, aby poradzić sobie z logiką lub renderowaniem trwającym dłużej niż oczekiwano . Ale sama pętla nie jest dławiona.

Edycja: użycie Thread.sleep()np. Przepustnicy głównej pętli w dół do 250 pętli na sekundę prowadzi do zmniejszenia, ale pętle działają z prędkością około 570 pętli na sekundę zamiast pożądanych 250 (doda kod, gdy będę na komputerze stacjonarnym ..)

Edycja: Proszę bardzo, działająca gra java w celu wyjaśnienia rzeczy. Możesz też z niego korzystać, ale nie rób sobie tego;)

private void gameLoop() {

    // Time that must elapse before a new run
    double timePerPoll = 1000000000l / targetPPS;
    double timePerTick = 1000000000l / targetTPS;
    double timePerFrame = 1000000000l / targetFPS;
    int maxFrameSkip = (int) ( (1000000000l / MINIMUM_FPS) / timePerTick);

    int achievedPPS = 0;
    int achievedFPS = 0;
    int achievedTPS = 0;

    long timer = TimeUtils.getMillis();

    int loops = 0;

    int achievedLoops = 0;

    long currTime = 0l;
    long loopTime = 0l;

    long accumulatorPPS = 0l;
    long accumulatorTPS = 0l;
    long accumulatorFPS = 0l;

    long lastTime = TimeUtils.getNano();

    while(!isRequestedToStop) {
        currTime = TimeUtils.getNano();
        loopTime = currTime - lastTime;
        lastTime = currTime;

        loops = 0;

        accumulatorPPS += loopTime;
        accumulatorTPS += loopTime;
        accumulatorFPS += loopTime;

        if(accumulatorPPS >= timePerPoll) {
            pollInputs();
            playerLogic();
            achievedPPS++;
            accumulatorPPS -= timePerPoll;
        }

        while(accumulatorTPS >= timePerTick && loops < maxFrameSkip) {
            tick();
            achievedTPS++;
            accumulatorTPS -= timePerTick;
            loops++;
        }

        // Max 1 render per loop so player movement stays fluent
        if(accumulatorFPS >= timePerFrame) {
            render();
            achievedFPS++;
            accumulatorFPS -= timePerFrame;
        }

        if(TimeUtils.getDeltaMillis(timer) > 1000) {
            timer += 1000;
            logger.debug(achievedTPS + " TPS, " + achievedFPS + " FPS, "
                    + achievedPPS + " Polls, " + achievedLoops + " Loops");
            achievedTPS = 0;
            achievedFPS = 0;
            achievedLoops = 0;
        }

        achievedLoops++;
    }
}

Jak widać, prawie nie ma kodu uruchamianego w każdej pętli, ale zawsze określony wybór na podstawie czasu, który upłynął. Pytanie dotyczy tej „pętli roboczej” i tego, jak wpływa ona na system.


2
obowiązkowy link „napraw swój timepep”: gafferongames.com/game-physics/fix-your-timestep przeczytaj go. Należy również pamiętać, że Thread.Sleep () nie może być używany do niezawodnego ograniczania czasu, ponieważ system operacyjny nie gwarantuje powrotu kontroli do aplikacji po wymaganym czasie. (Może się różnić).
Roy T.

Tak, to jest problem z pseudokodem. Zrobiłem większość tego, o czym pisze Gaffer (obecnie pracuję nad implementacją delty, która uzupełnia moją strukturę pętli). Jeśli nie mogę użyć Thread.sleep (), to co jeszcze jest dostępne w standardowej Javie?
dot_Sp0T

1
Zwykle używasz zajętej pętli z Thread.Sleep (0) i masz nadzieję na najlepsze. Gaffer dużo o tym mówi w kilku artykułach.
Roy T.

Cóż, to w zasadzie to, co robiłem, tylko bez thread.sleep (0) ORAZ to, co pierwotnie sprowokowało to pytanie. Czy jest coś złego w zajętej (lub nie zajętej, ponieważ nawet ta niewielka, powtarzalna logika jest ograniczana) dla systemu?
dot_Sp0T

Jakiś czas temu błąd spowodował, że Starcraft II zrobił to pod pewnymi warunkami. Skończyło się dosłownie stopieniem kilku procesorów graficznych. Więc tak, bardzo możliwe jest, że niekontrolowana pętla gry spowoduje szkody.
Mason Wheeler,

Odpowiedzi:


35

Spowoduje to, że jeden rdzeń procesora będzie zawsze działał na 100%. Zwykle nie powoduje to szkód w systemie. Procesory są zaprojektowane do pracy w 100% przez wiele godzin. Ale na urządzeniu mobilnym szybko rozładuje baterię i podgrzeje urządzenie, co prawdopodobnie będzie cię kosztować około gwiazdek w ocenach sklepów. Na komputerze stacjonarnym nie stanowi to większego problemu, ale zużywa więcej energii elektrycznej użytkowników, powoduje szybsze obracanie się wentylatora procesora, co może powodować hałas i marnować cykle procesora, które mogłyby zostać wykorzystane przez inne procesy. Chociaż nie są to wady krytyczne, nadal mają zły styl, dlatego należy ich unikać, jeśli to możliwe.

Nie powiedziałeś nic o tym, jak twoja pętla logiczna gry działa wewnętrznie, ale kiedy używasz podejścia delta-time (każde obliczenie, które wykonujesz, bierze pod uwagę czas od ostatniego połączenia), masz do czynienia z bardzo małą deltą- wartości czasu. Oznacza to, że możesz napotkać problemy z niedokładnością zmiennoprzecinkową, co może powodować różnego rodzaju dziwne zachowania. Ponadto rozdzielczość czasomierza systemowego jest często ograniczona, więc gdy twoja pętla logiczna jest zbyt szybka, możesz uzyskać wartość delta-t równą zero, co może spowodować podział przez zero, co spowoduje awarię.

Aby złagodzić ten problem, należy ograniczyć liczbę klatek na sekundę (grafikę i logikę) do maksimum tego, co może dostrzec ludzkie oko. To, o ile jest kwestionowane, zależy od rodzaju animacji i rodzaju wyświetlanego obrazu, ale szacunki wahają się od 40 FPS do 120 FPS. Oznacza to, że musisz ustawić minimalny czas wykonania każdej pętli między 20 ms a 8 ms. Jeśli iteracja pętli zakończy się szybciej, pozwól wątkowi procesora sleepna pozostały okres czasu.


Dzięki za wyjaśnienie, Philipp. Odniosłem się do faktu, że korzystam z pewnej liczby klatek na sekundę i tps, a także odpytuję tylko pewną ilość razy na sekundę. Postaram się jednak wyjaśnić.
dot_Sp0T

@ dot_Sp0T Po edycji pytania, tylko pierwszy akapit i ostatnie zdanie są istotne dla twojej sprawy. Chciałbym jednak zachować drugi i trzeci akapit mojej odpowiedzi, ponieważ mogą one być pomocne dla innych.
Philipp

Prawdopodobnie równie dobrze zaakceptuję twoją odpowiedź, ponieważ pierwszy akapit doskonale daje odpowiedź. Czy miałbyś coś przeciwko wizualnemu odróżnieniu go od reszty odpowiedzi?
dot_Sp0T

Zagraj w niepakowaną grę Civilization 2 na pulpicie, a przekonasz się, że to problem. Przynajmniej tak. wzruszenie ramionami
corsiKa

1
Oprócz kwestii związanych z baterią i wentylatorem, zwiększone użycie procesora może prowadzić do nadmiernego ciepła, które może być niewygodne (np. Laptop, szczególnie mój rMBP), a nawet doprowadzić do wyłączenia komputera (szczególnie starszych komputerów z zakurzonymi chłodnicami). Czynniki te są rozdrażnione, jeśli uruchomisz wszystkie rdzenie w 100%.
NPSF3000,

2

Marnujesz cykle procesora. Oznacza to krótszy czas pracy baterii w notebookach, tabletach i telefonach, wyższe rachunki za prąd, więcej ciepła wytwarzanego przez maszynę, głośniejsze wentylatory. Ponadto możesz spożywać cykle z innych ważnych procesów systemowych (np. Serwer okien może się popsuć), co może mieć wpływ na rozgrywkę. Niektóre programy planujące w dzisiejszych systemach wyprzedzających wielozadaniowość również nakładają kary na aplikacje, które wykorzystują zbyt wiele cykli, więc możesz być szybki, a potem nagle zobaczyć dziwne szarpnięcie, gdy system jest dławiony.

Wielu zapalonych graczy buduje również niestandardowe komputery od zera, które są na granicy specyfikacji ich wentylatora, w którym to przypadku gorący dzień i ciepło generowane przez grę mogą spowodować uruchomienie zabezpieczeń w maszynie (w najlepszym wypadku) lub nawet przegrzanie części i umrzeć (w najgorszym przypadku). Więc jeśli to twoi odbiorcy docelowi, możesz chcieć upewnić się, że zawsze pozostawiasz trochę miejsca „na górze”.

Wreszcie, istnieją problemy z grą, w których możesz być korzystniejszy dla graczy z szybszymi maszynami niż z wolniejszymi. Ograniczenie pętli gry do określonej częstotliwości i użycie dodatkowych cykli wyłącznie w aspektach niezwiązanych z rozgrywką (takich jak renderowanie grafiki o wyższej wierności, efekty dźwięku przestrzennego itp.) Sprawią, że gra będzie bardziej uczciwa.


Powiedziałbym odwrotnie, że ludzie, którzy z pasją budują swoje maszyny, używają dobrych fanów, a nawet chłodzenia wodnego. Tylko przypadki HTPC lub mini ITX mogą mieć ograniczenia w tej części. W przeciwnym razie ludzie biedni, ale entuzjaści będą korzystać z pudełka ventirad. co wystarczy nawet na 100%, gorąco, ale dobrze.
v.oddou

Po prostu powtarzam z doświadczenia. Jeśli spojrzysz na Star Trek Online, w rzeczywistości mają one osobne ustawienie w swoim GUI, które wstawi sleep () w głównej pętli, aby zapobiec przegrzaniu niektórych maszyn, więc nie może być niczym niezwykłym, jeśli producent Neverwinter i STO zauważyło potrzebę jego dodania. Pamiętaj, że pasja! = Umiejętność. Jest wielu wykwalifikowanych i namiętnych majsterkowiczów, ale są też początkujący i inni, którzy wciąż się uczą, a przy przyzwoitym odsetku osób przedwcześnie kończących naukę statystyki mówią, że są większością.
uliwitness

Dzięki tym komentarzom przyszedłem ponownie przeczytać twoją odpowiedź. Wymieniasz ważne punkty, ale niestety tak naprawdę nie rozwiązujesz tego problemu w kontrowersjach dotyczących szybszych maszyn w porównaniu do wolniejszych maszyn . Pętla logiczny (nazywany gameloop w pkt III) jest ograniczona. Pytanie dotyczyło ograniczenia pętli szkieletowej, która po prostu przewartościowuje każdy przebieg, jeśli wejście musi zostać pobrane, obliczona logika lub renderowana ramka - więc nie szybsza logika, chyba że twoja maszyna jest strasznie
słaba

1

Nie powinieneś mieć roztrzęsionego ruchu / rozgrywki

Istnieją dwa sposoby implementacji logiki gry - związane z czasem rzeczywistym lub związane z liczbą „tur” / kroków przetwarzania. Jeśli coś w twojej grze porusza się w lewo, to czy ruch będzie inny, jeśli twoja stepLogic () była nazywana 100 zamiast 50 razy?

Jeśli Twój kod traktuje upływający czas wyraźnie wszędzie i robi to poprawnie, może być w porządku; ale jeśli w kodzie jest coś, co zależy od liczby „kroków”, otrzymasz niepożądane efekty uboczne.

Po pierwsze, prędkość rzeczy, które powinny się ciągle poruszać, będzie się nieprzewidywalnie zmieniać (w zależności od użycia procesora), a to powoduje bardzo irytujące sterowanie - nie możesz wykonać precyzyjnego uderzenia lub skoku, jeśli nagle gra przyspieszy lub zwolni. Nawet w przypadku gier bez „drgania” wygląda denerwująco i roztrzęsiony, jeśli wszystko nie idzie gładko.

Po drugie, możesz mieć problemy ze zdolnościami postaci w zależności od szybkości komputera - klasycznym przykładem jest stary problem Quake'a, w którym maksymalny zasięg skoków był przypadkowo zależny od twojej fps.

Te problemy mogą się pojawiać, nawet jeśli próbujesz mieć kod niezależny od fps, nagromadzenie błędów zaokrąglania może często powodować takie błędy.


1
Tak bardzo, jak podoba mi się twoja odpowiedź, nie ma ona związku z pytaniem, ponieważ nie pytam o roztrzęsiony ruch / rozgrywkę, ale o szkodę, jaką niekontrolowana pętla (która nie kontroluje żadnej logiki, renderowania ani niczego innego) może zrobić systemowi
dot_Sp0T

1

Jedyną potencjalną szkodą jest zużycie energii, więc nie rób tego na urządzeniach mobilnych. I odwrotnie, wiele systemów wbudowanych spędza całe swoje życie w pętli, czekając, aż coś się wydarzy.

Kiedyś całkowicie normalne było renderowanie klatek gry tak szybko, jak to możliwe, czasami nawet bez timera, który kompensowałby rozgrywkę przy różnych prędkościach procesora. Wyścigowa gra Bullfroga, Hi-Octane, była szczególnie złą ofiarą tego i podejrzewam, że o tym mówi plakat z Civ2.

Radzę przynajmniej sprawdzać dane wejściowe przy każdym przejściu, jeśli korzystasz z systemu Windows lub podobnego, aby zapewnić szybką reakcję na dane wejściowe.


To nie jest kwestia timera, to kwestia chronometru, który wolałbym powiedzieć, lub licznika wydajności. W pewnym momencie trzeba uzyskać czas rzeczywisty, a wolna pętla działa idealnie. Jednak licznik czasu (w sensie regularnej przerwy) próbuje regulować prędkość pętli w ogólnym użyciu. Nie uważam tego za dobre. Lepiej jest użyć funkcji swap/ present/ flipdo czekania na sygnał vsync, aby regulować pętlę gry.
v.oddou
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.