should a game loop in a java program always run in the event dispatch thread?
Because my keylisteners are running in the AWT event thread and I want the key events to be processed in the same thread as the rest of the game to avoid thread based bugs.
If I don't run the game loop in the event thread can I use an ArrayBlockingQueue to send events from the event handler to the game loop? Is there a better solution?
thanks
Maybe something like this?
ConcurrentLinkedQueue<AWTEvent> eventQueue = new ConcurrentLinkedQueue<AWTEvent>();
JFrame frame = new JFrame() {
#override
protected void processEvent(AWTEvent e) {
if(e instanceof InputEvent) eventQueue.add(e);
else super.processEvent(e);
}
}
//game loop
while(true) {
while(!eventQueue.isEmpty()) frame.super.processEvent(eventQueue.poll());
...
}
edit: Thank you, I think I now know how to do this. I'm gonna do it like this
import java.util.concurrent.ConcurrentLinkedQueue;
import java.awt.AWTEvent;
import java.awt.Dimension;
import java.awt.event.*;
import javax.swing.*;
public class QueueTest {
static ConcurrentLinkedQueue<AWTEvent> eventQueue = new ConcurrentLinkedQueue<AWTEvent>();
public static void main(String[] args) {
final JPanel panel = new JPanel() {
#Override
protected void processEvent(AWTEvent e) {
eventQueue.add(e);
}
{
enableEvents( AWTEvent.MOUSE_EVENT_MASK
| AWTEvent.MOUSE_MOTION_EVENT_MASK
| AWTEvent.KEY_EVENT_MASK);
}
};
final JFrame frame = new JFrame();
frame.addComponentListener(new ComponentAdapter() {
#Override
public void componentShown(ComponentEvent e) {
panel.requestFocusInWindow();
}
});
frame.add(panel);
panel.setPreferredSize(new Dimension(800,600));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
while(true) {
while(!eventQueue.isEmpty()) System.out.println(eventQueue.poll()+"\n");
}
}
}
I'm not a game programmer so your question prompted some research :) You cannot run the game loop in the event dispatch thread because it is in a loop of its own servicing user input. You're application would essentially freeze up and not respond to any further input events. Your game loop should be a separate thread with a thread-safe queue for game events - the queue should be non-blocking because you want the game loop to continue even if there has been no user input. Any rendering you need to do as a result of input will probably have to be put back on the event queue - swing provides SwingUtilities for this although not sure which environment you are in.
I'll probably get slammed by experienced games developers now.
I had a similar problem in the game engine I wrote.
In the end I had to have a separate thread for my game engine loop. When AWT events were fired I had to store them, and fire them during the next logical update within the game loop.
This was my solution however, I believe that the 'easiest' thing for events is to actually poll them! :)
Related
Hello guys I am doing a thread to update a ball over JFrame so I repaint the screen... and then paint the ball update its position .. and then draw the screen again ... draw the ball and the same cycle ... here is the code
private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {
Thread t = new Thread()
{
public void run()
{
while(true)
{
repaint();
b2.update(ob,2);
b2.paint(ob.getGraphics());
b2.setT(b2.getT() + 1);
try {
Thread.sleep(50);
} catch (InterruptedException ex) {
System.out.println("Error in Sleeping");
}
}
}
};
t.start();
}
but the problem is that I don't see the ball... the paint of the screen always overrides the ball and the ball is like down under the Jframe ..
If you want to have animations in Swing, the recommended class to use is the javax.swing.Timer . This class allows you to perform operations on the Event Dispatch Thread at regular intervals.
The Swing Timer tutorial
An animation example posted here on SO (which is linked in the Swing wiki here on SO btw)
Some General Rules
Swing is not thread safe, you should only ever update UI components from within the context of the Event Dispatching Thread.
You do not control the paint process, the repaint manager does. You can request updates to occur by calling repaint, but you should never call update and paint directly when trying to update the display.
The Graphics context used by the paint sub system is a shared resource and is not guaranteed to be the same between paint cycles, you should never maintain a reference to it. You should also not rely on the results from JComponent#getGraphics this method is capable of returning null.
An Example Solution
You have a number of options, depending on what you want to ultimately achieve.
You could use a SwingWorker, but given the fact that all your going to is enter an infinite loop and it would easier to use SwingUtilities#invokeLater then actually use the publish method, this approach would actually be more work.
You could also use a Thread, but you'd end up with the same problems as using a SwingWorker
The simpliset solution, for what you're presented, is actually a javax.swing.Timer
public class Blinky {
public static void main(String[] args) {
new Blinky();
}
public Blinky() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new BlinkyPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected class BlinkyPane extends JPanel {
private JLabel blinkyLabel;
private boolean blink = false;
public BlinkyPane() {
setLayout(new GridBagLayout());
blinkyLabel = new JLabel("I'm blinking here");
blinkyLabel.setBackground(Color.RED);
add(blinkyLabel);
Timer timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
blink = !blink;
if (blink) {
blinkyLabel.setForeground(Color.YELLOW);
} else {
blinkyLabel.setForeground(Color.BLACK);
}
blinkyLabel.setOpaque(blink);
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 100);
}
}
}
You can take a look at Swing Timer and Concurrency in Swing for more info
If you access GUI components outside the EDT (Event Dispatch Thread) then you might encounter strange problems, Contrary if you perform long running tasks in the EDT then also you will get problems.
Check this post for more info on GUI Threading in Java
I keep getting a expected error at the displayTimer.start(); line... what's the reason for this? I am just trying to understand how to use the swing Timer with two inputs into the constructor, and nothing else fancier. I got this code from: http://albertattard.blogspot.com/2008/09/practical-example-of-swing-timer.html
import java.awt.event.*;
import javax.swing.Timer;
public class Five {
public static void main(String[] args){
ActionListener listener = new ActionListener(){
public void actionPerformed(ActionEvent event){
System.out.println("hello");
}
};
Timer displayTimer = new Timer(1000, listener);
displayTimer.start();
}
}
adding the main method that fixed that error, but now it doesn't seem to be constantly running... it never prints hello.
Probably because the JVM exists before the Timer has a chance to fire. The point of a Timer is to use it with a GUI.
So create a more practical example. First create a JFrame and make the frame visible. Then the JVM will not exit while the frame is visible. Then you can start the Timer.
Read the section from the Swing tutorial on Concurrency for more information on the different Threads used in Swing.
I have a Java program where I plan to take input from GUI, and use that input later for processing in main(). I am using Eclipse.
I am sending an HW object(called HWObj) to the GUI JFrame, and checking for a boolean field in the object to continue processing in main().
InputWindow is custom object which extends JPanel implements ActionListener
It contains a reference to the current JFrame(parentFrame). On clicking a JButton in InputWindow, I have written a custom ActionListener which sets the value of HWObj.check to true and disposes the parentFrame. This should cause execution to resume in main().
Code for HW class is as below :
import java.awt.*;
import javax.swing.*;
public class HW {
//globals
boolean check;
public HW() {
//initialisations
check = false;
}
public static void main(String args[]) {
final HW problem = new HW();
try {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
//Create and set up the window.
JFrame frame = new JFrame("Select folders");
frame.setPreferredSize(new Dimension(640, 480));
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
InputWindow Directories = new InputWindow(problem, frame);
Directories.setOpaque(true);
frame.add(Directories);
//Display the window.
frame.pack();
frame.setVisible(true);
}
});
} catch(Exception e) {
System.out.println("Exception:"+e.getLocalizedMessage());
}
while(!problem.finish);
//Do processing on problem
System.out.println("Done");
}
}
The Actionlistener in the gui is as follows:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class InputWindow extends JPanel
implements ActionListener {
private static final long serialVersionUID = 4228345704162790878L;
HW problem;
JFrame parentFrame;
//more globals
public InputWindow(HW problem, JFrame parentFrame) {
super();
this.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
this.parentFrame = parentFrame;
this.problem = problem;
JButton finishButton = new JButton("Finish");
finishButton.setActionCommand("fin");
finishButton.addActionListener(this);
gbc.gridx = 0;
gbc.gridy = 0;
this.add(finishButton, gbc);
//Initialize buttons and text areas and labels
//Code removed for ease of reading
}
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if(command.equals("fin")) {
//Do a lot of stuff, then
this.removeAll();
parentFrame.dispose();
problem.check = true;
}
}
}
I have checked, and the control to this function comes normally on button click.
Now, I would expect it to return to main, and exit the while loop, and continue processing.
This does not happen. The debugger in eclipse shows only the main thread running, and when I try to pause it, I see that the thread is stuck in the while loop. But if I try to step through, it exits the while loop as expected, and continues. However, it gets remains stuck in the while loop until I manually try to debug it.
What is the problem? Why is it not resuming the main thread as expected?
How do I resolve this issue?
Your problem is to do with how the Java memory model works. The loop in your main thread will be checking a stale value of check.
When you enter the debugger, the memory is forced to be updated, so that's why it starts working at that point.
If you mark your variable as volatile, that will force the JVM to ensure that all threads are using the up-to-date value:
volatile boolean check;
You can read more about volatile and the Java memory model in the documentation.
It looks like you're using a JFrame where you should be using a modal JDialog. If you use the modal JDialog for an input window, you will know exactly when it is "finished" since code flow will resume from the calling code from right after when the dialog was set visible.
Either that or if you are trying to swapviews, then use a CardLayout to swap your view, and use an observer type pattern to listen for change of state.
I wonder what is the best approach to make a JOptionPane style plain message box disappear after being displayed for a set amount of seconds.
I am thinking to fire up a separate thread (which uses a timer) from the main GUI thread to do this, so that the main GUI can carry on processing other events etc. But how do I actually make the message box in this separate thread disappear and terminate the thread properly. Thanks.
Edit: so this is what I come up with by following the solutions posted below
package util;
import java.awt.BorderLayout;
import java.awt.Dimension;
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 DisappearingMessage implements ActionListener
{
private final int ONE_SECOND = 1000;
private Timer timer;
private JFrame frame;
private JLabel msgLabel;
public DisappearingMessage (String str, int seconds)
{
frame = new JFrame ("Test Message");
msgLabel = new JLabel (str, SwingConstants.CENTER);
msgLabel.setPreferredSize(new Dimension(600, 400));
timer = new Timer (this.ONE_SECOND * seconds, this);
// only need to fire up once to make the message box disappear
timer.setRepeats(false);
}
/**
* Start the timer
*/
public void start ()
{
// make the message box appear and start the timer
frame.getContentPane().add(msgLabel, BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
timer.start();
}
/**
* Handling the event fired by the timer
*/
public void actionPerformed (ActionEvent event)
{
// stop the timer and kill the message box
timer.stop();
frame.dispose();
}
public static void main (String[] args)
{
DisappearingMessage dm = new DisappearingMessage("Test", 5);
dm.start();
}
}
Now the question is that, as i cam going to create multiple instances of this class throughout the course of the interaction between the user and the main GUI, I wonder whether the dispose() method cleans up everything properly every time. Otherwise, I may end up with accumulating lots of redundant objects in memory. thanks.
I think in your situation, you can't use any of JOptionPane static methods (showX...). You have to create a JOptionPane instance instead, then create a JDialog from it and show that JDialog yourself. Once you have JDialog, you can force its visibility.
// Replace JOptionPane.showXxxx(args) with new JOptionPane(args)
JOptionPane pane = new JOptionPane(...);
final JDialog dialog = pane.createDialog("title");
Timer timer = new Timer(DELAY, new ActionListener() {
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
// or maybe you'll need dialog.dispose() instead?
}
});
timer.setRepeats(false);
timer.start();
dialog.setVisible(true);
I haven't tried it so I can't guarantee that it works but I think it should ;-)
Of course, here Timer is javax.swing.Timer, as someone else already mentioned, thus you're sure the action will run in the EDT and you won't have any problem with creating or terminating your own Thread.
Timers have their own threads. I think what you probably should do is create a new Timer (or, preferably, make one that you reuse till you don't need it any more), schedule a task that will ask for the message box to disappear and then have that task add another task to the event queue, which will remove the message box.
There might be a better way though.
In addition:
Yes, using javax.swing.timer would probably be better. The reason I talk about using two tasks in the above is that I assume you will have to execute your hiding method inside of the AWT thread to avoid certain subtle race issues that might arise. If you use javax.swing.Timer you're already executing in the AWT thread, so that point becomes moot.
I'm working in Java, and I have a JPanel in a JFrame. In that JPanel, among other things, I have a JLabel that I want to make appear and disappear at will. I've tried setting visibility to true/false, adding and removing it from the JFrame and JPanel, and, having looked online, I tried validate()ing and revalidate()ing ad infinitum. What can be done here to solve this problem?
In general, calling the setVisible method is sufficient to make a Swing component to be shown or hidden.
Just to be sure that it works, I tried the following:
public class Visibility {
private void makeGUI() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JLabel l = new JLabel("Hello");
final JButton b = new JButton("Hide Label");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
l.setVisible(false);
}
});
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(l, BorderLayout.CENTER);
f.getContentPane().add(b, BorderLayout.SOUTH);
f.setSize(200, 200);
f.setLocation(200, 200);
f.validate();
f.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Visibility().makeGUI();
}
});
}
}
The above program is able to affect the visibility by clicking on a JButton.
Could it be a Threading Issue?
My next suspicion was that perhaps a Thread that is not on the event dispatch thread (EDT) may not be affecting the display immediately, so I added the following after initializing the JLabel and JButton.
Thread t = new Thread(new Runnable() {
public void run() {
while (true) {
b.setVisible(!b.isVisible());
try {
Thread.sleep(100);
} catch (InterruptedException e) { /* Handle exception /* }
}
}
});
t.start();
With the new Thread running, it changed the toggled the visibility of the JLabel every 100 ms, and this also worked without a problem.
Calling a Swing component off the event dispatch thread (EDT) is a bad thing, as Swing is not thread-safe. I was a little surprised it worked, and the fact that it works may just be a fluke.
Repaint the JPanel?
If the JLabel's visibility is only being affected on resizing, it probably means that the JLabel is being drawn only when the JPanel is being repainted.
One thing to try is to call the JPanel's repaint method to see if the visibility of the JLabel will change.
But this method seems to be just a band-aid to a situation, if the main cause is due to a thread off the EDT is attempting to make changes to the GUI. (Just as a note, the repaint method is thread-safe, so it can be called by off-EDT threads, but relying on repaint is a workaround than a solution.)
Try using SwingUtilities.invokeLater
Finally, probably the thing I would try is the SwingUtilities.invokeLater method, which can be called (and should only be called) from a thread running separate from the EDT, if it wants to affect the GUI.
So, the earlier Thread example should be written as:
Thread t = new Thread(new Runnable() {
public void run() {
while (true) {
try {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
b.setVisible(!b.isVisible());
}
});
} catch (Exception e1) { /* Handle exception */ }
try {
Thread.sleep(100);
} catch (InterruptedException e) { /* Handle exception */ }
}
}
});
t.start();
If the change to the GUI is indeed occurring on a separate thread, then I would recommend reading Lesson: Concurrency in Swing from The Java Tutorials in order to find out more information on how to write well-behaving multi-threaded code using Swing.
setVisible() or removing it should work fine, make sure you are doing it from the event dispatch thread though. There are utility methods in EventQueue for running blocks in that thread.
http://helpdesk.objects.com.au/java/how-do-i-update-my-gui-from-any-thread
You would need to call revalidate() on the parent JPanel if you need its components to be re-laid out.
If you can post an example that demonstrates the problem I can have a look at it for you.