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 就是一个明显的例子。

现在,我一直在网上四处寻找解决这个问题的办法。我见过很多人只使用 Thread.yield(); 而不是 Thread.sleep(n); CPU 内核满载,无论您的游戏实际需要多少 CPU。这不适合在笔记本电脑或高能耗工作站上玩游戏,而且在 Mac 和 Linux 系统上也是一种不必要的权衡。

进一步观察,我发现了一种常用的纠正 sleep 时间不一致的方法,称为“自旋 sleep ”,您一次只能命令 sleep 1 毫秒,并使用 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 为了在我的 Windows 7 机器上获得最佳性能,我通常将 SPIN_YIELD_PRECISION 设置为大约 2 毫秒,将 SPIN_YIELD_PRECISION 设置为大约 10000 纳秒。

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

我也想知道有没有人知道微软和甲骨文正在研究更好的Thread.sleep(n);方法的实现,或者甲骨文 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/7831435/

相关文章:

java - 扫描 jTextArea 中的单词

java - Google 通讯录示例 "Select specific contact data"出现问题

java - 如何让代码在继续执行 Java 中的 println 之前等待对象?

java - 如何使用neo4j-jdbc连接Neo4j 3.0数据库?

unity-game-engine - 使用 USS 样式在 Unity UI Toolkit 中循环过渡

2d - 你应该如何在 Vulkan 中更新每帧的纹理?

C# 从 DLL 扩展类

javascript - 为什么样式分配在创建后立即执行而不是动画?

javascript - 用于开始 CSS 转换的 requestAnimationFrame

ios - 向固定点旋转箭头(类似时钟的动画)