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.