Game loop completely freezes my program - java

Original Question
I'm currently working on a simple application that displays a map and will later implement pathfinding logic for units. I've implemented the map and view so far and everything runs just fine until I implemented the game loop.
With the game loop enabled, the program just freezes. I can't close the window anymore and the map isn't presented, even though the game loop is executed just fine. I've used this game loop twice in the past and never had any problems until now.
Edit: The game loop continues to execute just fine while everything else freezes.
Here are the two functions involved:
public GameController() {
paused = true;
frame = new GameFrame(this);
map = new Map(500, 500);
mvm = new MapViewModel(map.getMap(), map.getWidth(), map.getHeight());
//TODO: gameLoop() currently breaks the game.
gameLoop();
}
public void gameLoop() {
double tickTime, lastTick;
for (;;) {
tickTime = System.nanoTime();
lastTick = tickTime;
//Repaints the frame
update();
while (tickTime - lastTick < NANOSECONDS_PER_UPDATE) {
try {
Thread.sleep(1);
} catch (InterruptedException ignored) {}
tickTime = System.nanoTime();
}
}
}
edit2: I'm using Swing. The actual painting happens in the paintComponent method of my GamePanel (JPanel):
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
//Paints the map
painter.paintMap(g2, controller.getMvm());
}
Obviously, if you have any further questions feel free to ask. Thanks in advance.
Solution
Here's the code I'm using now, GameController and update haven't changed.
public void gameLoop() {
timer = new Timer(MILLISECONDS_PER_UPDATE, updater);
timer.start();
}
updater is an ActionListener that I have added as a private variable to the class.
private ActionListener updater = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("test2");
update();
}
};
You could add updater locally but I prefer it this way.

You tell us nothing about what GUI library you might be using, but assuming that it might be Swing, it looks like you're running some long-running code on the Swing event thread, the main thread responsible for doing all Swing graphics and for interacting with the user. If so, then the code will prevent Swing from redrawing the GUI, freezing your application.
My suggestions are:
Don't do this. Don't run any long-running code on the main event thread.
Instead consider using a Swing Timer to do your "game loop".
Or if you must use your while loop and Thread sleep, do it off of the event thread, but then be sure that all Swing calls that mutate the state of Swing objects be done on the event thread.
For more on Swing threading, please read Concurrency in Swing.

Related

While loop to repeat actionPerformed of a JButton

I've got a JFrame with some JButtons. The "update" button (named JButton1) executes a number of queries which fill a form.
Now I'd like another JButton to be the "automatic update" (named JButton2), so I would like it to loop inside the JButton1ActionPerformed method, until JButton2 is no longer selected.
When I press JButton2 the window freezes.
Is my code right? Do you think I would need a stop button?
private void jToggleButton2ActionPerformed(java.awt.event.ActionEvent evt) {
long start = System.currentTimeMillis();
if(jToggleButton2.isSelected()){
start = System.currentTimeMillis();
do{
do{
jButton1ActionPerformed(evt);
}
while(System.currentTimeMillis()-start>3000);
}
while(jToggleButton2.isSelected());
}
if(jToggleButton2.isSelected()){
jToggleButton2.setSelected(false);
}
}
Swing is:
Single threaded; this means that you should not perform any long running or blocking operations within the context of the Event Dispatching Thread
Not thread safe; this means you should not update the UI or anything the UI might depend on outside of the EDT. It's also risky to try and address UI elements outside the EDT, as the thread model might return false values
Without more context to your problem, I might suggest using a Swing Timer. Two main reasons:
The ActionListener is called within the context of the EDT, making it safe to update the UI from within in
You can specify a delay between updates, which reduces the risk of overloading the EDT which will cause performance issues
For example...
private void jToggleButton2ActionPerformed(java.awt.event.ActionEvent evt) {
long start = System.currentTimeMillis();
if (jToggleButton2.isSelected()) {
// You could use an instance field instead and determine
// if the Timer is already running or not
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!jToggleButton2.isSelected()) {
((Timer) e.getSource()).stop();
return;
}
jButton1ActionPerformed(evt);
}
});
timer.start();
// Just want to point out that this doesn't make sense
// As if the button was selected, you'd still be in
// your previous loop
//if (jToggleButton2.isSelected()) {
// jToggleButton2.setSelected(false);
//}
}
}
Another solution might be to use a SwingWorker, which provides you with a means to perform long running or blocking operations off the EDT, but which also provides functionality to update the UI safely from within the EDT.
Take a look at:
Concurrency in Swing
Worker Threads and SwingWorker
How to Use Swing Timers
for more details
Lets put aside that you should not crete Thread like I will show you (but use SwingWorker for example) you need to do something like this:
private void jToggleButton2ActionPerformed(java.awt.event.ActionEvent evt) {
if(jToggleButton2.isSelected()){
new Thread(new Runnable(){
public void run(){
long start = null;
do{
start = System.currentTimeMillis();
jButton1ActionPerformed(evt);
}while(jToggleButton2.isSelected() &&jSystem.currentTimeMillis()-start>3000)
}
}).start();
}
Im not focusing on if your code is valid or not, its just shows how to run something in background, so you will not freeze your GUI.

Reentering Method with New Thread in Java with Swing GUI

I am a beginner with multithreading and GUI development in Java with Swing and Window Builder in Eclipse.
I am trying to get a jlabel that shows the status of the program to display text and quickly fade from red to black when a button is pressed and then to disappear in ten seconds.
I tried to combine suggestions for this from several answers on Stack Overflow using a new thread and a timed task (How to fade color from white to black?) and the code works correctly the first time the button is pressed. However, when the button is pressed again and the method is called again, it only briefly blinks red and the text disappears very quickly.
I believe that it is because when the button is pressed a second time the old thread is still running and both are trying to control the label. I obviously found several question on Stack Overflow on how to kill treads in Java (including How do you kill a thread in Java?), but none of them seemed to help in my situation. I am relatively inexperienced with multithreading and Swing GUIs, so I probably made some really dumb mistakes with my code.
Here it the method that is called by a event listener when a button is pressed (this method is in a separate "Program Main" controller class):
private static void setBriefLblStatus(String content) {
one = new Thread() {
public void run() {
setLblStatus(content);
fading = Color.RED;
TimerTask fadingTask = new TimerTask() {
#Override
public void run() {
if (fading.getRed() > 0) {
fading = new Color(fading.getRed() - 1, 0, 0);
setLblStatusColor(fading);
}
}
};
timer = new Timer();
timer.scheduleAtFixedRate(fadingTask, 7, 7);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setLblStatus("");
}
};
one.start();
}

Why is my while loop not working in paintComponent?

When I run this code, I see nothing but a blank(white) Panel and I would like to know why.
Here is my code:
Graph.java
public class Graph extends JPanel {
private static final long serialVersionUID = -397959590385297067L;
int screen=-1;
int x=10;
int y=10;
int dx=1;
int dy=1;
boolean shouldrun=true;
imageStream imget=new imageStream();
protected void Loader(Graphics g){
g.setColor(Color.black);
g.fillRect(0,0,x,y);
x=x+1;
y=y+2;
}
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
while(shouldrun){
Loader(g);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Do not ever call Thread.sleep() on the Event Dispatch Thread!!!
This causes the thread that actually redraws the screen and makes controls responsive to stop doing anything.
For animations, use a Timer. Don't worry about writing the while loop yourself, just tell the Timer to fire every so often, and change the values of x and y inside that timer. Something like:
// this is an **inner** class of Graph
public class TimerActionListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
x += dx;
y += dy;
}
}
// snip
private final Timer yourTimer;
public Graph() {
yourTimer = new Timer(2000, new TimerActionListener());
timer.start();
}
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.black);
g.fillRect(0,0,x,y);
}
You never change the state of shouldrun within the loop -- so it will never end.
Also, never call Thread.sleep(...) within a painting method. This method is for painting and can never be put to sleep, else the GUI will be put to sleep, will be frozen.
First of all, your paintComponent method should only handle all painting and nothing else (if possible). You should not implement your program loop within paintComponent.
The blank screen can be caused by a number of reasons. You can easily debug it manually by commenting off certain section of your codes and run it. See whether it is still blank.
At least from what I see here, your paintComponent will give your problems.
If you want an animation, you can:
Use a swing timer
Create a loop in a new thread (not Event Dispatch Thread). Your loop will look something like this:
As below:
while(running){
update();
render();
try(
Thread.sleep(1000/fps);
)catch(InterruptedException ie){
ie.printStackTrace();
}
}
Note: To make a proper loop for animation, you will need more than that.

Trigger event only after repaint in Java Swing?

I am making a simple board game in java, where I want to animate a dice roll. So I flash pictures of a dice like this:
public Timer roll_dice = new Timer(50, this);
...
public void actionPerformed(ActionEvent evt) {
if(roll_dice.getDelay() > 500){
roll_dice.setDelay(50);
roll_dice.stop();
movePiece();
}else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
}
movePiece(){
//do some more painting
}
So the die is going so show random numbers for a few times, and then slowly settle on a number. After that is done I would like to call the movePiece() method. However, as it is, the the repaint occurs sporadically and screws everything up so that movePiece() gets called before the dice roll is actually finished animating.
Does anyone have any ideas how I can call movePiece only after the final repaint has happened?
So the die is going so show random numbers for a few times, and then slowly settle on a number. After that is done I would like to call the movePiece() method. However, as it is, the the repaint occurs sporadically and screws everything up so that movePiece() gets called before the dice roll is actually finished animating.
What worries me here is why your painting is occurring sporadically -- it simply shouldn't be doing that, and perhaps that is what you need to fix. I wonder if you're reading in the images from the file each time you do the drawing or some other cause for slowing the drawing down. If you need more help regarding this issue, then you'll have to give us more information on how you do your painting. Regardless, you should avoid having program logic be dependent on painting as you don't have full control over when or even if painting will occur.
Rather than redrawing images and calling repaint(), why not simply put your rolling dice images into ImageIcons on program start up, and then in your Swing Timer, swap icons in a JLabel? Then stop your Timer when the delay gets long enough and in that if block, move your piece.
So, assuming that you have several dice, each can be displayed by a JLabel that is held in an array of JLabel called diceLabels, and the ImageIcons can be held in an array called diceIcons. Then you can do something like:
public void actionPerformed(ActionEvent e) {
if (roll_dice.getDelay() > 500) {
roll_dice.setDelay(50);
roll_dice.stop();
movePiece(); // I like this -- this shouldn't change
} else {
roll_dice.setDelay(roll_dice.getDelay() + 50);
// dice_panel.repaint(0);
for (JLabel dieLabel : diceLabels) {
int randomIndex = random.nextInt(diceIcons.length);
dieLabel.setIcon(diceIcons[randomIndex]);
}
}
}
I like your logic on when you call movePiece() and I think that this should remain unchanged.
You can call the rolling in another thread and join() the current thread to the rolling one. That way the main code will wait until the roll thread dies (finished rolling).
public void actionPerformed(ActionEvent evt) {
if(roll_dice.getDelay() > 500){
Thread rollerThread = new RollerThread();
rollerThread.start();
rollerThread.join();
movePiece();
}
else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
private RollerThread extends Thread
{
public void run(){
roll_dice.setDelay(50);
roll_dice.stop();
}
}
However, this might not work with the EDT - because repaints should be scheduled to the queue. Maybe you can shedule the event using the SwingUtilities.invokeAndWait():
public void actionPerformed(ActionEvent evt) {
Thread thread = new Thread(){
public void run(){
if(roll_dice.getDelay() > 500){
SwingUtilities.invokeAndWait(new Runnable(){
public void run(){
roll_dice.setDelay(50);
roll_dice.stop();
}
});
movePiece();
}
else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
};
thread.start();
}
Does anything change if you put that call to movePiece(); in a SwingUtilities.invokeLater(Runnable);?
if(roll_dice.getDelay() > 500){
roll_dice.setDelay(50);
roll_dice.stop();
SwingUtilities.invokeLater(new Runnable() {
public void run() { movePiece(); }
});
}
...

Counter to Close JFrame, bring up Confirm Dialog

This post relates to my last one regarding a timer. I decided the easiest thing to do for immediate results was to just write a Counter thread that counts down from a certain time (in this case 5 seconds) and if the counter reaches 0, the JFrame closes and let's the user know that time has expired.
I'm running into some trouble, however. I cannot seem to make the JFrame close when the counter reaches 0. I'm not sure if I'm missing something stupid or if I am misunderstanding the way threads work and the way JFrames work. Here is the code, let me know what you think.
On a side note, I understand it would probably be most efficient to use a swing.Timer, but I just don't quite grasp the nature of them yet. I'm under self-imposed time constraints (I'm not a student or anything, I just like to stay motivated) and so I'm "jerry-rigging" this thing for now.
Anyway, on to the code!
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
public class RacerDoom extends JFrame {
boolean timesUp=false;
public RacerDoom() {
//create JFrame
super("Racer Doom Squared");
setSize(WIDTH,HEIGHT);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
if(timesUp==true) {
dispose();
JOptionPane.showConfirmDialog(null, "Time's Up! Click Okay to try again!");
}
Counter c1 = new Counter();
c1.start();
//Counter
private class Counter extends Thread {
public Counter() {}
public void run() {
for(int i=5;i>=0;i--) {
if(i==0) {
timesUp=true;
}
System.out.println(i);
try{
Thread.sleep(1000);
}
catch(InterruptedException e){}
}
}
}
...
EDIT: I have the timer implemented and working. It does exactly what I need it to, but I can't get the timer.stop(); command to work. I get the error "The local variable timer may not have been initialized.
Like I said, the timer works, it just never stops working until the program is terminated. Here is the constructor code for the JFrame, where the timer is located.
int counter = 0;
public RacerDoom() {
//create JFrame
super("Racer Doom Squared");
setSize(WIDTH,HEIGHT);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
final Timer timer=new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(counter>=10) {
timer.stop(); //the error occurs here
dispose();
JOptionPane.showConfirmDialog(null, "Time's Up!");
}
else{
counter++;
}
System.out.println(counter);
}
});
//inner thread
Move1 m1 = new Move1();
m1.start();
timer.start();
}
Thats easy to do with the help of a swing timer.. See this code sample:
final java.swing.Timer timer=new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(counter>5) {
timer.stop();
<dispose the fram here>
}else{
counter++;
}
}
});
timer.start();
I put this code in the constructor of my JFrame which will run in the Event despatch thread. If you dont want hang up your GUI, make sure that you run this timer on another thread and when you are disposing the JFrame wrap the call with SwingUtilities.invokeLater() - This ensures that the call gets queued on the event despatch thread.
I think your code is not working for the same reason, that you trying to something that does not get queued up in the event despatch thread. Here's an article that will get you going
http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html

Categories