游戏循环中最佳睡眠时间计算的研究 [英] Investigation of optimal sleep time calculation in game loop

查看:29
本文介绍了游戏循环中最佳睡眠时间计算的研究的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

When programming animations and little games I've come to know the incredible importance of Thread.sleep(n); I rely on this method to tell the operating system when my application won't need any CPU, and using this making my program progress in a predictable speed.

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

My problem is that the JRE uses different methods of implementation of this functionality on different operating systems. On UNIX-based (or influenced) OS:es such as Ubuntu and OS X, the underlying JRE implementation uses a well-functioning and precise system for distributing CPU-time to different applications, and so making my 2D game smooth and lag-free. However, on Windows 7 and older Microsoft systems, the CPU-time distribution seems to work differently, and you usually get back your CPU-time after the given amount of sleep, varying with about 1-2 ms from target sleep. However, you get occasional bursts of extra 10-20 ms of sleep time. This causes my game to lag once every few seconds when this happens. I've noticed this problem exists on most Java games I've tried on Windows, Minecraft being a noticeable example.

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

Now, I've been looking around on the Internet to find a solution to this problem. I've seen a lot of people using only Thread.yield(); instead of Thread.sleep(n);, which works flawlessly at the cost of the currently used CPU core getting full load, no matter how much CPU your game actually needs. This is not ideal for playing your game on laptops or high energy consumption workstations, and it's an unnecessary trade-off on Macs and Linux systems.

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

Looking around further I found a commonly used method of correcting sleep time inconsistencies called "spin-sleep", where you only order sleep for 1 ms at a time and check for consistency using the System.nanoTime(); method, which is very accurate even on Microsoft systems. This helps for the normal 1-2 ms of sleep inconsistency, but it won't help against the occasional bursts of +10-20 ms of sleep inconsistency, since this often results in more time spent than one cycle of my loop should take all together.

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

After tons of looking I found this cryptic article of Andy Malakov, which was very helpful in improving my loop: http://andy-malakov.blogspot.com/2010/06/alternative-to-threadsleep.html

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

Based on his article I wrote this sleep method:

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

SLEEP_PRECISION I usually put to about 2 ms, and SPIN_YIELD_PRECISION to about 10 000 ns for best performance on my Windows 7 machine.

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

After tons of hard work, this is the absolute best I can come up with. So, since I still care about improving the accuracy of this sleep method, and I'm still not satisfied with the performance, I would like to appeal to all of you java game hackers and animators out there for suggestions on a better solution for the Windows platform. Could I use a platform-specific way on Windows to make it better? I don't care about having a little platform specific code in my applications, as long as the majority of the code is OS independent.

我还想知道是否有人了解 Microsoft 和 Oracle 的 Thread.sleep(n); 方法的更好实现,或者 Oracle 未来的改进计划是什么他们的环境作为需要高计时精度的应用程序的基础,例如音乐软件和游戏?

I would also like to know if there is anyone who knows about Microsoft and Oracle working out a better implementation of the Thread.sleep(n); method, or what's Oracle's future plans are on improving their environment as the basis of applications requiring high timing accuracy, such as music software and games?

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

Thank you all for reading my lengthy question/article. I hope some people might find my research helpful!

推荐答案

您可以使用 循环计时器互斥.这是 IHMO 做你想做的最有效的方式.但是,您应该考虑在计算机滞后的情况下跳过帧(您可以在计时器代码中使用另一个非阻塞互斥锁.)

You could use a cyclic timer associated with a mutex. This is IHMO the most efficient way of doing what you want. But then you should think about skipping frames in case the computer lags (You can do it with another nonblocking mutex in the timer code.)

一些伪代码来澄清

定时器代码:

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

睡眠代码:

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

起始值:

mutexSkipRender = 1
mutexRender = 0

编辑:更正初始化值.

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

The following code work pretty well on windows (loops at exactly 50fps with a precision to the millisecond)

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();
        }
    }
}

这篇关于游戏循环中最佳睡眠时间计算的研究的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆