Search code examples
javaswingtimerframe-rate

java swing timer works fine in debug but not in runtime


I've been trying to work this one for hours now and i am at a lost.I want to run my game at 30 fps. the swing timer delay is set to 1000/30. Even if i slow it down to 1000 or 2000 it doesnt change anything. In step by step debug everything works as intended. The "updateALL()" method is called the next time the timer refresh. But in runtime updateAll is never called, not even once (the sysout never prints). Ive trimmed down the code below just to show the essential parts. It should compile.

This is my listener class:

public class Observer implements ActionListener {

    private int timerFrame;
    private int fps;
    private boolean frameComplete;

    public Observer() {
        this.frameComplete = true;
        this.timerFrame = 0;
    }

    @Override
    public void actionPerformed(ActionEvent aEvent) {
            nextTimerFrame();
    }

    public void frameCompleted() {
        frameComplete = true;
        System.out.println("Fcompleted");
    }

    public int getTimerFrame() {
        return timerFrame;
    }

    public void setFps(int fps) {
        this.fps = fps;
    }

    private void nextTimerFrame() {
        System.out.println(timerFrame);
        if(frameComplete) {
            if(timerFrame == fps) {
                timerFrame = 1;
            } else {
                ++timerFrame;
            }
            frameComplete = false;
        }
    } 
}

Here is the code in my GameClock Class:

public class GameClock {
    private Timer timer;
    private Observer observer;
    private GameEngine gameEngine;
    private View view;
    private boolean paused;
    private boolean quit;
    private int clockFrame;

    public GameClock() {}
    public GameClock(GameEngine gameEngine) {
        this.gameEngine = gameEngine;
        this.paused = false;
        this.quit = false;
        setupTimer();
        this.clockFrame = 0;
        run();
    }

    public void updateAll() {
            readInputs();
            updateGameEngine();
            updateCanvas2D();
            updateCanvas3D();
            clockFrame = clockFrame == 30? 1:clockFrame+1;  
            observer.frameCompleted(); // puts frameComplete at "true" in Observer
            System.out.println("update" + clockFrame);
    }

    public void run() {
        while(!quit) {
            int timerFrame = observer.getRefreshFrame();
            if(!paused && (clockFrame < timerFrame || (clockFrame == 30 && timerFrame == 1))) {
                updateAll();
                //System.out.println("update");
            }
        }
    }

    private void setupTimer() {
        int fps = Config.getFps();
        int delay = Math.round(1000/fps); 
        observer.setFps(fps);
        this.timer = new Timer(delay, observer);
        timer.start();
    }   
    private void readInputs() {

    }

    private void updateGameEngine() {
    }

    private void updateCanvas2D() {
    }

    private void updateCanvas3D() {
    }
}

Update:

some weird observation:

if i put 2 sysout inside the "if" statement of my while loop they never print. This gives no printout:

public void run() {
    while(!quit) {
        int clockFrame = observer.getRefreshFrame();
        //System.out.println("clock" + clockFrame);
        //System.out.println("frame" + refreshFrame);
        if(!paused && (refreshFrame < clockFrame || (refreshFrame == 30 && clockFrame == 1))) {
            System.out.println("clock" + clockFrame);
            System.out.println("frame" + refreshFrame);
            updateAll();
            //System.out.println("update");
        }
    }
}

If i put the previous sysout in comments and remove the // from the 2 outside the "if" statement like this:

public void run() {
    while(!quit) {
        int clockFrame = observer.getRefreshFrame();
        System.out.println("clock" + clockFrame);
        System.out.println("frame" + refreshFrame);
        if(!paused && (refreshFrame < clockFrame || (refreshFrame == 30 && clockFrame == 1))) {
            //System.out.println("clock" + clockFrame);
            //System.out.println("frame" + refreshFrame);
            updateAll();
            //System.out.println("update");
        }
    }
}

i see that refreshFrame is always incremented with clockFrame. This makes no sense. If updateAll is never called refreshFrame shout not be incremented.

UPDATE 2 - some explanations:

  • A swing timer needs a listener and generates an actionPerformed() event when it ticks. Thats why i need an observer.
  • I cant make the timer call "updateAll()" directly because the timer is on its own thread. If i do that the timer might call an "updateAll()" while the previous update has not completely finished.
  • The way it "should" work: the while keeps looping doing nothing until it sees the timer has ticked (the clockFrame is lower than the timerFrame). It then updateAll(). The timerFrame cant be incremented unless the last updateAll() has completely been finished (frameComplete set to true at the end of updateAll). Once the update is finished the next timer tick will increment the timerFrame thus generating a new update.
  • In theory and in step by step debug it works but not at runtime.

Solution

  • The following is a one-file mre (copy-paste the entire code into GameClock.java) demonstrating use of a swing Timer to update a swing View at 30fps:

    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.SwingConstants;
    import javax.swing.Timer;
    
    public class GameClock {
    
        private final Observer observer;
        private final View view;
    
        public GameClock() {
            view = new View();
            observer = new Observer(view);
            setupTimer();
        }
    
        private void setupTimer() {
            int fps = 30;
            int delay = Math.round(1000/fps);
            observer.setFps(fps);
            Timer timer = new Timer(delay, observer);
            timer.setInitialDelay(0);
            timer.start();
        }
    
        public static void main(String[] args) {
            new GameClock();
        }
    }
    
    class View {
    
        private JLabel counter;
    
        View(){
            createAndShowGui();
        }
    
        void createAndShowGui(){
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setLocationRelativeTo(null);
            counter = new JLabel("0", SwingConstants.CENTER);
            f.add(counter);
            f.pack();
            f.setVisible(true);
        }
    
        void setCounter(int i){
            counter.setText(String.valueOf(i));
        }
    }
    
    class Observer implements ActionListener {
    
        private int timerFrame;
        private int fps;
        private final boolean paused;
        private final View view;
    
        public Observer(View view) {
            this.view = view;
            paused = false;
            timerFrame = 0;
        }
    
        @Override
        public void actionPerformed(ActionEvent aEvent) {
            if (! paused) {
                updateAll();
            }
        }
    
        public int getTimerFrame() {
            return timerFrame;
        }
    
        public void setFps(int fps) {
            this.fps = fps;
        }
    
        private void nextTimerFrame() {
            timerFrame = timerFrame >= fps ?    0 : timerFrame + 1;
        }
    
        public void updateAll() {
            nextTimerFrame();
            view.setCounter(timerFrame);
        }
    }