feat(renderer): Refactor rendering object pool management and update related tests
This commit is contained in:
@@ -204,6 +204,8 @@ public class G2DRenderer implements Renderer {
|
|||||||
for (final RenderingBo bo : usedRenderingBoTypes.values()) {
|
for (final RenderingBo bo : usedRenderingBoTypes.values()) {
|
||||||
bo.staticCleanupAfterFrame();
|
bo.staticCleanupAfterFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.rendererData.getRenderingBoPool().releasePending();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Rendering cycle END
|
// Rendering cycle END
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ public class G2DRendererControl implements RendererControl {
|
|||||||
|
|
||||||
private RendererData rendererData;
|
private RendererData rendererData;
|
||||||
|
|
||||||
private CoordI2 absoluteViewportDimension = ImmutableCoordI2.one();
|
private CoordI2 absoluteViewportDimension = RendererControl.REFERENCE_RESOLUTION;
|
||||||
|
|
||||||
private CoordI2 effectiveViewportDimension = ImmutableCoordI2.one();
|
private CoordI2 effectiveViewportDimension = RendererControl.REFERENCE_RESOLUTION;
|
||||||
|
|
||||||
private double scaleFactor = 1.0;
|
private double scaleFactor = 1.0;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
|
|||||||
.next()
|
.next()
|
||||||
.put(work.getId(), work);
|
.put(work.getId(), work);
|
||||||
if (previous != null && previous != work) {
|
if (previous != null && previous != work) {
|
||||||
this.rendererBoPool.release(previous);
|
this.rendererBoPool.returnToPool(previous);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
|
|||||||
for (final RenderingBo bo : work) {
|
for (final RenderingBo bo : work) {
|
||||||
final var previous = nextMap.put(bo.getId(), bo);
|
final var previous = nextMap.put(bo.getId(), bo);
|
||||||
if (previous != null && previous != bo) {
|
if (previous != null && previous != bo) {
|
||||||
this.rendererBoPool.release(previous);
|
this.rendererBoPool.returnToPool(previous);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
|
|||||||
.next()
|
.next()
|
||||||
.remove(id);
|
.remove(id);
|
||||||
if (previous != null) {
|
if (previous != null) {
|
||||||
this.rendererBoPool.release(previous);
|
this.rendererBoPool.returnToPool(previous);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
|
|||||||
final var previousLiveSet = previousLiveRef.get();
|
final var previousLiveSet = previousLiveRef.get();
|
||||||
if (previousLiveSet != null) {
|
if (previousLiveSet != null) {
|
||||||
if (clone) {
|
if (clone) {
|
||||||
previousLiveSet.values().forEach(this.rendererBoPool::release);
|
previousLiveSet.values().forEach(this.rendererBoPool::returnToPool);
|
||||||
}
|
}
|
||||||
this.returnMapToPool(previousLiveSet);
|
this.returnMapToPool(previousLiveSet);
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
|
|||||||
@Override
|
@Override
|
||||||
public void clearNext(final String producerId) {
|
public void clearNext(final String producerId) {
|
||||||
final var next = this.getWorkingSetByProducer(producerId).next();
|
final var next = this.getWorkingSetByProducer(producerId).next();
|
||||||
next.values().forEach(this.rendererBoPool::release);
|
next.values().forEach(this.rendererBoPool::returnToPool);
|
||||||
next.clear();
|
next.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,8 +127,8 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
|
|||||||
public void clear(final String producerId) {
|
public void clear(final String producerId) {
|
||||||
final RendererWork rendererWork = this.workingSet.remove(producerId);
|
final RendererWork rendererWork = this.workingSet.remove(producerId);
|
||||||
if (rendererWork != null) {
|
if (rendererWork != null) {
|
||||||
rendererWork.live().values().forEach(this.rendererBoPool::release);
|
rendererWork.live().values().forEach(this.rendererBoPool::returnToPool);
|
||||||
rendererWork.next().values().forEach(this.rendererBoPool::release);
|
rendererWork.next().values().forEach(this.rendererBoPool::returnToPool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,14 @@ public interface RenderingBoPool {
|
|||||||
<T extends RenderingBo> T copy(T original);
|
<T extends RenderingBo> T copy(T original);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases a rendering business object back to the pool. This is usually managed by the renderer.
|
* Queues a rendering business object for return back to the pool. Renderer will release the BO after the current rendering cycle is complete.
|
||||||
*/
|
*/
|
||||||
void release(RenderingBo obj);
|
void returnToPool(RenderingBo obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases all pending rendering business objects back to the pool. This is managed by the renderer.
|
||||||
|
*/
|
||||||
|
void releasePending();
|
||||||
|
|
||||||
void setRendererData(RendererData rendererData);
|
void setRendererData(RendererData rendererData);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package de.extio.game_engine.renderer.work;
|
package de.extio.game_engine.renderer.work;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
import de.extio.game_engine.renderer.RendererData;
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
import de.extio.game_engine.renderer.model.RenderingBo;
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
@@ -14,6 +18,10 @@ public class RenderingBoPoolImpl implements RenderingBoPool {
|
|||||||
|
|
||||||
private final Map<Class<? extends RenderingBo>, Stack<RenderingBo>> pool = new HashMap<>();
|
private final Map<Class<? extends RenderingBo>, Stack<RenderingBo>> pool = new HashMap<>();
|
||||||
|
|
||||||
|
private final BlockingQueue<RenderingBo> returnQueue = new ArrayBlockingQueue<>(1000);
|
||||||
|
|
||||||
|
private final Deque<RenderingBo> returnSink = new ArrayDeque<>(1000);
|
||||||
|
|
||||||
private final LastMapping lastMapping = new LastMapping();
|
private final LastMapping lastMapping = new LastMapping();
|
||||||
|
|
||||||
private final LastReverseMapping lastReverseMapping = new LastReverseMapping();
|
private final LastReverseMapping lastReverseMapping = new LastReverseMapping();
|
||||||
@@ -98,28 +106,37 @@ public class RenderingBoPoolImpl implements RenderingBoPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void release(final RenderingBo obj) {
|
public void returnToPool(final RenderingBo obj) {
|
||||||
|
this.returnQueue.offer(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void releasePending() {
|
||||||
try {
|
try {
|
||||||
obj.close();
|
this.returnQueue.drainTo(this.returnSink);
|
||||||
|
RenderingBo obj;
|
||||||
final Class<? extends RenderingBo> clazz = obj.getClass();
|
while ((obj = this.returnSink.poll()) != null) {
|
||||||
Stack<RenderingBo> pooled;
|
obj.close();
|
||||||
|
|
||||||
if (this.lastReverseMapping != null && this.lastReverseMapping.clazz == clazz) {
|
final Class<? extends RenderingBo> clazz = obj.getClass();
|
||||||
pooled = this.lastReverseMapping.pooled;
|
Stack<RenderingBo> pooled;
|
||||||
}
|
|
||||||
else {
|
if (this.lastReverseMapping != null && this.lastReverseMapping.clazz == clazz) {
|
||||||
pooled = this.pool.get(clazz);
|
pooled = this.lastReverseMapping.pooled;
|
||||||
if (pooled == null) {
|
}
|
||||||
pooled = new Stack<>();
|
else {
|
||||||
this.pool.put(clazz, pooled);
|
pooled = this.pool.get(clazz);
|
||||||
|
if (pooled == null) {
|
||||||
|
pooled = new Stack<>();
|
||||||
|
this.pool.put(clazz, pooled);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastReverseMapping.clazz = clazz;
|
||||||
|
this.lastReverseMapping.pooled = pooled;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastReverseMapping.clazz = clazz;
|
pooled.push(obj);
|
||||||
this.lastReverseMapping.pooled = pooled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pooled.push(obj);
|
|
||||||
}
|
}
|
||||||
catch (final Exception e) {
|
catch (final Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|||||||
@@ -194,8 +194,8 @@ public class RendererWorkingSetImplTest {
|
|||||||
final var uncommittedAfter = this.workingSet.getUncommittedWork(this.moduleA.getId());
|
final var uncommittedAfter = this.workingSet.getUncommittedWork(this.moduleA.getId());
|
||||||
assertTrue(uncommittedAfter.isEmpty());
|
assertTrue(uncommittedAfter.isEmpty());
|
||||||
|
|
||||||
verify(this.renderingBoPool, times(1)).release(bo1);
|
verify(this.renderingBoPool, times(1)).returnToPool(bo1);
|
||||||
verify(this.renderingBoPool, times(1)).release(bo2);
|
verify(this.renderingBoPool, times(1)).returnToPool(bo2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -240,8 +240,8 @@ public class RendererWorkingSetImplTest {
|
|||||||
this.workingSet.put(this.moduleA.getId(), bo3);
|
this.workingSet.put(this.moduleA.getId(), bo3);
|
||||||
this.workingSet.commit(this.moduleA.getId(), true);
|
this.workingSet.commit(this.moduleA.getId(), true);
|
||||||
|
|
||||||
verify(this.renderingBoPool, times(1)).release(bo1);
|
verify(this.renderingBoPool, times(1)).returnToPool(bo1);
|
||||||
verify(this.renderingBoPool, times(1)).release(bo2);
|
verify(this.renderingBoPool, times(1)).returnToPool(bo2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package de.extio.game_engine.renderer.work;
|
package de.extio.game_engine.renderer.work;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
@@ -89,7 +90,8 @@ public class RenderingBoPoolImplTest {
|
|||||||
assertEquals("id1", acquired1.getId());
|
assertEquals("id1", acquired1.getId());
|
||||||
assertEquals(0, acquired1.getCloseCallCount());
|
assertEquals(0, acquired1.getCloseCallCount());
|
||||||
|
|
||||||
this.pool.release(acquired1);
|
this.pool.returnToPool(acquired1);
|
||||||
|
this.pool.releasePending();
|
||||||
assertEquals(1, acquired1.getCloseCallCount());
|
assertEquals(1, acquired1.getCloseCallCount());
|
||||||
|
|
||||||
final TestRenderingBo acquired2 = this.pool.acquire("id2", TestRenderingBo.class);
|
final TestRenderingBo acquired2 = this.pool.acquire("id2", TestRenderingBo.class);
|
||||||
@@ -106,10 +108,12 @@ public class RenderingBoPoolImplTest {
|
|||||||
final TestRenderingBo bo = this.pool.acquire("id", TestRenderingBo.class);
|
final TestRenderingBo bo = this.pool.acquire("id", TestRenderingBo.class);
|
||||||
assertEquals(0, bo.getCloseCallCount());
|
assertEquals(0, bo.getCloseCallCount());
|
||||||
|
|
||||||
this.pool.release(bo);
|
this.pool.returnToPool(bo);
|
||||||
|
this.pool.releasePending();
|
||||||
assertEquals(1, bo.getCloseCallCount());
|
assertEquals(1, bo.getCloseCallCount());
|
||||||
|
|
||||||
this.pool.release(bo);
|
this.pool.returnToPool(bo);
|
||||||
|
this.pool.releasePending();
|
||||||
assertEquals(2, bo.getCloseCallCount());
|
assertEquals(2, bo.getCloseCallCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,10 +173,12 @@ public class RenderingBoPoolImplTest {
|
|||||||
assertEquals(0, second.getCloseCallCount());
|
assertEquals(0, second.getCloseCallCount());
|
||||||
assertNotSame(first, second);
|
assertNotSame(first, second);
|
||||||
|
|
||||||
this.pool.release(first);
|
this.pool.returnToPool(first);
|
||||||
|
this.pool.releasePending();
|
||||||
assertEquals(1, first.getCloseCallCount());
|
assertEquals(1, first.getCloseCallCount());
|
||||||
|
|
||||||
this.pool.release(second);
|
this.pool.returnToPool(second);
|
||||||
|
this.pool.releasePending();
|
||||||
assertEquals(1, second.getCloseCallCount());
|
assertEquals(1, second.getCloseCallCount());
|
||||||
|
|
||||||
final TestRenderingBo reacquired = this.pool.acquire("id3", TestRenderingBo.class);
|
final TestRenderingBo reacquired = this.pool.acquire("id3", TestRenderingBo.class);
|
||||||
@@ -182,6 +188,66 @@ public class RenderingBoPoolImplTest {
|
|||||||
assertTrue(reacquired == first || reacquired == second);
|
assertTrue(reacquired == first || reacquired == second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testReturnToPoolWithoutReleasePending() {
|
||||||
|
final TestRenderingBo first = this.pool.acquire("id1", TestRenderingBo.class);
|
||||||
|
final int id1 = System.identityHashCode(first);
|
||||||
|
assertEquals(0, first.getCloseCallCount());
|
||||||
|
|
||||||
|
this.pool.returnToPool(first);
|
||||||
|
assertEquals(0, first.getCloseCallCount());
|
||||||
|
|
||||||
|
final TestRenderingBo second = this.pool.acquire("id2", TestRenderingBo.class);
|
||||||
|
final int id2 = System.identityHashCode(second);
|
||||||
|
|
||||||
|
assertNotSame(first, second);
|
||||||
|
assertNotEquals(id1, id2);
|
||||||
|
assertEquals("id2", second.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBatchReturnToPoolThenReleasePending() {
|
||||||
|
final TestRenderingBo first = this.pool.acquire("id1", TestRenderingBo.class);
|
||||||
|
final TestRenderingBo second = this.pool.acquire("id2", TestRenderingBo.class);
|
||||||
|
final TestRenderingBo third = this.pool.acquire("id3", TestRenderingBo.class);
|
||||||
|
|
||||||
|
assertEquals(0, first.getCloseCallCount());
|
||||||
|
assertEquals(0, second.getCloseCallCount());
|
||||||
|
assertEquals(0, third.getCloseCallCount());
|
||||||
|
|
||||||
|
this.pool.returnToPool(first);
|
||||||
|
this.pool.returnToPool(second);
|
||||||
|
this.pool.returnToPool(third);
|
||||||
|
|
||||||
|
assertEquals(0, first.getCloseCallCount());
|
||||||
|
assertEquals(0, second.getCloseCallCount());
|
||||||
|
assertEquals(0, third.getCloseCallCount());
|
||||||
|
|
||||||
|
this.pool.releasePending();
|
||||||
|
|
||||||
|
assertEquals(1, first.getCloseCallCount());
|
||||||
|
assertEquals(1, second.getCloseCallCount());
|
||||||
|
assertEquals(1, third.getCloseCallCount());
|
||||||
|
|
||||||
|
final TestRenderingBo reacquired = this.pool.acquire("id4", TestRenderingBo.class);
|
||||||
|
assertTrue(reacquired == first || reacquired == second || reacquired == third);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultipleReleasePendingCalls() {
|
||||||
|
final TestRenderingBo bo = this.pool.acquire("id1", TestRenderingBo.class);
|
||||||
|
this.pool.returnToPool(bo);
|
||||||
|
|
||||||
|
this.pool.releasePending();
|
||||||
|
assertEquals(1, bo.getCloseCallCount());
|
||||||
|
|
||||||
|
this.pool.releasePending();
|
||||||
|
assertEquals(1, bo.getCloseCallCount());
|
||||||
|
|
||||||
|
this.pool.releasePending();
|
||||||
|
assertEquals(1, bo.getCloseCallCount());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConcurrentAccess() throws InterruptedException {
|
void testConcurrentAccess() throws InterruptedException {
|
||||||
final int[] thread1Count = {0};
|
final int[] thread1Count = {0};
|
||||||
@@ -192,7 +258,7 @@ public class RenderingBoPoolImplTest {
|
|||||||
final TestRenderingBo bo = this.pool.acquire("id-" + i, TestRenderingBo.class);
|
final TestRenderingBo bo = this.pool.acquire("id-" + i, TestRenderingBo.class);
|
||||||
assertNotNull(bo);
|
assertNotNull(bo);
|
||||||
assertNotNull(bo.getId());
|
assertNotNull(bo.getId());
|
||||||
this.pool.release(bo);
|
this.pool.returnToPool(bo);
|
||||||
thread1Count[0]++;
|
thread1Count[0]++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -202,7 +268,7 @@ public class RenderingBoPoolImplTest {
|
|||||||
final TestRenderingBo bo = this.pool.acquire("id-" + i, TestRenderingBo.class);
|
final TestRenderingBo bo = this.pool.acquire("id-" + i, TestRenderingBo.class);
|
||||||
assertNotNull(bo);
|
assertNotNull(bo);
|
||||||
assertNotNull(bo.getId());
|
assertNotNull(bo.getId());
|
||||||
this.pool.release(bo);
|
this.pool.returnToPool(bo);
|
||||||
thread2Count[0]++;
|
thread2Count[0]++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -216,6 +282,8 @@ public class RenderingBoPoolImplTest {
|
|||||||
assertEquals(10, thread1Count[0]);
|
assertEquals(10, thread1Count[0]);
|
||||||
assertEquals(10, thread2Count[0]);
|
assertEquals(10, thread2Count[0]);
|
||||||
|
|
||||||
|
this.pool.releasePending();
|
||||||
|
|
||||||
final TestRenderingBo bo = this.pool.acquire("final-id", TestRenderingBo.class);
|
final TestRenderingBo bo = this.pool.acquire("final-id", TestRenderingBo.class);
|
||||||
assertNotNull(bo);
|
assertNotNull(bo);
|
||||||
assertEquals("final-id", bo.getId());
|
assertEquals("final-id", bo.getId());
|
||||||
|
|||||||
Reference in New Issue
Block a user