* <p>Cell data is stored in parallel arrays ({@code cell1} through {@code cell8})
* for memory efficiency. Each array stores different aspects of cell data.</p>
*
+ * <p><b>Status:</b> 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).</p>
+ *
* @see eu.svjatoslav.aukio.e3d.renderer.octree.raytracer.RayTracer
* @see eu.svjatoslav.aukio.e3d.renderer.octree.raytracer.Ray
*/
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;
}
/**
/**
* Scans cells arrays and returns pointer to found unused cell.
+ *
+ * <p>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).</p>
+ *
* @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)
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.
*
--- /dev/null
+/*
+ * 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));
+ }
+}