We have an existing Augmented Reality application that uses another java graphics library but for various reasons (predominantly better support and many more features) are trying to switch over to jMonkeyEngine.
The existing application downloads data in separate threads and builds 3D objects already. I’m now trying to feed these on-the-fly objects into the engine but cannot get them to appear. (For example, a set of nine textured tiles based around the user’s position).
Previously we had a message handler set-up so the ‘other’ threads could post a message with a list of objects the graphics engine, and then onDrawFrame would check a Stack to see if there were any new objects to add or old ones to remove.
Can anyone suggest how this process could be re-worked for jme3 - I’ve checked the multi-threading Tutorial, but that seems to be based on the engine requesting a process to run on another thread, compared to another thread notifying the engine with new (or old) data.
I believe that what you’re trying to do is pretty much exactly the same in JME. @pspeed suggested to use a ConcurrentLinkedQueue in jayfella’s terrain project and I think it’s a good choice here too. Basically put elements in it, as you already did in your ‘Stack’, and in the update method of jme basically do the same as you did in onDrawFrame (ConcurrentLinkedQueue#poll) and add it to the scene there.
After a few days work I’ve reworked the application update process so that all other threads add or remove the features to be processed directly in to a ConcurrentLinkedQueue.
Every couple of seconds the update() loop within a new AppState checks the relevant Que and issues a new Callable to process the objects in to Spatials and attach them to the root.
This seems to be working very well, so thanks for the pointers!
I don’t think polling your own queue is a good idea, why don’t you simply enqueue the attach() call like outlined in our multithreading docs? Simple asynchronous threading. Basically like this:
[java]
// running on separate thread:
final Spatial spat = assetManager.loadModel(“blah”);
doProcessThatTakesTime(spat);
app.enqueue(new Callable(){
public Void call(){
rootNode.attachChild(spat);
return null;
}
});
[/java]
This way you do the processing and loading on the other thread and the enqueued call with the attach command will be executed in the next frame without any polling from your side.
I did start with something along those lines, but started encountering issues when manipulating the Spatial’s attributes, such as setting CullHint and Material. (Nearly all the Spatial’s are built manually from other data).
Originally I couldn’t even get the new Spatials to display at all, which I put down to their creation in another thread, but that was about 100 hours of coding ago, so could’ve been something else!
I followed most of the code from a couple of the threading tutorials / source; The skeleton result looks like this;
[java]
public static final ConcurrentLinkedQueue<TileLoadRequest> newBuildRequests = new ConcurrentLinkedQueue<TileLoadRequest>();
@Override
public void update(float tpf) {
if (futureState==null && transformECEF!=null) {
if(newBuildRequests.isEmpty()==false) {
TOGGLE_STATE = PROC_ADD_FEAT;
futureState = threadpool.submit( buildObjects );
}
} else if (futureState!=null) {
if (futureState.isDone() ) {
// Completed task
futureState = null;
}
} else if (futureState.isCancelled()) {
Log.d(LOG_TAG, "Didn't correctly process items");
futureState = null;
}
// Still processing..
}
[/java]
With the Callable;
[java]
private Callable<Integer[]> buildObjects = new Callable<Integer[]>(){
public Integer[] call() throws Exception {
final TransformGeomFilter to4326 = transform4326.clone();
final TransformGeomFilter toECEF = transformECEF.clone();
//Read or write data from the scene graph -- via the execution queue:
Future<Integer[]> futureTask = app.enqueue(new Callable<Integer[]>() {
public Integer[] call() throws Exception {
TileLoadRequest tlr = newBuildRequests.poll();
if (tlr==null) return new Integer[]{PROC_ADD_FEAT, 0};
int numAdded = 0;
Spatial featureRoot = null;
/* Cycle and Build the Geometry and node objects here... */
// Add to the scene and physics space
featureRoot.updateModelBound();
app.getRootNode().attachChild( featureRoot );
app.getPhysicsSpace().addAll(featureRoot);
numAdded++;
return new Integer[]{PROC_ADD_FEAT, numAdded };
}
});
return futureTask.get();
}
If you make a task and then immediately call get() you can just as well not use the task at all… Asynchronous threading means that you enqueue the calls without waiting for them to finish, just by issuing the enqueue commands in the correct order you make sure the tasks are executed in the correct order, without really having to care about when exactly they get executed.
[java]
//private Callable<Integer[]> buildObjects = new Callable<Integer[]>(){
// public Integer[] call() throws Exception {
// final TransformGeomFilter to4326 = transform4326.clone();
// final TransformGeomFilter toECEF = transformECEF.clone();
//Read or write data from the scene graph — via the execution queue:
Future<Integer[]> buildObjects = app.enqueue(new Callable<Integer[]>() {
public Integer[] call() throws Exception {
final TransformGeomFilter to4326 = transform4326.clone();
final TransformGeomFilter toECEF = transformECEF.clone();
TileLoadRequest tlr = newBuildRequests.poll();
if (tlr==null) return new Integer[]{PROC_ADD_FEAT, 0};
int numAdded = 0;
Spatial featureRoot = null;
/* Cycle and Build the Geometry and node objects here… */
// Add to the scene and physics space
featureRoot.updateModelBound();
app.getRootNode().attachChild( featureRoot );
app.getPhysicsSpace().addAll(featureRoot);
numAdded++;
return new Integer[]{PROC_ADD_FEAT, numAdded };
}
});
Do the creation on another thread. You will need another thread to do that.
Then app.enqueue the ATTACHMENT of the spatial.
All but your first example(that was normen’s example)All of your examples seem to have this in the app.enqueue:
/* Cycle and Build the Geometry and node objects here… */
…which implies that you are doing that on the rendering thread… which seems counter productive to doing things on another thread.
Okay, so what we’re saying is that the buildObjects callable should/ can be submitted to the threadpool which generates the new spatial’s adding them in to a list which is then passed to app.enqueue
I’ve amended to the following…
[java]
private Callable<Integer[]> buildObjects = new Callable<Integer[]>(){
public Integer[] call() throws Exception {
final TransformGeomFilter to4326 = transform4326.clone();
final TransformGeomFilter toECEF = transformECEF.clone();
final List<Spatial> allSpatials = new ArrayList<Spatial>();
for (Thing thing : thingsToDo) {
/* Do Spatial creation here adding in to allSpatials */
allSpatials.add( newThing );
}
//Read or write data from the scene graph -- via app.enqueue
app.enqueue(new Callable<Integer[]>() {
public Integer[] call() throws Exception {
// Add to the scene and physics space
int numAdded = 0;
for (Spatial s : allSpatials) {
app.getRootNode().attachChild( s );
app.getPhysicsSpace().addAll(s);
numAdded++;
}
return new Integer[]{PROC_ADD_FEAT, numAdded};
}
});
return new Integer[]{PROC_ADD_FEAT, allSpatials.size() };
}
};
[/java]
This does run although given the amount of test features there isn’t currently a perceptible difference in speed, but may solve a random thread issue i’m having…!
I’ve left the submission to the threadpool as; futureState = threadpool.submit( buildObjects );
I was trying to re-work some of the code from jayfella’s terrain project as suggested, but obviously got confused by what submits to what and how it gets returned.
Just a quick update to say I’ve re-worked the threading again to simplify things further.
I’ve reverted to a simple Asynch thread that runs permanently with build requests being put on a Stack.
After all the building it calls app.enque() to attach the new Spatials.
I’m sure this is exactly what you were getting at @pspeed but I got rather confused by the threadpool and future tasks in the example I was originally pointed at!
@MikeR said:
Just a quick update to say I've re-worked the threading again to simplify things further.
I've reverted to a simple Asynch thread that runs permanently with build requests being put on a Stack.
After all the building it calls app.enque() to attach the new Spatials.
I’m sure this is exactly what you were getting at @pspeed but I got rather confused by the threadpool and future tasks in the example I was originally pointed at!