feat(renderer): Refactor rendering object pool management and update related tests

This commit is contained in:
extio-de
2025-12-26 17:09:01 +01:00
parent a119b4b5e9
commit c393fa55c8
7 changed files with 132 additions and 40 deletions

View File

@@ -205,6 +205,8 @@ public class G2DRenderer implements Renderer {
bo.staticCleanupAfterFrame();
}
this.rendererData.getRenderingBoPool().releasePending();
//
// Rendering cycle END
//

View File

@@ -26,9 +26,9 @@ public class G2DRendererControl implements RendererControl {
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;

View File

@@ -31,7 +31,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
.next()
.put(work.getId(), 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) {
final var previous = nextMap.put(bo.getId(), bo);
if (previous != null && previous != bo) {
this.rendererBoPool.release(previous);
this.rendererBoPool.returnToPool(previous);
}
}
}
@@ -52,7 +52,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
.next()
.remove(id);
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();
if (previousLiveSet != null) {
if (clone) {
previousLiveSet.values().forEach(this.rendererBoPool::release);
previousLiveSet.values().forEach(this.rendererBoPool::returnToPool);
}
this.returnMapToPool(previousLiveSet);
}
@@ -119,7 +119,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
@Override
public void clearNext(final String producerId) {
final var next = this.getWorkingSetByProducer(producerId).next();
next.values().forEach(this.rendererBoPool::release);
next.values().forEach(this.rendererBoPool::returnToPool);
next.clear();
}
@@ -127,8 +127,8 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
public void clear(final String producerId) {
final RendererWork rendererWork = this.workingSet.remove(producerId);
if (rendererWork != null) {
rendererWork.live().values().forEach(this.rendererBoPool::release);
rendererWork.next().values().forEach(this.rendererBoPool::release);
rendererWork.live().values().forEach(this.rendererBoPool::returnToPool);
rendererWork.next().values().forEach(this.rendererBoPool::returnToPool);
}
}

View File

@@ -20,9 +20,14 @@ public interface RenderingBoPool {
<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);
}

View File

@@ -1,9 +1,13 @@
package de.extio.game_engine.renderer.work;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
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.model.RenderingBo;
@@ -14,6 +18,10 @@ public class RenderingBoPoolImpl implements RenderingBoPool {
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 LastReverseMapping lastReverseMapping = new LastReverseMapping();
@@ -98,28 +106,37 @@ public class RenderingBoPoolImpl implements RenderingBoPool {
}
@Override
public synchronized void release(final RenderingBo obj) {
public void returnToPool(final RenderingBo obj) {
this.returnQueue.offer(obj);
}
@Override
public synchronized void releasePending() {
try {
obj.close();
this.returnQueue.drainTo(this.returnSink);
RenderingBo obj;
while ((obj = this.returnSink.poll()) != null) {
obj.close();
final Class<? extends RenderingBo> clazz = obj.getClass();
Stack<RenderingBo> pooled;
final Class<? extends RenderingBo> clazz = obj.getClass();
Stack<RenderingBo> pooled;
if (this.lastReverseMapping != null && this.lastReverseMapping.clazz == clazz) {
pooled = this.lastReverseMapping.pooled;
}
else {
pooled = this.pool.get(clazz);
if (pooled == null) {
pooled = new Stack<>();
this.pool.put(clazz, pooled);
if (this.lastReverseMapping != null && this.lastReverseMapping.clazz == clazz) {
pooled = this.lastReverseMapping.pooled;
}
else {
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;
this.lastReverseMapping.pooled = pooled;
pooled.push(obj);
}
pooled.push(obj);
}
catch (final Exception e) {
throw new RuntimeException(e);

View File

@@ -194,8 +194,8 @@ public class RendererWorkingSetImplTest {
final var uncommittedAfter = this.workingSet.getUncommittedWork(this.moduleA.getId());
assertTrue(uncommittedAfter.isEmpty());
verify(this.renderingBoPool, times(1)).release(bo1);
verify(this.renderingBoPool, times(1)).release(bo2);
verify(this.renderingBoPool, times(1)).returnToPool(bo1);
verify(this.renderingBoPool, times(1)).returnToPool(bo2);
}
@Test
@@ -240,8 +240,8 @@ public class RendererWorkingSetImplTest {
this.workingSet.put(this.moduleA.getId(), bo3);
this.workingSet.commit(this.moduleA.getId(), true);
verify(this.renderingBoPool, times(1)).release(bo1);
verify(this.renderingBoPool, times(1)).release(bo2);
verify(this.renderingBoPool, times(1)).returnToPool(bo1);
verify(this.renderingBoPool, times(1)).returnToPool(bo2);
}
@Test

View File

@@ -1,6 +1,7 @@
package de.extio.game_engine.renderer.work;
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.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
@@ -89,7 +90,8 @@ public class RenderingBoPoolImplTest {
assertEquals("id1", acquired1.getId());
assertEquals(0, acquired1.getCloseCallCount());
this.pool.release(acquired1);
this.pool.returnToPool(acquired1);
this.pool.releasePending();
assertEquals(1, acquired1.getCloseCallCount());
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);
assertEquals(0, bo.getCloseCallCount());
this.pool.release(bo);
this.pool.returnToPool(bo);
this.pool.releasePending();
assertEquals(1, bo.getCloseCallCount());
this.pool.release(bo);
this.pool.returnToPool(bo);
this.pool.releasePending();
assertEquals(2, bo.getCloseCallCount());
}
@@ -169,10 +173,12 @@ public class RenderingBoPoolImplTest {
assertEquals(0, second.getCloseCallCount());
assertNotSame(first, second);
this.pool.release(first);
this.pool.returnToPool(first);
this.pool.releasePending();
assertEquals(1, first.getCloseCallCount());
this.pool.release(second);
this.pool.returnToPool(second);
this.pool.releasePending();
assertEquals(1, second.getCloseCallCount());
final TestRenderingBo reacquired = this.pool.acquire("id3", TestRenderingBo.class);
@@ -182,6 +188,66 @@ public class RenderingBoPoolImplTest {
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
void testConcurrentAccess() throws InterruptedException {
final int[] thread1Count = {0};
@@ -192,7 +258,7 @@ public class RenderingBoPoolImplTest {
final TestRenderingBo bo = this.pool.acquire("id-" + i, TestRenderingBo.class);
assertNotNull(bo);
assertNotNull(bo.getId());
this.pool.release(bo);
this.pool.returnToPool(bo);
thread1Count[0]++;
}
});
@@ -202,7 +268,7 @@ public class RenderingBoPoolImplTest {
final TestRenderingBo bo = this.pool.acquire("id-" + i, TestRenderingBo.class);
assertNotNull(bo);
assertNotNull(bo.getId());
this.pool.release(bo);
this.pool.returnToPool(bo);
thread2Count[0]++;
}
});
@@ -216,6 +282,8 @@ public class RenderingBoPoolImplTest {
assertEquals(10, thread1Count[0]);
assertEquals(10, thread2Count[0]);
this.pool.releasePending();
final TestRenderingBo bo = this.pool.acquire("final-id", TestRenderingBo.class);
assertNotNull(bo);
assertEquals("final-id", bo.getId());