0

For a college project I was tasked with making an adventure game in Java. I have used threads and the swing library among other things, but now I have encountered a problem.

// The following is in the constructor of a subclass of JPanel
gameViewThread = new Thread(() -> {
    double previous = System.currentTimeMillis();
        double lag = 0.0;
        double current;
        double elapsed;
        while(true){
            current = System.currentTimeMillis();
            elapsed = current - previous;
            previous = current;
            lag += elapsed;
            System.out.println("Checking up on stuff...");
            while(OverworldMap.initialised && lag >= 17) {
                generateBackground();
                lag -= 17;
            }
        }
});

On line 11 of the above pasted code, I have a print statement which I would like to remove. (I don't want to be flooding the console with not needed information) However, when I remove that statement, then the visuals do not update. I have tested this time and time again, and I have made sure that OverworldMap.initialised returns true. When running the code in debug mode, I made sure that generateBackground() runs. So to me, it looks like the visuals are just not updating unless you 'wake up' the System.

NOTE: generateBackground() generates and saves an image in a BufferedImage variable, and repaint() makes sure it is drawn in the correct location. (repaint() is called in the main thread 30 times a second) These two methods do work. They worked before I tried shifting the background generation (Think tile animations from Pokemon Fire Red o/e) to a different thread. (I didn't want the logic and animation to interfere with one another.(I had low frame rate))

Alexz
  • 85
  • 1
  • 1
  • 10
  • This looks like lack of "happens before" relationship between threads. How do you pass the results of `generateBackground()` to `repaint()`? Is there a volatile variable of a synchronized section? –  Mar 22 '17 at 19:46
  • generateBackround() saves the image to a static field in the main thread. I don't know how to create a "middleman" if that's what you mean. I was told that you can't. I was told that when one of the values change on the main thread, all other threads are invoked/updated. – Alexz Mar 22 '17 at 19:59
  • Make the static variable volatile and do not reuse the generated bitmaps –  Mar 22 '17 at 20:18
  • The background image itself is only referenced once by paint component which gets it's subimage. Other that that, it is also overwritten by a new background Image. I made it volatile, but that by itself didn't fix the problem. :( – Alexz Mar 22 '17 at 20:35

1 Answers1

1

Because you are trying to update UI from the main thread. It won't work that way. Try using SwingUtilities.invokeLater() method in your program.

The following code snippet might be useful:

public void init() {
    gameViewThread = new Thread(() -> {
        double previous = System.currentTimeMillis();
        double lag = 0.0;
        double current;
        double elapsed;
        while (true) {
            current = System.currentTimeMillis();
            elapsed = current - previous;
            previous = current;
            lag += elapsed;
            //System.out.println("Checking up on stuff...");
            while (OverworldMap.initialised && lag >= 17) {
                updateUI();
                //generateBackground();
                lag -= 17;
            }
        }
    });
}


public void updateUI() {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            // All swing UI update code here
            generateBackground();                    
        }
    });
}

PS: Also, remember, do NOT use the run() method for your thread. You should call the start() method to make it run. First, in your constructor call the init() method that I provided and somewhere in your code (could be in your constructor too) use gameViewThread.start() to start your thread going.

  • You mean something like this? `gameViewThread = new Thread(() -> { SwingUtilities.invokeLater(() -> { ... }}` – Alexz Mar 22 '17 at 19:55
  • You may try to add it within your while loop. – Socal Coder Mar 22 '17 at 19:58
  • Like this? `SwingUtilities.invokeLater(this::generateBackground);` – Alexz Mar 22 '17 at 20:04
  • Yes, that should work too. It will be better if you create a method, for example updateUI(), and update it accordingly, so that all the UI related code could be updated within that area. – Socal Coder Mar 22 '17 at 20:13
  • Thank you for your help. However, now I'm getting a NullPointerException. It seems that generateBackground runs even when OverworldMap.initialised is false. – Alexz Mar 22 '17 at 20:17
  • It shouldn't because of your while statement: while (OverworldMap.initialised && lag >= 17) – Socal Coder Mar 22 '17 at 20:19
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/138764/discussion-between-ben-o-and-alexz). – Socal Coder Mar 22 '17 at 20:21