GUI running at 30 fps?

While testing some real-time simulation code which uses a Swingworker I noticed my GUI always seems to run at 30 fps, no more, no less. I update the GUI everytime the user interacts with the application (like a mouse move) or when the process() method of the Swingworker is called. The Swingworker doesn't do anything right now, it just grabs the mouse location from the GUI and sends it back as a clone through the publish() and process() methods (I'm just doing this to see what I can and can't do when communicating between threads because multithreading is still fairly new to me). I don't have any timers anywhere, the process() method of the Swingworker calls repaint() on the GUI, so I was wondering what causes the GUI to update at 30 fps? Is there maybe like a vsync active in the GUI by default or is it some behaviour of the process() method in the Swingworker? And finally: is there a way to get higher framerates?

Here's an sscce which exhibits this behaviour:

public class SimGameTest implements Runnable {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new SimGameTest());
    }

    @Override
    public void run() {
        MainWindow mainWindow = new MainWindow(new Game());

        mainWindow.setLocationRelativeTo(null);
        mainWindow.setVisible(true);
    }
}

public class MainWindow extends JFrame {
    private Game        game;
    private GamePanel   gamePanel;

    public MainWindow(Game game) {
        this.game = game;

        createAndShowGUI();

        startGame();
    }

    private void startGame() {
        GameSim gameSim = new GameSim(game, gamePanel);
        gameSim.execute();
    }

    private void createAndShowGUI() {
        setTitle("Sim game test");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);

        JPanel contentPane = new JPanel(new GridBagLayout());

        gamePanel = new GamePanel(game);
        contentPane.add(gamePanel);

        add(contentPane);
        pack();
    }
}

public class Game {
    public Point            mouseLocation       = new Point();
    public Point            clonedMouseLocation = new Point();
}

public class GameSim extends SwingWorker<Point, Point> {
    private Game                game;
    private GamePanel           gamePanel;

    public GameSim(Game game, GamePanel gamePanel) {
        this.game = game;
        this.gamePanel = gamePanel;
    }

    @Override
    protected Point doInBackground() throws Exception {
        while (true) {
            publish((Point) game.mouseLocation.clone());
        }
    }

    @Override
    protected void process(List<Point> pointList) {
        game.clonedMouseLocation = pointList.get(pointList.size() - 1);
        gamePanel.repaint();
    }
}

public class GamePanel extends JPanel {
    private Game                game;
    private long                lastTime;

    public GamePanel(Game game) {
        this.game = game;
        setPreferredSize(new Dimension(512, 512));
    addMouseMotionListener(new GamePanelListener(););
    }

    @Override
    public void paintComponent(Graphics g) {
        // draw background
        g.setColor(new Color(0, 0, 32));
        g.fillRect(0, 0, getWidth(), getHeight());

        g.setColor(new Color(192, 192, 255));
        g.drawString(game.clonedMouseLocation.x + ", " + game.clonedMouseLocation.y, 10, 502);

        long now = System.nanoTime();
        long timePassed = now - lastTime;
        lastTime = now;
        double fps = ((double) 1000000000 / timePassed);
        g.drawString("fps: " + fps, 10, 482);
    }

    private class GamePanelListener extends MouseInputAdapter {
        @Override
        public void mouseMoved(MouseEvent e) {
            if (contains(e.getPoint())) {
                game.mouseLocation = e.getPoint();
            }
        }
    }
}

I created another version where I just count the number of times the GUI was repainted and show the count on screen, and it seems to be increasing at a rate of 30 per second, so I guess the fps calculation isn't the problem.


It turns out SwingWorker posts instances of Runnable to the EventQueue using a javax.swing.Timer with exactly that DELAY.

private static class DoSubmitAccumulativeRunnable 
      extends AccumulativeRunnable<Runnable> implements ActionListener {
    private final static int DELAY = (int) (1000 / 30);
    ...
}

You can get a higher frame rate, but not with javax.swing.SwingWorker. You can build SwingWorker from source, but eventually you saturate the thread with instances of Runnable. One alternative is to run the model flat out and sample it periodically, as shown here.