diff --git a/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java index e978a0fc6..045d9f617 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java @@ -74,7 +74,9 @@ public class WorldGenerationQueue implements Closeable this.maxDataDetail = generator.getMaxDataDetailLevel(); this.minDataDetail = generator.getMinDataDetailLevel(); - this.waitingTaskQuadTree = new QuadTree<>(Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH, DhBlockPos2D.ZERO /*the quad tree will be re-centered later*/); + int treeWidth = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH; + byte treeMinDetailLevel = LodUtil.BLOCK_DETAIL_LEVEL; // the tree shouldn't need to go this low, but just in case + this.waitingTaskQuadTree = new QuadTree<>(treeWidth, DhBlockPos2D.ZERO /*the quad tree will be re-centered later*/, treeMinDetailLevel); if (this.minGranularity < LodUtil.CHUNK_DETAIL_LEVEL) @@ -117,10 +119,17 @@ public class WorldGenerationQueue implements Closeable LodUtil.assertTrue(pos.detailLevel > requiredDataDetail + LodUtil.CHUNK_DETAIL_LEVEL/*TODO is chunkDetailLevel the correct replacement? otherwise the magic number was 4*/); - - CompletableFuture future = new CompletableFuture<>(); - this.waitingTaskQuadTree.set(new DhSectionPos(pos.detailLevel, pos.x, pos.z), new WorldGenTask(pos, requiredDataDetail, tracker, future)); - return future; + DhSectionPos requestPos = new DhSectionPos(pos.detailLevel, pos.x, pos.z); + if (this.waitingTaskQuadTree.isSectionPosInBounds(requestPos)) + { + CompletableFuture future = new CompletableFuture<>(); + this.waitingTaskQuadTree.set(requestPos, new WorldGenTask(pos, requiredDataDetail, tracker, future)); + return future; + } + else + { + return CompletableFuture.completedFuture(WorldGenResult.CreateFail()); + } } diff --git a/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java index 731a57b46..ed3a470b6 100644 --- a/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java @@ -6,16 +6,13 @@ import com.seibel.lod.core.pos.DhBlockPos2D; import com.seibel.lod.core.pos.DhSectionPos; import com.seibel.lod.core.file.renderfile.ILodRenderSourceProvider; import com.seibel.lod.core.logging.DhLoggerBuilder; -import com.seibel.lod.core.pos.Pos2D; -import com.seibel.lod.core.util.BitShiftUtil; import com.seibel.lod.core.util.DetailDistanceUtil; import com.seibel.lod.core.util.LodUtil; -import com.seibel.lod.core.util.MathUtil; import com.seibel.lod.core.util.gridList.MovableGridRingList; +import com.seibel.lod.core.util.objects.quadTree.QuadNode; +import com.seibel.lod.core.util.objects.quadTree.QuadTree; import org.apache.logging.log4j.Logger; -import java.util.concurrent.CompletableFuture; - /** * This quadTree structure is our core data structure and holds * all rendering data.

@@ -27,21 +24,8 @@ import java.util.concurrent.CompletableFuture; * -by adding data with the lodBuilder
*

* The QuadTree is built from several layers of 2d ring buffers. - *

- * - * Example of how the tree is visualized (please view in code, otherwise the spacing isn't maintained): - * - * C---C---C---C--- Detail level = 2
- * B-B-B-B- Detail level = 1
- * AAAA Detail level = 0
- *
- *

- * The tree doesn't go all the way down for all areas. Looking at the example above, - * detail level 0 only exists for the middle 4 positions, attempting to access detail level 0 - * outside that range will always return null, and cannot be set. - * This is done to reduce memory and processing since we only render detail levels out a certain distance. */ -public class LodQuadTree implements AutoCloseable +public class LodQuadTree extends QuadTree implements AutoCloseable { /** * Note: all config values should be via the class that extends this class, and @@ -61,13 +45,6 @@ public class LodQuadTree implements AutoCloseable public final byte getLayerSectionDetail(byte dataDetail) { return (byte) (dataDetail + this.getLayerSectionDetailOffset()); } - /** AKA how many detail levels are in this quad tree */ - public final byte numbersOfSectionDetailLevels; - /** related to {@link LodQuadTree#numbersOfSectionDetailLevels}, the largest number detail level in this tree. */ - public final byte treeMaxDetailLevel; - - private final MovableGridRingList[] renderSectionRingLists; - public final int blockRenderDistance; private final ILodRenderSourceProvider renderSourceProvider; @@ -84,97 +61,22 @@ public class LodQuadTree implements AutoCloseable - /** - * Constructor of the quadTree - * @param viewDistance View distance in blocks - * @param initialPlayerX player x block coordinate - * @param initialPlayerZ player z block coordinate - */ public LodQuadTree( - IDhClientLevel level, int viewDistance, - int initialPlayerX, int initialPlayerZ, + IDhClientLevel level, int viewDistanceInBlocks, + int initialPlayerBlockX, int initialPlayerBlockZ, ILodRenderSourceProvider provider) { + super(viewDistanceInBlocks, new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL); + DetailDistanceUtil.updateSettings(); //TODO: Move this to somewhere else this.level = level; this.renderSourceProvider = provider; - this.blockRenderDistance = viewDistance; + this.blockRenderDistance = viewDistanceInBlocks; - - - // Calculate the max section detail level // - - byte maxDetailLevel = this.getMaxDetailInRange(viewDistance * Math.sqrt(2)); - this.treeMaxDetailLevel = this.getLayerSectionDetail(maxDetailLevel); - this.numbersOfSectionDetailLevels = (byte) (this.treeMaxDetailLevel + 1); - this.renderSectionRingLists = new MovableGridRingList[this.numbersOfSectionDetailLevels - TREE_LOWEST_DETAIL_LEVEL]; - - - - // Construct the ringLists // - - LOGGER.info("Creating "+MovableGridRingList.class.getSimpleName()+" with player center at {}", new Pos2D(initialPlayerX, initialPlayerZ)); - for (byte sectionDetailLevel = TREE_LOWEST_DETAIL_LEVEL; sectionDetailLevel < this.numbersOfSectionDetailLevels; sectionDetailLevel++) - { - byte targetDetailLevel = this.getLayerDataDetail(sectionDetailLevel); - int maxDist = this.getFurthestDistance(targetDetailLevel); - - // always 10 - int halfSize = MathUtil.ceilDiv(maxDist, BitShiftUtil.powerOfTwo(sectionDetailLevel)) + 8; // +8 to make sure the section is fully contained in the ringList //TODO what does the "8" represent? - - - // check that the detail level and position are valid - DhSectionPos checkedPos = new DhSectionPos(sectionDetailLevel, halfSize, halfSize); - byte checkedDetailLevel = this.calculateExpectedDetailLevel(new DhBlockPos2D(initialPlayerX, initialPlayerZ), checkedPos); - // validate the detail level - LodUtil.assertTrue(checkedDetailLevel > targetDetailLevel, - "in "+sectionDetailLevel+", getFurthestDistance would return "+maxDist+" which would be contained in range "+(halfSize-2)+", but calculateExpectedDetailLevel at "+checkedPos+" is "+checkedDetailLevel+" <= "+targetDetailLevel); - - - // create the new ring list - Pos2D ringListCenterPos = new Pos2D(BitShiftUtil.divideByPowerOfTwo(initialPlayerX, sectionDetailLevel), BitShiftUtil.divideByPowerOfTwo(initialPlayerZ, sectionDetailLevel)); - - LOGGER.info("Creating "+MovableGridRingList.class.getSimpleName()+" centered on "+ringListCenterPos+" with halfSize ["+halfSize+"] (maxDist ["+maxDist+"], dataDetail ["+targetDetailLevel+"])"); - this.renderSectionRingLists[sectionDetailLevel - TREE_LOWEST_DETAIL_LEVEL] = new MovableGridRingList<>(halfSize, ringListCenterPos.x, ringListCenterPos.y); - - } - - int breakPoint = 0; } - /** - * This method return the LodSection given the Section Pos - * @param pos the section position. - * @return the LodSection - */ - public LodRenderSection getSection(DhSectionPos pos) { return this.getSection(pos.sectionDetailLevel, pos.sectionX, pos.sectionZ); } - - /** - * This method returns the RingList of a given detail level - * @apiNote The returned ringList should not be modified! // TODO why? - * @param detailLevel the detail level - * @return the RingList, will return null if no ringList exists for the given detailLevel - */ - public MovableGridRingList getRingListForDetailLevel(byte detailLevel) - { - int index = detailLevel - TREE_LOWEST_DETAIL_LEVEL; - if (index < 0 || index > this.renderSectionRingLists.length) - { - return null; - } - return this.renderSectionRingLists[index]; - } - - /** - * This method returns the number of detail levels in the quadTree - * @return the number of detail levels - */ - public byte getNumbersOfSectionDetailLevels() { return this.numbersOfSectionDetailLevels; } - - public byte getStartingSectionLevel() { return TREE_LOWEST_DETAIL_LEVEL; } - /** * This method return the LodSection at the given detail level and level coordinate x and z * @param detailLevel detail level of the section @@ -182,13 +84,14 @@ public class LodQuadTree implements AutoCloseable * @param z z coordinate of the section * @return the LodSection */ - public LodRenderSection getSection(byte detailLevel, int x, int z) { return this.renderSectionRingLists[detailLevel - TREE_LOWEST_DETAIL_LEVEL].get(x, z); } - + public LodRenderSection getSection(byte detailLevel, int x, int z) { return this.get(new DhSectionPos(detailLevel, x, z)); } + public LodRenderSection getSection(DhSectionPos pos) { return this.get(pos); } - - /** + + + /** * This method will compute the detail level based on player position and section pos * Override this method if you want to use a different algorithm * @param playerPos player position as a reference for calculating the detail level @@ -253,43 +156,10 @@ public class LodQuadTree implements AutoCloseable { try { - // recenter the grid lists if necessary - for (int sectionDetailLevel = TREE_LOWEST_DETAIL_LEVEL; sectionDetailLevel < this.numbersOfSectionDetailLevels; sectionDetailLevel++) - { - byte sectionDetailLevelByte = (byte) sectionDetailLevel; - Pos2D expectedCenterPos = new Pos2D(BitShiftUtil.divideByPowerOfTwo(playerPos.x, sectionDetailLevel), BitShiftUtil.divideByPowerOfTwo(playerPos.z, sectionDetailLevel)); - MovableGridRingList gridList = this.renderSectionRingLists[sectionDetailLevel - TREE_LOWEST_DETAIL_LEVEL]; - - if (!gridList.getCenter().equals(expectedCenterPos)) - { - LOGGER.info("TreeTick: Moving ring list "+sectionDetailLevel+" from "+gridList.getCenter()+" to "+expectedCenterPos); -// gridList.moveTo(expectedCenterPos.x, expectedCenterPos.y, LodRenderSection::disposeRenderData); - - gridList.moveTo(expectedCenterPos.x, expectedCenterPos.y, null, (gridListPos, lodRenderSection) -> - { - if (lodRenderSection != null && lodRenderSection.childCount != -1) - { - lodRenderSection.disposeRenderData(); - - DhSectionPos sectionPos = new DhSectionPos(sectionDetailLevelByte, gridListPos.x, gridListPos.y); - LodRenderSection parentSection = this.getParentSection(sectionPos); - if (parentSection != null) - { - parentSection.childCount--; - } - - lodRenderSection.childCount = -1; - - LOGGER.info("deleting renderSection at: "+sectionPos); - } - }); - } - } + // recenter if necessary + this.setCenterBlockPos(playerPos, LodRenderSection::disposeRenderData); - - updateAllRenderSectionChildCounts(playerPos); - - updateAllRenderSections(); + updateAllRenderSections(playerPos); } catch (Exception e) { @@ -298,491 +168,103 @@ public class LodQuadTree implements AutoCloseable } } - private void updateAllRenderSectionChildCounts(DhBlockPos2D playerPos) + + + private void updateAllRenderSections(DhBlockPos2D playerPos) { - - for (byte sectionDetailLevelIteration = TREE_LOWEST_DETAIL_LEVEL; sectionDetailLevelIteration < this.numbersOfSectionDetailLevels; sectionDetailLevelIteration++) + this.forEachRootNodePos((rootNode, rootSectionPos) -> { - final byte sectionDetailLevel = sectionDetailLevelIteration; // final to prevent accidentally setting (and because intellij highlights final values different so it is easier to identify) - - final MovableGridRingList ringList = this.renderSectionRingLists[sectionDetailLevel- TREE_LOWEST_DETAIL_LEVEL]; - - // child and parent are relative to the detail level - final MovableGridRingList childRingList = (sectionDetailLevel == TREE_LOWEST_DETAIL_LEVEL) ? null : this.renderSectionRingLists[sectionDetailLevel- TREE_LOWEST_DETAIL_LEVEL -1]; - final MovableGridRingList parentRingList = (sectionDetailLevel == this.treeMaxDetailLevel) ? null : this.renderSectionRingLists[sectionDetailLevel- TREE_LOWEST_DETAIL_LEVEL +1]; - - - ringList.forEachPosOrdered((renderSection, tree2dPos) -> + if (rootNode == null) { - // TODO why do we need to use the halfPos to get sections? - final Pos2D halfPos = new Pos2D(BitShiftUtil.half(tree2dPos.x), BitShiftUtil.half(tree2dPos.y)); - - - final DhSectionPos sectionPos = new DhSectionPos(sectionDetailLevel, tree2dPos.x, tree2dPos.y); - // confirm sectionPos is correct - LodUtil.assertTrue(sectionPos.sectionDetailLevel == sectionDetailLevel - && sectionPos.sectionX == tree2dPos.x - && sectionPos.sectionZ == tree2dPos.y, - "sectionPos "+sectionPos+" != "+tree2dPos+" @ "+sectionDetailLevel); - - - byte targetDetailLevel = this.calculateExpectedDetailLevel(playerPos, sectionPos); - boolean renderSectionDetailLevelTooHigh = targetDetailLevel > this.getLayerDataDetail(sectionDetailLevel); - - - - if (renderSection != null) - { - if (sectionDetailLevel == TREE_LOWEST_DETAIL_LEVEL) - { - // this section is a leaf node, set its children to 0 - renderSection.childCount = 0; - - runValidations(renderSection); - } - else if (renderSection.childCount > 0) - { - // this section is NOT a leaf node - LodUtil.assertTrue(childRingList != null); - - - if (renderSectionDetailLevelTooHigh) - { - // this section is a higher detail level than we want, mark it for deletion - renderSection.childCount = -1; - - - // update the parent section's child count if present - if (parentRingList != null) - { - LodRenderSection parent = this._getNotNull(parentRingList, halfPos.x, halfPos.y); - LodUtil.assertTrue(parent.childCount >= 1 && parent.childCount <= 4, "parent section at target detail level ["+targetDetailLevel+"] has the wrong number of children. Expected 1 to 4, actual count: ["+parent.childCount+"]."); - - parent.childCount--; - if (SUPER_VERBOSE_LOGGING) - { - LOGGER.info("parent sect "+renderSection.pos+" now has "+parent.childCount+" child."); - } - } - - - // TODO confirm children are also deleted correctly, should happen automatically when going through the layers, but just in case - - if (SUPER_VERBOSE_LOGGING) - { - LOGGER.info("sect "+renderSection.pos+" in top detail level & target>current. Mark as free."); - } - - runValidations(renderSection); - } - else - { - // this section is at or below the requested detail level, - // make sure its parent and children are loaded - - - // parentRingList will be null if we are at the top detail level - if (parentRingList != null) - { - boolean createdNewParent = false; - - LodRenderSection parentSection = this._getRenderSectionFromGridList(parentRingList, halfPos.x, halfPos.y); - if (parentSection == null) - { - // the parent render section is missing, create it - if (SUPER_VERBOSE_LOGGING) - { - LOGGER.info("sect "+renderSection.pos+" missing parent. Creating at "+renderSection.pos.getParentPos()); - } - - parentSection = new LodRenderSection(renderSection.pos.getParentPos()); - parentSection = this._setRenderSectionInGridList(parentRingList, halfPos.x, halfPos.y, parentSection); - LodUtil.assertTrue(parentSection != null); // if the section is null, that means the position is outside the quad tree - - parentSection.childCount = 1; - - if (SUPER_VERBOSE_LOGGING) - { - LOGGER.info("parent sect "+renderSection.pos.getParentPos()+" now has "+parentSection.childCount+" children."); - } - - createdNewParent = true; - } - - if (parentSection.childCount == 0 || parentSection.childCount == -1) - { - byte parentSectionChildCount = 0; - for (int innerChildIndex = 0; innerChildIndex < 4; innerChildIndex++) - { - DhSectionPos parentChildPos = parentSection.pos.getChildByIndex(innerChildIndex); - LodRenderSection parentChildSection = this._getRenderSectionFromGridList(ringList, parentChildPos.sectionX, parentChildPos.sectionZ); - if (parentChildSection != null && parentChildSection.childCount != -1) - { - // TODO this isn't getting this section's position, I probably goofed the math - parentSectionChildCount++; - } - } - - parentSection.childCount = parentSectionChildCount; - } - - LodUtil.assertTrue(parentSection.childCount > 0 && parentSection.childCount <= 4, (createdNewParent ? "New " : "")+" Parent section expected to have 1-4 children, actual child count: "+parentSection.childCount); - } - - - // load this section's children - for (int childIndex = 0; childIndex < 4; childIndex++) - { - DhSectionPos childPos = renderSection.pos.getChildByIndex(childIndex); - LodRenderSection childRenderSection = this._getRenderSectionFromGridList(childRingList, childPos.sectionX, childPos.sectionZ); - if (childRenderSection == null) - { - // no child exists, create one - - if (SUPER_VERBOSE_LOGGING) - { - LOGGER.info("sect "+renderSection.pos+" missing child at "+childPos+". Creating."); - } - - childRenderSection = new LodRenderSection(childPos); - childRenderSection = this._setRenderSectionInGridList(childRingList, childPos.sectionX, childPos.sectionZ, childRenderSection); - LodUtil.assertTrue(childRenderSection != null); // the childPos is outside the quadTree - } - else if (childRenderSection.childCount == -1) - { - // a child existed but was marked for deletion, - // rescue (reuse) it - - if (SUPER_VERBOSE_LOGGING) - { - LOGGER.info("sect "+renderSection.pos+" rescued child at "+childPos+"."); - } - - // TODO this hasn't been hit yet, but make sure it gets the right number of children - - MovableGridRingList grandChildRingList = getRingListForDetailLevel((byte) (childRenderSection.pos.sectionDetailLevel-1)); - if (grandChildRingList != null) - { - byte childSectionChildCount = 0; - for (int innerChildIndex = 0; innerChildIndex < 4; innerChildIndex++) - { - DhSectionPos innerChildPos = renderSection.pos.getChildByIndex(innerChildIndex); - LodRenderSection r = this._getRenderSectionFromGridList(grandChildRingList, innerChildPos.sectionX, innerChildPos.sectionZ); - if (r != null && r.childCount != -1) - { - childSectionChildCount++; - } - } - childRenderSection.childCount = childSectionChildCount; - } - else - { - childRenderSection.childCount = 0; - } - - } - else - { - // the child render section exists in a usable state, nothing needs to be done - } - } - - // this section is now fully loaded - renderSection.childCount = 4; - - runValidations(renderSection); - } - - runValidations(renderSection); - } - else - { - // render section has 0 children - - // make sure all children are marked for disposal - for (int innerChildIndex = 0; innerChildIndex < 4; innerChildIndex++) - { - DhSectionPos childPos = renderSection.pos.getChildByIndex(innerChildIndex); - LodRenderSection childSection = this._getRenderSectionFromGridList(childRingList, childPos.sectionX, childPos.sectionZ); - if (childSection != null && childSection.childCount != -1) - { - childSection.disposeRenderData(); - childSection.childCount = -1; - } - } - - runValidations(renderSection); - } - } - else - { - // render section is null - - if (SUPER_VERBOSE_LOGGING) - { - String layerDetailLevel = (sectionDetailLevel == this.treeMaxDetailLevel) ? "N/A" : this.getLayerDataDetail((byte) (sectionDetailLevel+1))+""; - LOGGER.info("0 child sect "+sectionPos+"(null?"+ true +") - target:"+targetDetailLevel+"/"+this.getLayerDataDetail(sectionDetailLevel)+" (parent:"+layerDetailLevel+")"); - } - - if (targetDetailLevel < this.getLayerDataDetail((byte) (sectionDetailLevel + 1))) // TODO replace with renderSectionDetailLevelTooHigh? - { - // the render section for this detail level is missing, create it - - if (SUPER_VERBOSE_LOGGING) - { - LOGGER.info("null sect "+sectionPos+" target ringList = this.renderSectionRingLists[sectionDetailLevelIteration- TREE_LOWEST_DETAIL_LEVEL]; - ringList.forEachPosOrdered((renderSection, tree2dPos) -> + LodRenderSection newRenderSection = new LodRenderSection(rootSectionPos); + this.set(rootSectionPos, newRenderSection); + return; // update next tick + } + + + rootNode.forEachDirectChild((quadNode, sectionPos) -> { - runValidations(renderSection); + recursivelyUpdateRenderSectionNode(playerPos, rootNode, quadNode, sectionPos); }); - } + }); } - private void runValidations(LodRenderSection renderSection) + private void recursivelyUpdateRenderSectionNode(DhBlockPos2D playerPos, QuadNode rootNode, QuadNode nullableQuadNode, DhSectionPos sectionPos) { - if (renderSection != null && renderSection.childCount != -1) + LodRenderSection nullableRenderSection = null; + if (nullableQuadNode != null) { - try + nullableRenderSection = nullableQuadNode.value; + } + + + byte expectedDetailLevel = calculateExpectedDetailLevel(playerPos, sectionPos); + expectedDetailLevel += DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL; + expectedDetailLevel = (byte) Math.min(expectedDetailLevel, this.treeMaxDetailLevel); + + if (sectionPos.sectionDetailLevel > expectedDetailLevel) + { + // detail level too high... + + if (nullableRenderSection != null) { - assertRenderSectionIsValid(renderSection); + nullableRenderSection.disableRender(); } - catch (LodUtil.AssertFailureException e) + + if (nullableQuadNode == null) { - LOGGER.error(e.getMessage(), e); - int k = 2; + // ...create self + if (this.isSectionPosInBounds(sectionPos)) + { + rootNode.setValue(sectionPos, new LodRenderSection(sectionPos)); + } + } + else + { + // ...children exist, recurse down them to the next layer + nullableQuadNode.forEachDirectChild((childQuadNode, childSectionPosition) -> + { + recursivelyUpdateRenderSectionNode(playerPos, rootNode, childQuadNode, childSectionPosition); + }); } } - } - - - - private void updateAllRenderSections() - { - // TODO: inline comments should be added everywhere for this tick pass, so this comment block should be removed (having duplicate comments in two places is a bad idea) - // Second tick pass: - // Cascade the layers that is in Always Cascade Mode from top to bottom. (Not yet exposed or used) - // At the same time, load and unload sections (and can also be used to assert everything is working). - // - // // ===Assertion steps=== - // assert childCount == 4 || childCount == 0 || childCount == -1 - // if childCount == 4 assert all children exist - // if childCount == 0 assert all children are null - // if childCount == -1 assert parent childCount is 0 - // // ====================== - // - // if childCount == 4 && section is loaded: - // - unload section - // if childCount == 0 && section is unloaded: - // - load section - // if childCount == -1: // (section could be loaded or unloaded if the player is moving fast) - // - set this section to null (TODO: Is this needed to be first or last or don't matter for concurrency?) - // - If loaded unload section - - // start with close sections and move outward - for (byte sectLevel = TREE_LOWEST_DETAIL_LEVEL; sectLevel < (byte) (this.numbersOfSectionDetailLevels - 1); sectLevel++) + else if (sectionPos.sectionDetailLevel == expectedDetailLevel) { - final MovableGridRingList ringList = this.renderSectionRingLists[sectLevel - TREE_LOWEST_DETAIL_LEVEL]; - //final MovableGridRingList childRingList = sectLevel == TREE_LOWEST_DETAIL_LEVEL ? null : this.renderSectionRingLists[sectLevel - TREE_LOWEST_DETAIL_LEVEL - 1]; - //final boolean doCascade = false; // TODO: Utilize this cascade mode or at least expose this option - - ringList.forEachPosOrdered((section, pos) -> + if (nullableQuadNode == null) { - if (section == null) + if (this.isSectionPosInBounds(sectionPos)) { - return; + rootNode.setValue(sectionPos, new LodRenderSection(sectionPos)); + } + } + + if (nullableQuadNode != null) + { + // create a new render section if missing + if (nullableRenderSection == null) + { + LodRenderSection newRenderSection = new LodRenderSection(sectionPos); + rootNode.setValue(sectionPos, newRenderSection); + + nullableRenderSection = newRenderSection; } - // Cascade layers -// if (doCascade && section.childCount == 0) { -// LodUtil.assertTrue(childRingList != null); -// // Create children to cascade the layer. -// for (byte i = 0; i < 4; i++) { -// DhSectionPos childPos = section.pos.getChild(i); -// LodRenderSection child = childRingList.get(childPos.sectionX, childPos.sectionZ); -// if (child == null) { -// child = childRingList.setChained(childPos.sectionX, childPos.sectionZ, -// new LodRenderSection(childPos)); -// child.childCount = 0; -// } else { -// LodUtil.assertTrue(child.childCount == -1, -// "Self has child count 0 but an existing child's child count != -1!"); -// child.childCount = 0; -// } -// } -// section.childCount = 4; -// } - - - //======================// - // load new sections, // - // tick existing ones, // - // dispose old sections // - //======================// - - if (section.childCount == -1) - { - // dispose the old section - - if (section.pos.sectionDetailLevel < this.treeMaxDetailLevel) - { - int parentChildCount = this.getParentSection(section.pos).childCount; - if (parentChildCount != 0 && parentChildCount != -1) - { - LodUtil.assertNotReach("Incorrect section removal. Parent has ["+parentChildCount+"] children, expected [0] (empty parent) or [-1] (parent also marked for deletion)."); - } - } - - ringList.remove(pos.x, pos.y); - section.disposeRenderData(); - - return; - } - else - { - if (!section.isLoaded() && !section.isLoading()) - { - // load in the new section - section.setRenderSourceProvider(this.renderSourceProvider); - } - - - // enable rendering if this section is a leaf node in the tree, otherwise disable rendering - if (section.childCount == 4) - { - // only disable rendering if the next section is ready to render, - // isRenderingEnabled check to prevent calling the recursive method more than necessary - if (section.isRenderingEnabled()) // && areChildRenderSectionsLoaded(section)) // FIXME: this is an imperfect solution, some sections will still appear/disappear incorrectly and/or not disappear when they should - { - section.disableRender(); - } - } - else if (section.childCount == 0) - { - // limit how many render sections can be loading at a time - if (!section.isRenderingEnabled() && this.numberOfRenderSectionsLoading < MAX_NUMBER_OF_LOADING_RENDER_SECTIONS) - { - section.loadRenderSourceAndEnableRendering(); - - this.numberOfRenderSectionsLoading++; - - CompletableFuture future = section.getRenderSourceLoadingFuture(); - if (future != null) - { - future.whenComplete((renderSource, ex) -> this.numberOfRenderSectionsLoading-- ); - } - else - { - // the future will be null if the section was already loaded - this.numberOfRenderSectionsLoading--; - } - } - } - - - // update the section - section.tick(this, this.level); - } - - + // enable the render section + nullableRenderSection.loadRenderSourceAndEnableRendering(this.renderSourceProvider); + } + + if (nullableRenderSection != null) + { // should be called after the section has been updated - assertRenderSectionIsValid(section); - }); - } - } - - /** @throws LodUtil.AssertFailureException if the section isn't valid */ - private void assertRenderSectionIsValid(LodRenderSection section) throws LodUtil.AssertFailureException - { - // section validation - LodUtil.assertTrue(section.childCount == 4|| section.childCount == 0,"Expected render section to have a child count of 0, or 4. Found value: "+ section.childCount); - - if (section.pos.sectionDetailLevel == TREE_LOWEST_DETAIL_LEVEL) - { - // sections at the bottom of the tree (leaves) should have no additional children - LodUtil.assertTrue(section.childCount == 0); - } - else - { - LodRenderSection child0 = this.getChildSection(section.pos, 0); - LodRenderSection child1 = this.getChildSection(section.pos, 1); - LodRenderSection child2 = this.getChildSection(section.pos, 2); - LodRenderSection child3 = this.getChildSection(section.pos, 3); - - if (section.childCount == 4) - { - LodUtil.assertTrue( - child0 != null && child0.childCount != -1 && - child1 != null && child1.childCount != -1 && - child2 != null && child2.childCount != -1 && - child3 != null && child3.childCount != -1, - "Sect "+ section.pos+" has a child count of 4 but one or more children is null or marked for disposal: \n{} \n{} \n{} \n{}", - child0, child1, child2, child3); + nullableRenderSection.tick(this, this.level); } - else if (section.childCount == 0) + } + else //if (sectionPos.sectionDetailLevel < expectedDetailLevel) + { + // detail level too low, disable rendering if active + if (nullableRenderSection != null) { - LodUtil.assertTrue( - (child0 == null || child0.childCount == -1) && - (child1 == null || child1.childCount == -1) && - (child2 == null || child2.childCount == -1) && - (child3 == null || child3.childCount == -1), - "Sect "+ section.pos+" has a child count of 0 but has one or more children that are neither null or marked for disposal: \n{} \n{} \n{} \n{}", - child0, child1, child2, child3); + nullableRenderSection.disableRender(); } } } @@ -834,17 +316,28 @@ public class LodQuadTree implements AutoCloseable { LOGGER.info("Clearing render cache..."); - // clear each ring list - for (byte sectionDetailLevel = TREE_LOWEST_DETAIL_LEVEL; sectionDetailLevel < this.numbersOfSectionDetailLevels; sectionDetailLevel++) + this.forEachRootNode((rootNode) -> { - MovableGridRingList ringList = this.renderSectionRingLists[sectionDetailLevel-TREE_LOWEST_DETAIL_LEVEL]; - if (ringList != null) + rootNode.forEachDirectChild((quadNode, sectionPos) -> { - ringList.clear((section) -> section.disposeRenderData()); - - LOGGER.info("Finished deleting render files for detail level ["+sectionDetailLevel+"]..."); - } - } + if (quadNode != null && quadNode.value != null) + { + quadNode.value.disposeRenderData(); + } + }); + }); + + this.forEachRootNode((rootNode) -> + { + rootNode.forEachDirectChild((quadNode, sectionPos) -> + { + if (quadNode != null && quadNode.value != null) + { + quadNode.value.disposeRenderData(); + quadNode.value = null; + } + }); + }); // delete the cache files this.renderSourceProvider.deleteRenderCache(); @@ -905,34 +398,34 @@ public class LodQuadTree implements AutoCloseable // base methods // //==============// - public String getDebugString() - { - StringBuilder sb = new StringBuilder(); - for (byte i = 0; i < this.renderSectionRingLists.length; i++) - { - sb.append("Layer ").append(i + TREE_LOWEST_DETAIL_LEVEL).append(":\n"); - sb.append(this.renderSectionRingLists[i].toDetailString()); - sb.append("\n"); - sb.append("\n"); - } - return sb.toString(); - } +// public String getDebugString() +// { +// StringBuilder sb = new StringBuilder(); +// for (byte i = 0; i < this.renderSectionRingLists.length; i++) +// { +// sb.append("Layer ").append(i + TREE_LOWEST_DETAIL_LEVEL).append(":\n"); +// sb.append(this.renderSectionRingLists[i].toDetailString()); +// sb.append("\n"); +// sb.append("\n"); +// } +// return sb.toString(); +// } @Override public void close() { LOGGER.info("Shutting down "+ LodQuadTree.class.getSimpleName()+"..."); - for (MovableGridRingList ringList : this.renderSectionRingLists) + this.forEachRootNode((rootNode) -> { - ringList.forEach((section) -> + rootNode.forEachDirectChild((quadNode, sectionPos) -> { - if (section != null) + if (quadNode != null && quadNode.value != null) { - section.disposeRenderData(); + quadNode.value.disposeRenderData(); } }); - } + }); LOGGER.info("Finished shutting down "+ LodQuadTree.class.getSimpleName()); } 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 bdf209036..5ddbae4f8 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 @@ -5,6 +5,7 @@ import com.seibel.lod.core.level.IDhClientLevel; import com.seibel.lod.core.logging.DhLoggerBuilder; import com.seibel.lod.core.pos.DhSectionPos; import com.seibel.lod.core.file.renderfile.ILodRenderSourceProvider; +import com.seibel.lod.core.util.objects.quadTree.QuadNode; import org.apache.logging.log4j.Logger; import java.util.concurrent.CompletableFuture; @@ -18,11 +19,6 @@ public class LodRenderSection // 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) - /* Following used for LodQuadTree tick() method, and ONLY for that method! */ - // the number of children of this section - // (Should always be 4 after tick() is done, or 0 only if this is an unloaded node) - public byte childCount = 0; - private CompletableFuture loadFuture; private boolean isRenderEnabled = false; @@ -41,13 +37,14 @@ public class LodRenderSection // rendering // //===========// - public void loadRenderSourceAndEnableRendering() + public void loadRenderSourceAndEnableRendering(ILodRenderSourceProvider renderDataProvider) { if (this.isRenderEnabled) { return; } + this.renderSourceProvider = renderDataProvider; if (this.renderSourceProvider == null) { return; @@ -76,8 +73,6 @@ public class LodRenderSection // render source provider // //========================// - public void setRenderSourceProvider(ILodRenderSourceProvider renderDataProvider) { this.renderSourceProvider = renderDataProvider; } - public void reload(ILodRenderSourceProvider renderDataProvider) { // don't accidentally enable rendering for a disabled section @@ -165,7 +160,6 @@ public class LodRenderSection public String toString() { return "LodRenderSection{" + "pos=" + this.pos + - ", childCount=" + this.childCount + ", lodRenderSource=" + this.renderSource + ", loadFuture=" + this.loadFuture + ", isRenderEnabled=" + this.isRenderEnabled + 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 655304320..6d33e3652 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,6 +6,7 @@ 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; @@ -13,11 +14,10 @@ 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 */ public class RenderBufferHandler { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); @@ -34,9 +34,11 @@ public class RenderBufferHandler { this.quadTree = quadTree; - MovableGridRingList referenceList = quadTree.getRingListForDetailLevel((byte) (quadTree.getNumbersOfSectionDetailLevels() - 1)); - Pos2D center = referenceList.getCenter(); - this.renderBufferNodesGridList = new MovableGridRingList<>(referenceList.getHalfWidth(), center); + 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); } @@ -52,7 +54,7 @@ public class RenderBufferHandler { ELodDirection[] axisDirections = new ELodDirection[3]; - // Do the axis that are longest first (i.e. the largest absolute value of the lookForwardVector), + // Do the axis that are the longest first (i.e. the largest absolute value of the lookForwardVector), // with the sign being the opposite of the respective lookForwardVector component's sign float absX = Math.abs(lookForwardVector.x); float absY = Math.abs(lookForwardVector.y); @@ -169,45 +171,46 @@ public class RenderBufferHandler public void update() { - byte topDetailLevel = (byte) (this.quadTree.getNumbersOfSectionDetailLevels() - 1); - MovableGridRingList renderSectionGridList = this.quadTree.getRingListForDetailLevel(topDetailLevel); + 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)); - Pos2D newCenterPos = renderSectionGridList.getCenter(); - this.renderBufferNodesGridList.moveTo(newCenterPos.x, newCenterPos.y, RenderBufferNode::close); // Note: may lock the list + this.renderBufferNodesGridList.moveTo(expectedCenterPos.x, expectedCenterPos.y, RenderBufferNode::close); // Note: may lock the list - this.renderBufferNodesGridList.forEachPosOrdered((renderBufferNode, pos2d) -> + this.renderBufferNodesGridList.forEachPosOrdered((rootRenderBufferNode, pos2d) -> { try { - DhSectionPos sectionPos = new DhSectionPos(topDetailLevel, pos2d.x, pos2d.y); - LodRenderSection renderSection = this.quadTree.getSection(sectionPos); + DhSectionPos rootSectionPos = new DhSectionPos(topDetailLevel, pos2d.x, pos2d.y); + LodRenderSection rootRenderSection = this.quadTree.getSection(rootSectionPos); - if (renderSection == null && renderBufferNode != null) + if (rootRenderSection == null && rootRenderBufferNode != null) { // section is null, but a node exists, remove the node this.renderBufferNodesGridList.remove(pos2d).close(); } - else if (renderSection != null) + else if (rootRenderSection != null) { - if (renderBufferNode == null) + if (rootRenderBufferNode == null) { // renderSection exists, but node does not - renderBufferNode = this.renderBufferNodesGridList.setChained(pos2d, new RenderBufferNode(sectionPos)); + rootRenderBufferNode = this.renderBufferNodesGridList.setChained(pos2d, new RenderBufferNode(rootSectionPos)); } // Update the render node - renderBufferNode.update(); + 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.toString()+", exception: "+e.getMessage(), e); + LOGGER.error(RenderBufferHandler.class.getSimpleName()+" exception in update for the quadTree: "+this.quadTree+", exception: "+e.getMessage(), e); int breaker = 0; } }); @@ -286,15 +289,15 @@ public class RenderBufferHandler if (!renderSection.shouldRender()) { //TODO: Does this really need to force the old buffer to not be rendered? - AbstractRenderBuffer renderBuffer = this.renderBufferRef.getAndSet(null); - if (renderBuffer != null) + AbstractRenderBuffer previousRenderBuffer = this.renderBufferRef.getAndSet(null); + if (previousRenderBuffer != null) { - renderBuffer.close(); + previousRenderBuffer.close(); } } else { - LodUtil.assertTrue(currentRenderSource != null); // section.isLoaded() should have ensured this + LodUtil.assertTrue(currentRenderSource != null); // section.shouldRender() should have ensured this currentRenderSource.trySwapRenderBufferAsync(quadTree, this.renderBufferRef); } @@ -303,15 +306,28 @@ public class RenderBufferHandler // 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! - boolean sectionHasChildren = renderSection.childCount > 0; + int loadedChildCount = quadTree.getNonNullChildCountAtPos(renderSection.pos); + boolean sectionHasChildren = loadedChildCount != 0; if (sectionHasChildren) { if (this.children == null) { - this.children = new RenderBufferNode[4]; - for (int i = 0; i < 4; i++) + RenderBufferNode[] potentialChildren = new RenderBufferNode[loadedChildCount]; + AtomicInteger childIndexRef = new AtomicInteger(0); + renderSection.pos.forEachChild((childSectionPos) -> { - this.children[i] = new RenderBufferNode(this.pos.getChildByIndex(i)); + 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; } }