java - 游戏循环中最佳 sleep 时间计算的研究

标签 java animation game-engine

在编写动画和小游戏时,我开始了解 Thread.sleep(n); 我依靠这种方法告诉操作系统我的应用程序何时不需要任何 CPU,并使用它使我的程序以可预测的速度运行。

我的问题是 JRE 在不同的操作系统上使用不同的方法来实现此功能。在基于 UNIX(或受影响)的操作系统上,例如 Ubuntu 和 OS X,底层 JRE 实现使用功能良好且精确的系统将 CPU 时间分配给不同的应用程序,从而使我的 2D 游戏流畅且无延迟.但是,在 Windows 7 和较旧的 Microsoft 系统上,CPU 时间分布似乎有所不同,您通常会在给定的 sleep 量后恢复 CPU 时间,从目标 sleep 开始大约 1-2 毫秒。但是,您偶尔会获得额外 10-20 毫秒的 sleep 时间。发生这种情况时,这会导致我的游戏每隔几秒就会滞后一次。我注意到我在 Windows 上尝试过的大多数 Java 游戏都存在这个问题,Minecraft 就是一个明显的例子。

现在,我一直在 Internet 上四处寻找解决此问题的方法。我见过很多人只使用 Thread.yield(); 而不是 Thread.sleep(n);,它以当前使用的成本为代价完美运行无论您的游戏实际需要多少 CPU,CPU 内核都会满负荷运行。这不适合在笔记本电脑或高能耗工作站上玩游戏,而且在 Mac 和 Linux 系统上是不必要的权衡。

环顾四周,我发现了一种常用的纠正 sleep 时间不一致的方法,称为“spin-sleep”,您一次只订购 1 毫秒的 sleep 并使用 System.nanoTime(); 检查一致性。 方法,即使在 Microsoft 系统上也非常准确。这有助于解决正常的 1-2 毫秒的 sleep 不一致,但对于偶尔爆发的 +10-20 毫秒的 sleep 不一致无济于事,因为这通常会导致花费的时间比我的循环的一个周期应该花费的时间要多在一起。

经过大量查找,我发现了 Andy Malakov 的这篇神秘文章,它对改进我的循环非常有帮助:http://andy-malakov.blogspot.com/2010/06/alternative-to-threadsleep.html

根据他的文章我写了这个 sleep 方法:

// 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 我通常设置为大约 2 ms,SPIN_YIELD_PRECISION 设置为大约 10 000 ns,以便在我的 Windows 7 机器上获得最佳性能。

经过大量的努力,这绝对是我能想到的最好的。所以,由于我仍然关心提高这种 sleep 方法的准确性,而且我对性能仍然不满意,所以我想呼吁所有的 Java 游戏黑客和动画师,为更好的解决方案提供建议。 window 平台。我可以在 Windows 上使用特定于平台的方式来使其更好吗?我不关心在我的应用程序中有一些特定于平台的代码,只要大部分代码是独立于操作系统的。

我还想知道是否有人知道 Microsoft 和 Oracle 正在制定更好的 Thread.sleep(n); 方法实现,或者 Oracle future 的改进计划是什么他们的环境是音乐软件和游戏等需要高计时精度的应用程序的基础吗?

感谢大家阅读我冗长的问题/文章。我希望有些人会发现我的研究有帮助!

最佳答案

您可以使用 cyclic timermutex 相关联.这是 IHMO 做你想做的最有效的方式。但是你应该考虑跳帧以防计算机滞后(你可以在计时器代码中使用另一个非阻塞互斥体来做到这一点。)

编辑:需要澄清的一些伪代码

定时器代码:

While(true):
  if acquireIfPossible(mutexSkipRender):
    release(mutexSkipRender)
    release(mutexRender)

sleep 代码:

acquire(mutexSkipRender)
acquire(mutexRender)
release(mutexSkipRender)

起始值:

mutexSkipRender = 1
mutexRender = 0

编辑:更正初始化值。

以下代码在 Windows 上运行良好(以 50fps 精确循环,精确到毫秒)

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Semaphore;


public class Main {
    public static void main(String[] args) throws InterruptedException {
        final Semaphore mutexRefresh = new Semaphore(0);
        final Semaphore mutexRefreshing = new Semaphore(1);
        int refresh = 0;

        Timer timRefresh = new Timer();
        timRefresh.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                if(mutexRefreshing.tryAcquire()) {
                    mutexRefreshing.release();
                    mutexRefresh.release();
                }
            }
        }, 0, 1000/50);

        // The timer is started and configured for 50fps
        Date startDate = new Date();
        while(true) { // Refreshing loop
            mutexRefresh.acquire();
            mutexRefreshing.acquire();

            // Refresh 
            refresh += 1;

            if(refresh % 50 == 0) {
                Date endDate = new Date();
                System.out.println(String.valueOf(50.0*1000/(endDate.getTime() - startDate.getTime())) + " fps.");
                startDate = new Date();
            }

            mutexRefreshing.release();
        }
    }
}

关于java - 游戏循环中最佳 sleep 时间计算的研究,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5274619/

相关文章:

c++ - 游戏开发-更新方法和状态模式

c++ - Racket 作为游戏引擎中的脚本语言

java - OpenGL es 2. Java for Android 中的顶点

java - 如何在我的 Spring 应用程序中映射静态 Assets ?

java - 我如何实现这个比较器?

javascript - 如何隐藏粒子效果(圆圈)直到加载其动画的 .png?

c++ - 避免与 Bullet 发生地面碰撞

java - Spring - 从现有的 BindingResult 和 Model 构建新的 modelAndView

ios - 按时间间隔重复功能?

CSS 伪元素的 jQuery .click() 事件 ( :before/:after)?