A more precise exception report about modifying the scene from another thread:

Obs.: I am improving the explanation of this OP based on the discussions below.

Coming from this topic (wich is a completely different context that actually has an answer here),
I managed to get a more precise error report concerning such exception thrown at Spatial.checkCulling().

Yes, I understand I must learn how to avoid that exception by coding properly. But a more precise exception information would have been very helpful years ago when it first happened to me, and I simply made my project single threaded (but it is multi-threaded again now).

To just say the problem happened at “Root Node” left me clueless for several days (may be months).

So basically, patching Node.java with this code, that will only be executed whenever the exception is ready to be thrown, makes things much easier to me, and I think will help other people too:

public ArrayList<Spatial> recursiveFindModifications(ArrayList<Spatial> asptIn){
	if(asptIn==null)asptIn = new ArrayList<Spatial>();
	for(Spatial sptChild : getChildren()){
		if(sptChild.refreshFlags!=0){
			asptIn.add(sptChild);
		}
		if(sptChild instanceof Node){
			((Node) sptChild).recursiveFindModifications(asptIn);
		}
	}
	return asptIn;
}

@Override
public boolean checkCulling(Camera cam) {
  if (refreshFlags != 0) {
  	ArrayList<Spatial> aspt = recursiveFindModifications(null);
  	String strSpatialNames = getName();
  	for(Spatial spt : aspt){
  		strSpatialNames+=","+spt.getName();
  	}
    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(s): " + strSpatialNames);
  }
  return super.checkCulling(cam);
}

and here as a patch

--- ./src/com/jme3/scene/Node.java
+++ ./src/com/jme3/scene/Node.java
@@ -38,8 +38,10 @@
 import com.jme3.export.JmeImporter;
 import com.jme3.export.Savable;
 import com.jme3.material.Material;
+import com.jme3.renderer.Camera;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -231,7 +233,36 @@
             s.updateLogicalState(tpf);
         }
     }
-
+    
+    public ArrayList<Spatial> recursiveFindModifications(ArrayList<Spatial> asptIn){
+    	if(asptIn==null)asptIn = new ArrayList<Spatial>();
+    	for(Spatial sptChild : getChildren()){
+    		if(sptChild.refreshFlags!=0){
+    			asptIn.add(sptChild);
+    		}
+    		if(sptChild instanceof Node){
+    			((Node) sptChild).recursiveFindModifications(asptIn);
+    		}
+    	}
+    	return asptIn;
+    }
+    
+    @Override
+    public boolean checkCulling(Camera cam) {
+      if (refreshFlags != 0) {
+      	ArrayList<Spatial> aspt = recursiveFindModifications(null);
+      	String strSpatialNames = getName();
+      	for(Spatial spt : aspt){
+      		strSpatialNames+=","+spt.getName();
+      	}
+        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(s): " + strSpatialNames);
+      }
+      return super.checkCulling(cam);
+    }
+    
     @Override
     public void updateGeometricState(){
         if (refreshFlags == 0) {

I believe it can be better implemented, so if you can improve it, post back please.
It will show not only the root node, but all other nodes and spatials (names) that got modified improperly. That is the important/precise clue to we pin-point the problem in our project.

This problem can also happen if you use someone else’s library incorrectly, what would make things even more difficult by not knowing enough about how that library works.

So basically, to make it actually useful, you have to set your Spatial names with high quality, like putting the name of a class, unique identifiers, your fancy types etc. Mine looks like this: “MySpecialSpatial.java:123:Hero2”. So now, the error message is like “Root Node,MySpecialSpatial.java:123:Hero2”.

1 Like

Hey it’s your project and if you want to do this it’s your choice but this code would blow up my game both on the client and on the server. Why would anyone want to make their game single threaded anyhow??

@zissis, no no!!!

My project is Multithreaded!

I had to make it singlethreaded (in the past) because things were breaking without I being able to understand why they broke. The problem was the exception speaking only about “Root Node”.

So again, my project is now multithread. That problem rarely happens (like I added a library that caused it because I misused the library). This is a newbie helper, but even after you learned a lot, this may help on finding and fixing this kind of problem. I mean, initially I gaveup on JME because of that, but I kept coming back as java is excellent, and JME is the way to go in 3D and gaming world :slight_smile:

You don’t understand. For example, I use that call on my server … server doesn’t render anything … so if I added your code it would break my server. As I stated in your other post, your approach it wrong. Just learn how the scene graph and rendering pipeline work and you will be fine and not need these hacks. There are many use cases where it’s of to make calls to the scene graph outside the JME thread … in server code for example.

But this is not a hack. The recursiveFindModifications() is only called if an exception is ready to be thrown at overriden checkCulling().

This topic is not an extension of the other topic, it is a completely different approach, that actually simply expands the exception “information”. The result was so excellent here that I decided to post it! As I promptly knew what caused the problem!

Now I need to know if it has flaws that could prevent that extra information of being of high quality.

Do NOT override checkCulling() … I use that on my server from many threads. Once again, if the user knows when they can or can not call the scene graph this is not needed. But in the end you are free to do what you want in your code. Just please don’t propagate your pattern to others.

Could you please explain why checkCulling() cannot be overriden the way I did?
Please, re-read the code I exposed, I cant see a single problem on it, so if you could precisely point it out, I would be grateful. Because if you are really right, it will help me too on avoiding propagating such possible mistake (?) on my own project.

Well in my case for example, the server controls everything but does not use the JME rendering thread obviously … so I have a multi threaded environment that uses the single scene graph and ALL the client connected cameras by many threads carefully orchestrated because I also do server side scene culling. So your check for refreshFlags in checkCulling will break my server. What you have done is bound the refreshFlags to the checkCulling method and effectively changed the design of the JME spatial.

Oh and on top of that, you code does not prevent anyone from changing tons of other things outside the JME thread.

That is exactly what I am trying to explain. This topic is not about preventing anything! I think I traumatized you at the other topic, sorry :), but I liked very much your explanations concerning the other topic context. This topic context is completely DIFFERENT, it is about more information whenever that exception happens.

Actually, if you check Spatial.checkCulling() it is already bound to refreshFlags and the exception, as I copied the very code and error message from there!

I see now. So you added more debugging to check culling. I still don’t understand how it will help identify where in someone’s code another thread made a change to the scene graph? Especially since some scene graph changes inadvertently make many changes to many spatials.

That’s the “cat leap” part:

You simply have to set the name of your spatials with high quality.

The name of mine contains the class name holding/connecting to it + the internal ID + the type of the spatial in my database.

With all that information, I know exactly where to find it. If for any reason it is still too complex, further breakpoints will help me detect what point of the code caused the trouble, but now, I know exactly where to place these breakpoints!

So again, this is the comparison of the previous error message: “Root Node”
with the new: “Root Node,MySpecialSpatial.java:123:Hero2”.

Before I was clueless for months, then days, then hours, then I manage to multi-thread again. Now, with this patch, I know what to do in less then a minute!

Unless, this precise spatial naming is not completely under control in the way people are implementing their projects, now that would really make this approach limited. Because of that, I asked for more tips. May be other things than the name could help in these cases.

Will not work in my game since I heavily use geometry instancing optimizations for example.

I guess they are created and destroyed dynamically, and they do not exist for a long time so are not important to be saved to reload later, so you don’t care on heavily tracking them, right?

So, could you suggest some improvement to that code? Will be greatly appreciated! :slight_smile:

almost every geometry in my space game is instanced … which means that a single geometry instance in some cases lives in hundreds of places in my scene graph at any one time. Since it’s the same geometry instance of course it will only have one name. This is a new feature in 3.1 and extremely important in scenes with thousands of independent geometries like in space games of large forests etc.

1 Like

Good to know it is on JME already!
I actually am just learning more about Geometry Instancing.

By what I read there, it seems we could modify each instance individually with potential risk of generating that exception. But… there must have something that uniquely identifies each instance. That information could be added to the default spatial name error report. Do you know what could that be? may be a geometry instance index?

EDIT: I need some sleep, gn!

Look at TestInstanceNode in the 3.1 tests

1 Like

As I saw, TestInstanceNode creates 3600 geometry objects that share only 2 meshes.
It also produces 12 Instanced Geometries.

I managed to name each geometry indifidually (based on their position).
I also saw that all InstancedGeometry where independent objects, they were named against the mesh mainly, but certainly we can make them have unique names too!

EDIT: therefore, each geometry has its userdata, that we can link to our project classes, what will help on creating a precise match to find the culprit code for the exception.

Instead of having to come up with an understandable naming convention for 10000 dynamic objects in my scene in hopes that I will use this information in a stack trace to fins where I modified the scene graph outside the JME thread, I would rather just write my code properly and just use the provided enqueue method and never have to go through all the naming and tracking issues that I must implement throughout my design in order to read that exception that should never be thrown. If you ever see that exception it means you don’t know how to properly use the enqueue method. Just my two cents worth.

3 Likes

The Queue
I actually have my own queue, and I have been using it to exactly avoid that exception.

My queue allow me to choose if my code will be run threaded or at main thread (at the update per frame method).
Actually I learned about enqueue after my queue was fully functional, so I dont really know how it works yet.

Veteran Newbied
Then, one day, I used an external library improperly, I didnt know how to use it yet, and suddenly that obnoxious error exception happened and I was clueless again, because I was implementing several different things by several hours without testing. My only hope was to focus on what was new (external) to my code, so I fixed the library usage and the problem was gone.

So I implemented this patch and simulated the error again, and the culprit spatial name poped like magic, and instantly I would have known what would be necessary to be fixed…

Lawful Newbie
Now, imagine a newbie, doing that, multithreaded and receiving that message, trying to implement several fancy ideas and test them and being prevented by that limited exception message. I was like that several years ago. I abbandoned JME, tryied UnrealEngine, tried many other engines and couldnt like them at all. So my only choice was to come back :smile:

I just believe, if some newbie give up, that it is not by that exception reason.