6

在测试一些使用 Swingworker 的实时模拟代码时,我注意到我的 GUI 似乎总是以 30 fps 运行,不多也不少。每次用户与应用程序交互(如鼠标移动)或调用 Swingworker 的 process() 方法时,我都会更新 GUI。Swingworker 现在不做任何事情,它只是从 GUI 中获取鼠标位置并通过 publish() 和 process() 方法将其作为克隆发送回(我这样做只是为了看看我能做什么和能做什么'在线程之间进行通信时不要这样做,因为多线程对我来说还是相当新的)。我在任何地方都没有任何计时器,Swingworker 的 process() 方法在 GUI 上调用 repaint(),所以我想知道是什么导致 GUI 以 30 fps 更新?默认情况下,在 GUI 中是否有类似活动的 vsync 或者它是 Swingworker 中 process() 方法的某些行为?最后:有没有办法获得更高的帧率?

这是一个表现出这种行为的 sscce:

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

我创建了另一个版本,我只计算重新绘制 GUI 的次数并在屏幕上显示计数,它似乎以每秒 30 次的速度增加,所以我猜 fps 计算不是问题。

4

1 回答 1

11

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.

于 2012-08-07T02:02:33.883 回答