joachimwedin
joachimwedin

Reputation: 1564

Java thread.sleep(1) sleeping longer than 1 ms

I'm trying to develop a 2D game using Java. So far I've managed to set up the game to use the full screen exclusive mode and to do active rendering in a custom thread. The game loop I've decided to use is of the type fixed time step variable rendering. This type of game loop is supposed to render as fast as possible as the device can handle, which I'm not entirely happy about. So I'm trying to limit the frame rate using Thread.sleep().

If I turn off all the rendering, and simply update the game in the game loop, Thread.sleep(1) sleeps successfully about 1 ms. However, if I turn on the rendering, sometimes Thread.sleep(1) sleeps way longer than 1 ms, like 15 ms. I'm turning on/off the rendering by adding/removing the lines:

BufferedImage drawImage = render(Math.min(1d, lag / TIME_PER_UPDATE));
drawToScreen(drawImage);

What is causing the thread to sleep for too long?

This is my first time posting on these forums, so please, do tell me if I've done something wrong in my post, or if this is a duplicate (I've not managed to find a similar post).

import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;

public class Main implements KeyListener
{

    private static final long serialVersionUID = 1L;
    private boolean gameRunning = false;
    private final double UPDATE_RATE = 60;
    private final double TIME_PER_UPDATE = 1000000000 / UPDATE_RATE;
    private final int MAX_UPDATES_BEFORE_RENDERING = 5;
    private final int TARGET_FPS = 60;
    private int windowWidth;
    private int windowHeight;
    private GraphicsDevice graphicsDevice;
    private DisplayMode defaultDisplayMode;
    private Frame frame;
    private BufferStrategy bufferStrategy;
    private Player player;

    public Main()
    {
        GraphicsDevice[] screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
        this.graphicsDevice = screenDevices[0];

        // This is later used to restore the original display mode when closing
        // the game
        defaultDisplayMode = this.graphicsDevice.getDisplayMode();

        frame = new Frame("GameTest");

        frame.setIgnoreRepaint(true);
        frame.setResizable(false);
        frame.setUndecorated(true);

        // Ensure that the user device supports full screen exclusive mode
        if (this.graphicsDevice.isFullScreenSupported())
        {
            graphicsDevice.setFullScreenWindow(frame);
        }

        windowWidth = frame.getWidth();
        windowHeight = frame.getHeight();

        frame.createBufferStrategy(2);

        bufferStrategy = frame.getBufferStrategy();

        // The frame receives keyboard event dispatched on the EDT-thread.
        frame.addKeyListener(this);

        initGame();

        // Starts the gameThread. The updating of the game state and rendering
        GameThread gameThread = new GameThread();
        gameThread.start();

    }

    private void initGame()
    {
        player = new Player(300, 300);
    }

    private class GameThread extends Thread
    {

        @Override
        public void run()
        {
            gameLoop();
        }
    }

    public static void main(String[] Args)
    {
        new Main();
    }

    private void gameLoop()
    {
        gameRunning = true;

        double lastStartTime = System.nanoTime();
        double startTime;
        double elapsedTime = 0;
        double lag = 0;
        double lastRenderTime;
        int updateCount = 0;

        while (gameRunning)
        {
            System.out.println("");
            System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
            System.out.println("New Gameloop");
            System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");

            startTime = System.nanoTime();
            elapsedTime = startTime - lastStartTime;
            lag += elapsedTime;

            updateCount = 0;

            while (lag >= TIME_PER_UPDATE && updateCount < MAX_UPDATES_BEFORE_RENDERING)
            {
                updateGameState();
                lag -= TIME_PER_UPDATE;
                updateCount++;
            }

            if (startTime - lastStartTime > TIME_PER_UPDATE)
            {
                lastStartTime = startTime - TIME_PER_UPDATE;
            }

            BufferedImage drawImage = render(Math.min(1d, lag / TIME_PER_UPDATE));
            drawToScreen(drawImage);
            lastRenderTime = System.nanoTime();

            double currentFPS = 1000000000d / (lastRenderTime - startTime);

            //Sleeps until target FPS is reached
            System.out.println("");
            System.out.println("Before sleeping");
            System.out.println("");
            System.out.println("Current FPS:");
            System.out.println(currentFPS);

            while (currentFPS > TARGET_FPS && (lastRenderTime - startTime) < TIME_PER_UPDATE)
            {

                //Lets the CPU rest
                Thread.yield();

                double beginSleepTime = System.nanoTime();

                try
                {

                    Thread.sleep(1);        

                } catch (Exception e)
                {
                    e.printStackTrace();
                }

                double endSleepTime = System.nanoTime();

                lastRenderTime = System.nanoTime();
                currentFPS = 1000000000d / (lastRenderTime - startTime);

                System.out.println("");
                System.out.println("--------------------------------");
                System.out.println("Sleeping");
                System.out.println("");
                System.out.println("Time slept in ms:");
                System.out.println("");
                System.out.println((endSleepTime - beginSleepTime) / 1000000d);
                System.out.println("");
                System.out.println("current FPS");
                System.out.println("");
                System.out.println(currentFPS);     
            }

            lastStartTime = startTime;
        }   
    }

    private void updateGameState()
    {
        player.update();
    }

    private void drawToScreen(BufferedImage drawImage)
    {
        try
        {
            Graphics2D g2d = (Graphics2D) bufferStrategy.getDrawGraphics();

            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            g2d.clearRect(0, 0, windowWidth, windowHeight);
            g2d.setBackground(Color.BLACK);

            g2d.drawImage(drawImage, 0, 0, windowWidth, windowHeight, null);

            g2d.dispose();

            if (!bufferStrategy.contentsLost())
            {
                bufferStrategy.show();
            }

        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    private BufferedImage render(double delta)
    {
        BufferedImage drawImage = new BufferedImage(windowWidth, windowHeight, BufferedImage.TYPE_INT_ARGB);
        drawImage.createGraphics();
        Graphics2D g = (Graphics2D) drawImage.getGraphics();

        g.setBackground(Color.WHITE);
        g.clearRect(0, 0, windowWidth, windowHeight);

        //Render player
        g.setColor(Color.BLUE);
        g.fillRect((int) Math.round(player.getLocX() + delta * player.getSpeedX()), (int) Math.round(player.getLocY() + delta * player.getSpeedY()), 64, 64);

        g.dispose();

        return drawImage;
    }

    @Override
    public void keyPressed(KeyEvent keyEvent)
    {
        switch (keyEvent.getKeyCode())
        {

        case KeyEvent.VK_ESCAPE:
            graphicsDevice.setDisplayMode(defaultDisplayMode);
            System.exit(0);
            break;
        case KeyEvent.VK_A:
            player.setSpeedX(-player.getMoveSpeed());
            break;
        case KeyEvent.VK_D:
            player.setSpeedX(player.getMoveSpeed());
            break;
        case KeyEvent.VK_W:
            player.setSpeedY(-player.getMoveSpeed());
            break;
        case KeyEvent.VK_S:
            player.setSpeedY(player.getMoveSpeed());
            break;
        case KeyEvent.VK_SPACE:

            break;
        case KeyEvent.VK_LESS:

            break;
        case KeyEvent.VK_I:

            break;

        }
    }

    @Override
    public void keyReleased(KeyEvent keyEvent)
    {
        switch (keyEvent.getKeyCode())
        {
        case KeyEvent.VK_A:
            player.setSpeedX(0);
            break;
        case KeyEvent.VK_D:
            player.setSpeedX(0);
            break;
        case KeyEvent.VK_W:
            player.setSpeedY(0);
            break;
        case KeyEvent.VK_S:
            player.setSpeedY(0);
            break;
        case KeyEvent.VK_SPACE:

            break;
        case KeyEvent.VK_LESS:

            break;
        case KeyEvent.VK_I:

            break;
        }
    }

    @Override
    public void keyTyped(KeyEvent keyEvent)
    {

    }

    private class Player
    {
        protected double speedX;
        protected double speedY;
        protected double locX;
        protected double locY;
        protected double moveSpeed;

        public Player(int locX, int locY)
        {
            speedX = 0;
            speedY = 0;
            this.locX = locX;
            this.locY = locY;

            moveSpeed = 3d;
        }

        public void update()
        {
            locY +=  speedY;

            locX += speedX;
        }

        public void setSpeedX(double speedX)
        {
            this.speedX = speedX;
        }

        public void setSpeedY(double speedY)
        {
            this.speedY = speedY;
        }

        public double getSpeedX()
        {
            return speedX;
        }


        public double getSpeedY()
        {
            return speedY;
        }

        public double getLocX()
        {
            return locX;
        }

        public double getLocY()
        {
            return locY;
        }

        public double getMoveSpeed()
        {
            return moveSpeed;
        }   
    }
}

Upvotes: 4

Views: 5509

Answers (3)

bvdb
bvdb

Reputation: 24760

According to javadoc:

Thread.sleep() causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.

So, the Thread.sleep(ms) has a low accuracy.

Secondly, note that this method throws a checked exception ThreadInterruptedException. Which can be triggered by a spurious wakeup. So even a Thread.sleep(1000) could finish just after a ms.

An alternative solution with better precision is LockSupport.parkNanos(). But also with this method you should watch out for interruptions by other threads. PS: There is also a Thread.sleep(ms,nanos) which has the same low accuracy as the Thread.sleep(ms) (the nano seconds are just rounded to ms).

Upvotes: 0

Ram Patra
Ram Patra

Reputation: 16674

The sleep() method in java puts the currently executing thread (in the running state) to sleep for 1 ms.

After 1 ms the thread comes to runnable state (able to run), now it depends on the scheduler when to take the thread from the runnable state and execute it (i.e, running state).

For this reason, you can assume that the thread sleeps for a minimum of 1ms before running again.

Below figure describes the different thread states: enter image description here

Upvotes: 6

Dave
Dave

Reputation: 166

The java docs clearly state (for thread.sleep())

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds plus the specified number of nanoseconds, subject to the precision and accuracy of system timers and schedulers.

You are at the mercy of scheduling and system timing within the system here. The only way to guarantee the time would be to make sure that the thread blocks on its execution and is prevented from being cycled through but that will create issues elsewhere.

For what its worth I find it generally bad to sleep threads for fixed amounts of time unless there is an absolute reason to do so things should be triggered by other events not in fixed time intervals.

Upvotes: 0

Related Questions