How do I use a local transformation method for a spacial in other thread then the render thread?

Hello guys, i`m new in JMonkey.I want to call the move(Vector3f) method for a spacial in other thread the reader thread, but i get an Illegal state exception witch look like this:



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: Root Node



here is the code too…Thank you



— Game Class ----



public class MyGame extends SimpleApplication implements AnalogListener, ActionListener

{

public static AssetManager assetManager;

public static Node rootNode;

private static float tpf;

private static long threadSleepTime;



private Player player;

public static void main(String args[])

{

MyGame game=new MyGame();

game.start();

}

@Override

public void simpleInitApp()

{

assetManager=super.assetManager; //largesc vizibilitatea la assetManager si rootNode

rootNode=super.rootNode;

assetManager.registerLocator(“assets”, FileLocator.class.getName());



player=new Player();



addLight();

setCamPoz();

flyCam.setMoveSpeed(10);



simpleBgPlan();

}

@Override

public void simpleUpdate(float tpf)

{

this.tpf=tpf;

threadSleepTime=(long)(tpf*1000);

cam.lookAt(player.getLoc(), Vector3f.UNIT_Y);

// System.out.println(cam.getLocation().x+" “+cam.getLocation().y+” "+cam.getLocation().z);

}



}



— Sprite Class —



public abstract class Sprite implements Runnable

{

protected Vector3f loc;

protected Spatial model;

private Thread th;

protected float speed;

public Vector3f speedVect;



public Sprite()

{

loc=new Vector3f();

speed=0;

speedVect=new Vector3f();

}

@Override

public void run()

{

while(true)

{

syncLocWithModelLoc();

move();

runIndividualMethod();

try {

Thread.sleep(MyGame.getThreadSleepTime());

} catch (InterruptedException e) {

// TODO: handle exception

}

}

}

protected abstract void runIndividualMethod();

protected void startTh()

{

if(th==null)

th=new Thread(this);

if(th.isAlive()==false)

th.start();

}

public void stopTh()

{

if(th.isAlive())

th.stop();

if(th!=null)

th=null;

}

private void syncLocWithModelLoc()

{

if(model!=null)

loc=model.getLocalTranslation();

else

loc=new Vector3f();

}

private void move()

{

move(speedVect);

}

public void move(Vector3f distVector)

{

model.move(distVector); // ??? !!! HERE IS THE PROBLEM !!!

}



}



---- Player class ----

public class Player extends Sprite implements Ship

{

public Player()

{

model=MyGame.assetManager.loadModel(“Models/nava.obj”);

MyGame.rootNode.attachChild(model);

startTh();

}





}



Pls help me, and thank you again :slight_smile:

I personally introduced a new Vector3f called nextPos



in the main update loop you adjust your spatial to the ‘nextPos’. From the other tread you can also modify the vector. I don’t know if this is a good solution but it works for me

This question comes up all the time… deserves a FAQ entry, really



You need to attach an UpdateControl to your scene node which gets processed automatically the next time the update loop is called. Do this in your method that is in another thread:

[java]

scene.addControl(new UpdateControl());

scene.getControl(UpdateControl.class).enqueue(new Callable() {

public Object call() throws Exception {

// Do something, like manipulating vectors…

}

});

[/java]

Except don’t add the control from another thread. Make sure it was added on the render thread.

@zzuegg said:
I don't know if this is a good solution but it works for me

Its not a good solution, it only gets rid of the error. In your case it might be one thread has written only the x and y components while the other reads the x, y and z components.. Or worse, it only wrote the x and the first byte of the y component, in which case your values are completely off.

A single float set is guaranteed to be atomic from a threading perspective… it just may be a really long time before other threads can see it. But you won’t see just one byte of the float change.



On multicore systems, non-volatile and unsynchronized data access from multiple threads may be really inconsistent. If you never cause a memory barrier it could be possible for one thread to never see the changes another thread makes as they stay cached local to the thread. It’s just really hard to go through Java life without causing memory barriers (every synchronize and volatile access causes them)… and data gets flushed anyway sometimes.



If you are going to go with a shared Vector3f approach then at least put it in an AtomicReference and set a new vector every time (don’t just set the values on the old one).

@pspeed said:
A single float set is guaranteed to be atomic from a threading perspective... it just may be a really long time before other threads can see it. But you won't see just one byte of the float change.

Heh, the things you learn. That was my exact question the first time I was asking a question here and back then I was also thinking all primitives are atomic..
@normen said:
Heh, the things you learn. That was my exact question the first time I was asking a question here and back then I was also thinking all primitives are atomic..


And they are... but being atomic and being thread safe are two different things.

If primitives weren't at least atomic then it would be possible to thoroughly corrupt the Java heap as even object references are just a primitive... and if you partially updated one object reference then seriously bad things could happen.

On a two core system... if you have one thread updating a shared float in a busy loop and another thread reading it in a busy loop... it's possible that the second thread will rarely or never see the changes of the first thread as they will all have their own local cache view that never gets updated.

Worse yet, this can be completely unpredictable. So in the Vector3f case from that other thread, it's possible that you see z update and not x, y... which can be kind of counter-intuitive. In fact, if you set z a lot then it's possible that you see lots of changes for z without ever seeing x,y change from their original values. And it gets worse still if that other thread is calculating some new x,y values from z. The reason we don't see more of this is because of all of the incidental memory barriers forcing cache resets.

One need only read some old papers on why "double checked locking" didn't work in Java to really make your head spin. (In new Java, I think the tightening of what 'volatile' means has made some double-checked locking scenarios work and I do some scary volatile tricks in some of my own shared data code. :))

let me have 啊 look ,
:Make sure you do not modify the scene from another thread!

how to use multithread in jme3?

@ravenocean said: let me have 啊 look , :Make sure you do not modify the scene from another thread!

how to use multithread in jme3?


Did you try to enter “multithread” in one of the multiple search boxes for the manual already?

Actually it would be cool if you added an entire simple example. It’s really hard to understand all features of multithreading. @Dodikles can you make a very very very simple example? Just to move a box from another thread for example. You will save many people!

For example you can modify this class: http://code.google.com/p/jme-simple-examples/source/browse/JMESimpleExamples/src/com/basics/MoveNode.java

Or take your own.

Thanks.

Multithreading is kind of like juggling chain saws… you really have to know what you’re doing to avoid being totally dangerous.

This is the first link in the search that Normen suggests:
https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:multithreading?s[]=multithreading

If this is not enough to understand multithreading in JME then best to keep your app single threaded for as long as possible. Cut and pasting an example app is not going to help.

OK, i get you, thanks. I should learn it.

Threading is the single easiest way to write something that looks fine but breaks horribly in unpredictable ways. Race conditions, deadlocks, caching, contention, etc, etc.

If you don’t understand threading then just enqueue everything onto the render thread and it may be a bit less efficient but at least it will work.

I like the chainsaw analogy above, but to extend it slightly:
Chainsaws are great for cutting down trees.
Unfortunately if you don’t know what you are doing the tree lands on your house.
And then the chainsaw flies out of the stump and cuts off your leg.
And then one of the birds flying out of the branches drops a present in your eye while you are lying there bleeding to death.

Before using a chainsaw:
Read the manual.
Get some training.
And cut down a few small things before you start on the big tree right next to your house.

@normen said: Did you try to enter "multithread" in one of the multiple search boxes for the manual already?

哦,i misunderstanding the multithread in the jme,the scense operating is all in the “Update” function 里面

so i try that and make it happen :

package com.ravenocean.jme3.app;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;

import javax.xml.crypto.Data;

import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.UpdateControl;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Line;
import com.jme3.scene.shape.Sphere;
import com.jme3.system.AppSettings;
import com.jme3.texture.Texture;
import com.jme3.util.TangentBinormalGenerator;

/***
*

*/
public class AllJmeHelloWorld extends SimpleApplication
{
class TriThread extends Thread
{
private Vector3f absTrans = Vector3f.ZERO;
int R = 5;
int i = 0;
Vector3f targtvsf = Vector3f.ZERO;

	public TriThread()
	{
		super();

	}

	@Override
	public void run()
	{
		// TODO Auto-generated method stub
		while (true)
		{
			try
			{
				Thread.sleep(10);
			}
			catch (InterruptedException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			targtvsf = new Vector3f((float) (R * Math.cos(i * Math.PI / 180)), (float) (R * Math.sin(i * Math.PI
							/ 180)), 90f);
			Vector3f worldCenter2 = Vector3f.ZERO;
			absTrans = targtvsf.subtract(worldCenter2);
			i++;
		}
	}

	public Vector3f getAbsTrans()
	{
		return absTrans;
	}
}

/**
 * @param args
 */
public static void main(String[] args)
{
	AppSettings tAppSettings = new AppSettings(true);
	tAppSettings.setResolution(800, 600);
	tAppSettings.setFullscreen(false);
	tAppSettings.setFrameRate(24);

	//
	AllJmeHelloWorld tGameApplication = new AllJmeHelloWorld();
	tGameApplication.setShowSettings(false);
	tGameApplication.setSettings(tAppSettings);
	tGameApplication.start();

}

protected Geometry tGeometry;

TriThread tri;

public AllJmeHelloWorld()
{

}

@Override
public void simpleInitApp()
{
	flyCam.setEnabled(true); //Activate the flyby cam
	flyCam.setMoveSpeed(50); //Control the move speed
	flyCam.setRotationSpeed(10); //Control the rotation speed
	flyCam.setDragToRotate(true);
	inputManager.addMapping("w", new KeyTrigger(KeyInput.KEY_W));
	inputManager.addMapping("s", new KeyTrigger(KeyInput.KEY_S));
	inputManager.addMapping("a", new KeyTrigger(KeyInput.KEY_A));
	inputManager.addMapping("d", new KeyTrigger(KeyInput.KEY_D));
	//
	Material mat_stl = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
	guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
	float fsize = 0.3f;
	for (int i = -100; i < 100; i++)
	{
		BitmapText xi = new BitmapText(guiFont, false);
		xi.setColor(ColorRGBA.Red);
		xi.setSize(fsize);
		xi.setText(String.valueOf(i));
		xi.setLocalTranslation(new Vector3f(i, 0f, 0f));
		rootNode.attachChild(xi);

	}
	Line linex = new Line(new Vector3f(-100f, 0f, 0f), new Vector3f(100f, 0f, 0f));
	//linex.updatePoints(new Vector3f(0f, 0f, 0f), new Vector3f(10f, 10f, 10f));
	linex.setLineWidth(2);
	Geometry geomx = new Geometry("x", linex);
	geomx.setMaterial(mat_stl);
	rootNode.attachChild(geomx);

	//
	for (int i = -100; i < 100; i++)
	{
		BitmapText yi = new BitmapText(guiFont, false);
		yi.setColor(ColorRGBA.Green);
		yi.setSize(fsize);
		yi.setText(String.valueOf(i));
		yi.setLocalTranslation(new Vector3f(0f, i, 0f));
		rootNode.attachChild(yi);

	}
	Line liney = new Line(new Vector3f(0f, -100f, 0f), new Vector3f(0f, 100f, 0f));
	//linex.updatePoints(new Vector3f(0f, 0f, 0f), new Vector3f(10f, 10f, 10f));
	liney.setLineWidth(2);
	Geometry geomy = new Geometry("y", liney);
	geomy.setMaterial(mat_stl);
	rootNode.attachChild(geomy);
	//
	for (int i = -100; i < 100; i++)
	{

		BitmapText zi = new BitmapText(guiFont, false);
		zi.setSize(fsize);
		zi.setColor(ColorRGBA.Blue);
		zi.setText(String.valueOf(i));
		zi.setLocalTranslation(new Vector3f(0f, 0f, i));

		//	zi.set.setLocalTranslation(110, 0, i);

		rootNode.attachChild(zi);

	}
	Line linez = new Line(new Vector3f(0f, 0f, -100f), new Vector3f(0f, 0f, 100f));
	//linex.updatePoints(new Vector3f(0f, 0f, 0f), new Vector3f(10f, 10f, 10f));
	linez.setLineWidth(2);
	Geometry geomz = new Geometry("z", linez);
	geomz.setMaterial(mat_stl);
	rootNode.attachChild(geomz);
	//
	Box b = new Box(new Vector3f(90f, 90f, 90f), 1, 1, 1);//new Box(Vector3f.ZERO, 1, 1, 1);
	Geometry geom = new Geometry("Box", b);
	Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
	mat.setColor("Color", ColorRGBA.Blue);
	geom.setMaterial(mat);
	rootNode.attachChild(geom);

	//------------------------------------------------

	// 在坐标(1,-1,1)处创建一个y色盒子
	Box box1 = new Box(new Vector3f(1, -1, 1), 1, 1, 1);
	Geometry blue = new Geometry("Box", box1);
	Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
	mat1.setColor("Color", ColorRGBA.Yellow);
	blue.setMaterial(mat1);

	// 在蓝色盒子上边坐标为(1,3,1)处垂直创建一个红色盒子
	Box box2 = new Box(new Vector3f(1, 3, 1), 1, 1, 1);
	Geometry red = new Geometry("Box", box2);
	Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
	mat2.setColor("Color", ColorRGBA.Red);
	red.setMaterial(mat2);

	// 在坐标为(0,0,0)处创建一个pivot节点并把它附加到根节点上
	Node pivot = new Node("pivot");
	rootNode.attachChild(pivot);

	// 附加两个盒子到pivot节点上!
	pivot.attachChild(blue);
	pivot.attachChild(red);
	// 旋转pivot节点: 两个盒子都被旋转了!

	//挪动位置
	Vector3f worldTrans = new Vector3f(90f, 90f, 90f);
	Vector3f worldCenter = pivot.getWorldBound().getCenter();
	Vector3f absTrans = worldTrans.subtract(worldCenter);
	pivot.setLocalTranslation(absTrans);

	//--------------------------------------------
	Node asset = new Node("asset");
	rootNode.attachChild(asset);

	Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
	Material mat_default = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
	teapot.setMaterial(mat_default);
	asset.attachChild(teapot);

	// 用test_data里的简单纹理创建一面墙
	Box box = new Box(Vector3f.ZERO, 2.5f, 2.5f, 1.0f);
	Spatial wall = new Geometry("Box", box);
	Material mat_brick = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
	mat_brick.setTexture("ColorMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
	wall.setMaterial(mat_brick);
	wall.setLocalTranslation(2.0f, -2.5f, 0.0f);
	asset.attachChild(wall);

	// 用默认的字体显示一行文本
	guiNode.detachAllChildren();
	guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
	BitmapText helloText = new BitmapText(guiFont, false);
	helloText.setSize(guiFont.getCharSet().getRenderedSize());
	helloText.setText("Hello World");
	helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);
	asset.attachChild(helloText);

	// 从test_data载入一个简单的模型(OgreXML + material + texture)
	Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
	ninja.scale(0.05f, 0.05f, 0.05f);
	ninja.rotate(0.0f, -3.0f, 0.0f);
	ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
	asset.attachChild(ninja);
	// 为了使这个模型可见你必须加一道光
	DirectionalLight sun = new DirectionalLight();
	sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
	asset.addLight(sun);

	//挪动位置
	Vector3f targtvsf = new Vector3f(-90f, -90f, -90f);
	Vector3f worldCenter2 = asset.getWorldBound().getCenter();
	Vector3f absTrans2 = targtvsf.subtract(worldCenter2);
	asset.setLocalTranslation(absTrans2);

	//-------------------------------------------------------------------
	tri = new TriThread();
	tri.start();
	Box bb = new Box(Vector3f.ZERO, 1, 1, 1);
	tGeometry = new Geometry("blue cube222", bb);
	Material matb = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
	matb.setColor("Color", ColorRGBA.Blue);
	tGeometry.setMaterial(matb);
	rootNode.attachChild(tGeometry);

	//-------------------------------------------------------------------
	Node materialnode = new Node("materialnode");
	rootNode.attachChild(materialnode);

	/** A simple textured cube -- in good MIP map quality. */
	Box boxshape1 = new Box(new Vector3f(-3f, 1.1f, 0f), 1f, 1f, 1f);
	Geometry cube = new Geometry("My Textured Box", boxshape1);
	Material mat_stl2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
	Texture tex_ml = assetManager.loadTexture("Interface/Logo/Monkey.jpg");
	mat_stl2.setTexture("ColorMap", tex_ml);
	cube.setMaterial(mat_stl2);
	materialnode.attachChild(cube);

	/** A translucent/transparent texture, similar to a window frame. */
	Box boxshape3 = new Box(new Vector3f(0f, 0f, 0f), 1f, 1f, 0.01f);
	Geometry window_frame = new Geometry("window frame", boxshape3);
	Material mat_tt = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
	mat_tt.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
	mat_tt.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); // activate transparency
	window_frame.setQueueBucket(Bucket.Transparent);
	window_frame.setMaterial(mat_tt);
	materialnode.attachChild(window_frame);

	/** A cube with its base color "leaking" through a partially transparent texture */
	Box boxshape4 = new Box(new Vector3f(3f, -1f, 0f), 1f, 1f, 1f);
	Geometry cube_leak = new Geometry("Leak-through color cube", boxshape4);
	Material mat_tl = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
	mat_tl.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
	mat_tl.setColor("Color", new ColorRGBA(1f, 0f, 1f, 1f)); // purple
	cube_leak.setMaterial(mat_tl);
	materialnode.attachChild(cube_leak);
	// cube_leak.setMaterial((Material) assetManager.loadAsset( "Materials/LeakThrough.j3m"));

	/** A bumpy rock with a shiny light effect. To make bumpy objects you must create a NormalMap. */
	Sphere rock = new Sphere(32, 32, 2f);
	Geometry shiny_rock = new Geometry("Shiny rock", rock);
	rock.setTextureMode(Sphere.TextureMode.Projected); // better quality on spheres
	TangentBinormalGenerator.generate(rock); // for lighting effect
	Material mat_lit = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
	mat_lit.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"));
	mat_lit.setTexture("NormalMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png"));
	mat_lit.setBoolean("UseMaterialColors", true);
	mat_lit.setColor("Specular", ColorRGBA.White);
	mat_lit.setColor("Diffuse", ColorRGBA.White);
	mat_lit.setFloat("Shininess", 5f); // [0,128]
	shiny_rock.setMaterial(mat_lit);
	shiny_rock.setLocalTranslation(0, 2, -2); // Move it a bit
	shiny_rock.rotate(1.6f, 0, 0); // Rotate it a bit
	materialnode.attachChild(shiny_rock);

	/** Must add a light to make the lit object visible! */
	DirectionalLight sun3 = new DirectionalLight();
	sun3.setDirection(new Vector3f(1, 0, -2).normalizeLocal());
	sun3.setColor(ColorRGBA.White);
	materialnode.addLight(sun3);

	Vector3f targtvsf3 = new Vector3f(80f, 80f, 80f);
	Vector3f worldCenter3 = materialnode.getWorldBound().getCenter();
	Vector3f absTrans3 = targtvsf.subtract(worldCenter3);
	materialnode.setLocalTranslation(absTrans3);

	//-------------------------------------------------------

}

boolean isRotating = false;

/* Use the main event loop to trigger repeating actions. */
@Override
public void simpleUpdate(float tpf)
{
	// make the player rotate:
	//player.rotate(0, 2 * tpf, 0);
	//		if (!isRotating)
	//		{
	//			Future future = executor.submit(findWay);
	//			Future future2 = executor.submit(rotate2);
	//			Future future3 = executor.submit(rotate3);
	//			isRotating = true;
	//		}
	if (tri != null && tGeometry != null)
	{
		tGeometry.setLocalTranslation(tri.getAbsTrans());
		System.out.println("t.getAbsTrans():" + tri.getAbsTrans().getX() + " - " + tri.getAbsTrans().getY() + " - "
						+ tri.getAbsTrans().getZ());
	}
	System.out.println("tpf:" + tpf);
}

}


it made box to rounding the z轴

:-o

@ravenocean said: 哦,i misunderstanding the multithread in the jme,the scense operating is all in the “Update” function 里面

so i try that and make it happen :

Sorry, please post your code in code tags, I can’t really make sense of it. This page probably contains the solution to your problem, the manual generally contains solutions to most beginner issues: https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:multithreading