Getting errors running app in a loop

This is quite strange. I am getting errors and warnings when I run my application in a loop. The errors and warning are sporadic but happen very often.

I pared my code down to a test case. Here is the main class:

package test._3dmathpuzzles.cubes;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.shape.Box;
import com.jme3.system.JmeContext;
import java.io.File;

public class CubePuzzleGenerator extends SimpleApplication {
  @Override
  public void simpleInitApp() {
    viewPort.setBackgroundColor(ColorRGBA.White);
    
    Mesh m = new Box(10, 10, 10);
    Geometry geom = new Geometry("Box", m);
    Material mat = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    ColorRGBA bgColor = ColorRGBA.Black;
    mat.setColor("Color", bgColor);      
    geom.setMaterial(mat);
    rootNode.attachChild(geom);

    ScreenshotAppStateWithCallbacks ssAppState = new ScreenshotAppStateWithCallbacks(
        "C:"+File.separator+"Tmp"+File.separator, 
        "test");
    ssAppState.setIsNumbered(false);
    ssAppState.setWriteImageFileCallback(new Runnable() {
      @Override
      public void run() {
        stop();
      }
    });
    stateManager.attach(ssAppState);
    ssAppState.takeScreenshot();
  }
  
  public static void main(String[] args) 
  throws Exception {

    for( int i = 0; i<5; i++ ) {
      System.out.println("Starting iteration "+i);
      new CubePuzzleGenerator().start(JmeContext.Type.OffscreenSurface);        
      System.out.println("Ended iteration "+i);
    }
  }
}

Here is the helper class:

package test._3dmathpuzzles.cubes;

import com.jme3.app.state.ScreenshotAppState;
import java.io.File;
import java.io.IOException;

public class ScreenshotAppStateWithCallbacks extends ScreenshotAppState {
  private Runnable writeImageFileCallback;
  
  public ScreenshotAppStateWithCallbacks(String filePath, String fileName) {
    super(filePath, fileName);
  }

  public void setWriteImageFileCallback(Runnable writeImageFileCallback) {
    this.writeImageFileCallback = writeImageFileCallback;
  }
  
  @Override
  protected void writeImageFile(File file) throws IOException {
    super.writeImageFile(file);
    if( writeImageFileCallback != null )
      writeImageFileCallback.run();
  }
}

I just ran it and I got this output:

Starting iteration 0
Feb 28, 2023 12:25:40 PM com.jme3.system.JmeDesktopSystem initialize
INFO: Running on jMonkeyEngine 3.5.2-stable
 * Branch: HEAD
 * Git Hash: 8ab3d24
 * Build Date: 2022-04-21
Ended iteration 0
Starting iteration 1
Ended iteration 1
Starting iteration 2
Ended iteration 2
Starting iteration 3
Ended iteration 3
Starting iteration 4
Ended iteration 4
Feb 28, 2023 12:25:40 PM com.jme3.app.LegacyApplication handleError
SEVERE: Win32: Failed to register window class: Class already exists. 
java.lang.Exception: Win32: Failed to register window class: Class already exists. 
	at com.jme3.system.lwjgl.LwjglWindow$1.invoke(LwjglWindow.java:199)
	at org.lwjgl.glfw.GLFWErrorCallbackI.callback(GLFWErrorCallbackI.java:43)
	at org.lwjgl.system.JNI.invokeI(Native Method)
	at org.lwjgl.glfw.GLFW.glfwInit(GLFW.java:1047)
	at com.jme3.system.lwjgl.LwjglWindow.createContext(LwjglWindow.java:203)
	at com.jme3.system.lwjgl.LwjglWindow.initInThread(LwjglWindow.java:529)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:662)
	at java.lang.Thread.run(Thread.java:750)

Feb 28, 2023 12:25:40 PM com.jme3.app.LegacyApplication handleError
SEVERE: Win32: Failed to register window class: Class already exists. 
java.lang.Exception: Win32: Failed to register window class: Class already exists. 
	at com.jme3.system.lwjgl.LwjglWindow$1.invoke(LwjglWindow.java:199)
	at org.lwjgl.glfw.GLFWErrorCallbackI.callback(GLFWErrorCallbackI.java:43)
	at org.lwjgl.system.JNI.invokeI(Native Method)
	at org.lwjgl.glfw.GLFW.glfwInit(GLFW.java:1047)
	at com.jme3.system.lwjgl.LwjglWindow.createContext(LwjglWindow.java:203)
	at com.jme3.system.lwjgl.LwjglWindow.initInThread(LwjglWindow.java:529)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:662)
	at java.lang.Thread.run(Thread.java:750)

Feb 28, 2023 12:25:40 PM com.jme3.app.LegacyApplication handleError
SEVERE: Win32: Failed to register window class: Class already exists. 
java.lang.Exception: Win32: Failed to register window class: Class already exists. 
	at com.jme3.system.lwjgl.LwjglWindow$1.invoke(LwjglWindow.java:199)
	at org.lwjgl.glfw.GLFWErrorCallbackI.callback(GLFWErrorCallbackI.java:43)
	at org.lwjgl.system.JNI.invokeI(Native Method)
	at org.lwjgl.glfw.GLFW.glfwInit(GLFW.java:1047)
	at com.jme3.system.lwjgl.LwjglWindow.createContext(LwjglWindow.java:203)
	at com.jme3.system.lwjgl.LwjglWindow.initInThread(LwjglWindow.java:529)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:662)
	at java.lang.Thread.run(Thread.java:750)

Feb 28, 2023 12:25:40 PM com.jme3.app.LegacyApplication handleError
SEVERE: Win32: Failed to register window class: Class already exists. 
java.lang.Exception: Win32: Failed to register window class: Class already exists. 
	at com.jme3.system.lwjgl.LwjglWindow$1.invoke(LwjglWindow.java:199)
	at org.lwjgl.glfw.GLFWErrorCallbackI.callback(GLFWErrorCallbackI.java:43)
	at org.lwjgl.system.JNI.invokeI(Native Method)
	at org.lwjgl.glfw.GLFW.glfwInit(GLFW.java:1047)
	at com.jme3.system.lwjgl.LwjglWindow.createContext(LwjglWindow.java:203)
	at com.jme3.system.lwjgl.LwjglWindow.initInThread(LwjglWindow.java:529)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:662)
	at java.lang.Thread.run(Thread.java:750)

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x712f6160, pid=18120, tid=0x00005788
#
# JRE version: Java(TM) SE Runtime Environment (8.0_351-b10) (build 1.8.0_351-b10)
# Java VM: Java HotSpot(TM) Client VM (25.351-b10 mixed mode, sharing windows-x86 )
# Problematic frame:
# CFeb 28, 2023 12:25:40 PM com.jme3.app.LegacyApplication handleError
SEVERE: Failed to create display
java.lang.IllegalStateException: Unable to initialize GLFW
	at com.jme3.system.lwjgl.LwjglWindow.createContext(LwjglWindow.java:204)
	at com.jme3.system.lwjgl.LwjglWindow.initInThread(LwjglWindow.java:529)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:662)
	at java.lang.Thread.run(Thread.java:750)

Feb 28, 2023 12:25:40 PM com.jme3.app.LegacyApplication handleError
SEVERE: Failed to create display
java.lang.IllegalStateException: Unable to initialize GLFW
	at com.jme3.system.lwjgl.LwjglWindow.createContext(LwjglWindow.java:204)
	at com.jme3.system.lwjgl.LwjglWindow.initInThread(LwjglWindow.java:529)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:662)
	at java.lang.Thread.run(Thread.java:750)

  [glfw.dll+0x6160]Feb 28, 2023 12:25:40 PM com.jme3.app.LegacyApplication handleError
SEVERE: Failed to create display
java.lang.IllegalStateException: Unable to initialize GLFW
	at com.jme3.system.lwjgl.LwjglWindow.createContext(LwjglWindow.java:204)
	at com.jme3.system.lwjgl.LwjglWindow.initInThread(LwjglWindow.java:529)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:662)
	at java.lang.Thread.run(Thread.java:750)

Feb 28, 2023 12:25:40 PM com.jme3.system.lwjgl.LwjglWindow run
SEVERE: Display initialization failed. Cannot continue.
Feb 28, 2023 12:25:40 PM com.jme3.system.lwjgl.LwjglWindow run
SEVERE: Display initialization failed. Cannot continue.
Feb 28, 2023 12:25:40 PM com.jme3.system.lwjgl.LwjglWindow run
SEVERE: Display initialization failed. Cannot continue.
Execution protection violation
[thread 22508 also had an error]

#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# G:\My Drive\Dev\3DMathPuzzlesWeb\hs_err_pid18120.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

If you look at the output, the error comes after the loop finishes its final loop. There is no code to execute after that.

I am also sporadically getting this warning:

WARN: Driver claims that default framebuffer is not sRGB capable. Enabling anyway.

Any ideas what is going on?

So all at once you try to open 5 application windows from the same application. Yeah, it’s not going to like that.

One application per application. You could maybe wait until one has completely stopped before starting another one… but still one application at a time per application.

what are you ACTUALLY trying to do?

1 Like

I am trying to generate several images in a loop. Each loop calls ssAppState.takeScreenshot() and then the callback issues the stop command.

Start the application once. Then do your screen shots one at a time.

Alternately, the super-duper-pooper-scooper-most expensive way possible: loop like you are but then after starting each app, spin-loop waiting for that one to compete before starting the next one. (Which may still not work depending on how good the lwjgl lib, etc. are at cleaning up.)

I see a stop method with a waitFor argument so I changed my code to this:

package test._3dmathpuzzles.cubes;

import com.jme3.app.SimpleApplication;
import com.jme3.app.state.ScreenshotAppState;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.shape.Box;
import com.jme3.system.JmeContext;
import java.io.File;

public class CubePuzzleGenerator extends SimpleApplication {
  @Override
  public void simpleInitApp() {
    viewPort.setBackgroundColor(ColorRGBA.White);
    
    Mesh m = new Box(10, 10, 10);
    Geometry geom = new Geometry("Box", m);
    Material mat = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    ColorRGBA bgColor = ColorRGBA.Black;
    mat.setColor("Color", bgColor);      
    geom.setMaterial(mat);
    rootNode.attachChild(geom);

    ScreenshotAppState ssAppState = new ScreenshotAppState(
        "C:"+File.separator+"Tmp"+File.separator, 
        "test");
    stateManager.attach(ssAppState);
    ssAppState.takeScreenshot();
  }
  
  public static void main(String[] args) 
  throws Exception {
    for( int i = 0; i<2; i++ ) {
      System.out.println("Starting iteration "+i);
      CubePuzzleGenerator generator = new CubePuzzleGenerator();
      generator.start(JmeContext.Type.OffscreenSurface);
      generator.stop(true);
      System.out.println("Ended iteration "+i);
    }
  }
}

I removed my helper class and added a true argument to the stop call.
I don’t see how one instance is related to another. They are not hanging around, each one is stopped in the loop and de-referenced.

But even assuming it can work (which is an assumption outside the realm of JME, by the way) why would you want to go through the trouble of doing the heaving lifting of starting up the whole OGL context, opening a Window, etc. just to take a screen shot, and go through the heavy lifting of shutting down again… for EVERY screen shot you want?

Why not just spin the app up ONCE and let it take as many screen shots as you want?

OK, I need to re-organize my code. Thanks for the suggestion!

Easiest way would be to have a ConcurrentLinkedQueue that the main() method (or whatever) feeds with requests and then simpleUpdate can pull an item off the key and take a screen shot.

I changed my code to this:

package test._3dmathpuzzles.cubes;

import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AppState;
import com.jme3.app.state.ScreenshotAppState;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext;
import java.io.File;
import java.util.ArrayList;

public class CubePuzzleGenerator extends SimpleApplication {
  ScreenshotAppState ssAppState = null;
  private int puzzleSize;
  private int puzzleNumber = 2;  
  private ArrayList<PuzzleInfo> puzzlesToWrite = new ArrayList<PuzzleInfo>();
  
  public CubePuzzleGenerator() 
  throws Exception {
    super((AppState[])null);
  }
   
  public void createSolvableGrid(int puzzleSize) 
  throws Exception {
    this.puzzleSize = puzzleSize;
    
    PuzzleInfo puzzleInfo = new PuzzleInfo();
    puzzleInfo.puzzleNumber = puzzleNumber++;
    puzzlesToWrite.add(puzzleInfo);
  }
    
  @Override
  public void simpleInitApp() {
    ssAppState = new ScreenshotAppState(
        "C:"+File.separator+"Tmp"+File.separator, 
        "test");
    ssAppState.setIsNumbered(false);
    stateManager.attach(ssAppState);
  }
  
  @Override
  public void simpleUpdate(float tpf) {    
    for( PuzzleInfo puzzle: puzzlesToWrite) {
      System.out.println("Writing puzzle number "+puzzle.puzzleNumber);
      
      rootNode.detachAllChildren();  
      viewPort.setBackgroundColor(ColorRGBA.White);
      
      Mesh m = new Box(10, 10, 10);
      Geometry geom = new Geometry("Box", m);
      Material mat = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
      ColorRGBA bgColor = ColorRGBA.Black;
      mat.setColor("Color", bgColor);      
      geom.setMaterial(mat);
      rootNode.attachChild(geom);
      
      ssAppState.setFileName("3DMathPuzzle-Cube-"+puzzleSize+"-"+
          puzzle.puzzleNumber);
      ssAppState.takeScreenshot();
    }
    
    stop(true);
  }
  
  public static void main(String[] args) 
  throws Exception {
    CubePuzzleGenerator generator = new CubePuzzleGenerator();

    for(int i=0; i<5; i++ )
      generator.createSolvableGrid(3);

    AppSettings appSettings = new AppSettings(true);
    appSettings.setHeight(1024);
    appSettings.setWidth(1024);
    generator.setSettings(appSettings);
    generator.start(JmeContext.Type.OffscreenSurface);
  }
}

Here is the PuzzleInfo class:

package test._3dmathpuzzles.cubes;

public class PuzzleInfo {
  /** The puzzle number */
  public int puzzleNumber;
}

According to the output, it should have written puzzles with numbers 2,3,4,5, and 6:

Writing puzzle number 2
Writing puzzle number 3
Writing puzzle number 4
Writing puzzle number 5
Writing puzzle number 6

But, my output directory only contains a file for puzzle 6.
What happened to the others?

takeSceenshot() is part of rendering. The frame has to render.

update
render
update
render
update
render
update
render
update
render
update
render

You are looping through everything on one update and then just rendering the last one.

One render per frame.

Edit: change this:

for( PuzzleInfo puzzle: puzzlesToWrite) {

To:

if( puzzlesToWrite.isEmpty() ) {
    stop();
    return;
}
PuzzleInfo puzzle = puzzlesToWrite.remove(0);
1 Like

Got it! I am not used to thinking with the larger framework. I am used to having to control things myself.

1 Like

I don’t believe there is an issue here, but I wanted to double-check with the experts.

Based on a suggestion from a very helpful user on this forum, I created a subclass of ScreenshotAppState which has a callback for when an image file is written:

package com.propfinancing.jme3;

import com.jme3.app.state.ScreenshotAppState;
import java.io.File;
import java.io.IOException;

public class ScreenshotAppStateWithCallbacks extends ScreenshotAppState {
  private Runnable writeImageFileCallback;
  
  public ScreenshotAppStateWithCallbacks(String filePath, String fileName) {
    super(filePath, fileName);
  }

  public void setWriteImageFileCallback(Runnable writeImageFileCallback) {
    this.writeImageFileCallback = writeImageFileCallback;
  }
  
  @Override
  protected void writeImageFile(File file) throws IOException {
    super.writeImageFile(file);
    if( writeImageFileCallback != null )
      writeImageFileCallback.run();
  }
}

I instantiate the class in my application’s simpleInitApp method and set its callback in the simpleUpdate method:

...
import com.propfinancing.jme3.ScreenshotAppStateWithCallbacks;
...
public class CubePuzzleGenerator extends SimpleApplication {
  ScreenshotAppStateWithCallbacks ssAppState = null;
  ...
  public void simpleInitApp() {
    ...
    ssAppState = new ScreenshotAppStateWithCallbacks(
        "C:"+File.separator+"Tmp"+File.separator, 
        "test");
    ssAppState.setIsNumbered(false);
    stateManager.attach(ssAppState);
  }
  
  @Override
  public void simpleUpdate(float tpf) {
    // Exit the application if we are done
    ...

    // Get the next puzzle
    ...

    ssAppState.setFileName(puzzle.getFileName());
    ssAppState.setWriteImageFileCallback(new CallbackToWritePuzzlePdf(puzzle));
    ssAppState.takeScreenshot();
  }

  ...
}  

I hope this is enough code to illustrate my point without being too verbose. Let me know if I need to add more details.

I am assuming JME does the render and update in a single thread so the update gets called and then the render code executes, including my callback.

I can see how this code might break if the code is multi-threaded.

Are there any pitfalls of this approach I might be missing?

Would be useful for me to add callbacks to the JME ScreenshotAppState class and contribute it? Let me know if you guys want me to do that.

JME is single-threaded. So you should be fine.

3 Likes

Perfect, thanks for confirming!

out of topic: also remember, if you want create multithread tasks, its very cool to sychronize with JME thread via app.enqueue() And the only things to you need synchronize are related to Scene Graph if you are doing them out of JME thread.

1 Like