TestChooser infinite loop

There’s a small error in TestChooser.java that I need help with.

When the TestChooser is ran with setShowSettings, which starts the selected TestChooser list object with the display settings dialog of jme, and the user clicks Cancel rather than Continue, it leads to a infinite loop because context is never set.

//User has selected setShowSettings
if (app instanceof SimpleApplication) {
    final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class);
    settingMethod.invoke(app, showSetting);
}
final Method mainMethod = clazz.getMethod("start");

//Jme settings dialog starts
mainMethod.invoke(app);

Field contextField = LegacyApplication.class.getDeclaredField("context");
contextField.setAccessible(true);
JmeContext context = null;  

//User cancels rather than continues so context never gets set and we enter loop
//with null context and contextField.get() always returns null.
while (context == null) {
    context = (JmeContext) contextField.get(app);
    Thread.sleep(100);
}

What’s the best approach to determine that the user has canceled from the TestChooser thread?

I came up with a hack that doesn’t completely solve the problem but allows the TestChooser to continue working instead of freezing and leaving hanging threads, forcing the user to kill the app.

The TestChosser would just crash otherwise and not exit gracefully.

//Using CachedThreadPool and Futures allows the JDialog to continue if the 
//user cancels SettingsDialog. Should be sufficient enough for the scope of 
//this program.
private final ExecutorService executorService = Executors.newCachedThreadPool();
//Hack fix to prevent infinite loop caused when user 
//cancels SettingsDialog rather than continuing, which 
//prevents context from being set for selected app. 
//Allows this JDialog to continue and shutdown cleaner.
Future<JmeContext> future = executorService.submit(() -> {
    JmeContext context = null;
    while (context == null) {
        context = (JmeContext) contextField.get(app);
        TimeUnit.MILLISECONDS.sleep(100);
    }
    return context;
});
JmeContext context = future.get();
                        
Future<Boolean> future2 = executorService.submit(() -> {
    while (!context.isCreated()) {
        TimeUnit.MILLISECONDS.sleep(100);
    }
    return true;
 });
future2.get();
                        
Future<Boolean> future3 = executorService.submit(() -> {
    while (context.isCreated()) {
        TimeUnit.MILLISECONDS.sleep(100);
    }
    return true;
});
future3.get();

What happens with this is when the user cancels the SettingsDialog, the app will drop out of the loop its in like before but rather than crashing and leaving the app unresponsive, now the app is still alive, no exception thrown. User can carry on.

Once the app is closed, canceled or escaped it will throw an exception, close the TestChooser gracefully and restart any SettingsDialog that was canceled.

Then the user can do what the want with no side effects.

Its a hack. If this is just shit and should be done different please let me know how to improve it.

If not, I can post the entire file, which is updated to 1.8 standard, for more critique and to let you see how it works.

Try a certain number of times and then give up. If you don’t have a context within a second or two then I think the app isn’t starting up.

That worked perfectly.

Future<JmeContext> future = executorService.submit(() -> {
    JmeContext context = null;
    while (context == null) {
        context = (JmeContext) contextField.get(app);
        TimeUnit.MILLISECONDS.sleep(100);
    }
    return context;
});
//Fix to prevent infinite loop caused when user
//cancels SettingsDialog rather than continuing, which
//prevents context from being set for selected app.
//If user cancels give context 2 seconds.
try {
    JmeContext context = future.get(2000, TimeUnit.MILLISECONDS);
                            
    Future<Boolean> future2 = executorService.submit(() -> {
        while (!context.isCreated()) {
            TimeUnit.MILLISECONDS.sleep(100);
        }
        return true;
    });
    future2.get();

    Future<Boolean> future3 = executorService.submit(() -> {
        while (context.isCreated()) {
            TimeUnit.MILLISECONDS.sleep(100);
        }
        return true;
    });
    future3.get();
} catch (TimeoutException ex) {
    future.cancel(true);
}

Are you guys interested in seeing the entire update to 1.8 to see if its worthy of submission?

I mean, the issue before was that the above loop would never complete, right?

All I was suggesting was to just add a counter to it so it only tries like 20 times (2 seconds).

int count = 0;
while (context == null) {
    context = (JmeContext) contextField.get(app);
    Thread.sleep(100);
    count++;
    if (count >= 20) {
        break;
    }
}
                            
if (context != null) {
    while (!context.isCreated()) {
        Thread.sleep(100);
    }

    while (context.isCreated()) {
        Thread.sleep(100);
    }
}

That’s to easy :frowning:.

However, ill keep mine the way I have it, I don’t like seeing 100s of warnings when I open a file. Not to mention the original uses deprecated methods and obsolete list.

I did pull as is shown.

Thanks for guidance Paul.

While im at it, there was another bug I quashed.

//        list.addKeyListener(new KeyAdapter() {
//            @Override
//            public void keyTyped(KeyEvent e) {
//                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
//                    startApp(selectedClass);
//                } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
//                    dispose();
//                }
//            }
//        });
        //For key typed events, the getKeyCode method always returns VK_UNDEFINED. 
        //Use Key Bindings instead.
        Action doEnter = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                startApp(selectedClass);
            }
        };
        KeyStroke enterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
        list.getInputMap().put(enterKey, "doEnter");
        list.getActionMap().put("doEnter", doEnter);
        //For key typed events, the getKeyCode method always returns VK_UNDEFINED. 
        //Use Key Bindings instead.
        Action doEscape = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dispose();
            }
        };
        KeyStroke escapeKey = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        list.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeKey, "doEscape");
        list.getActionMap().put("doEscape", doEscape);

I know I could just use key pressed or released but…

Does this pass for a fix?