Well if you want to know where it happens:
Drop in replacment for Node, with no real runtime cost additionally, as the asserts must be actvated, and are removed at class loading time if not enabled. (The subrenderer was a bitch to fix, because I got the exact same error, but it was a internal jme state problem, not a threading one apparently.) However noticing that it was actually not my error took quite some time, wich is why this class was created.
package de.visiongamestudios.client.jme.subrenderer;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import com.jme3.asset.AssetKey;
import com.jme3.bounding.BoundingVolume;
import com.jme3.collision.Collidable;
import com.jme3.collision.CollisionResults;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.light.Light;
import com.jme3.light.LightList;
import com.jme3.material.Material;
import com.jme3.math.Matrix3f;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.Camera.FrustumIntersect;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Node;
import com.jme3.scene.SceneGraphVisitor;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
public class ThreadSaveNode extends Node {
private Thread thread;
public ThreadSaveNode(final String string, final Thread openGLThread) {
super(string);
this.thread = openGLThread;
}
@Override
public int getQuantity() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getQuantity();
}
@Override
protected void setTransformRefresh() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setTransformRefresh();
}
@Override
protected void setLightListRefresh() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLightListRefresh();
}
@Override
protected void updateWorldBound() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.updateWorldBound();
}
@Override
protected void setParent(final Node parent) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setParent(parent);
}
@Override
public void updateLogicalState(final float tpf) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.updateLogicalState(tpf);
}
@Override
public void updateGeometricState() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.updateGeometricState();
}
@Override
public int getTriangleCount() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getTriangleCount();
}
@Override
public int getVertexCount() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getVertexCount();
}
@Override
public int attachChild(final Spatial child) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.attachChild(child);
}
@Override
public int attachChildAt(final Spatial child, final int index) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.attachChildAt(child, index);
}
@Override
public int detachChild(final Spatial child) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.detachChild(child);
}
@Override
public int detachChildNamed(final String childName) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.detachChildNamed(childName);
}
@Override
public Spatial detachChildAt(final int index) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.detachChildAt(index);
}
@Override
public void detachAllChildren() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.detachAllChildren();
}
@Override
public int getChildIndex(final Spatial sp) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getChildIndex(sp);
}
@Override
public void swapChildren(final int index1, final int index2) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.swapChildren(index1, index2);
}
@Override
public Spatial getChild(final int i) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getChild(i);
}
@Override
public Spatial getChild(final String name) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getChild(name);
}
@Override
public boolean hasChild(final Spatial spat) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.hasChild(spat);
}
@Override
public List<Spatial> getChildren() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getChildren();
}
@Override
public void setMaterial(final Material mat) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setMaterial(mat);
}
@Override
public void setLodLevel(final int lod) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLodLevel(lod);
}
@Override
public int collideWith(final Collidable other, final CollisionResults results) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.collideWith(other, results);
}
@Override
public <T extends Spatial> List<T> descendantMatches(final Class<T> spatialSubclass, final String nameRegex) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.descendantMatches(spatialSubclass, nameRegex);
}
@Override
public <T extends Spatial> List<T> descendantMatches(final Class<T> spatialSubclass) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.descendantMatches(spatialSubclass);
}
@Override
public <T extends Spatial> List<T> descendantMatches(final String nameRegex) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.descendantMatches(nameRegex);
}
@Override
public Node clone(final boolean cloneMaterials) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.clone(cloneMaterials);
}
@Override
public Spatial deepClone() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.deepClone();
}
@Override
public void write(final JmeExporter e) throws IOException {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.write(e);
}
@Override
public void read(final JmeImporter e) throws IOException {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.read(e);
}
@Override
public void setModelBound(final BoundingVolume modelBound) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setModelBound(modelBound);
}
@Override
public void updateModelBound() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.updateModelBound();
}
@Override
public void depthFirstTraversal(final SceneGraphVisitor visitor) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.depthFirstTraversal(visitor);
}
@Override
protected void breadthFirstTraversal(final SceneGraphVisitor visitor, final Queue<Spatial> queue) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.breadthFirstTraversal(visitor, queue);
}
@Override
public void setKey(final AssetKey key) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setKey(key);
}
@Override
public AssetKey getKey() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getKey();
}
@Override
protected void setRequiresUpdates(final boolean f) {
if (this.thread != null) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
}
super.setRequiresUpdates(f);
}
@Override
protected void setBoundRefresh() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setBoundRefresh();
}
@Override
public void forceRefresh(final boolean transforms, final boolean bounds, final boolean lights) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.forceRefresh(transforms, bounds, lights);
}
@Override
public boolean checkCulling(final Camera cam) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.checkCulling(cam);
}
@Override
public void setName(final String name) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setName(name);
}
@Override
public String getName() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getName();
}
@Override
public LightList getLocalLightList() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLocalLightList();
}
@Override
public LightList getWorldLightList() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getWorldLightList();
}
@Override
public Quaternion getWorldRotation() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getWorldRotation();
}
@Override
public Vector3f getWorldTranslation() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getWorldTranslation();
}
@Override
public Vector3f getWorldScale() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getWorldScale();
}
@Override
public Transform getWorldTransform() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getWorldTransform();
}
@Override
public void rotateUpTo(final Vector3f newUp) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.rotateUpTo(newUp);
}
@Override
public void lookAt(final Vector3f position, final Vector3f upVector) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.lookAt(position, upVector);
}
@Override
protected void updateWorldLightList() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.updateWorldLightList();
}
@Override
protected void updateWorldTransforms() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.updateWorldTransforms();
}
@Override
public void runControlRender(final RenderManager rm, final ViewPort vp) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.runControlRender(rm, vp);
}
@Override
public void addControl(final Control control) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.addControl(control);
}
@Override
public void removeControl(final Class<? extends Control> controlType) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.removeControl(controlType);
}
@Override
public boolean removeControl(final Control control) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.removeControl(control);
}
@Override
public <T extends Control> T getControl(final Class<T> controlType) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getControl(controlType);
}
@Override
public Control getControl(final int index) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getControl(index);
}
@Override
public int getNumControls() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getNumControls();
}
@Override
public Vector3f localToWorld(final Vector3f in, final Vector3f store) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.localToWorld(in, store);
}
@Override
public Vector3f worldToLocal(final Vector3f in, final Vector3f store) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.worldToLocal(in, store);
}
@Override
public Node getParent() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getParent();
}
@Override
public boolean removeFromParent() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.removeFromParent();
}
@Override
public boolean hasAncestor(final Node ancestor) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.hasAncestor(ancestor);
}
@Override
public Quaternion getLocalRotation() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLocalRotation();
}
@Override
public void setLocalRotation(final Matrix3f rotation) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLocalRotation(rotation);
}
@Override
public void setLocalRotation(final Quaternion quaternion) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLocalRotation(quaternion);
}
@Override
public Vector3f getLocalScale() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLocalScale();
}
@Override
public void setLocalScale(final float localScale) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLocalScale(localScale);
}
@Override
public void setLocalScale(final float x, final float y, final float z) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLocalScale(x, y, z);
}
@Override
public void setLocalScale(final Vector3f localScale) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLocalScale(localScale);
}
@Override
public Vector3f getLocalTranslation() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLocalTranslation();
}
@Override
public void setLocalTranslation(final Vector3f localTranslation) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLocalTranslation(localTranslation);
}
@Override
public void setLocalTranslation(final float x, final float y, final float z) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLocalTranslation(x, y, z);
}
@Override
public void setLocalTransform(final Transform t) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLocalTransform(t);
}
@Override
public Transform getLocalTransform() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLocalTransform();
}
@Override
public void addLight(final Light light) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.addLight(light);
}
@Override
public void removeLight(final Light light) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.removeLight(light);
}
@Override
public Spatial move(final float x, final float y, final float z) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.move(x, y, z);
}
@Override
public Spatial move(final Vector3f offset) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.move(offset);
}
@Override
public Spatial scale(final float s) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.scale(s);
}
@Override
public Spatial scale(final float x, final float y, final float z) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.scale(x, y, z);
}
@Override
public Spatial rotate(final Quaternion rot) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.rotate(rot);
}
@Override
public Spatial rotate(final float xAngle, final float yAngle, final float zAngle) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.rotate(xAngle, yAngle, zAngle);
}
@Override
public Spatial center() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.center();
}
@Override
public CullHint getCullHint() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getCullHint();
}
@Override
public BatchHint getBatchHint() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getBatchHint();
}
@Override
public Bucket getQueueBucket() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getQueueBucket();
}
@Override
public ShadowMode getShadowMode() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getShadowMode();
}
@Override
public Spatial clone() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.clone();
}
@Override
public void setUserData(final String key, final Object data) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setUserData(key, data);
}
@Override
public <T> T getUserData(final String key) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getUserData(key);
}
@Override
public Collection<String> getUserDataKeys() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getUserDataKeys();
}
@Override
public boolean matches(final Class<? extends Spatial> spatialSubclass, final String nameRegex) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.matches(spatialSubclass, nameRegex);
}
@Override
public BoundingVolume getWorldBound() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getWorldBound();
}
@Override
public void setCullHint(final CullHint hint) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setCullHint(hint);
}
@Override
public void setBatchHint(final BatchHint hint) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setBatchHint(hint);
}
@Override
public CullHint getLocalCullHint() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLocalCullHint();
}
@Override
public BatchHint getLocalBatchHint() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLocalBatchHint();
}
@Override
public Bucket getLocalQueueBucket() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLocalQueueBucket();
}
@Override
public ShadowMode getLocalShadowMode() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLocalShadowMode();
}
@Override
public FrustumIntersect getLastFrustumIntersection() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLastFrustumIntersection();
}
@Override
public void setLastFrustumIntersection(final FrustumIntersect intersects) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setLastFrustumIntersection(intersects);
}
@Override
public Matrix4f getLocalToWorldMatrix(final Matrix4f store) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.getLocalToWorldMatrix(store);
}
@Override
public void setQueueBucket(final Bucket queueBucket) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setQueueBucket(queueBucket);
}
@Override
public void setShadowMode(final ShadowMode shadowMode) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.setShadowMode(shadowMode);
}
@Override
public String toString() {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
return super.toString();
}
@Override
public void breadthFirstTraversal(final SceneGraphVisitor visitor) {
assert Thread.currentThread() == this.thread : "Expected " + this.thread.getName() + " but got " + Thread.currentThread();
super.breadthFirstTraversal(visitor);
}
}