Investigação do cálculo ideal do tempo de sono no loop do jogo

Ao programar animações e joguinhos, conheço a incrível importância doThread.sleep(n); Confio neste método para informar ao sistema operacional quando meu aplicativo não precisará de CPU e, usando isso, meu programa progride em uma velocidade previsíve

Meu problema é que o JRE usa métodos diferentes de implementação dessa funcionalidade em diferentes sistemas operacionais. Nos sistemas operacionais baseados em UNIX (ou influenciados), como Ubuntu e OS X, a implementação subjacente do JRE usa um sistema preciso e que funciona bem para distribuir o tempo da CPU para diferentes aplicativos e, assim, tornar meu jogo 2D suave e sem atrasos . No entanto, no Windows 7 e nos sistemas Microsoft mais antigos, a distribuição do tempo da CPU parece funcionar de maneira diferente, e você normalmente recupera o tempo da CPU após um determinado período de sono, variando de 1 a 2 ms a partir do sono de destino. No entanto, você recebe rajadas ocasionais de 10 a 20 ms extras de tempo de sono. Isso faz com que o meu jogo fique lento a cada poucos segundos quando isso acontece. Percebi que esse problema existe na maioria dos jogos Java que experimentei no Windows, sendo o Minecraft um exemplo notáve

Agora, eu estive procurando na Internet para encontrar uma solução para esse problema. Já vi muitas pessoas usando apenasThread.yield(); ao invés deThread.sleep(n);, que funciona perfeitamente ao custo do núcleo da CPU atualmente usado, obtendo carga máxima, independentemente da CPU que seu jogo realmente precise. Isso não é ideal para jogar em laptops ou estações de trabalho de alto consumo de energia, e é uma troca desnecessária em sistemas Macs e Linux.

Olhando em volta, encontrei um método comumente usado para corrigir inconsistências no tempo de sono chamado "sono em rotação", em que você solicita apenas o sono por 1 ms de cada vez e verifica a consistência usando oSystem.nanoTime();, que é muito preciso, mesmo nos sistemas Microsoft. Isso ajuda nos 1-2 ms normais de inconsistência do sono, mas não ajuda contra as rajadas ocasionais de + 10-20 ms de inconsistência do sono, já que isso geralmente resulta em mais tempo gasto do que um ciclo do meu loop. juntos

Depois de toneladas de procura, encontrei este artigo enigmático de Andy Malakov, que foi muito útil para melhorar meu loop:http: //andy-malakov.blogspot.com/2010/06/alternative-to-threadsleep.htm

Com base em seu artigo, escrevi este método para dormir:

// Variables for calculating optimal sleep time. In nanoseconds (1s = 10^-9ms).
private long timeBefore = 0L;
private long timeSleepEnd, timeLeft;

// The estimated game update rate.
private double timeUpdateRate;

// The time one game loop cycle should take in order to reach the max FPS.
private long timeLoop;

private void sleep() throws InterruptedException {

    // Skip first game loop cycle.
    if (timeBefore != 0L) {

        // Calculate optimal game loop sleep time.
        timeLeft = timeLoop - (System.nanoTime() - timeBefore);

        // If all necessary calculations took LESS time than given by the sleepTimeBuffer. Max update rate was reached.
        if (timeLeft > 0 && isUpdateRateLimited) {

            // Determine when to stop sleeping.
            timeSleepEnd = System.nanoTime() + timeLeft;

            // Sleep, yield or keep the thread busy until there is not time left to sleep.
            do {
                if (timeLeft > SLEEP_PRECISION) {
                    Thread.sleep(1); // Sleep for approximately 1 millisecond.
                }
                else if (timeLeft > SPIN_YIELD_PRECISION) {
                    Thread.yield(); // Yield the thread.
                }
                if (Thread.interrupted()) {
                    throw new InterruptedException();
            }
                timeLeft = timeSleepEnd - System.nanoTime();
            }
            while (timeLeft > 0);
        }
        // Save the calculated update rate.
        timeUpdateRate =  1000000000D / (double) (System.nanoTime() - timeBefore);
    }
    // Starting point for time measurement.
    timeBefore = System.nanoTime();
}

SLEEP_PRECISION Eu costumo colocar cerca de 2 ms eSPIN_YIELD_PRECISION a cerca de 10 000 ns para obter o melhor desempenho na minha máquina Windows 7.

Depois de toneladas de trabalho duro, este é o melhor absoluto que posso conseguir. Portanto, como ainda me preocupo em melhorar a precisão desse método de suspensão e ainda não estou satisfeito com o desempenho, gostaria de apelar a todos vocês, hackers e animadores de jogos em Java, por aí, em busca de sugestões sobre uma solução melhor para o problema. Plataforma Windows. Posso usar uma maneira específica da plataforma no Windows para torná-la melhor? Não me importo em ter um pequeno código específico de plataforma em meus aplicativos, desde que a maioria do código seja independente do SO.

Também gostaria de saber se há alguém que saiba sobre a Microsoft e a Oracle que está implementando uma melhor implementação doThread.sleep(n);, ou quais são os planos futuros da Oracle para melhorar seu ambiente como base de aplicativos que exigem alta precisão de tempo, como software e jogos de música?

Obrigado a todos por ler minha longa pergunta / artigo. Espero que algumas pessoas achem minha pesquisa útil!

questionAnswers(5)

yourAnswerToTheQuestion