Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Swing: How do I wake up the main thread from the event-dispatch thread?

I want to cause the "main thread" (the thread started which runs main()) to do some work from the actionPerformed() method of a button's ActionListener, but I do not know how to achieve this.

A little more context:

I am currently programming a 2D game using Swing (a flavour of Tetris).
When the application starts, a window opens which displays the main menu of the game. The user is presented several possibilities, one of them is to start the game by pushing a "Start" button, which causes the game panel to be displayed and triggers the main loop of the game.

To be able to switch between the two panels (that of the main menu and that of the game), I am using a CardLayout manager, then I can display one panel by calling show().
The idea is that I would like my start button to have a listener that looks like this:

public class StartListener implements ActionListener {
    StartListener() {}
    public void actionPerformed(ActionEvent e) {
        displayGamePanel();
        startGame();
    }
}

but this does not work because actionPerformed() is called from the event-dispatch thread, so the call to startGame() (which triggers the main loop: game logic update + repaint() call at each frame) blocks the whole thread.

The way I am handling this right now is that actionPerformed() just changes a boolean flag value: public void actionPerformed(ActionEvent e) { startPushed = true; }
which is then eventually checked by the main thread:

while (true) {
    while (!g.startPushed) {
        try { 
            Thread.sleep(100); 
        } catch (Exception e) {}
    }
    g.startPushed = false;
    g.startGame();
}

But I find this solution to be very inelegant.

I have read the Concurrency in Swing lesson but I am still confused (should I implement a Worker Thread – isn't that a little overkill?). I haven't done any actual multithreading work yet so I am a little lost.

Isn't there a way to tell the main thread (which would be sleeping indefinitely, waiting for a user action) "ok, wake up now and do this (display the game panel and start the game)"?.

Thanks for your help.

EDIT: Just to be clear, this is what my game loop looks like:

long lastLoopTime = System.currentTimeMillis();
long dTime;
int delay = 10;
while (running) {
    // compute the time that has gone since the last frame
    dTime = System.currentTimeMillis() - lastLoopTime;
lastLoopTime = System.currentTimeMillis();

    // UPDATE STATE
    updateState(dTime);
    //...

    // UPDATE GRAPHICS
    // thread-safe: repaint() will run on the EDT
    frame.repaint()

    // Pause for a bit
    try { 
    Thread.sleep(delay); 
    } catch (Exception e) {}
}
like image 552
Rez Avatar asked Nov 30 '25 16:11

Rez


2 Answers

This doesn't make sense:

but this does not work because actionPerformed() is called from the event-dispatch thread, so the call to startGame() (which triggers the main loop: game logic update + repaint() call at each frame) blocks the whole thread.

Since your game loop should not block the EDT. Are you using a Swing Timer or a background thread for your game loop? If not, do so.

Regarding:

while (true) {
    while (!g.startPushed) {
        try { 
            Thread.sleep(100); 
        } catch (Exception e) {}
    }
    g.startPushed = false;
    g.startGame();
}

Don't do this either, but instead use listeners for this sort of thing.

e.g.,

import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

public class GameState extends JPanel {
   private CardLayout cardlayout = new CardLayout();
   private GamePanel gamePanel = new GamePanel();
   private StartPanel startpanel = new StartPanel(this, gamePanel);

   public GameState() {
      setLayout(cardlayout);
      add(startpanel, StartPanel.DISPLAY_STRING);
      add(gamePanel, GamePanel.DISPLAY_STRING);
   }

   public void showComponent(String displayString) {
      cardlayout.show(this, displayString);
   }

   private static void createAndShowGui() {
      GameState mainPanel = new GameState();

      JFrame frame = new JFrame("GameState");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

class StartPanel extends JPanel {
   public static final String DISPLAY_STRING = "Start Panel";

   public StartPanel(final GameState gameState, final GamePanel gamePanel) {
      add(new JButton(new AbstractAction("Start") {

         @Override
         public void actionPerformed(ActionEvent e) {
            gameState.showComponent(GamePanel.DISPLAY_STRING);
            gamePanel.startAnimation();
         }
      }));
   }
}

class GamePanel extends JPanel {
   public static final String DISPLAY_STRING = "Game Panel";
   private static final int PREF_W = 500;
   private static final int PREF_H = 400;
   private static final int RECT_WIDTH = 10;
   private int x;
   private int y;

   public void startAnimation() {
      x = 0;
      y = 0;
      int timerDelay = 10;
      new Timer(timerDelay , new ActionListener() {

         @Override
         public void actionPerformed(ActionEvent e) {
            x++;
            y++;
            repaint();
         }
      }).start();
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      g.fillRect(x, y, RECT_WIDTH, RECT_WIDTH);
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(PREF_W, PREF_H);
   }
}
like image 80
Hovercraft Full Of Eels Avatar answered Dec 02 '25 05:12

Hovercraft Full Of Eels


you should be using a SwingWorker this will execute the code in doInBackground() in a background thread and the code in done() in the EDT after doInBackground() stops

like image 44
ratchet freak Avatar answered Dec 02 '25 07:12

ratchet freak



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!