当前位置: 首页 > 面试题库 >

游戏循环中最佳睡眠时间计算的研究

长孙作人
2023-03-14
问题内容

在编写动画和小游戏时,我已经知道Thread.sleep(n);我依靠这种方法告诉操作系统什么时候我的应用程序不需要任何CPU,并以此来使程序以可预测的速度前进,这具有不可思议的重要性。

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

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

进一步环顾四周,我发现了一种纠正睡眠时间不一致的常用方法,称为“旋转睡眠”,您一次只能订购1
ms的睡眠,并使用该System.nanoTime();方法检查一致性,即使在Microsoft系统上,该方法也非常准确。这有助于正常的1-2
ms的睡眠不一致,但对于偶尔出现+ 10-20 ms的睡眠不一致的突发情况却无济于事,因为这通常导致花费的时间超过循环的一个周期应占用的全部时间一起。

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

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

// 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``SPIN_YIELD_PRECISION为了使Windows 7机器获得最佳性能,我通常花费大约2毫秒和10000
ns。

经过大量的努力,这是我能想到的最好的选择。因此,由于我仍然很想提高这种睡眠方法的准确性,并且对性能仍然不满意,因此我想呼吁所有Java游戏黑客和动画制作者为该解决方案提供更好的解决方案的建议。
Windows平台。我可以在Windows上使用特定于平台的方式来使其更好吗?只要大多数代码与操作系统无关,我都不会在乎我的应用程序中只包含一些平台特定的代码。

我还想知道是否有人对Microsoft和Oracle知道如何更好地实现该Thread.sleep(n);方法,或者Oracle未来的计划是改善环境,将其作为要求高定时精度的应用程序的基础,例如音乐软件和软件。游戏?

谢谢大家阅读我冗长的问题/文章。我希望有人可能对我的研究有所帮助!


问题答案:

您可以使用与互斥锁关联的
循环计时器。这是IHMO做您想要的事的最有效方法。但是,然后您应该考虑跳过帧以防计算机延迟(您可以使用计时器代码中的另一个非阻塞互斥锁来做到这一点。)

编辑:一些伪代码来澄清

计时器代码:

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

睡眠代码:

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

起始值:

mutexSkipRender = 1
mutexRender = 0

编辑 :更正了初始化值。

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

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


 类似资料:
  • 脚本: 一群汽车从北向南(viceversa)沿着一条双车道道路行驶。过了一会儿,他们到达了一座桥。这座桥是单向的,通行能力有限。一辆汽车花100毫秒通过这座桥。不允许发生交通碰撞。 假设我需要计算,对于所有的车, 从车辆请求进入桥梁到开始穿越之间的时间。 例如:如果一辆向北行驶的汽车到达桥上,发现桥上有汽车向南行驶,它必须等待。它需要等多久?当然,如果只有一辆车(桥是空的),汽车的等待时间=0。

  • 本食谱演示了使用组合流来创建游戏循环的一种方式。本食谱旨在突出如何用响应式的方式来重新思考现有问题。在这个示例中,我们将提供整体循环以及自上帧以来的增量时间。与此相结合的是用户输入流,以及当前的游戏状态,我们可以用它来更新我们的对象,并根据每帧的发出来将其渲染到屏幕上。 示例代码 ( StackBlitz ) import { BehaviorSubject, Observable, of, fr

  • 我正在调用while循环内的线程Hibernate1秒。当标志为true时,循环将运行(标志为true无限时间)。在循环内,线程应Hibernate1秒,唤醒并增加计数器,检查IF条件,如果为FALSE,则应再次Hibernate1秒并继续29次。在第30次迭代中,IF条件为true,IF语句中调用的方法将收集并存储数据。最后,在第32次迭代中,第二个IF语句将把存储的数据发送到服务器,并将计数设

  • 包含在程序启动时启动的线程。这个线程包含一个循环,每40毫秒更新一次游戏并重新绘制()board。 备选办法B: 板创建一个摆动计时器。这个计时器的动作监听器是板本身。actionPerformed()方法每40毫秒运行一次,并更新game+repaints Board()。 谢谢

  • 问题内容: 关于PHP中的函数,我有两个问题: 睡眠时间是否会影响我的PHP脚本的最大执行时间限制?有时,PHP显示消息“超过30秒的最大执行时间”。如果我使用此消息会出现吗? 使用该功能有没有风险?它会消耗很多CPU性能吗? 问题答案: 您应该尝试一下,只需让脚本hibernate超过您的最大执行时间即可。 Spoiler:在Linux下,睡眠时间被忽略,但是在Windows下,它计为执行时间。

  • 我需要帮助写一些代码,计算最大可能的胜利在N游戏的岩石剪刀纸。 给我的是数字N,这是岩石纸剪刀游戏的数量,后面是N组整数(1,2,3),每个都链接到岩石,纸或剪刀。但是,我们不知道哪个数字链接到每个选项。我需要帮助计算第一个人可以赢的最多游戏数。