diff --git a/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java b/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java index 6f996084a..838ae80bb 100644 --- a/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java @@ -339,17 +339,16 @@ public class ColumnRenderSource // Render Methods // //================// - private void tryBuildBuffer(IDhClientLevel level, LodQuadTree quadTree) + private void tryBuildBuffer(IDhClientLevel level, ColumnRenderSource renderSource) { if (this.buildRenderBufferFuture == null && !ColumnRenderBuffer.isBusy() && !this.isEmpty) { ColumnRenderSource[] columnRenderSources = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length]; for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS) { - LodRenderSection renderSection = quadTree.getSection(this.sectionPos.getAdjacentPos(direction)); //FIXME: Handle traveling through different detail levels - if (renderSection != null && renderSection.getRenderSource() != null && renderSection.getRenderSource() instanceof ColumnRenderSource) + if (renderSource != null) { - columnRenderSources[direction.ordinal() - 2] = ((ColumnRenderSource) renderSection.getRenderSource()); + columnRenderSources[direction.ordinal() - 2] = renderSource; //LOGGER.info("attempting to build buffer for: "+renderSection.pos); } } @@ -383,7 +382,7 @@ public class ColumnRenderSource * @param renderBufferToSwap The slot for swapping in the new buffer. * @return True if the swap was successful. False if swap is not needed or if it is in progress. */ - public boolean trySwapRenderBufferAsync(LodQuadTree quadTree, AtomicReference renderBufferToSwap) + public boolean trySwapRenderBufferAsync(ColumnRenderSource renderSource, AtomicReference renderBufferToSwap) { // prevent swapping the buffer to quickly if (this.lastNs != -1 && System.nanoTime() - this.lastNs < SWAP_TIMEOUT_IN_NS) @@ -425,7 +424,7 @@ public class ColumnRenderSource } else { - this.tryBuildBuffer(this.level, quadTree); + this.tryBuildBuffer(this.level, renderSource); } } } diff --git a/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java index 5ddbae4f8..a8c1480b8 100644 --- a/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java @@ -9,6 +9,7 @@ import com.seibel.lod.core.util.objects.quadTree.QuadNode; import org.apache.logging.log4j.Logger; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; public class LodRenderSection { @@ -16,19 +17,16 @@ public class LodRenderSection public final DhSectionPos pos; - // TODO create an enum to represent the section's state instead of using magic numbers in the childCount - // states (may not be a complete or correct list): loaded (childCount 4), unloaded (childCount 0), markedForDeletion/markedForFreeing (childCount -1) - private CompletableFuture loadFuture; private boolean isRenderEnabled = false; - // TODO: Should I provide a way to change the render source? private ColumnRenderSource renderSource; private ILodRenderSourceProvider renderSourceProvider = null; + public final AtomicReference abstractRenderBufferRef = new AtomicReference<>(); + - // Create sub region public LodRenderSection(DhSectionPos pos) { this.pos = pos; } @@ -129,6 +127,12 @@ public class LodRenderSection this.renderSource = null; } + if (this.abstractRenderBufferRef.get() != null) + { + this.abstractRenderBufferRef.get().close(); + this.abstractRenderBufferRef.set(null); + } + if (this.loadFuture != null) { this.loadFuture.cancel(true); diff --git a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java index 6d33e3652..8be52bfa2 100644 --- a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java +++ b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java @@ -6,40 +6,30 @@ import com.seibel.lod.core.logging.DhLoggerBuilder; import com.seibel.lod.core.pos.Pos2D; import com.seibel.lod.core.pos.DhSectionPos; import com.seibel.lod.core.render.renderer.LodRenderer; -import com.seibel.lod.core.util.BitShiftUtil; import com.seibel.lod.core.util.LodUtil; -import com.seibel.lod.core.util.gridList.MovableGridRingList; import com.seibel.lod.core.util.math.Vec3f; import com.seibel.lod.core.util.objects.SortedArraySet; import org.apache.logging.log4j.Logger; import java.util.Comparator; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -/** This object tells the {@link LodRenderer} what buffers to render */ +/** + * This object tells the {@link LodRenderer} what buffers to render + * TODO rename this class, maybe RenderBufferOrganizer or something more specific? + */ public class RenderBufferHandler { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + /** contains all relevant data */ public final LodQuadTree quadTree; - private final MovableGridRingList renderBufferNodesGridList; // TODO: Make sorting go into the update loop instead of the render loop as it doesn't need to be done every frame private SortedArraySet loadedNearToFarBuffers = null; - public RenderBufferHandler(LodQuadTree quadTree) - { - this.quadTree = quadTree; - - Pos2D expectedCenterPos = new Pos2D( - BitShiftUtil.divideByPowerOfTwo(this.quadTree.getCenterBlockPos().x, this.quadTree.treeMaxDetailLevel), - BitShiftUtil.divideByPowerOfTwo(this.quadTree.getCenterBlockPos().z, this.quadTree.treeMaxDetailLevel)); - - this.renderBufferNodesGridList = new MovableGridRingList<>(quadTree.ringListWidth()/4, expectedCenterPos); - } + public RenderBufferHandler(LodQuadTree lodQuadTree) { this.quadTree = lodQuadTree; } @@ -107,7 +97,7 @@ public class RenderBufferHandler } // Now that we have the axis directions, we can sort the render list - Comparator sortFarToNear = (loadedBufferA, loadedBufferB) -> + Comparator farToNearComparator = (loadedBufferA, loadedBufferB) -> { Pos2D aPos = loadedBufferA.pos.getCenter().getCenterBlockPos().toPos2D(); Pos2D bPos = loadedBufferB.pos.getCenter().getCenterBlockPos().toPos2D(); @@ -144,16 +134,19 @@ public class RenderBufferHandler }; // Build the sorted list - this.loadedNearToFarBuffers = new SortedArraySet<>((a, b) -> -sortFarToNear.compare(a, b)); - - // Add all the loaded buffers to the sorted list - this.renderBufferNodesGridList.forEach((renderBufferNode) -> + this.loadedNearToFarBuffers = new SortedArraySet<>((a, b) -> -farToNearComparator.compare(a, b)); // TODO is the comparator named wrong? + this.quadTree.forEachLeafValue((renderSection, sectionPos) -> { - if (renderBufferNode != null) + if (renderSection != null && renderSection.shouldRender()) { - renderBufferNode.collect(this.loadedNearToFarBuffers); + // this should always be true + if (renderSection.abstractRenderBufferRef.get() != null) + { + this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(renderSection.abstractRenderBufferRef.get(), sectionPos)); + } } }); + } public void renderOpaque(LodRenderer renderContext) @@ -171,52 +164,43 @@ public class RenderBufferHandler public void update() { - byte topDetailLevel = this.quadTree.treeMaxDetailLevel; - Pos2D expectedCenterPos = new Pos2D( - BitShiftUtil.divideByPowerOfTwo(this.quadTree.getCenterBlockPos().x, this.quadTree.treeMaxDetailLevel), - BitShiftUtil.divideByPowerOfTwo(this.quadTree.getCenterBlockPos().z, this.quadTree.treeMaxDetailLevel)); - this.renderBufferNodesGridList.moveTo(expectedCenterPos.x, expectedCenterPos.y, RenderBufferNode::close); // Note: may lock the list - - - - this.renderBufferNodesGridList.forEachPosOrdered((rootRenderBufferNode, pos2d) -> + this.quadTree.forEachLeafValue((renderSection, sectionPos) -> { - try + if (renderSection != null) { - - DhSectionPos rootSectionPos = new DhSectionPos(topDetailLevel, pos2d.x, pos2d.y); - LodRenderSection rootRenderSection = this.quadTree.getSection(rootSectionPos); + ColumnRenderSource currentRenderSource = renderSection.getRenderSource(); - if (rootRenderSection == null && rootRenderBufferNode != null) + // Update self's render buffer state + if (!renderSection.shouldRender()) { - // section is null, but a node exists, remove the node - this.renderBufferNodesGridList.remove(pos2d).close(); - } - else if (rootRenderSection != null) - { - - if (rootRenderBufferNode == null) + //TODO: Does this really need to force the old buffer to not be rendered? + AbstractRenderBuffer previousRenderBuffer = renderSection.abstractRenderBufferRef.getAndSet(null); + if (previousRenderBuffer != null) { - // renderSection exists, but node does not - rootRenderBufferNode = this.renderBufferNodesGridList.setChained(pos2d, new RenderBufferNode(rootSectionPos)); + previousRenderBuffer.close(); } - - // Update the render node - rootRenderBufferNode.update(); } - - } - catch (Exception e) - { - // TODO when we are stable this shouldn't be necessary - LOGGER.error(RenderBufferHandler.class.getSimpleName()+" exception in update for the quadTree: "+this.quadTree+", exception: "+e.getMessage(), e); - int breaker = 0; + else + { + LodUtil.assertTrue(currentRenderSource != null); // section.shouldRender() should have ensured this + currentRenderSource.trySwapRenderBufferAsync(renderSection.getRenderSource(), renderSection.abstractRenderBufferRef); + } } }); } - public void close() { this.renderBufferNodesGridList.clear(RenderBufferNode::close); } + public void close() + { + this.quadTree.forEachLeafValue((renderSection) -> + { + if (renderSection.abstractRenderBufferRef.get() != null) + { + renderSection.abstractRenderBufferRef.get().close(); + renderSection.abstractRenderBufferRef.set(null); + } + }); + } @@ -236,141 +220,4 @@ public class RenderBufferHandler } } - - private class RenderBufferNode implements AutoCloseable - { - private final DhSectionPos pos; - private volatile RenderBufferNode[] children = null; - - //FIXME: The multiple Atomics will cause race conditions between them! - private final AtomicReference renderBufferRef = new AtomicReference<>(); - - - - public RenderBufferNode(DhSectionPos pos) { this.pos = pos; } - - - - public void collect(SortedArraySet sortedSet) - { - AbstractRenderBuffer renderBuffer = this.renderBufferRef.get(); - if (renderBuffer != null) - { - sortedSet.add(new LoadedRenderBuffer(renderBuffer, this.pos)); - } - else - { - RenderBufferNode[] children = this.children; - if (children != null) - { - for (RenderBufferNode child : children) - { - child.collect(sortedSet); - } - } - } - } - - - //TODO: In the future make this logic a bit more complete so that when children are just created, - // the buffer is only unloaded if all children's buffers are ready. This will make the - // transition between buffers no longer causing any flicker. - public void update() - { - LodRenderSection renderSection = quadTree.getSection(this.pos); - - // If this fails, there may be a concurrent modification of the quad tree - // (as this update() should be called from the same thread that calls update() on the quad tree) - LodUtil.assertTrue(renderSection != null, RenderBufferHandler.class.getSimpleName()+" update failed. Expected to find a "+LodRenderSection.class.getSimpleName()+" at pos: "+this.pos+" in the "+LodQuadTree.class.getSimpleName()); - - ColumnRenderSource currentRenderSource = renderSection.getRenderSource(); - - // Update self's render buffer state - if (!renderSection.shouldRender()) - { - //TODO: Does this really need to force the old buffer to not be rendered? - AbstractRenderBuffer previousRenderBuffer = this.renderBufferRef.getAndSet(null); - if (previousRenderBuffer != null) - { - previousRenderBuffer.close(); - } - } - else - { - LodUtil.assertTrue(currentRenderSource != null); // section.shouldRender() should have ensured this - currentRenderSource.trySwapRenderBufferAsync(quadTree, this.renderBufferRef); - } - - - // Update children's render buffer state - // TODO: Improve this! (Checking section.isLoaded() as if its not loaded, it can only be because - // it has children. (But this logic is... really hard to read!) - // FIXME: Above comment is COMPLETELY WRONG! I am an idiot! - int loadedChildCount = quadTree.getNonNullChildCountAtPos(renderSection.pos); - boolean sectionHasChildren = loadedChildCount != 0; - if (sectionHasChildren) - { - if (this.children == null) - { - RenderBufferNode[] potentialChildren = new RenderBufferNode[loadedChildCount]; - AtomicInteger childIndexRef = new AtomicInteger(0); - renderSection.pos.forEachChild((childSectionPos) -> - { - LodRenderSection childRenderSection = quadTree.get(childSectionPos); - if (childRenderSection != null) - { - int i = childIndexRef.get(); - potentialChildren[i] = new RenderBufferNode(childSectionPos); - childIndexRef.getAndAdd(1); - } - }); - - if (childIndexRef.get() != 0) - { - this.children = potentialChildren; - } - } - - for (RenderBufferNode child : this.children) - { - child.update(); - } - } - else - { - if (this.children != null) - { - //FIXME: Concurrency issue here: If render thread is concurrently using the child's buffer, - // and this thread got priority to close the buffer, it causes a bug where the render thread - // will be using a closed buffer!!!! - RenderBufferNode[] children = this.children; - this.children = null; - for (RenderBufferNode child : children) - { - child.close(); - } - } - } - } - - @Override - public void close() - { - if (this.children != null) - { - for (RenderBufferNode child : this.children) - { - child.close(); - } - } - - AbstractRenderBuffer renderBuffer = this.renderBufferRef.getAndSet(null); - if (renderBuffer != null) - { - renderBuffer.close(); - } - } - } - - } diff --git a/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadNode.java b/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadNode.java index 44936a8cd..6047db70f 100644 --- a/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadNode.java +++ b/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadNode.java @@ -262,16 +262,18 @@ public class QuadNode } } + /** TODO comment */ + public void forAllLeafValues(Consumer consumer) { this.forAllLeafValues((value, sectionPos) -> { consumer.accept(value); }); } /** * Applies the given consumer to all leaf nodes below this node.
* Note: this will pass in null values. */ - public void forAllLeafValues(Consumer callback) + public void forAllLeafValues(BiConsumer consumer) { if (this.getChildCount() == 0 || this.sectionPos.sectionDetailLevel == this.minimumDetailLevel) { // base case, bottom leaf node found - callback.accept(this.value); + consumer.accept(this.value, this.sectionPos); } else { @@ -281,7 +283,7 @@ public class QuadNode if (childNode != null) { // TODO should this pass in a null value if the child node is null? - childNode.forAllLeafValues(callback); + childNode.forAllLeafValues(consumer); } } } diff --git a/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadTree.java b/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadTree.java index e22e0da12..272947461 100644 --- a/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadTree.java +++ b/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadTree.java @@ -201,7 +201,8 @@ public class QuadTree }); } - public void forEachLeafValue(Consumer consumer) + public void forEachLeafValue(Consumer consumer) { this.forEachLeafValue((value, sectionPos) -> { consumer.accept(value); }); } + public void forEachLeafValue(BiConsumer consumer) { this.forEachRootNode((rootNode) -> { @@ -260,7 +261,9 @@ public class QuadTree return count.get(); } - public int ringListWidth() { return this.topRingList.getWidth(); } + // TODO comment, currently a tree will always have 9 root nodes, because the tree will grow all the way up to the top, if this is ever changed then these values must also change + public int ringListWidth() { return 3; } + public int ringListHalfWidth() { return 1; } public int diameterInBlocks() { return this.widthInBlocks; } // public String getDebugString()