From c393fa55c84a818135b81ee89ebb21edfa578462 Mon Sep 17 00:00:00 2001 From: extio-de Date: Fri, 26 Dec 2025 17:09:01 +0100 Subject: [PATCH] feat(renderer): Refactor rendering object pool management and update related tests --- .../game_engine/renderer/g2d/G2DRenderer.java | 2 + .../renderer/g2d/G2DRendererControl.java | 4 +- .../renderer/work/RendererWorkingSetImpl.java | 14 ++-- .../renderer/work/RenderingBoPool.java | 9 +- .../renderer/work/RenderingBoPoolImpl.java | 53 ++++++++---- .../work/RendererWorkingSetImplTest.java | 8 +- .../work/RenderingBoPoolImplTest.java | 82 +++++++++++++++++-- 7 files changed, 132 insertions(+), 40 deletions(-) diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java index 02f7d46..2a3fb92 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java @@ -204,6 +204,8 @@ public class G2DRenderer implements Renderer { for (final RenderingBo bo : usedRenderingBoTypes.values()) { bo.staticCleanupAfterFrame(); } + + this.rendererData.getRenderingBoPool().releasePending(); // // Rendering cycle END diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/G2DRendererControl.java b/src/main/java/de/extio/game_engine/renderer/g2d/G2DRendererControl.java index 18c5718..9ebe2f0 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/G2DRendererControl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/G2DRendererControl.java @@ -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; diff --git a/src/main/java/de/extio/game_engine/renderer/work/RendererWorkingSetImpl.java b/src/main/java/de/extio/game_engine/renderer/work/RendererWorkingSetImpl.java index a0dcec5..a66fc71 100644 --- a/src/main/java/de/extio/game_engine/renderer/work/RendererWorkingSetImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/work/RendererWorkingSetImpl.java @@ -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); } } diff --git a/src/main/java/de/extio/game_engine/renderer/work/RenderingBoPool.java b/src/main/java/de/extio/game_engine/renderer/work/RenderingBoPool.java index c263087..73d6904 100644 --- a/src/main/java/de/extio/game_engine/renderer/work/RenderingBoPool.java +++ b/src/main/java/de/extio/game_engine/renderer/work/RenderingBoPool.java @@ -20,9 +20,14 @@ public interface RenderingBoPool { 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); } diff --git a/src/main/java/de/extio/game_engine/renderer/work/RenderingBoPoolImpl.java b/src/main/java/de/extio/game_engine/renderer/work/RenderingBoPoolImpl.java index f45f020..07993bc 100644 --- a/src/main/java/de/extio/game_engine/renderer/work/RenderingBoPoolImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/work/RenderingBoPoolImpl.java @@ -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, Stack> pool = new HashMap<>(); + private final BlockingQueue returnQueue = new ArrayBlockingQueue<>(1000); + + private final Deque 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(); - - final Class clazz = obj.getClass(); - Stack 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.returnQueue.drainTo(this.returnSink); + RenderingBo obj; + while ((obj = this.returnSink.poll()) != null) { + obj.close(); + + final Class clazz = obj.getClass(); + Stack 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); diff --git a/src/test/java/de/extio/game_engine/renderer/work/RendererWorkingSetImplTest.java b/src/test/java/de/extio/game_engine/renderer/work/RendererWorkingSetImplTest.java index 160079b..9006468 100644 --- a/src/test/java/de/extio/game_engine/renderer/work/RendererWorkingSetImplTest.java +++ b/src/test/java/de/extio/game_engine/renderer/work/RendererWorkingSetImplTest.java @@ -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 diff --git a/src/test/java/de/extio/game_engine/renderer/work/RenderingBoPoolImplTest.java b/src/test/java/de/extio/game_engine/renderer/work/RenderingBoPoolImplTest.java index a25ac98..343d97c 100644 --- a/src/test/java/de/extio/game_engine/renderer/work/RenderingBoPoolImplTest.java +++ b/src/test/java/de/extio/game_engine/renderer/work/RenderingBoPoolImplTest.java @@ -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());