Starting jme with SwingWorker

I use JME integrated into a bigger software so it is a special canvas that some times is started.
To keep the GUI nice and responsive, I want to to all the starting up in a SwingWorker thread.

This is the MWE:
[java]
import java.awt.Canvas;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.util.concurrent.ExecutionException;

import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingWorker;

public class Main extends JPanel {
private JmeDummy jme;

public static void main(String[] args) {
    Main main = new Main();
    main.init();

    JFrame frame = new JFrame("FrameDemo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(main);
    frame.pack();
    frame.setVisible(true);
}

private void init() {

    // Create a regular text field.
    final JTextField errorMsg = new JTextField();

    JButton startButton = new JButton("Start 3D");
    startButton.setVerticalTextPosition(AbstractButton.CENTER);
    startButton.setHorizontalTextPosition(AbstractButton.LEADING); // aka LEFT, for left-to-right locales
    startButton.setMnemonic(KeyEvent.VK_ENTER);
    startButton.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            // start 3D engine
            SwingWorker worker = new SwingWorker() {

                @Override
                public Canvas doInBackground() {
                    jme = new JmeDummy();
                    System.out.println("in doInBackground");
                    return jme.getComponentAndStartIt();
                }

                @Override
                public void done() {
                    removeAll();
                    try {
                        add(get());
                        Main.this.addComponentListener(new ComponentAdapter() {
                            @Override
                            public void componentResized(ComponentEvent e) {
                                super.componentResized(e);
                                jme.resize(getBounds());
                            }
                        });
                    }
                    catch (InterruptedException | ExecutionException e1) {
                        e1.printStackTrace();
                    }
                }
            };
            worker.execute();
        }
    });

    this.add(startButton);
    this.add(errorMsg);
}

}
[/java]

[java]

import java.awt.Canvas;
import java.awt.Rectangle;
import java.util.logging.Level;

import com.jme3.app.SimpleApplication;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeCanvasContext;

public class JmeDummy extends SimpleApplication {
private JmeCanvasContext ctx;

@Override
public void simpleInitApp() {
    createApplicationAndCanvas();

}

private void createApplicationAndCanvas() {
    AppSettings settings = new AppSettings(true);
    settings.setWidth(500);
    settings.setHeight(500);
    settings.setAudioRenderer(null);
    java.util.logging.Logger.getLogger("").setLevel(Level.WARNING);
    setSettings(settings);
    setShowSettings(false);
    setDisplayStatView(false);
    setDisplayFps(false);
    createCanvas();
    ctx = (JmeCanvasContext) getContext();
    ctx.setSystemListener(this);
}

public Canvas getComponentAndStartIt() {
    startCanvas();
    return ctx.getCanvas();
}

public void resize(Rectangle bounds) {
    ctx.getCanvas().setBounds(bounds);
}

}
[/java]

This is the error:

in doInBackground java.util.concurrent.ExecutionException: java.lang.NullPointerException at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:252) at java.util.concurrent.FutureTask.get(FutureTask.java:111) at javax.swing.SwingWorker.get(SwingWorker.java:602) at pt.up.fe.dceg.neptus.plugins.r3d.Main$1$1.done(Main.java:76) at javax.swing.SwingWorker$5.run(SwingWorker.java:737) at javax.swing.SwingWorker$DoSubmitAccumulativeRunnable.run(SwingWorker.java:832) at sun.swing.AccumulativeRunnable.run(AccumulativeRunnable.java:112) at javax.swing.SwingWorker$DoSubmitAccumulativeRunnable.actionPerformed(SwingWorker.java:842) at javax.swing.Timer.fireActionPerformed(Timer.java:312) at javax.swing.Timer$DoPostEvent.run(Timer.java:244) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:251) at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:721) at java.awt.EventQueue.access$200(EventQueue.java:103) at java.awt.EventQueue$3.run(EventQueue.java:682) at java.awt.EventQueue$3.run(EventQueue.java:680) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76) at java.awt.EventQueue.dispatchEvent(EventQueue.java:691) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:244) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:163) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:151) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:147) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:139) at java.awt.EventDispatchThread.run(EventDispatchThread.java:97) Caused by: java.lang.NullPointerException at com.jme3.app.Application.startCanvas(Application.java:442) at com.jme3.app.Application.startCanvas(Application.java:429) at pt.up.fe.dceg.neptus.plugins.r3d.JmeDummy.getComponentAndStartIt(JmeDummy.java:55) at pt.up.fe.dceg.neptus.plugins.r3d.Main$1$1.doInBackground(Main.java:69) at pt.up.fe.dceg.neptus.plugins.r3d.Main$1$1.doInBackground(Main.java:1) at javax.swing.SwingWorker$1.call(SwingWorker.java:296) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334) at java.util.concurrent.FutureTask.run(FutureTask.java:166) at javax.swing.SwingWorker.run(SwingWorker.java:335) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603) at java.lang.Thread.run(Thread.java:722)

Can I even do this wih JME?

In case anyone runs into this the solution was to RTM, in particular section Swing Canvas.

I think the trick was to use enqueue:
[java]app.enqueue(new Callable() {
@Override
public Void call() {
if (app instanceof SimpleApplication) {
SimpleApplication simpleApp = app;
simpleApp.getFlyByCamera().setDragToRotate(true);
}
return null;
}
});[/java]

1 Like

Yep. You will find the threading tutorial helpful as well.

I am having trouble making the code run.

I’m using multithreading for launching JME (not updating).
What I want is:
[java]enqueue(new Callable() {
@Override
public Void call() {
createCanvas();
startCanvas();
return null;
}
});[/java]

After digging into JME code, I think it doesn’t run because the Application class only calls enqueue on the update loop.
If this is true, it can’t be used to call startCanvas() (right?).
Without enqueue, the init methods run fine but when I get to the update loop I get this exception when calling spatial.checkCulling(cam)
[java]
java.lang.IllegalStateException: Scene graph is not properly updated for rendering.
State was changed after rootNode.updateGeometricState() call.
Make sure you do not modify the scene from another thread!
Problem spatial name: Box
at com.jme3.scene.Spatial.checkCulling(Spatial.java:260)
at pt.up.fe.dceg.neptus.plugins.r3d.jme3.spacials.Marker.updateLabelGui(Marker.java:72)
at pt.up.fe.dceg.neptus.plugins.r3d.jme3.spacials.MarkersNode.updateMarkers(MarkersNode.java:95)
at pt.up.fe.dceg.neptus.plugins.r3d.jme3.ShowBathymetryState.update(ShowBathymetryState.java:267)
at com.jme3.app.state.AppStateManager.update(AppStateManager.java:254)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:238)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
at com.jme3.system.lwjgl.LwjglCanvas.runLoop(LwjglCanvas.java:229)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:228)
at java.lang.Thread.run(Thread.java:722)[/java]

Am I using enqueue the wrong way?

Enqueue is for updating elements once the application is up and running and the scene graph is going.

Follow the tutorials to show how to get the application running in the first place…

I have redone my code a lot of times (and RTM a lot of times) but I still can’t understand the above error.

The launching code is different (I put it at the bottom just in case), it runs fine (can load a terrain, add some hud and a help screen) but the second I call checkCulling(cam) it gives the above /(IllegalStateException) error.

I have printed the id of the thread in simpleInitApp and simpleUpdate - they are the same. There is no one messing with JME except them.

I have looked at Swing Canvas example but it first starts JME and then builds the interface. This is not an option for me.
If I take the code provided and bring createCanvas(appClass) inside the run method it blows up.

Any insight into this will be greatly appreciated.
Maybe I’m missing something basic but I did read the manual and I am running around this for some time now.

[java]AppSettings settings = new AppSettings(true);
settings.setWidth(rectangle.width);
settings.setHeight(rectangle.height);
settings.setAudioRenderer(null);
setSettings(settings);
java.util.logging.Logger.getLogger("").setLevel(Level.WARNING);
setShowSettings(false);
setPauseOnLostFocus(false);
setDisplayStatView(false);
setDisplayFps(false);
createCanvas();
startCanvas();
JmeCanvasContext context = (JmeCanvasContext) getContext();
context.getCanvas().setSize(rectangle.width, rectangle.height);[/java]

So around now I have changed the Swing Canvas example to init in the order I need and use the checkCulling(cam) method.

I have printed out what thread is used for each thing in the modified Swing Canvas.
The createCanvas and startCanvas are done in the EventDispatcherThread. The simpleInitApp and update methods are not using the EventDispatcherThread, but the same thread is used by both.

On my code the order of the calls is the same, the methods are executed in the same thread architecture. Or at least as far as I can understand.
But it gives the infamous IllegalStateException.

I am unsure what code to post here since I can’t reproduce the error in a smaller example.
But I will post my modified Swing Canvas for reference.

[java]
import java.awt.BorderLayout;

public class TestCanvas {

private static JmeCanvasContext context;
private static Canvas canvas;
private static Application app;
private static JFrame frame;
private static Container canvasPanel1, canvasPanel2;
private static Container currentPanel;
private static JTabbedPane tabbedPane;
private static final String appClass = "pt.up.fe.dceg.neptus.plugins.r3d.jme3.JmeTestCanvasClone";

private static void createTabs(){

private static void createMenu(){

private static void createFrame(){
    frame = new JFrame("Test");
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.addWindowListener(new WindowAdapter(){
        @Override
        public void windowClosed(WindowEvent e) {
            app.stop();
        }
    });

    createTabs();
    createMenu();
}

public static void createCanvas(String appClass){
    System.out.println("createCanvas EvtDispatchThread? " + EventQueue.isDispatchThread() + " Thread id:"
            + Thread.currentThread().getId());
    AppSettings settings = new AppSettings(true);
    settings.setWidth(640);
    settings.setHeight(480);

    try{
        @SuppressWarnings("unchecked")
        Class clazz = (Class) Class.forName(appClass);
        app = clazz.newInstance();
    }catch (ClassNotFoundException ex){
        ex.printStackTrace();
    }catch (InstantiationException ex){
        ex.printStackTrace();
    }catch (IllegalAccessException ex){
        ex.printStackTrace();
    }

    app.setPauseOnLostFocus(false);
    app.setSettings(settings);
    app.createCanvas();

// app.startCanvas();

    context = (JmeCanvasContext) app.getContext();
    canvas = context.getCanvas();
    canvas.setSize(settings.getWidth(), settings.getHeight());
}

public static void startApp(){
    System.out.println("startApp EvtDispatchThread? " + EventQueue.isDispatchThread() + " Thread id:"
            + Thread.currentThread().getId());
    app.startCanvas();
    app.enqueue(new Callable(){
        @Override
        public Void call(){
            if (app instanceof SimpleApplication){
                SimpleApplication simpleApp = (SimpleApplication) app;
                simpleApp.getFlyByCamera().setDragToRotate(true);
            }
            return null;
        }
    });
   
}

public static void main(String[] args){
    JmeFormatter formatter = new JmeFormatter();

    Handler consoleHandler = new ConsoleHandler();
    consoleHandler.setFormatter(formatter);

    Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);
    Logger.getLogger("").addHandler(consoleHandler);
   
   
    try {
        Thread.sleep(500);
    } catch (InterruptedException ex) {
    }
   
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            JPopupMenu.setDefaultLightWeightPopupEnabled(false);

            createFrame();

        }
    });
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            JPopupMenu.setDefaultLightWeightPopupEnabled(false);

            createCanvas(appClass);
            currentPanel.add(canvas, BorderLayout.CENTER);
            frame.pack();
            startApp();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);

        }
    });
}

}[/java]

[java]
import java.awt.EventQueue;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

public class JmeTestCanvasClone extends SimpleApplication {

Geometry geom;
int count;


@Override
public void simpleInitApp() {
    System.out.println("simpleInitApp EvtDispatchThread? " + EventQueue.isDispatchThread() + " Thread id:"
            + Thread.currentThread().getId());
    Box b = new Box(Vector3f.ZERO, 1, 1, 1); // create cube shape at the origin
    geom = new Geometry("Box", b); // create cube geometry from the shape
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); // create a simple material
    mat.setColor("Color", ColorRGBA.Blue); // set color of material to blue
    geom.setMaterial(mat); // set the cube's material
    rootNode.attachChild(geom); // make the cube appear in the scene
}

@Override
public void update() {
    super.update();
    if (count > 1000) {
        System.out.println("update EvtDispatchThread? " + EventQueue.isDispatchThread() + " Thread id:"
                + Thread.currentThread().getId());
        System.out.println(geom.checkCulling(getCamera()));
        count = 0;
    }
    count++;
}

}
[/java]