Merge the data structure in LodQuadTree and RenderBufferHandler
This prevents needing to sync both data structures, reducing the chance for things to go wrong
This commit is contained in:
@@ -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<AbstractRenderBuffer> renderBufferToSwap)
|
||||
public boolean trySwapRenderBufferAsync(ColumnRenderSource renderSource, AtomicReference<AbstractRenderBuffer> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ColumnRenderSource> 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<AbstractRenderBuffer> 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);
|
||||
|
||||
@@ -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<RenderBufferNode> 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<LoadedRenderBuffer> 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<LoadedRenderBuffer> sortFarToNear = (loadedBufferA, loadedBufferB) ->
|
||||
Comparator<LoadedRenderBuffer> 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<AbstractRenderBuffer> renderBufferRef = new AtomicReference<>();
|
||||
|
||||
|
||||
|
||||
public RenderBufferNode(DhSectionPos pos) { this.pos = pos; }
|
||||
|
||||
|
||||
|
||||
public void collect(SortedArraySet<LoadedRenderBuffer> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -262,16 +262,18 @@ public class QuadNode<T>
|
||||
}
|
||||
}
|
||||
|
||||
/** TODO comment */
|
||||
public void forAllLeafValues(Consumer<? super T> consumer) { this.forAllLeafValues((value, sectionPos) -> { consumer.accept(value); }); }
|
||||
/**
|
||||
* Applies the given consumer to all leaf nodes below this node. <br>
|
||||
* Note: this will pass in null values.
|
||||
*/
|
||||
public void forAllLeafValues(Consumer<? super T> callback)
|
||||
public void forAllLeafValues(BiConsumer<? super T, DhSectionPos> 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<T>
|
||||
if (childNode != null)
|
||||
{
|
||||
// TODO should this pass in a null value if the child node is null?
|
||||
childNode.forAllLeafValues(callback);
|
||||
childNode.forAllLeafValues(consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +201,8 @@ public class QuadTree<T>
|
||||
});
|
||||
}
|
||||
|
||||
public void forEachLeafValue(Consumer<? super T> consumer)
|
||||
public void forEachLeafValue(Consumer<? super T> consumer) { this.forEachLeafValue((value, sectionPos) -> { consumer.accept(value); }); }
|
||||
public void forEachLeafValue(BiConsumer<? super T, DhSectionPos> consumer)
|
||||
{
|
||||
this.forEachRootNode((rootNode) ->
|
||||
{
|
||||
@@ -260,7 +261,9 @@ public class QuadTree<T>
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user