|
@@ -1,11 +1,7 @@
|
|
|
= Multithreading Optimization
|
|
|
-:author:
|
|
|
-:revnumber:
|
|
|
-:revdate: 2016/03/17 20:48
|
|
|
+:revnumber: 2.0
|
|
|
+:revdate: 2020/07/24
|
|
|
:keywords: loop, game, performance, state, states, documentation
|
|
|
-:relfileprefix: ../../
|
|
|
-:imagesdir: ../..
|
|
|
-ifdef::env-github,env-browser[:outfilesuffix: .adoc]
|
|
|
|
|
|
|
|
|
|
|
@@ -33,11 +29,11 @@ This example does not fetch the returned value by calling `get()` on the Future
|
|
|
If the processing thread needs to wait or needs the return value then `get()` or the other methods in the returned Future object such as `isDone()` can be used.
|
|
|
====
|
|
|
|
|
|
-First, make sure you know what <<jme3/advanced/application_states#,Application States>> and <<jme3/advanced/custom_controls#,Custom Controls>> are.
|
|
|
+First, make sure you know what xref:app/state/application_states.adoc[Application States] and <<jme3/advanced/custom_controls#,Custom Controls>> are.
|
|
|
|
|
|
More complex games may feature complex mathematical operations or artificial intelligence calculations (such as path finding for several NPCs). If you make many time-intensive calls on the same thread (in the update loop), they will block one another, and thus slow down the game to a degree that makes it unplayable. If your game requires long running tasks, you should run them concurrently on separate threads, which speeds up the application considerably.
|
|
|
|
|
|
-Often multithreading means having separate detached logical loops going on in parallel, which communicate about their state. (For example, one thread for AI, one Sound, one Graphics). However we recommend to use a global update loop for game logic, and do multithreading within that loop when it is appropriate. This approach scales way better to multiple cores and does not break up your code logic.
|
|
|
+Often multithreading means having separate detached logical loops going on in parallel, which communicate about their state. (For example, one thread for AI, one Sound, one Graphics). However we recommend to use a global update loop for game logic, and do multithreading within that loop when it is appropriate. This approach scales way better to multiple cores and does not break up your code logic.
|
|
|
|
|
|
Effectively, each for-loop in the main update loop might be a chance for multithreading, if you can break it up into self-contained tasks.
|
|
|
|
|
@@ -47,7 +43,7 @@ Effectively, each for-loop in the main update loop might be a chance for multith
|
|
|
The java.util.concurrent package provides a good foundation for multithreading and dividing work into tasks that can be executed concurrently (hence the name). The three basic components are the Executor (supervises threads), Callable Objects (the tasks), and Future Objects (the result). You can link:http://download.oracle.com/javase/tutorial/essential/concurrency/[read about the concurrent package more here], I will give just a short introduction.
|
|
|
|
|
|
* A Callable is one of the classes that gets executed on a thread in the Executor. The object represents one of several concurrent tasks (e.g, one NPC's path finding task). Each Callable is started from the updateloop by calling a method named `call()`.
|
|
|
-* The Executor is one central object that manages all your Callables. Every time you schedule a Callable in the Executor, the Executor returns a Future object for it.
|
|
|
+* The Executor is one central object that manages all your Callables. Every time you schedule a Callable in the Executor, the Executor returns a Future object for it.
|
|
|
* A Future is an object that you use to check the status of an individual Callable task. The Future also gives you the return value in case one is returned.
|
|
|
|
|
|
|
|
@@ -55,14 +51,14 @@ The java.util.concurrent package provides a good foundation for multithreading a
|
|
|
|
|
|
So how do we implement multithreading in jME3?
|
|
|
|
|
|
-Let's take the example of a Control that controls an NPC Spatial. The NPC Control has to compute a lengthy pathfinding operation for each NPC. If we would execute the operations directly in the simpleUpdate() loop, it would block the game each time a NPC wants to move from A to B. Even if we move this behaviour into the update() method of a dedicated NPC Control, we would still get annoying freeze frames, because it still runs on the same update loop thread.
|
|
|
+Let's take the example of a Control that controls an NPC Spatial. The NPC Control has to compute a lengthy pathfinding operation for each NPC. If we would execute the operations directly in the simpleUpdate() loop, it would block the game each time a NPC wants to move from A to B. Even if we move this behaviour into the update() method of a dedicated NPC Control, we would still get annoying freeze frames, because it still runs on the same update loop thread.
|
|
|
|
|
|
To avoid slowdown, we decide to keep the pathfinding operations in the NPC Control, _but execute it on another thread_.
|
|
|
|
|
|
|
|
|
== Executor
|
|
|
|
|
|
-You create the executor object in a global AppState (or the initSimpleApp() method), in any case in a high-level place where multiple controls can access it.
|
|
|
+You create the executor object in a global AppState (or the initSimpleApp() method), in any case in a high-level place where multiple controls can access it.
|
|
|
|
|
|
[source,java]
|
|
|
----
|
|
@@ -72,12 +68,12 @@ ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(4);
|
|
|
|
|
|
----
|
|
|
|
|
|
-Pool size means the executor will keep four threads alive at any time. Having more threads in the pool means that more tasks can run concurrently. But a bigger pool only results in a speed gain if the PC can handle it! Allocating a pool that is uselessly large just wastes memory, so you need to find a good compromise: About the same to double the size of the number of cores in the computer makes sense.
|
|
|
+Pool size means the executor will keep four threads alive at any time. Having more threads in the pool means that more tasks can run concurrently. But a bigger pool only results in a speed gain if the PC can handle it! Allocating a pool that is uselessly large just wastes memory, so you need to find a good compromise: About the same to double the size of the number of cores in the computer makes sense.
|
|
|
|
|
|
[WARNING]
|
|
|
====
|
|
|
Executor needs to be shut down when the application ends, in order to make the process die properly
|
|
|
-In your simple application you can override the destroy method and shutdown the executor:
|
|
|
+In your simple application you can override the destroy method and shutdown the executor:
|
|
|
====
|
|
|
|
|
|
[source,java]
|
|
@@ -140,8 +136,8 @@ public void update(float tpf) {
|
|
|
future = null;
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
- catch(Exception e){
|
|
|
+ }
|
|
|
+ catch(Exception e){
|
|
|
Exceptions.printStackTrace(e);
|
|
|
}
|
|
|
if(wayList != null){
|
|
@@ -152,7 +148,7 @@ public void update(float tpf) {
|
|
|
|
|
|
Note how this logic makes its decision based on the Future object.
|
|
|
|
|
|
-Remember not to mess with the class fields after starting the thread, because they are being accessed and modified on the new thread. In more obvious terms: You cannot change the “desired location of the NPC while the path finder is calculating a different path. You have to cancel the current Future first.
|
|
|
+Remember not to mess with the class fields after starting the thread, because they are being accessed and modified on the new thread. In more obvious terms: You cannot change the "`desired`" location of the NPC while the path finder is calculating a different path. You have to cancel the current Future first.
|
|
|
|
|
|
|
|
|
== The Callable
|
|
@@ -186,7 +182,7 @@ private Callable<MyWayList> findWay = new Callable<MyWayList>(){
|
|
|
}).get();
|
|
|
|
|
|
// This world class allows safe access via synchronized methods
|
|
|
- Data data = myWorld.getData();
|
|
|
+ Data data = myWorld.getData();
|
|
|
|
|
|
//... Now process data and find the way ...
|
|
|
|
|
@@ -201,7 +197,7 @@ private Callable<MyWayList> findWay = new Callable<MyWayList>(){
|
|
|
=== Useful Links
|
|
|
|
|
|
High level description which describes how to manage the game state and the rendering in different threads: +
|
|
|
-link:http://jahej.com/alt/2011_07_03_threading-and-your-game-loop.html[Threading and your game loop]. +
|
|
|
+link:http://jahej.com/alt/2011_07_03_threading-and-your-game-loop.html[Threading and your game loop]. +
|
|
|
A C++ example can be found at: +
|
|
|
link:http://gamasutra.com/blogs/AndreaMagnorsky/20130527/193087/Multithreading_rendering_in_a_game_engine_with_CDouble_buffer_implementation.php[Multithreading-rendering in a game engine with CDouble buffer implementation].
|
|
|
|