Thread.sleep and spatials updates

I have some code that does something like this:

for(int i = 0; i<3; i++) {
                spatial.setLocalTranslation(x, 0.5f, y);
                spatial.rotate(0, 2 * FastMath.HALF_PI, 0);
                Thread.sleep(1000);
}  

I would expect it to show the first update spatial, then sleep 1s, then the second spatial, etc…
Instead what I observe is that it sleeps 3s and then shows up all the spatials at the same time.
Can somebody explain to me why is happening?

There’s rendering code that needs to run in order for anything to be rendered to the display. If that code is run on the thread that does the rendering, then the sleep() call is preventing the rendering code from running.

But if the rendering code is on the same thread, then everything should be sequential right? Unless we can imagine spatial.rotate as an asynchronous call that will be rendered later by the same thread, at which point I have some questions:

  1. Why is the rendering happening in the same thread?
  2. Where exactly is the code rendering all the spatials invoked?
  3. Is it normal practice to fork a new thread to move your spatials?

First I assume that you have the basic application and put your code into the update method.

  1. Rendering happens in the same thread because update is run by the render thread.
  2. Rendering happens between update calls.
  3. No, usually you don’t use Thread.sleep to progress game logic. You check the elapsed time each update/frame and trigger your code when the desired time has elapsed. You can run your game logic in a separate thread, but spatials should be moved in the render thread.

Hi, welcome to your first game and probably your first UI-related project. And welcome to the very deep end of that giant ocean.

As others have stated, games work on the concept that each update you prepare a new frame of graphics.
update
render
update
render
update
render

If you want to do something over some period of time then you have to track that and do pieces of it on each update. You want to keep updates as small as possible to not break frame.

This is the way game engines work. This is also similar to the way that any user interface works (though those will be interaction based instead of frame based).

TLDR; You need to think of everything in terms of small slices of time call a frame.

Edit: you should also do the jMonkeyEngine tutorials on the wiki. Especially as a brand new game-dev beginner, these will be extremely vital.

You can use tpf (time per frame) in the update() loop to iterate another variable, and then test whether one second has passed.

private float time = 0f;

...

update(float tpf) {
        time += tpf;      
  
        if (time >= 1.0f) {
            //code here is executed every 1 second.
            time = 0f;
        }
}

This is better than sleeping the thread, because it means that you can use the thread for other things, whilst waiting for 1 second to pass.

True, but in this case it’s not so much that you “can” use the thread for other things at the same time as it is that you’ve just completely locked jME out of doing any of its many critical duties as long as the thread is asleep.

1 Like

More than that actually, if you leave it blocked for longer the OS might think the app hung and will show it as not responding, possibly crashing it if you click on it.

This is why loading needs to be done on a separate thread and why the asset manager is thread safe.

1 Like

Are there any examples that show how to perform asset loading on a separate thread?

Once I used to load it in a separate thread like below and it was working fine.

/**
     * This is a multi thread model loader which loads model in loader
     * thread and after loading it attaches model to scene in render
     * thread.
     */
    private class ModelLoader implements Supplier<Spatial> {

        private String modelName;
        private Node modelNode;

        /**
         * @param modelName This is model name used when loading model
         * by assetManager
         * @param modelNode This is the node where model will be attached
         * to after loading
         */
        public ModelLoader(String modelName, Node modelNode) {
            this.modelName = modelName;
            this.modelNode = modelNode;
        }

        @Override
        public Spatial get() {
           
            ModelKey modelKey = new ModelKey(modelName.concat(AssetUtils.getEnabledModelFormat().getType()));
            Spatial model = getApplication().getAssetManager().loadModel(modelKey);
            
            try {
                int numberOfChildren = getApplication().enqueue(new Callable<Integer>() {
                    @Override
                    public Integer call() throws Exception {
                        return modelNode.attachChild(model);
                    }
                }).get();
            } catch (InterruptedException ex) {
                log.error("Problem occurred while loading model" + modelKey.getName(), ex);
            } 
            return modelNode;
        }
    }

and then using a CompletableFuture to load it and attach it to resultModelNode .

CompletableFuture future = CompletableFuture.supplyAsync(new ModelLoader(path, resultModelNode));

1 Like

And here’s a less complicated version:

boolean loaded = false; // don't run main loop until this is true

Geometry model;

@Override
public void simpleInitApp() {

	//attach loading screen to gui

	Thread load = new Thread("Loading"){
		@Override public void run(){
			try{
				
				//async load assets
				
				model = (Geometry)app.getAssetManager().loadModel("model.obj");
				TangentBinormalGenerator.generate(model); 
				
				app.enqueue(() -> {
				
					//rendering thread again
					//remove loading screen, setup viewports and attach processors to them, unblock input, launch main menu, etc.
				
					rootNode.attachChild(model);
					
					loaded = true;
				});
			}catch (Exception e) {
				e.printStackTrace();
			}
		}
	};
	load.setPriority(Thread.MAX_PRIORITY);
	load.setDaemon(true); //otherwise the thread will keep your game running if it crashes during loading
	load.start();
}
1 Like

There’s also an in-depth example that uses Nifty to show a loading screen while the scene is loaded on another thread https://wiki.jmonkeyengine.org/jme3/advanced/loading_screen.html#using-multithreading

1 Like

Thank you all for all the replies, I think my main problem is that all my logic is inside here in SimpleApplication

     @Override
     public void simpleInitApp() {}

and I actually never used the update function, I’ll study some tutorials and change the overall architecture.