[SOLVED] Jme3 Beginner: Using Threads…

Hello all,



I have this logic in my app:



1- Read trace file that has animation in Jme3 Cinematic format

2- From simpleApp execute the cinematic line by line and create the JME animation



However the trace file could have up to 300,000 lines (or events since each line is a specific event) meaning that doing so in one shot is not efficient as it would take me up to 5 minutes to load all the events and then start executing them. After the loading, the animation runs fine.



In order to improve the loading time, I was suggested to use threads (I have never used multithreading but I know the concept and read the advanced multi-threading tutorial). I was hoping if there’s a way to initiate the animation after reading the first chunk of the file and at the same time, using threads read the second chunk of it so I don’t have to wait for it. This shouldn’t be too hard as all the spatials are created at time 0, and the rest of the file just moves, rotates, and accelerate/decelerate those spatials.



So far my design is



Jme3Cinematics.java → contain the simpleApplication, createSpatials, rotateSpatials, etc



Adapter.java → reads trace file and parses line by line and according to what’s read calls the appropriate function from Jme3Cinematics



Question is how can I use multithreading to solve this issue? How can I divide the file into 2 parts (or probably more) and assign a thread for each?



Thanks

https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:multithreading

already checked it normen :wink: I just dk how to start with it.



For example my adapter.java has:



[java]/** Constructor: starts reading the file */

public Jme3Adapter( ) {



// This function has all the available motion commands to date

attachCommands(hm); // attaches each event to its relative function (rotate, accelerate, etc)



//reads trace file ( reads .txt, .3datf, or .atf line by line and parses it)

readTraceFile();

}[/java]



so how can I divide reading the trace file into multiple parts?

You would a) probably use an AppState instead of a Control and b) replace “Data data = myWorld.getData();” with “Jme3Adapter ad=new Jme3Adapter();” so its run in parallel. Apart from that the example explains what part of the code is being run on a different thread and how you can get the data back into your update loop afterwards…? Whats unclear?

but how to divide the tracefile to be read into different chunks?

“Data data = myWorld.getData();” with “Jme3Adapter ad=new Jme3Adapter();”
this will create a thread for the whole adapter and the whole file will be read on a separate thread. In that case, the animation will be empty at the beginning (or probably throw an error since no spatials are yet created).



I want to read say the first 5000 lines and start the animation, in the meantime, another 50,000 lines are read on one thread and another 50,000 on another , and so on…

… just add some options like JmeAdapter(startLine, endLine); ?

and wouldn’t you need the file to be read sequentially for animation? I mean if you have three threads going wouldn’t that mean the file or events or what ever get fired off out of order?

Yes, thats what the example also explains, it has to be self-contained, e.g. parse the data on the thread (self-contained), then send the events on the update loop. “Events” are nothing else than calls of methods, so the code in that method executes on whatever thread was sending the event, causing the trouble you outlined.

Not sure I fully understand the example, and that’s the problem :s. I already have the animation initiated and stops at 5000 lines let’s say with:

[java]

//Inside class Jme3Cinematics

// inside a big loop that reads trace file

call Jme3Adapter (0, 5000) // read only till time 5000



start thread

{

Jme3Adapter(5001, end);

}

[/java]



How can I create this pseudocode?



I have already created:



[java]/** Multithreading: This constructor creates a new executor with a core pool size of 4. */

ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(4);

[/java]



not sure how to proceed with creating the Future Objects and the Callables (I think in my case it’s only one callable which the the readFile() method). Also I am not sure how to relate my application with Appstates as I don’t have any (it’s a simple animation of a trace file, no AI , no other computational logic here)

Read it again and again and try to understand. If you don’t understand this basic way of threading you should rather live with the freeze frames for now if you don’t want to end in programming hell. Also you misunderstand AppStates. An AppState is simply kind of an extension to SimpleApplication, you have a update() and initialize() method just like in SimpleApplication but you have it in a separate class. You can also just have the executor variable and all this happen in the main application if you don’t want to add an AppState.

I think there’s much more going on in the example than what I need though. It should be simpler than that, right?



I got here so far:

[java]

// Start reading the trace file (now after everything is initialized)

// this is the main animation thread where only a small portion of the file is parsed

// the rest is parsed on a separate thread (below) to optimize loading time and performance

readFile(0 , 5000);



// Separate thread that reads the rest of the file concurrently with the execution

try

{

//If we have not started a callable yet, do so!

if(thread didn’t start) // how to check if a thread started?

{

//Thread starts!

executor.submit(readFile(5001, 200000); // HOW CAN I PUT THIS INTO A THREAD?

}



}

catch(Exception e){

e.printStackTrace();

}[/java]

No it should not be much simpler. The Callable you submit to the executor will be run in parallel (that is on another thread). And the part with the Future is also important, else you could not check from the main thread (update loop) if the other thread finished execution.

ok can you please help me set it up?



[java]@Override

public void simpleInitApp() {



/** Initialize an instance of SimpleApplication (includes AssetManager)

  • without this initialized and accessed first, any call from any external
  • class will have a null asset manager /



    thisApp = this; // must be accessed first to load all animation data into an instance



    /
    * Multithreading: This constructor creates a new executor with a core pool size of 4. /

    ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(4);



    /
    * Multithreading: The future that is used to check the execution status /

    Future future = null;



    /
    * Multithreading: The adapter object that performs the long-running task /

    final Jme3Adapter adapter = null;



    // A self-contained time-intensive task:

    Callable<Jme3Adapter> fileLoader = new Callable<Jme3Adapter>(){

    public Jme3Adapter call() throws Exception

    {



    // Now continue parsing from where we left of

    Jme3Adapter ad =new Jme3Adapter(101, 10000);



    //Process the data and pass it back to Jme3Cinematics

    return adapter; // I shouldn’t return right? <<<<<<<<<<<<<<<<

    }

    };

    [/java]



    Problem here that I can’t return void (incompatible with Callable)



    and later:



    [java]// Start reading the trace file (now after everything is initialized)

    // this is the main animation thread where only a small portion of the file is parsed

    // the rest is parsed on a separate thread (below) to optimize loading time and performance

    readFile(0 , 100); // to be changed to a much higher number



    /
    * Separate thread that reads the rest of the file concurrently with the execution */

    try

    {

    //If we have not started a callable yet, do so!

    if(adapter == null && future == null)

    {

    //Thread starts!

    executor.submit(fileLoader);

    }

    //If we have started a callable already, we check the status

    else if(future != null)

    {

    //Get the waylist when its done

    if(future.isDone())

    {

    adapter = (Jme3Adapter) future.get();

    future = null;

    }

    else if(future.isCancelled())

    {

    //Set future to null. Maybe we succeed next time…

    future = null;

    }



    }

    }

    catch(Exception e){

    e.printStackTrace();

    }



    if(adapter != null){

    System.out.println(“Success!!!”);

    }

    [/java]



    what am I doing wrong with this approach?

Whats the issue? In line 21 you get an adapter class and all the processing has been done on another thread. I don’t know much about your processing so idk what you need or want to do with that data then. You are aware that the update loop will run through that code many times before the value of the Future is actually returned, yeah? So thats when your game runs on and the processing happens on another thread (!future.isDone()). When you get the Jme3Adapter class back then you can process that data on the main thread (and e.g. parse the next lines).

1- The thing is that I am not passing/getting data , Jme3Adapter ad =new Jme3Adapter(101, 10000); takes care of everything (parses, call appropriate methods, and triggers cinematics). Also not sure the threads have to be synchronized bc when cinematics reads the events it puts them according to their trigger time (which is already passed from the Jme3Adapter)



2- line 21, I get The final local variable adapter cannot be assigned. It must be blank and not using a compound assignment if I remove the final from adapter, I will get an error above that it must be final.

a) Don’t make the variable final b) Since Jme3Adapter took care of everything, you can just use the data it assembled in line 21.

1 Like

a) if It’s not final then I will get an error here:



[java]// A self-contained time-intensive task:

Callable<Jme3Adapter> fileLoader = new Callable<Jme3Adapter>(){

public Jme3Adapter call() throws Exception

{



System.out.println("THREAD RUNNING IN BACKGROUND");

// Now continue parsing from where we left of

Jme3Adapter ad =new Jme3Adapter(101, 10000);



//Process the data and pass it back to Jme3Cinematics

return adapter; <<<<<<<<<<<<<<<<<<<<<<<<<<<Cannot refer to a non-final variable adapter inside an inner class defined in a different method

}

};[/java]



Note that I placed the fileLoader Callable in simpleInitApp().





b) )

Since Jme3Adapter took care of everything, you can just use the data it assembled in line 21.




but when Jme3Adapter ad =new Jme3Adapter(101, 10000); is called why would I need to perform a get() on the data?

Dude, you have to return “ad”…

a) it works perfectly, thank you



b) I’m a shame on humanity lol

Alright, let that experience sink in before you start adding new threads :wink: Glad you got it working.