BatchNode: How to update changed scenes correctly?

Hello everyone,

The situation
I have a bunch of Nodes with children. From time to time, some of those children get replaced by another child. All those Nodes with children are gathered in one BatchNode.

My solution
Since I don’t need to update the visuals immediately, I thought rebatching the thing every 1000ms or so is enough. I placed this code in the update loop:
[java]
if (System.currentTimeMillis() - lastBatched > 1000) {
lastBatched = System.currentTimeMillis();
// secondScene.detachChildAt(0);
secondScene.batch();
}
[/java]

The problem
As the code stands right now, the replacement does not take place.
However, when uncomment this detachment line, it works perfectly.

So the question is: How do I correctly rebatch a BatchNode?

Hi,

I am also using the Batchnode for bunch of objects where I have to add/remove some of them from time to time.
At first I am using the removeFromParent of the child element. Second I am “rebatch” the BatchNode just if something changes.
This works very good for me.

Are you sure that the correct child is detached with the detachChildAt?

Hm, there might be a misunderstanding.
I constantly add and remove children of children somewhere else in the code, but the batching does not show these changes.

If I call detachChildAt (I don’t care what child is detached), not only is the one child detached, but all the other changes are now visible too.

My goal is to have those other changes visible, without having to detach some child of the BatchNode prior to batching.

ok, i see…strange!

How do you add/remove the children in your code?
Maybe there are some update flags in batchnode implementation and they are used in methode detachChildAt. Maybe you can debug and watch the batchnode if some other changes happen (so the list of child should change but maybe something else?) when you call detachChildAt.

I also immediately call the .batch() after detach the child and it works.
So imagine that the the common update of the batchnode change the update flags in the batchnode. So if you call the .batch() some update loops later the flags are resecced? Maybe you can als check this also.

This is the control which takes care of replacing the children:
[java]
@Override
protected void controlUpdate(float tpf) {
if (cam==null) {return;}
float distance = spatial.getWorldTranslation().distance(cam.getLocation());
int level = distances.length - 1;
while (distance < distances[level]) {
level–;
}
if (level != oldLevel) {
oldLevel = level;
((Node) spatial).detachChildAt(0);
((Node) spatial).attachChild(nodes[level]);
}
}
[/java]

Yeah, I thought about the flags too, so meanwhile I investigated a bit.

BatchNode:
[java]
@Override
public Spatial detachChildAt(int index) {
Spatial s = super.detachChildAt(index);
if (s instanceof Node) {
unbatchSubGraph(s);
}
return s;
}
[/java]

So this apparently unbatchSubGraph(s) is called:
[java]
/**
* recursively visit the subgraph and unbatch geometries
* @param s
*/
private void unbatchSubGraph(Spatial s) {
if (s instanceof Node) {
for (Spatial sp : ((Node) s).getChildren()) {
unbatchSubGraph(sp);
}
} else if (s instanceof Geometry) {
Geometry g = (Geometry) s;
if (g.isBatched()) {
g.unBatch();
}
}
}
[/java]

Apparently the Geometry is “unBatched”. Aha! Of course this does not happen when I remove a chlid of child of the batchNode!

Let’s see what g.unBatch() actually does:
[java]
/**
* unBatch this geometry.
*/
protected void unBatch() {
this.startIndex = 0;
//once the geometry is removed from the screnegraph the batchNode needs to be rebatched.
if (batchNode != null) {
this.batchNode.setNeedsFullRebatch(true);
this.batchNode = null;
}
setCullHint(CullHint.Dynamic);
}
[/java]

Would you look at that!
So here batchNode.setNeedsFullRebatch(true) is getting called!

I think this is the core of the problem.
It looks like BatchNode can only hande detachment of direct children.

The problem is that none of those methods are public, so I can’t force a full rebatch. :expressionless:

So I think I’m forced to attach and detach an empty Node in order to force the rebatch.
This is no major problem, but I think the author of BatchNode should be informed of this (if it’s not already known).

@drothindev Thanks for your help and suggestions!

@nehon since you are the author in javadoc…is this a known issue? (attach and detach of non direct children in batchnode does not effect a full rebatch)

Not that I know.
I’m gonna test this, thanks for the report.

@nehon said: I'm gonna test this, thanks for the report.

Thanks.

Sadly, there is one other problem.
At the moment I have 16 nodes an every 50ms one of them is rebatched.
Now the game crahes from time to time seemingly randomly, throwing this exception:
[java]
java.lang.IndexOutOfBoundsException
at java.nio.Buffer.checkBounds(Buffer.java:559)
at java.nio.DirectFloatBufferU.get(DirectFloatBufferU.java:259)
at com.jme3.scene.BatchNode.doTransforms(BatchNode.java:611)
at com.jme3.scene.BatchNode.updateSubBatch(BatchNode.java:172)
at com.jme3.scene.Geometry.updateWorldTransforms(Geometry.java:286)
at com.jme3.scene.Spatial.updateGeometricState(Spatial.java:713)
at com.jme3.scene.Node.updateGeometricState(Node.java:175)
at com.jme3.scene.Node.updateGeometricState(Node.java:175)
at com.jme3.scene.Node.updateGeometricState(Node.java:175)
at com.jme3.scene.BatchNode.updateGeometricState(BatchNode.java:120)
at com.jme3.scene.Node.updateGeometricState(Node.java:175)
at com.jme3.scene.Node.updateGeometricState(Node.java:175)
at com.jme3.scene.Node.updateGeometricState(Node.java:175)
at com.jme3.scene.Node.updateGeometricState(Node.java:175)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:247)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:185)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:228)
at java.lang.Thread.run(Thread.java:744)
[/java]

Do you have any ideas?
I don’t know what other information could be useful to solve the problem, so feel free to ask. :slight_smile:

Hard to tell from there, but random crashes are pretty often due to threading issues.
Do you use several threads?

The only custom thread I use is loading the scene in the beginning.
The crashes, however, happen at some point in the game.

I don’t know what else to tell you…

Do you rebatch them in order or randam? I mean rebatch the first, the second,…, the 16th and then the first again? The result is that every 800ms a Node is getting rebatch? is that correct?
Is the problem more often when you resuce the 50ms to 25ms? What happen if you increase the time to 100ms?

Maybe a rebatch take to long and you call the .batch() function bevor the last executin is done?

I rebatch them in order, your assumption is correct.

It’s hard to judge what happens when I change the interval, because the might occur after fifteen minutes of ingame time, so testing takes long.
However I tested it extensively with 2ms and 100ms and did not find any relation to the frequency of crashes.

I do not think the .batch function can take too long since it’s blocking, isn’t it?
In another old Thread I heard that problems might occur when calling the batch function multiple times in one update loop. This is not the case.

Just to be sure I didn’t miss something, you can have a look at my code:
[java]
public class TileControl extends AbstractControl {
private long lastUpdate;
private int nextIndex;
private boolean rebatch;
private Camera cam;

public TileControl(Camera cam) {
	this.cam = cam;
	lastUpdate = System.currentTimeMillis();
	rebatch = true;
	nextIndex = 0;
}

@Override
protected void controlUpdate(float tpf) {
	if (System.currentTimeMillis() - lastUpdate &gt; 25) {
		lastUpdate = System.currentTimeMillis();
		rebatch = !rebatch;
		
		if (rebatch) { //rebatch
			rebatch(nextIndex);
			nextIndex++;
			if (nextIndex &gt;= ((Node) spatial).getQuantity()) {nextIndex = 0;}
		} else { // replacing objects if necessary
			Node tile = (Node) ((Node) spatial).getChild(nextIndex);
			for (Spatial child : tile.getChildren()) {
				if (child instanceof Node) {
					ReplacementControl control = ((Node) child).getChild(0).getControl(ReplacementControl.class);
					if (control != null) {control.update(cam.getLocation());}
				}
			}
		}
	}
}

private void rebatch(int tileIndex) {
	BatchNode tile = (BatchNode) ((Node) spatial).getChild(tileIndex);
	if (!tile.getChildren().isEmpty()) {
		tile.attachChild(tile.detachChildAt(0)); // forcing rebatch manually
		tile.batch();
	}
}

@Override
protected void controlRender(RenderManager rm, ViewPort vp) {}

@Override
public Control cloneForSpatial(Spatial spatial) {
	TileControl clone = new TileControl(cam);
	return clone;
}

}
[/java]

The TileControl is attached to a Node that always contains 16 BatchNodes. Those BatchNodes contain the game objects.

Weird! Maybe you can use an Exception Breakpoint and debug it. Look at the batchnode in this moment und if all 16 nodes still exist. Check also there children.
I am not sure about the attach detach part. Check the condition of the child list. Maybe the list is working strange!

Could you also try it with just 1 Batchnode (instead of 16). Maybe it is easier to debug since there are less nodes to check.

I assume u are attaching the same model several times? Are your models animated? Since not animates models share the VertexBuffer…maybe this could be a problem?! Try to use deepClones of your model (loading time will increase) and check if this also happens.

This are just a some ideas, I do not really have a clue why this happens.

1 Like

I am going through your suggestions at the moment. It takes a while, because after I changed the code I can’t immediately tell if it’s fixed.

@drothindev said: I am not sure about the attach detach part. Check the condition of the child list. Maybe the list is working strange!
I don't know what you mean with "Check the conditition", but you're right about being careful. The code detaches the first child and attaches it at the end, so it is at least a possibility that at some point the batch geometry gets detached and reattached. I don't know if that's bad, but it could cause those wrong indexes.

I’ll see if the bug comes up again through the rest of the day.

@drothindev said: This are just a some ideas, I do not really have a clue why this happens.
Your ideas are much appreciated, I was running out of them :)

By “condtition” I mean excatly what you mentioned (that the geometry from first possition is attatched at last possition). Ist the length of the list the same? Are a lot of null elements in the list. (shouldn’t be a problem in general…) Maybe the batchnode does not really check that the first objects is gone and there is another object on first position (with different geometry and different buffer size).

Could you als try to forche rebatching with just an emtpy new Node()? Just add the node (should be at the end of the list) and then detach the node. So no index will be change at all…

It just crashed again.
So
[java]
tile.attachChild(tile.detachChildAt(0));
[/java]
was not the problem, tough I feel like the crashes are less often now… strange.

I’ll continue testing.

Edit: Forget it, it crashes just as much as it did before.

So it this attaching and detaching right before batching does not have any effect at all. The game crashes when I don’t do this as well:
[java]
private void rebatch(int tileIndex) {
BatchNode tile = (BatchNode) ((Node) spatial).getChild(tileIndex);
if (!tile.getChildren().isEmpty()) {
// tile.attachChildAt(tile.detachChildAt(0), 0);
tile.batch();
}
}
[/java]

You really shouldn’t use the spatial indices for your own purposes, keep your own list instead of trying to get children by index. You’re kind of trying to cram your own code logic into the scenegraph which is never a good idea.

@drothindev said: I assume u are attaching the same model several times? Are your models animated? Since not animates models share the VertexBuffer...maybe this could be a problem?! Try to use deepClones of your model (loading time will increase) and check if this also happens.

Just tried using deepClones. Game crashed regardless unfortunately.