Skip to content

Commit d05fadb

Browse files
committed
fix: Update bindings and avoid per-frame allocation in debug renderer and task system
1 parent bd66b8a commit d05fadb

File tree

76 files changed

+4455
-614
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+4455
-614
lines changed

gdx-box2d-utils/src/main/java/com/badlogic/gdx/box2d/utils/Box2dDebugRenderer.java

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package com.badlogic.gdx.box2d.utils;
22

33
import com.badlogic.gdx.box2d.enums.b2HexColor;
4-
import com.badlogic.gdx.box2d.structs.b2DebugDraw;
5-
import com.badlogic.gdx.box2d.structs.b2Transform;
6-
import com.badlogic.gdx.box2d.structs.b2Vec2;
7-
import com.badlogic.gdx.box2d.structs.b2WorldId;
4+
import com.badlogic.gdx.box2d.structs.*;
85
import com.badlogic.gdx.graphics.Color;
96
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
7+
import com.badlogic.gdx.jnigen.runtime.closure.Closure;
108
import com.badlogic.gdx.jnigen.runtime.closure.ClosureObject;
9+
import com.badlogic.gdx.jnigen.runtime.closure.PointingPoolManager;
1110
import com.badlogic.gdx.jnigen.runtime.pointer.VoidPointer;
1211
import com.badlogic.gdx.jnigen.runtime.pointer.integer.BytePointer;
1312
import com.badlogic.gdx.math.Matrix4;
@@ -18,23 +17,40 @@
1817

1918
public class Box2dDebugRenderer implements Disposable {
2019

20+
private static final b2Vec2 TEMP_VEC_PTR = new b2Vec2(0L, false);
21+
private static final b2Rot TEMP_ROT_PTR = new b2Rot(0L, false);
22+
2123
private final ShapeRenderer renderer;
2224

2325
private final b2DebugDraw b2DebugDraw;
26+
private final PointingPoolManager pointingPoolManager;
2427

2528
public Box2dDebugRenderer () {
29+
pointingPoolManager = new PointingPoolManager(5);
30+
pointingPoolManager.addPool(b2Vec2.b2Vec2Pointer::new, 5);
31+
pointingPoolManager.addPool(VoidPointer::new, 5);
32+
pointingPoolManager.addPool(b2Transform::new, 5);
33+
pointingPoolManager.addPool(b2Vec2::new, 5);
34+
pointingPoolManager.addPool(BytePointer::new, 5);
35+
2636
renderer = new ShapeRenderer();
2737
renderer.setAutoShapeType(true);
2838
b2DebugDraw = b2DefaultDebugDraw();
29-
b2DebugDraw.DrawPolygonFcn(ClosureObject.fromClosure(this::drawPolygon));
30-
b2DebugDraw.DrawSolidPolygonFcn(ClosureObject.fromClosure(this::drawSolidPolygon));
31-
b2DebugDraw.DrawCircleFcn(ClosureObject.fromClosure(this::drawCircle));
32-
b2DebugDraw.DrawSolidCircleFcn(ClosureObject.fromClosure(this::drawSolidCircle));
33-
b2DebugDraw.DrawSolidCapsuleFcn(ClosureObject.fromClosure(this::drawSolidCapsule));
34-
b2DebugDraw.DrawSegmentFcn(ClosureObject.fromClosure(this::drawSegment));
35-
b2DebugDraw.DrawTransformFcn(ClosureObject.fromClosure(this::drawTransform));
36-
b2DebugDraw.DrawPointFcn(ClosureObject.fromClosure(this::drawPoint));
37-
b2DebugDraw.DrawStringFcn(ClosureObject.fromClosure(this::drawString));
39+
b2DebugDraw.DrawPolygonFcn(createPooledClosure(this::drawPolygon));
40+
b2DebugDraw.DrawSolidPolygonFcn(createPooledClosure(this::drawSolidPolygon));
41+
b2DebugDraw.DrawCircleFcn(createPooledClosure(this::drawCircle));
42+
b2DebugDraw.DrawSolidCircleFcn(createPooledClosure(this::drawSolidCircle));
43+
b2DebugDraw.DrawSolidCapsuleFcn(createPooledClosure(this::drawSolidCapsule));
44+
b2DebugDraw.DrawSegmentFcn(createPooledClosure(this::drawSegment));
45+
b2DebugDraw.DrawTransformFcn(createPooledClosure(this::drawTransform));
46+
b2DebugDraw.DrawPointFcn(createPooledClosure(this::drawPoint));
47+
b2DebugDraw.DrawStringFcn(createPooledClosure(this::drawString));
48+
}
49+
50+
private <T extends Closure> ClosureObject<T> createPooledClosure(T closure) {
51+
ClosureObject<T> closureObject = ClosureObject.fromClosure(closure);
52+
closureObject.setPoolManager(pointingPoolManager);
53+
return closureObject;
3854
}
3955

4056
/** This assumes that the projection matrix has already been set. */
@@ -49,9 +65,9 @@ private void drawPolygon(b2Vec2.b2Vec2Pointer vertices, int vertexCount, b2HexCo
4965

5066
float[] verticesArray = new float[vertexCount * 2];
5167
for (int i = 0; i < vertexCount; i++) {
52-
b2Vec2 vec2 = vertices.asStackElement(i);
53-
verticesArray[i * 2] = vec2.x();
54-
verticesArray[i * 2 + 1] = vec2.y();
68+
vertices.asStackElement(i, TEMP_VEC_PTR);
69+
verticesArray[i * 2] = TEMP_VEC_PTR.x();
70+
verticesArray[i * 2 + 1] = TEMP_VEC_PTR.y();
5571
}
5672
renderer.set(ShapeRenderer.ShapeType.Line);
5773
Box2dUtils.box2dColorToGDXColor(renderer.getColor(), color);
@@ -62,15 +78,18 @@ private void drawSolidPolygon(b2Transform transform, b2Vec2.b2Vec2Pointer vertic
6278
float[] verticesArray = new float[vertexCount * 2];
6379

6480
for (int i = 0; i < vertexCount; i++) {
65-
b2Vec2 vertex = vertices.asStackElement(i);
66-
67-
float vx = vertex.x();
68-
float vy = vertex.y();
81+
vertices.asStackElement(i, TEMP_VEC_PTR);
6982

70-
float px = transform.p().x();
71-
float py = transform.p().y();
72-
float cos = transform.q().c();
73-
float sin = transform.q().s();
83+
float vx = TEMP_VEC_PTR.x();
84+
float vy = TEMP_VEC_PTR.y();
85+
86+
transform.p(TEMP_VEC_PTR);
87+
float px = TEMP_VEC_PTR.x();
88+
float py = TEMP_VEC_PTR.y();
89+
90+
transform.q(TEMP_ROT_PTR);
91+
float cos = TEMP_ROT_PTR.c();
92+
float sin = TEMP_ROT_PTR.s();
7493

7594
float x = px + cos * vx - sin * vy;
7695
float y = py + sin * vx + cos * vy;
@@ -100,7 +119,8 @@ private void drawCircle(b2Vec2 center, float radius, b2HexColor color, VoidPoint
100119
private void drawSolidCircle(b2Transform transform, float radius, b2HexColor color, VoidPointer context) {
101120
renderer.set(ShapeRenderer.ShapeType.Filled);
102121
Box2dUtils.box2dColorToGDXColor(renderer.getColor(), color);
103-
renderer.circle(transform.p().x(), transform.p().y(), radius);
122+
transform.p(TEMP_VEC_PTR);
123+
renderer.circle(TEMP_VEC_PTR.x(), TEMP_VEC_PTR.y(), radius);
104124
}
105125

106126
private void drawSolidCapsule(b2Vec2 p1, b2Vec2 p2, float radius, b2HexColor color, VoidPointer context) {
@@ -115,11 +135,13 @@ private void drawSegment(b2Vec2 p1, b2Vec2 p2, b2HexColor color, VoidPointer con
115135

116136
private void drawTransform(b2Transform transform, VoidPointer context) {
117137
float axisScale = 0.4f;
118-
119-
float px = transform.p().x();
120-
float py = transform.p().y();
121-
float cos = transform.q().c();
122-
float sin = transform.q().s();
138+
139+
transform.p(TEMP_VEC_PTR);
140+
transform.q(TEMP_ROT_PTR);
141+
float px = TEMP_VEC_PTR.x();
142+
float py = TEMP_VEC_PTR.y();
143+
float cos = TEMP_ROT_PTR.c();
144+
float sin = TEMP_ROT_PTR.s();
123145

124146
renderer.set(ShapeRenderer.ShapeType.Line);
125147
renderer.setColor(Color.RED);

gdx-box2d-utils/src/main/java/com/badlogic/gdx/box2d/utils/Box2dWorldTaskSystem.java

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
import com.badlogic.gdx.box2d.Box2d;
44
import com.badlogic.gdx.box2d.structs.b2WorldDef;
55
import com.badlogic.gdx.jnigen.runtime.closure.ClosureObject;
6+
import com.badlogic.gdx.jnigen.runtime.closure.PointingPoolManager;
67
import com.badlogic.gdx.jnigen.runtime.pointer.VoidPointer;
78
import com.badlogic.gdx.utils.Disposable;
89

910
import java.util.concurrent.ArrayBlockingQueue;
1011
import java.util.concurrent.BlockingQueue;
11-
import java.util.concurrent.CountDownLatch;
1212
import java.util.concurrent.atomic.AtomicInteger;
13+
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
1314

1415
/**
1516
* This class implements a basic TaskSystem in java, for multithreading of a box2d world.
@@ -28,9 +29,13 @@ public class Box2dWorldTaskSystem implements Disposable {
2829
private final AtomicInteger aliveWorkers;
2930
private final Box2dWorldTaskSystemDeathHandler deathHandler;
3031
private final BlockingQueue<Box2dTaskChunk> taskChunks;
31-
private final CountDownLatch[] countDownLatches = new CountDownLatch[MAX_TASKS];
32+
private final Box2dCountdown[] countDownLatches = new Box2dCountdown[MAX_TASKS];
3233
private final ClosureObject<Box2d.b2EnqueueTaskCallback> enqueueTaskCallback;
3334
private final ClosureObject<Box2d.b2FinishTaskCallback> finishTaskCallback;
35+
private final PointingPoolManager poolManager = new PointingPoolManager(2);
36+
private final ArrayBlockingQueue<Box2dTaskChunk> chunkPool;
37+
private final VoidPointer TASK_INDEX_WRAPPER = new VoidPointer(0L, false);
38+
private final boolean poolingEnabled;
3439

3540
private int taskCount = 0;
3641
private boolean running = true;
@@ -43,23 +48,31 @@ public class Box2dWorldTaskSystem implements Disposable {
4348
* @param worldDef The world to configure
4449
* @param numWorkers The amount of worker
4550
* @param deathHandler Handler for when the task system dies. see {@link Box2dWorldTaskSystemDeathHandler}
51+
* @param enablePooling Whether the TaskSystem should avoid per-frame allocation
4652
* @return The created {@link Box2dWorldTaskSystem}
4753
*/
48-
public static Box2dWorldTaskSystem createForWorld(b2WorldDef worldDef, int numWorkers, Box2dWorldTaskSystemDeathHandler deathHandler) {
49-
Box2dWorldTaskSystem multiThreader = new Box2dWorldTaskSystem(numWorkers, deathHandler);
54+
public static Box2dWorldTaskSystem createForWorld(b2WorldDef worldDef, int numWorkers, Box2dWorldTaskSystemDeathHandler deathHandler, boolean enablePooling) {
55+
Box2dWorldTaskSystem multiThreader = new Box2dWorldTaskSystem(numWorkers, deathHandler, enablePooling);
5056
multiThreader.configureForWorld(worldDef);
5157
return multiThreader;
5258
}
5359

54-
private Box2dWorldTaskSystem(int numWorkers, Box2dWorldTaskSystemDeathHandler deathHandler) {
60+
private Box2dWorldTaskSystem(int numWorkers, Box2dWorldTaskSystemDeathHandler deathHandler, boolean enablePooling) {
5561
if (numWorkers <= 1)
5662
throw new IllegalArgumentException("Number of workers must be greater than 1");
63+
64+
this.poolingEnabled = enablePooling;
5765
this.numWorkers = numWorkers;
5866
this.workers = new Thread[numWorkers];
5967
this.taskChunks = new ArrayBlockingQueue<>(MAX_TASKS * numWorkers);
68+
this.chunkPool = new ArrayBlockingQueue<>(MAX_TASKS * numWorkers);
6069
this.aliveWorkers = new AtomicInteger(numWorkers);
6170
this.deathHandler = deathHandler;
6271

72+
for (int i = 0; i < MAX_TASKS; i++) {
73+
countDownLatches[i] = new Box2dCountdown();
74+
}
75+
6376
for (int i = 0; i < numWorkers; i++) {
6477
final int workerId = i;
6578
workers[i] = new Thread(() -> {
@@ -69,6 +82,8 @@ private Box2dWorldTaskSystem(int numWorkers, Box2dWorldTaskSystemDeathHandler de
6982
Box2dTaskChunk task = taskChunks.take();
7083
task.execute(workerId);
7184
countDownLatches[task.taskIndex].countDown();
85+
if (poolingEnabled)
86+
chunkPool.offer(task);
7287
} catch (InterruptedException e) {
7388
break;
7489
}
@@ -90,22 +105,26 @@ private Box2dWorldTaskSystem(int numWorkers, Box2dWorldTaskSystemDeathHandler de
90105
int baseItemsPerTask = itemCount / numTasks;
91106
int remainingItems = itemCount % numTasks;
92107

93-
countDownLatches[taskCount] = new CountDownLatch(numTasks);
108+
countDownLatches[taskCount].setCount(numTasks);
94109

95110
int start = 0;
96111
for (int i = 0; i < numTasks; i++) {
97112
int itemsInThisTask = baseItemsPerTask + (i < remainingItems ? 1 : 0);
98113
int end = start + itemsInThisTask;
99114

100-
Box2dTaskChunk box2DTaskChunk = new Box2dTaskChunk(taskCount, task, start, end, taskContext);
115+
Box2dTaskChunk box2DTaskChunk = poolingEnabled ? chunkPool.poll() : new Box2dTaskChunk();
116+
if (box2DTaskChunk == null)
117+
box2DTaskChunk = new Box2dTaskChunk();
118+
119+
box2DTaskChunk.setData(taskCount, task, start, end, taskContext.getPointer());
101120
if (!taskChunks.offer(box2DTaskChunk))
102121
throw new IllegalStateException("Task queue is full - impossible");
103122
start = end;
104123
}
105124

106-
VoidPointer taskIndex = new VoidPointer((long) taskCount + 1, false);
125+
TASK_INDEX_WRAPPER.setPointer(taskCount + 1);
107126
taskCount++;
108-
return taskIndex;
127+
return TASK_INDEX_WRAPPER;
109128
} else {
110129
task.getClosure().b2TaskCallback_call(0, itemCount, 0, taskContext);
111130
return VoidPointer.NULL;
@@ -119,11 +138,16 @@ private Box2dWorldTaskSystem(int numWorkers, Box2dWorldTaskSystemDeathHandler de
119138
int taskId = (int) userTask.getPointer() - 1;
120139
try {
121140
countDownLatches[taskId].await();
122-
countDownLatches[taskId] = null;
123141
} catch (InterruptedException e) {
124142
throw new Box2dWorldTaskSystemInterruptException(e);
125143
}
126144
});
145+
146+
if (poolingEnabled) {
147+
poolManager.addPool(VoidPointer::new, 2);
148+
enqueueTaskCallback.setPoolManager(poolManager);
149+
finishTaskCallback.setPoolManager(poolManager);
150+
}
127151
}
128152

129153
private void onThreadDies() {
@@ -166,18 +190,18 @@ public void dispose() {
166190
}
167191

168192
private static class Box2dTaskChunk {
169-
private final int taskIndex;
170-
private final ClosureObject<Box2d.b2TaskCallback> task;
171-
private final int start;
172-
private final int end;
173-
private final VoidPointer taskContext;
193+
private int taskIndex;
194+
private ClosureObject<Box2d.b2TaskCallback> task;
195+
private int start;
196+
private int end;
197+
private final VoidPointer taskContext = new VoidPointer(0L, false);
174198

175-
public Box2dTaskChunk(int taskIndex, ClosureObject<Box2d.b2TaskCallback> task, int start, int end, VoidPointer taskContext) {
199+
public void setData(int taskIndex, ClosureObject<Box2d.b2TaskCallback> task, int start, int end, long taskContext) {
176200
this.taskIndex = taskIndex;
177201
this.task = task;
178202
this.start = start;
179203
this.end = end;
180-
this.taskContext = taskContext;
204+
this.taskContext.setPointer(taskContext);
181205
}
182206

183207
public void execute(int workerId) {
@@ -205,4 +229,41 @@ public interface Box2dWorldTaskSystemDeathHandler {
205229
*/
206230
void taskSystemDied();
207231
}
232+
233+
private static class Box2dCountdown extends AbstractQueuedSynchronizer {
234+
235+
private Box2dCountdown() {
236+
setState(0);
237+
}
238+
239+
public void setCount(int count) {
240+
setState(count);
241+
}
242+
243+
public void await() throws InterruptedException {
244+
acquireSharedInterruptibly(1);
245+
}
246+
247+
public void countDown() {
248+
releaseShared(1);
249+
}
250+
251+
@Override
252+
protected int tryAcquireShared(int acquires) {
253+
return (getState() == 0) ? 1 : -1;
254+
}
255+
256+
@Override
257+
protected boolean tryReleaseShared(int releases) {
258+
// Decrement count; signal when transition to zero
259+
for (;;) {
260+
int c = getState();
261+
if (c == 0)
262+
return false;
263+
int nextc = c - 1;
264+
if (compareAndSetState(c, nextc))
265+
return nextc == 0;
266+
}
267+
}
268+
}
208269
}

gdx-box2d-utils/src/test/java/com/badlogic/gdx/box2d/utils/test/Box2dWorldTaskSystemTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static void tiltedStacks(int testIndex, int workerCount, b2Vec2[][] final
2222
Box2dWorldTaskSystem multiThreader = null;
2323
if (workerCount > 1) {
2424
Thread currentThread = Thread.currentThread();
25-
multiThreader = Box2dWorldTaskSystem.createForWorld(worldDef, workerCount, currentThread::interrupt);
25+
multiThreader = Box2dWorldTaskSystem.createForWorld(worldDef, workerCount, currentThread::interrupt, true);
2626
}
2727

2828
b2WorldId worldId = b2CreateWorld(worldDef.asPointer());

0 commit comments

Comments
 (0)