"State was changed after ..." Error although using app.enqueue

Hello,

following my previous thread about pausing the game using my own rootnode,
I got this error I cant resolve

SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
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: myRootNode

it happens when user clicks the mouse (dropping an object on the ground)
the whole logic is in a custom appstate (PlayerAppState) initialized as follow

 @Override
    public void simpleInitApp() 
    {
        //-------------------------------------------------------------------------------------------------------------------------------
        ScreenshotAppState screenShotState = new ScreenshotAppState("screenshots"+File.separator);
        this.stateManager.attach(screenShotState);
        //-------------------------------------------------------------------------------------------------------------------------------
        myRootNode= new Node("myRootNode");
        viewPort.attachScene(myRootNode);
        //-------------------------------------------------------------------------------------------------------------------------------
        bulletAppState = new BulletAppState();
        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(DEBUG_PHYSICS);

        //-------------------------------------------------------------------------------------------------------------------------------
        Factory.Init(myRootNode, sceneDataBundle, assetManager, bulletAppState);
        gameState= new GameAppState(myRootNode,sceneDataBundle);                       stateManager.attach(gameState);
        SceneParser.Parse("assets/models/"+"level.xml",myRootNode,cam,this,sceneDataBundle,assetManager,bulletAppState,this,DEBUG_NAVMESH);
        
        if(sceneDataBundle.cameraNodes.size()>0)
        {
            if(sceneDataBundle.navMeshes.size()>0)
            {
                Node cameraNode = sceneDataBundle.cameraNodes.get(0);
                
                Vector3f camPos =cameraNode.getLocalTranslation();
                NavigationMesh naviMesh = sceneDataBundle.FindCurrentNavMesh(camPos);
                NavigationCell startCell = naviMesh.FindClosestCell(cameraNode.getLocalTranslation());
                startCell.MapVectorHeightToCell(camPos);
                
                playerAppState = new PlayerAppState(camPos,cam,myRootNode,sceneDataBundle);    stateManager.attach(playerAppState);
                computerAppState = new ComputerAppState(myRootNode,sceneDataBundle);           stateManager.attach(computerAppState);
                hudAppState=new HudAppState(myRootNode,sceneDataBundle);                       stateManager.attach(hudAppState);
                scenarioAppState= new GameScenarioAppState(myRootNode,sceneDataBundle);        stateManager.attach(scenarioAppState);
                myRootNodeAppState = new MyRootNodeAppState(myRootNode);                       stateManager.attach(myRootNodeAppState);
...

so here is the flow when I click (onAction is inside PlayerAppState)

  public void onAction(String name, boolean value, float tpf)
    {
        if (name.equals("LeftClick")) 
        {
            if(value)
            {
                
                    if(playerController.isCarrying())
                    {
                        //------------------------------------------------------- drop
                        Vector3f pos=playerController.GetDroppingPosition();
                        if(pos!=null)
                        {
                            Node node =playerController.getCurrentProp();
                            PropController controller = node.getUserData("Controller");
                            rootNode.attachChild(node); 
                            node.setLocalTranslation(pos);
                            playerController.PopProp();
                            controller.AfterDroppedOnGround();
                            app.getStateManager().getState(ScenarioAppState.class).DropObject((PropController) node.getUserData("Controller"));
                        }
                    }

        }

so a call to DropObject is causing the issue

void DropObject(final PropController controller) 
    {
        if(listener!=null)
        {
            app.enqueue(new Callable() 
            {
                public Object call() throws Exception 
                {
                    ZoneController zone=sceneDataBundle.FindCollisionZone(controller.getBoundingSphereGhostControl().getOverlappingObjects());
                    listener.onDroppedObject(controller,zone,ScenarioAppState.this);
                    return null;
                }
            });
        }
    }

the listener point to this : (the culprit beeing .CreateContainer call)

public void onDroppedObject(PropController controller,ZoneController zone,ScenarioAppState scenario) 
    {
        switch(state)
        {
            case WAIT_FOR_YELLOW_BERRIES:
            {
                if(currentZone!=null)
                    if(zone==currentZone)
                        if(controller instanceof YellowBerriesController)
                        {
                            scenario.ShowDialogText(characterController,THANKS_GIVE_POTION);
                            ContainerInterface potionController = scenario.CreateContainer(currentZone.getPosition(),GlassContainerController.class,ManaPotionController.class);
                            scenario.FocusProp(potionController);
                        }
            }break;
        }
    }

wich leads here :

public static AIController Instanciate(Vector3f pos,SceneDataBundle sceneDataBundle,Node rootNode,AssetManager assetManager,BulletAppState bulletAppState)
    {
        GlassContainerController controller=null;
        Spatial model = LoadAsset("potion.j3o",assetManager);
        if(model!=null)
        {
            Node glassContainer= new Node("GlassContainer");
            
            rootNode.attachChild(glassContainer); 
        }
        return controller;
    }

and the gui update

void ShowDialogText(CharacterController ctl,String dialogID)
    {
        String characterName=ctl.getName();
        //System.out.println("dialog");
        HashMap<String, CharacterDialog> dialogs=characterDialogs.get(characterName);
        if(dialogs!=null)
        {
            CharacterDialog characterDialog=dialogs.get(dialogID);
            if(characterDialog!=null)
            {
                String finalDialog=ReplaceTextVars(characterDialog.dialog,characterName);
                app.getStateManager().getState(HudAppState.class).setDialogText(finalDialog);
            }
            else
                System.out.println("Error, cannot find dialog '"+dialogID+"'");
        }
        else
            System.out.println("Error, cannot find character dialogs for '"+characterName+"'");

        lastCharacter=ctl;
        lastDialogID=dialogID; 
        app.getStateManager().getState(HudAppState.class).ShowDialogButton(true);
        eraseDialogTextOnResume=true;
        WaitForPlayer();
    }

here is my own rootNode appstate

public class MyRootNodeAppState extends AbstractAppState
{
    private final Node myRootNode;

    public MyRootNodeAppState(Node myRootNode) {
        this.myRootNode=myRootNode;
    }

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

I tried to put the 2 update lines in render(), but it gives the same error (also it has no tpf value…)
also, in debug mode, the whole thing does not cause the error directly, it is once I go back to the application that I get the error

can someone check this out ?

thanks

I recreated the problem you described, however the error does not appear.

public class TestCallableAttach extends SimpleApplication {

    private RootNodeAppState state = new RootNodeAppState();
    
    private final ActionListener actionListener = new ActionListener(){
        @Override
        public void onAction(String name, boolean pressed, float tpf){
            enqueue(new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                    Box b = new Box(1, 1, 1);
                    Geometry geom = new Geometry("Box", b);
                    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
                    geom.setMaterial(mat);
                    state.getRootNode().attachChild(geom);
                    return null;
                }
            });
        }
    };
    
    public static void main(String[] args) {
        TestCallableAttach app = new TestCallableAttach();
        app.start();
    }
    
    @Override
    public void simpleInitApp() {
        inputManager.addMapping("My Action", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addListener(actionListener, "My Action");
        stateManager.attach(state);
    }
}

I realized, my call to draw some character dialog also called

app.getStateManager().getState(MyRootNodeAppState.class).setEnabled(false);

so it seems pausing the MyRootNodeAppState in an enqueue is causing the issue
but I dont get it, why pausing an appstate would give such an error

here I simplified the code that causes the error

void DropObject(final PropController controller) { if(listener!=null) { app.enqueue(new Callable() { public Object call() throws Exception { app.getStateManager().getState(MyRootNodeAppState.class).setEnabled(false); return null; } }); } }

actualy, if I enqueu an appstate pause in onAction()
I get that error

    public void onAction(String name, boolean value, float tpf)
    {
            app.enqueue(new Callable() 
            {
                public Object call() throws Exception 
                {
                    app.getStateManager().getState(MyRootNodeAppState.class).setEnabled(false);
                    return null;
                }
            });
    }

I dont get it

You can’t just pause the RootNodeAppState as it will stop updating its node then - but its still rendered.

I can’t see your root node app state so I can’t comment. Maybe you don’t disable the ViewPort? I have no idea since I’m only wildly stabbing at code I cannot see.

…but yeah, if you don’t disable the viewport then it will still try to render the stuff but now you aren’t updating it anymore. And whether you enqueue or not really shouldn’t matter for that error.

I especialy pasted the code (above) so people would’nt say I did’nt, lol, here it is (again) :

as per my previous thread, nehon told me it should be alright to pause the appstate that updates my own rootnode

but I need myRootNode to be visible while the game is paused
and I cant disable the viewport, cos I was planning to have another “rootnode” that would keep on beeing updated

while the game is paused, an informative text is displayed to the player :
I want to have some spatials/controls not beeing updated (enemies animations/controls)
and I want to have other anims to keep running (animated plot indicators)

Then you can’t do things like you are doing. If a node is being rendered then you need to update it… which is 1000000000x different than pausing it.

yes but updating it makes its animcontrols updated too wich I want to avoid
actually it’s the only thing I want to do , is to pause all enemies anims while game is paused
wich is, to me, a regular feature in a video games, so if nehon’s suggestion does not work, how do I do that ?

You can’t move the nodes if you stop updating them, some way you invalidate the update state of the node. If you actually stop doing anything to the node then you can in theory also stop updating it but apparently you don’t.

I think you can also updateGeometricState() without updateLogicalState()… the second of which is what runs the controls.

gonna give it a try :slight_smile:

[edit]
it works (so far), I hope it wont cause any other unexpected issues

public class MyRootNodeAppState extends AbstractAppState
{
    private final Node myRootNode;
    private boolean logicalStateUpdateEnabled=true;

    public MyRootNodeAppState(Node myRootNode) {
        this.myRootNode=myRootNode;
    }

    @Override
    public void update(float tpf) {
        if(logicalStateUpdateEnabled)
            myRootNode.updateLogicalState(tpf);
        myRootNode.updateGeometricState();
    }

    public void setLogicalStateUpdateEnabled(boolean enabled)
    {
        logicalStateUpdateEnabled=enabled;
    }

}