AppState and ParticleEmitter

Hello Guys, i want a bug.
This bug is sporadic, does not always occur.

When i change Appstate1 to appstate2 and appstate2 have a ParticleEmitter, then, sporadic, causes error:

IllegalStateException: Scene graph is not properly updated for rendering. Make sure scene graph state was not changed after rootNode.

and… the weird is… i dont chall method emitAllParticles of ParticleEmitter, but it emits . :woot:

why ?

You can achieve this by randomly accessing the scene graph from another thread.

I dont using other thread :frowning:

@sradkiller said: and... the weird is... i dont chall method emitAllParticles of ParticleEmitter, but it emits . :woot:
This is not weird at all. If you have a value >0 in particlePerSeconds the emitter will emit continuously. That's the default behavior. If you want the emitter to emit all the particles when you call emitAllParticles only, you have to set the particlesPerSeconds to 0.
@nehon said: This is not weird at all. If you have a value >0 in particlePerSeconds the emitter will emit continuously. That's the default behavior. If you want the emitter to emit all the particles when you call emitAllParticles only, you have to set the particlesPerSeconds to 0.

ok,
this information is very important.
this can too solve my sporadic problem with IllegalStateException ?

Read the threading tutorial - you may think you aren’t doing threading but 99.9% of the time that is what causes the exception you are seeing…

@zarch said: Read the threading tutorial - you may think you aren't doing threading but 99.9% of the time that is what causes the exception you are seeing...

I’m not using thread.
Is just a SimpleApplication and AbstractAppState.
Just 2 classes

There could be a bug… maybe you can put together a simple test case illustrating the issue?

Post the full exception and the bit if your code that is listed in the stack trace.

@zarch said: Post the full exception and the bit if your code that is listed in the stack trace.

I can nearly guarantee that the stack trace comes from the renderer. The error is always elsewhere.

If we believe the OP is not using other threads then it may be that the particle emitter has a state problem. Perhaps it does things on update and on render and adding it at a certain time causes issue.

At any rate, a simple test case will show whether the problem is on JME’s side or the OP’s.

Well… my code is simple:
is this https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:application_states, but with alterations:
i have a class that extends AbstractAppState:

[java]
public class InitialScene extends AbstractAppState {

private SimpleApplication app;

private Node rootNode = new Node("Root Node InitialScene");
public Node getRootNode(){ return InitialScene; } 

[/java]
in method initialize i create the particleemiter

[java]

@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
this.app = app;

 ParticleEmitter fire = 
        new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
Material mat_red = new Material(assetManager, 
        "Common/MatDefs/Misc/Particle.j3md");
mat_red.setTexture("Texture", assetManager.loadTexture(
        "Effects/Explosion/flame.png"));
fire.setMaterial(mat_red);
fire.setImagesX(2); 
fire.setImagesY(2); // 2x2 texture animation
fire.setEndColor(  new ColorRGBA(1f, 0f, 0f, 1f));   // red
fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 2, 0));
fire.setStartSize(1.5f);
fire.setEndSize(0.1f);
fire.setGravity(0, 0, 0);
fire.setLowLife(1f);
fire.setHighLife(3f);
fire.getParticleInfluencer().setVelocityVariation(0.3f);
rootNode.attachChild(fire);

}
}
[/java]

And in the main application:

[java]
public void initialize()
{
super.initialize();
InitialScene initialScene = new InitialScene();
stateManager.attach(initialScene);
viewPort.attachScene(initialScene.getRootNode);
}
[/java]

only this :frowning:

Sorry by code, the tag [java] java [/java] dont working :frowning:

the error is in

[java]
throw new IllegalStateException(“Scene graph is not properly updated for rendering.\n”
+ “State was changed after rootNode.updateGeometricState() call. \n”
+ “Make sure you do not modify the scene from another thread!\n”
+ "Problem spatial name: " + getName());
[/java]

help me.

remember: is sporadic

Please try to post a simple (and complete) test case illustrating the issue so that we can easily confirm it is or is not a bug in the engine.

maybe attach rootNode to viewport before attaching initialScene to statemanger?
also, does the state’s rootNode ever get added to the main app’s rootNode at any point,
or is “injection” into the existing scene graph accomplished when it is attached to the viewport?

Ah… I didn’t see that viewports were involved.

When managing a custom viewport you need to call your own updateLogicalState() and updateGeometricState() on the viewport’s root nodes. If you do this at the wrong time with respect to updates in that root’s graph then you will see this issue.

How are you managing the viewport? (Note: if you are using my posted ViewPortState from before then there is a bug in it that will cause this behavior if removing the state.)

1 Like

this is my code (i remove the imports):

[java]
public class Main extends Application {

private LogoInicial logoInicial;
@Override
public void start(JmeContext.Type contextType) {
	
	AppSettings settings = new AppSettings(true);
	settings.setResolution(800, 600);
	settings.setRenderer(AppSettings.LWJGL_OPENGL2);
	settings.setTitle("APP Teste");
	settings.setFullscreen(false);
	setSettings(settings);
	
	super.start(contextType);
}

@Override
public void initialize() {
	
	super.initialize();
	viewPort.setBackgroundColor(ColorRGBA.Black);
	if(GameProperties.getInstance().isDebugMode()){
		inputManager.setCursorVisible(true);
	}else{
		inputManager.setCursorVisible(false);
	}
	logoInicial = new LogoInicial(assetManager);

	viewPort.attachScene(logoInicial.getRootNode());
	stateManager.attach(logoInicial);
	
}
private float time = 0;
private boolean logoInicialJaRodou = false;

@Override
public void update() {
	super.update();
	
	float tpf = timer.getTimePerFrame();
	
	stateManager.update(tpf);
	stateManager.render(renderManager);

	renderManager.render(tpf, context.isRenderable());
}

@Override
public void destroy() {
	System.out.println("Finalizando...");
	super.destroy();

}
public static void main(String[] args) {
	System.out.println("Iniciando...");
	new Main().start();
}

}

[/java]

The LogoInicial class:

[java]
public class LogoInicial extends AbstractAppState{

private AssetManager 	assetManager;
private Node 			rootNode;
private Application 	app;
private AppStateManager stateManager;
private MainMenu		mainMenu;

public LogoInicial(AssetManager asm){
	rootNode = new Node("Root Node Logo Inicial");
	assetManager = asm;
	
	mainMenu = new MainMenu(assetManager);
}

public Node getRootNode(){
    return rootNode;
}

@Override
public void initialize(AppStateManager stateManager, Application app){
	this.stateManager = stateManager;
	this.app = app;
	
	app.getViewPort().setBackgroundColor(ColorRGBA.Black);
	
	Picture p = new Picture("Picture");
    p.move(0, 0, -1);
    p.setPosition(0, 0);
    p.setWidth(app.getContext().getSettings().getWidth());
    p.setHeight(app.getContext().getSettings().getHeight());
    p.setImage(assetManager, "Imagens/rdm_800x600.png", true);
    
    rootNode.attachChild(p);
    
    new ThreadTime().start();
}

@Override
public void update(float tpf) {
	
    //super.update(tpf);
    
    rootNode.updateLogicalState(tpf);
    rootNode.updateGeometricState();
    
}

private class ThreadTime extends Thread{
	
	@Override
	public void run(){    		
		try {
			if(GameProperties.getInstance().isDebugMode()){
				sleep(1000);
			}else{
				sleep(4000);
			}
			detachThisAndAttachMenu();
		} catch (InterruptedException e) {
			System.out.println("Erro: " + e);
		}
	}
}

private void detachThisAndAttachMenu(){
	app.getStateManager().detach(this);
	app.getViewPort().detachScene(rootNode);
	
	stateManager.attach(mainMenu);
	app.getViewPort().attachScene(mainMenu.getRootNode());
}

@Override
public void cleanup(){
	//rootNode.detachAllChildren();
}

}
[/java]

And the MainMenu:

[java]
public class MainMenu extends AbstractAppState{

private AssetManager 	assetManager;
private AppStateManager stateManager;
private Node 			rootNode;
private Application 	app;
//Imagens do menu
private Picture			menuItemNovoJogo;
private Picture			menuItemOpcoes;
private Picture			menuItemSair;
private Picture			menuSelect;
private int 			menuItemSelected = 1;
/*Para fazer testes de posição*/
private Spatial modelTest;
private float scaleModeltest = 0.5f;


public MainMenu(AssetManager asm){
	rootNode = new Node("Root Node Main Menu");
	assetManager = asm;
}

public Node getRootNode(){
    return rootNode;
}

@Override
public void initialize(AppStateManager stateManager, Application app){
	app.getViewPort().setBackgroundColor(ColorRGBA.Black);
	
	this.app = app;
	this.stateManager = stateManager;
	
	app.getInputManager().clearMappings();
    app.getInputManager().addMapping("up", new KeyTrigger(KeyInput.KEY_UP));
    app.getInputManager().addListener(actionListener, new String[] { "up" });
    app.getInputManager().addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN));
    app.getInputManager().addListener(actionListener, new String[] { "down" });
    app.getInputManager().addMapping("enter", new KeyTrigger(KeyInput.KEY_RETURN));
    app.getInputManager().addListener(actionListener, new String[] { "enter" });
    
    addSceneMenu();    	
	addMenuItem();
    
  rootNode.attachChild(getFire1(assetManager, new Vector3f(2.999f,  -1.8f, -3.99f), 10));
}

   public ParticleEmitter getFire1(AssetManager assetManager, Vector3f position, int numParticule){
	
	ParticleEmitter fire = new ParticleEmitter("Emitter", Type.Triangle, numParticule);
    Material material = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
    material.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
    fire.setMaterial(material);
    fire.setImagesX(2); 
    fire.setLocalTranslation(position);
    fire.setImagesY(2); // 2x2 texture animation
    fire.setStartColor(new ColorRGBA(1f, 0f, 0f, 1f));
    fire.setEndColor(  new ColorRGBA(1f, 1f, 0f, 0.5f));
    fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0.7f, 0));
    fire.setStartSize(1.5f);
    fire.setEndSize(0.1f);
    fire.setGravity(0, 0, 0);
    fire.setLowLife(1f);
    fire.setHighLife(3f);
    fire.getParticleInfluencer().setVelocityVariation(0.05f);
    //fire.emitAllParticles();
    
    return fire;
}
private void addSceneMenu(){
	
	Box box = new Box(Vector3f.ZERO, new Vector3f(50,0.05f,50));
	Geometry chao = new Geometry("chaoMenuInicialPrincipal", box);
    TangentBinormalGenerator.generate(box);//para o efeito de luz
    TangentBinormalGenerator.generate(chao);
    box.scaleTextureCoordinates(new Vector2f(50,50));
    
    Texture texture = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg");
    texture.setWrap(Texture.WrapMode.Repeat);
    texture.setAnisotropicFilter(2);
    
    Texture textureBump = assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png");
    textureBump.setWrap(Texture.WrapMode.Repeat);
    textureBump.setAnisotropicFilter(2);
    
    Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
    material.setTexture("DiffuseMap", texture);
    material.setTexture("NormalMap", textureBump);
    material.setBoolean("UseMaterialColors",true);    
    material.setColor("Specular",ColorRGBA.White);
    material.setColor("Diffuse",ColorRGBA.White);
    material.setFloat("Shininess", 2f);
    
    chao.setMaterial(material);
    chao.setLocalTranslation(-30,-1.5f,-40);
    chao.rotate(0, -0.25f, 0);
    chao.setShadowMode( ShadowMode.CastAndReceive );	    
    
    modelTest = assetManager.loadModel("Models/Calices/Calice1.obj");
    Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
    mat.setTexture("DiffuseMap", assetManager.loadTexture("Texturas/CaliceOuro1.jpg"));
    mat.setTexture("NormalMap", assetManager.loadTexture("Texturas/CaliceOuro1_Normal.jpg"));
    mat.setBoolean("UseMaterialColors",true);    
    mat.setColor("Specular",ColorRGBA.White);
    mat.setColor("Diffuse",ColorRGBA.White);
    mat.setFloat("Shininess", 0.05f);
    modelTest.setMaterial(mat);
    modelTest.scale(0.1f);
    modelTest.setLocalTranslation(-7, -1.4f, -6.0f);
    modelTest.setShadowMode(ShadowMode.CastAndReceive);
    TangentBinormalGenerator.generate(modelTest);
            
    PssmShadowRenderer pssmRenderer = new PssmShadowRenderer(assetManager, 1024, 3);
    //pssmRenderer.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
    pssmRenderer.setDirection(new Vector3f(2.5f, -0.7f, 0.8846725f).normalizeLocal());
    pssmRenderer.setLambda(0.55f);
    pssmRenderer.setShadowIntensity(0.5f);
    pssmRenderer.setEdgesThickness(10);
    //pssmRenderer.setCompareMode(CompareMode.Software);
    pssmRenderer.setFilterMode(FilterMode.PCF8);
    
    app.getViewPort().addProcessor(pssmRenderer);
    
    rootNode.addLight(getAmbientLight(ColorRGBA.White, 5.3f));
    rootNode.addLight(getDirectionalLight(new Vector3f(1,0,-2), ColorRGBA.White));
    rootNode.addLight(getPointLight(ColorRGBA.Yellow, new Vector3f(5.9894657f, -0.22122034f, -16.879707f), 30f));
    rootNode.addLight(getPointLight(ColorRGBA.Red, new Vector3f(7.5594657f, -0.22122034f, -4.879707f), 50f));
    rootNode.attachChild(chao);
    rootNode.attachChild(modelTest);
    
    
}

public PointLight getPointLight(ColorRGBA color, Vector3f position, float radius){

	PointLight pointLight = new PointLight();
	pointLight.setColor(color);
    pointLight.setRadius(radius);
    pointLight.setPosition(position);
    
    return pointLight;
}
public AmbientLight getAmbientLight(ColorRGBA color, float colorMult){
	
	AmbientLight ambientLight = new AmbientLight();
	ambientLight.setColor(color.mult(colorMult));
    
    return ambientLight;
}
public AmbientLight getAmbientLight(ColorRGBA color){
	
	AmbientLight ambientLight = new AmbientLight();
	ambientLight.setColor(color);
    
    return ambientLight;
}

public DirectionalLight getDirectionalLight(Vector3f direction, ColorRGBA color){
	
	DirectionalLight directionalLight = new DirectionalLight();
	directionalLight.setDirection(direction.normalizeLocal());
	directionalLight.setColor(color);
    
    return directionalLight;
}

private void addMenuItem(){
	menuItemNovoJogo 	= new Picture("Novo Jogo");//NEW GAME
	menuItemOpcoes 		= new Picture("Opcoes");//OPTIONS
	menuItemSair		= new Picture("Sair");//EXIT
	
	menuItemNovoJogo.setImage(assetManager, "Imagens/MenuItem/novoJogo.png", true);
	menuItemOpcoes.setImage(assetManager, "Imagens/MenuItem/opcoes.png", true);
	menuItemSair.setImage(assetManager, "Imagens/MenuItem/sair.png", true);
	
	menuItemNovoJogo.move(0, 0, -1);
	menuItemOpcoes.move(0,0,-1);
	menuItemSair.move(0, 0, -1);
	
	menuItemNovoJogo.setPosition(50, 150);
	menuItemOpcoes.setPosition(50, 80);
	menuItemSair.setPosition(50, 10);
	
	menuItemNovoJogo.setWidth(220);
	menuItemOpcoes.setWidth(220);
	menuItemSair.setWidth(220);
	
	menuItemNovoJogo.setHeight(50);
	menuItemOpcoes.setHeight(50);
	menuItemSair.setHeight(50);
	
	menuSelect = new Picture("menuSelect");
	menuSelect.move(0, 0, -1);
	menuSelect.setPosition(30, 145);
	menuSelect.setWidth(300);
	menuSelect.setHeight(60);
	menuSelect.setImage(assetManager, "Imagens/menuSelect.png", true);
	
    rootNode.attachChild(menuItemNovoJogo);
	rootNode.attachChild(menuItemOpcoes);
	rootNode.attachChild(menuItemSair);
	rootNode.attachChild(menuSelect);
    
}
    
private ActionListener actionListener = new ActionListener() {
	public void onAction(String name, boolean isPressed, float tpf) {
		
		//move the image in the option:
                   /*
                          NEW GAME
                          OPTIONS
                          EXIT
                  */
		if (name.equals("up") && !isPressed) {
			menuItemSelected--;				
		}else if (name.equals("down") && !isPressed) {
			menuItemSelected++;
		}else if (name.equals("enter") && !isPressed) {				
			if(menuItemSelected == 1){
				System.out.println("Selecionado 'Novo Jogo'");
			} else if(menuItemSelected == 2){
				System.out.println("Selecionado 'Opcoes'");
			}else if(menuItemSelected == 3){
				app.destroy();
				System.exit(0);
			}				
		}
		//verificar para não selecionar acima nem abaixo das opções
		if(menuItemSelected > 3){
			menuItemSelected = 1;
		}
		if(menuItemSelected < 1){
			menuItemSelected = 3;
		}
		
		//update menu	
		switch (menuItemSelected) {
			case 1: {
				menuSelect.setPosition(30, 145);
				break;
			}
			case 2:{
				menuSelect.setPosition(30, 75);
				break;
			}
			case 3:{
				menuSelect.setPosition(30, 5);
				break;
			}
		}
		
	}
};

@Override
public void update(float tpf) {
    
	super.update(tpf);
	
    rootNode.updateLogicalState(tpf);
    rootNode.updateGeometricState();
    
}

}

[/java]

is this, the error is in MainMenu :frowning:

You modify the scene from a separate thread. You can’t do that.

Funny because if you’d instead done it from the app state’s cleanup method then it would have been done on the render thread for you.

BTW, is there any reason you extend Application instead of SimpleApplication. (Note: these classes are kind of misnamed. SimpleApplication should be BaseApplication and Application should be DoNotExtendThisUnlessYouWantToWriteEverythingYourself.) It’s not a problem in this case but could get you into trouble in the future and there are no longer any great reasons not to extend SimpleApplication unless you are headless.

I didn’t read the rest of the code once I got to the bad threaded scene graph use… since that causes exactly the reported error. And by the way, was the very first response that you got.

Hint: it’s in detachThisAndAttachMenu().

Hi psspeed, is in the detachThisAndAttachMenu ?
but I have no idea :frowning:
i can not detach the LogoInicial (this)
app.getStateManager().detach(this);
app.getViewPort().detachScene(rootNode);

??

app.getViewPort().detachScene(rootNode);

…is modifying the scene graph from another thread. You can ONLY modify the scene graph from the render thread.

This:
app.getViewPort().attachScene(mainMenu.getRootNode());

is also modifying the scene graph from another thread.

a) threading and scene graphs seems like it might be too hard for you. b) there was no reason to use a thread for this in the first place. You could have just counted time on update and done the change after some time passed.

It’s kind of frustrating because you said:

@sradkiller said: I dont using other thread :(

And I believed you and wasted a whole bunch of posts going on a wild goose chase. Because clearly you DO have a thread right here:
new ThreadTime().start();

…it’s even in the name.