I have been working on this game for a while now, which actually has different game modes built into it. At first, I have been handling the execution by just exiting the program
after the user has lost, or wants to exit. Since not only is it annoying to have to re-open the program, and when users don't understand why it closed, I have a reason to after losing or wanting to exit, that they return to the main menu.
The problem that appears at this time is that I am using swing
in the main part of my design of the game. This not only includes the main menu, but other menus, and even in part with the game. Swing
is being used is for the interactivity of buttons and other primary features. So now that I am switching to returning to the main menu and everything, I have been having to rewrite basically the whole base of rendering
and switching between the windows.
Since I am rewriting the render
method of the game, I decided to make a StateRenderer
class. From this, it would handle and decide if it currently even needs to process. So within the run()
method, I put a line of code that checks if it even needs to render at a state of a menu.
@Override
public void run() {
long lastTime = System.nanoTime();
long timer = System.currentTimeMillis();
final double ns = BILLION / UPDATE_RATE;
double delta = 0;
int updates = 0, frames = 0;
while (running) {
// right here I am checking the state for it
GameState state = CellDefender.getGameState();
if (state == GameState.MAIN_MENU || state == GameState.STORE_MENU || state == GameState.SETTINGS_MENU) continue;
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1) {
update();
updates++;
delta--;
}
render();
frames++;
if (System.currentTimeMillis() - timer >= 1000) {
while (System.currentTimeMillis() - timer >= 1000) // while idling it builds up much, and makes it less annoying when debugging
timer += 1000;
System.out.println("UPS: " + updates + ", FPS: " + frames);
updates = 0;
frames = 0;
}
}
stop();
}
Now, that works fine when I decide to switch from the main menu to an actual game mode, but if I were to lose on the mode, or want to exit to main, I get this nasty error, which I have no idea how I would ever fix it:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: component argument pData
at sun.java2d.windows.GDIWindowSurfaceData.initOps(Native Method)
at sun.java2d.windows.GDIWindowSurfaceData.<init>(Unknown Source)
at sun.java2d.windows.GDIWindowSurfaceData.createData(Unknown Source)
at sun.java2d.d3d.D3DScreenUpdateManager.getGdiSurface(Unknown Source)
at sun.java2d.d3d.D3DScreenUpdateManager.createGraphics(Unknown Source)
at sun.awt.windows.WComponentPeer.getGraphics(Unknown Source)
at java.awt.Component.getGraphics(Unknown Source)
at sun.awt.RepaintArea.paint(Unknown Source)
at sun.awt.windows.WComponentPeer.handleEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$200(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue$4.run(Unknown Source)
at java.awt.EventQueue$4.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
The only part that I understand is I'm doing something completely wrong with AWT, which deals with things such as Graphics
and Canvas
. It would probably be a horrible idea to just engulf the error with a try..catch method, but then again I don't really know where it is caused.
To get into details about how I switch, here is from my main menu to an actual GameMode:
private void initListeners() {
btnRegular.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setGameState(GameState.REGULAR);
renderer.switchDisplay(RegularMode.class);
}
});
// more code is here, but useless for now
}
My renderer
is my StateRenderer
which is meant to be built to handle all rendering for the game, when you are actually on a screen that has the Canvas
element, and is tracked by the GameState
. Now I will show you what is thrown into the renderer.switchDisplay(Class class)
method.
public void switchDisplay(Class<? extends GameMode> mode) {
if (mode == RegularMode.class) {
currentMode = new RegularMode(size);
setPreferredSize(size);
screen = new Screen(size.width, size.height);
panel.add(currentMode.getFunctionBar(), BorderLayout.NORTH);
panel.add(currentMode.getScoreBar(), BorderLayout.SOUTH);
// -- extra stuff that is similar --
} else return;
JFrame frame = CellDefender.getFrame();
frame.remove(CellDefender.getMainPanel());
panel.add(this, BorderLayout.CENTER);
frame.add(panel);
frame.validate();
frame.repaint();
frame.pack();
currentMode.initialize();
requestFocus();
}
This may be somewhat inefficient, but all of this seems to work seemingly fine. Now to dive into when everything is getting switched back to the main menu that throws all of the errors!
This is the code that is ran that is directly causing the error:
public static void switchDisplay() {
setGameState(GameState.MAIN_MENU); // Switched the game state, should stop looping.
frame.setContentPane(panel);
frame.validate();
frame.repaint();
frame.pack();
}
This, of course, gives me a complete feeling that the error lies somewhere in my StateRenderer
class, and more specifically anything that relates into the run()
method. The part that I have already handled the loop about the rendering.
Summary
So I am having a problem when switching from a panel with a Canvas
component, that implements Runnable
. In my code, I have handled the problem about it rendering when it doesn't have a visible Canvas
, or when the GameState
is not one that is for the game to render. However, when switching from a canvas that is currently being rendered and updated to a menu that isn't doing so causes a NullPointerException.
I would like to go ahead and thank anyone and everyone for your help, because this issue really has me stumped.
Edit(s)
With further testing that I always decide to do when I ask for help, I see the problem occurs in the CellDefender.switchDisplay()
method at the line frame.validate()
. I don't understand why this is causing the problem.
According to the discussion in the comments, the problem is most likely related to a violation of the Swing "Single Thread Rule":
Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.
The results of violating this rule may be arbitrarily odd, but NullPointerException
s from deeply inside the Swing management infrastructure are among the more common ones.
In many cases, this issue can be resolved by a pragmatic pattern: Assume you have a method that modifies Swing components:
void modifySwingComponents()
{
someComponent.add(someOtherComponent);
someComponent.remove(somethingElse);
someTextComponent.setText("Text");
...
}
Then you can easily check whether the Single Thread Rule is violated by inserting something like
System.out.println(Thread.currentThread());
in this method. It should always print Thread[AWT-EventQueue-0,6,main]
(or similar, indicating that the method is executed on the Event Dispatch Thread). Alternatively, you can directly query
System.out.println(SwingUtilities.isEventDispatchThread());
If the method is called from a Thread that is not the Event Dispatch Thread (EDT), you may "wrap" this method into a Runnable that you put on the event queue:
void modifySwingComponents()
{
if (SwingUtilities.isEventDispatchThread())
{
// We're on the right thread - direcly call the method
modifySwingComponentsOnEDT();
}
else
{
// Put the task to execute the method in the event queue,
// so that it will be executed on the EDT as soon as possible
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
modifySwingComponentsOnEDT();
}
});
}
}
// Always called on the EDT!
void modifySwingComponentsOnEDT()
{
someComponent.add(someOtherComponent);
someComponent.remove(somethingElse);
someTextComponent.setText("Text");
...
}
But NOTE that although this looks simple and may seem to easily resolve certain issues, it does NOT release you from the duty to diligently check and document which method is executed on which thread.