From: Svjatoslav Agejenko Date: Sun, 20 Sep 2026 09:13:31 +0000 (+0300) Subject: OctreeVolume: fail loudly on cell-pool exhaustion instead of hanging X-Git-Tag: aukio-3d-1.0.0~4 X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=d0c804d95faad156c651d3ed6e5f71345e110e4a;p=aukio-3d.git OctreeVolume: fail loudly on cell-pool exhaustion instead of hanging getNewCellPointer looped forever when the pool was full (wrap-rescan with no exit). Two guards now: the usedCellsCount fast path (made honest — the master cell at index 0 is now counted at initWorld) and an authoritative full-scan-cycle check that throws IllegalStateException with capacity + remediation in the message. Verified with a tiny-pool harness: 7 allocations + master fill 8 cells, the 9th throws (was: hang). Also: class javadoc now states the subsystem's demo-only status and the traceCell coverage reality; OctreeVolumeTest (2 tests) pins the exhaustion behavior with a timeout that fails on the old implementation. --- diff --git a/src/main/java/eu/svjatoslav/aukio/e3d/renderer/octree/OctreeVolume.java b/src/main/java/eu/svjatoslav/aukio/e3d/renderer/octree/OctreeVolume.java index d07f08d..377192f 100755 --- a/src/main/java/eu/svjatoslav/aukio/e3d/renderer/octree/OctreeVolume.java +++ b/src/main/java/eu/svjatoslav/aukio/e3d/renderer/octree/OctreeVolume.java @@ -25,6 +25,14 @@ import static java.lang.Integer.min; *

Cell data is stored in parallel arrays ({@code cell1} through {@code cell8}) * for memory efficiency. Each array stores different aspects of cell data.

* + *

Status: demo-only subsystem (used by {@code OctreeDemo}). + * The ray-traversal core ({@code traceCell}, ~590 lines of per-octant + * code) has no unit coverage and is exercised only through the demo's + * golden image; treat changes there as unverified by anything except the + * golden. The cell-pool capacity is fixed at construction — + * {@link #getNewCellPointer()} throws {@link IllegalStateException} when + * the pool is exhausted (it used to hang in an infinite rescan loop).

+ * * @see eu.svjatoslav.aukio.e3d.renderer.octree.raytracer.RayTracer * @see eu.svjatoslav.aukio.e3d.renderer.octree.raytracer.Ray */ @@ -315,8 +323,10 @@ public class OctreeVolume { for (int i = 0; i < bufferLength; i++) cell1[i] = CELL_STATE_UNUSED; - // initialize master cell + // initialize master cell (occupies index 0 without being + // CELL_STATE_UNUSED — count it so usedCellsCount stays honest) clearCell(0); + usedCellsCount = 1; } /** @@ -331,9 +341,21 @@ public class OctreeVolume { /** * Scans cells arrays and returns pointer to found unused cell. + * + *

Fails loudly on pool exhaustion — the previous version looped + * forever, rescanning the full pool on every wrap. The authoritative + * check is a full scan cycle back to the start position (the + * {@code usedCellsCount} fast path alone is insufficient: the master + * cell at index 0 occupies a slot without being counted).

+ * * @return pointer to found unused cell + * @throws IllegalStateException when the cell pool is full */ public int getNewCellPointer() { + if (usedCellsCount >= cell1.length) + throw poolExhausted(); + + final int start = cellAllocationPointer; while (true) { // ensure that cell allocation pointer is in bounds if (cellAllocationPointer >= cell1.length) @@ -345,11 +367,23 @@ public class OctreeVolume { usedCellsCount++; return cellAllocationPointer; - } else - cellAllocationPointer++; + } + + cellAllocationPointer++; + if (cellAllocationPointer == start) + throw poolExhausted(); } } + /** Builds the pool-exhaustion exception (shared by both guards). */ + private IllegalStateException poolExhausted() { + return new IllegalStateException( + "Octree cell pool exhausted: all " + cell1.length + + " cells are in use. Increase the pool size" + + " at OctreeVolume construction or reduce" + + " scene voxel density."); + } + /** * Allocates a new solid cell with the given color and illumination. * diff --git a/src/test/java/eu/svjatoslav/aukio/e3d/renderer/octree/OctreeVolumeTest.java b/src/test/java/eu/svjatoslav/aukio/e3d/renderer/octree/OctreeVolumeTest.java new file mode 100644 index 0000000..64bd768 --- /dev/null +++ b/src/test/java/eu/svjatoslav/aukio/e3d/renderer/octree/OctreeVolumeTest.java @@ -0,0 +1,49 @@ +/* + * Aukio 3D engine. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. + */ +package eu.svjatoslav.aukio.e3d.renderer.octree; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Cell-pool behavior of {@link OctreeVolume} — most notably the + * exhaustion path, which used to hang in an infinite rescan loop. + */ +public class OctreeVolumeTest { + + /** + * Allocating more cells than the pool holds must fail loudly + * ({@link IllegalStateException}), not hang. The 5s timeout fails the + * test on the pre-fix implementation (infinite loop). + */ + @Test(timeout = 5000) + public void cellPoolExhaustionFailsLoudly() { + final OctreeVolume volume = new OctreeVolume(); + volume.initWorld(8, 64); // tiny pool: 8 cells + + try { + // master cell + up to 8 more allocations — must throw by then + for (int i = 0; i < 16; i++) + volume.makeNewCell(0x808080, 0); + fail("expected IllegalStateException on pool exhaustion"); + } catch (final IllegalStateException e) { + assertTrue("message should name the pool capacity: " + e.getMessage(), + e.getMessage().contains("8")); + } + } + + /** Sanity: within capacity, allocation keeps working and cells are solid. */ + @Test(timeout = 5000) + public void allocationWithinCapacityWorks() { + final OctreeVolume volume = new OctreeVolume(); + volume.initWorld(16, 64); + + final int pointer = volume.makeNewCell(0x123456, 7); + assertTrue(pointer >= 0); + assertTrue(volume.isCellSolid(pointer)); + } +}