9
9
import java .util .concurrent .ArrayBlockingQueue ;
10
10
import java .util .concurrent .BlockingQueue ;
11
11
import java .util .concurrent .CountDownLatch ;
12
+ import java .util .concurrent .atomic .AtomicInteger ;
12
13
13
14
/**
14
15
* This class implements a basic TaskSystem in java, for multithreading of a box2d world.
@@ -24,6 +25,8 @@ public class Box2dWorldTaskSystem implements Disposable {
24
25
25
26
private final Thread [] workers ;
26
27
private final int numWorkers ;
28
+ private final AtomicInteger aliveWorkers ;
29
+ private final Box2dWorldTaskSystemDeathHandler deathHandler ;
27
30
private final BlockingQueue <Box2dTaskChunk > taskChunks ;
28
31
private final CountDownLatch [] countDownLatches = new CountDownLatch [MAX_TASKS ];
29
32
private final ClosureObject <Box2d .b2EnqueueTaskCallback > enqueueTaskCallback ;
@@ -39,36 +42,41 @@ public class Box2dWorldTaskSystem implements Disposable {
39
42
*
40
43
* @param worldDef The world to configure
41
44
* @param numWorkers The amount of worker
45
+ * @param deathHandler Handler for when the task system dies. see {@link Box2dWorldTaskSystemDeathHandler}
42
46
* @return The created {@link Box2dWorldTaskSystem}
43
47
*/
44
- public static Box2dWorldTaskSystem createForWorld (b2WorldDef worldDef , int numWorkers ) {
45
- Box2dWorldTaskSystem multiThreader = new Box2dWorldTaskSystem (numWorkers );
48
+ public static Box2dWorldTaskSystem createForWorld (b2WorldDef worldDef , int numWorkers , Box2dWorldTaskSystemDeathHandler deathHandler ) {
49
+ Box2dWorldTaskSystem multiThreader = new Box2dWorldTaskSystem (numWorkers , deathHandler );
46
50
multiThreader .configureForWorld (worldDef );
47
51
return multiThreader ;
48
52
}
49
53
50
- private Box2dWorldTaskSystem (int numWorkers ) {
54
+ private Box2dWorldTaskSystem (int numWorkers , Box2dWorldTaskSystemDeathHandler deathHandler ) {
51
55
if (numWorkers <= 1 )
52
56
throw new IllegalArgumentException ("Number of workers must be greater than 1" );
53
57
this .numWorkers = numWorkers ;
54
58
this .workers = new Thread [numWorkers ];
55
59
this .taskChunks = new ArrayBlockingQueue <>(MAX_TASKS * numWorkers );
60
+ this .aliveWorkers = new AtomicInteger (numWorkers );
61
+ this .deathHandler = deathHandler ;
56
62
57
63
for (int i = 0 ; i < numWorkers ; i ++) {
58
64
final int workerId = i ;
59
65
workers [i ] = new Thread (() -> {
60
- while (running ) {
61
- try {
62
- Box2dTaskChunk task = taskChunks .take ();
66
+ try {
67
+ while (running ) {
63
68
try {
69
+ Box2dTaskChunk task = taskChunks .take ();
64
70
task .execute (workerId );
65
- } finally {
66
71
countDownLatches [task .taskIndex ].countDown ();
72
+ } catch (InterruptedException e ) {
73
+ break ;
67
74
}
68
- } catch (InterruptedException e ) {
69
- break ;
70
75
}
71
- }
76
+ } finally {
77
+ if (running )
78
+ onThreadDies ();
79
+ }
72
80
}, "Box2d-Worker-" + workerId );
73
81
workers [i ].setDaemon (true );
74
82
workers [i ].start ();
@@ -113,11 +121,17 @@ private Box2dWorldTaskSystem(int numWorkers) {
113
121
countDownLatches [taskId ].await ();
114
122
countDownLatches [taskId ] = null ;
115
123
} catch (InterruptedException e ) {
116
- throw new RuntimeException (e );
124
+ throw new Box2dWorldTaskSystemInterruptException (e );
117
125
}
118
126
});
119
127
}
120
128
129
+ private void onThreadDies () {
130
+ if (aliveWorkers .getAndDecrement () == numWorkers ) {
131
+ deathHandler .taskSystemDied ();
132
+ }
133
+ }
134
+
121
135
private void configureForWorld (b2WorldDef worldDef ) {
122
136
worldDef .enqueueTask (enqueueTaskCallback );
123
137
worldDef .finishTask (finishTaskCallback );
@@ -131,7 +145,6 @@ public void afterStep() {
131
145
taskCount = 0 ;
132
146
}
133
147
134
-
135
148
/**
136
149
* Cleans up all resources. Should not be called before destroying the world.
137
150
*/
@@ -171,4 +184,25 @@ public void execute(int workerId) {
171
184
task .getClosure ().b2TaskCallback_call (start , end , workerId , taskContext );
172
185
}
173
186
}
187
+
188
+ public static class Box2dWorldTaskSystemInterruptException extends RuntimeException {
189
+ public Box2dWorldTaskSystemInterruptException (InterruptedException e ) {
190
+ super (e );
191
+ }
192
+ }
193
+
194
+ @ FunctionalInterface
195
+ public interface Box2dWorldTaskSystemDeathHandler {
196
+ /**
197
+ * So, your task system died, hooray! This was caused by an exception on a worker thread. <br>
198
+ * The most important thing to do now is, to call {@link Thread#interrupt()} on the thread, that runs {@link Box2d#b2World_Step}.<br>
199
+ * This will make your {@link Box2d#b2World_Step} method throw a {@link Box2dWorldTaskSystemInterruptException}.<br>
200
+ * Now you have two options:<br>
201
+ * 1. Let everything crash and don't catch the exception <br>
202
+ * 2. Catch the exception and handle it gracefully. After you caught the exception, you can safely dispose the current TaskSystem.
203
+ * You should also be able to gracefully tear down the world, but that's more risky. In any case, you shouldn't use the world further and create a new one. <br>
204
+ * The exception that made the worker thread crash can be seen over the {@link Thread#setDefaultUncaughtExceptionHandler}}
205
+ */
206
+ void taskSystemDied ();
207
+ }
174
208
}
0 commit comments