Replace WorldGen and LodQuadTree with the new QuadTree<T>

There are some issues for the render tree, but the world gen tree appears to be working correctly
This commit is contained in:
James Seibel
2023-03-25 09:25:13 -05:00
parent 0b05ced5b9
commit a465a8da9d
4 changed files with 189 additions and 677 deletions
@@ -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<WorldGenResult> 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<WorldGenResult> future = new CompletableFuture<>();
this.waitingTaskQuadTree.set(requestPos, new WorldGenTask(pos, requiredDataDetail, tracker, future));
return future;
}
else
{
return CompletableFuture.completedFuture(WorldGenResult.CreateFail());
}
}
@@ -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. <br><br>
@@ -27,21 +24,8 @@ import java.util.concurrent.CompletableFuture;
* -by adding data with the lodBuilder <br>
* <br><br>
* The QuadTree is built from several layers of 2d ring buffers.
* <br><br>
*
* Example of how the tree is visualized (please view in code, otherwise the spacing isn't maintained):
* <code>
* C---C---C---C--- Detail level = 2 <br>
* B-B-B-B- Detail level = 1 <br>
* AAAA Detail level = 0 <br>
* </code>
* <br><br>
* 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<LodRenderSection> 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<LodRenderSection>[] 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<LodRenderSection> 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<LodRenderSection> 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<LodRenderSection> ringList = this.renderSectionRingLists[sectionDetailLevel- TREE_LOWEST_DETAIL_LEVEL];
// child and parent are relative to the detail level
final MovableGridRingList<LodRenderSection> childRingList = (sectionDetailLevel == TREE_LOWEST_DETAIL_LEVEL) ? null : this.renderSectionRingLists[sectionDetailLevel- TREE_LOWEST_DETAIL_LEVEL -1];
final MovableGridRingList<LodRenderSection> 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<LodRenderSection> 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<nextLevel. Creating.");
}
// add the new renderSection
renderSection = this._setRenderSectionInGridList(ringList, tree2dPos.x, tree2dPos.y, new LodRenderSection(sectionPos));
// update the parent if necessary
if (parentRingList != null)
{
LodRenderSection parent = this._getRenderSectionFromGridList(parentRingList, halfPos.x, halfPos.y);
if (parent == null)
{
// this render section's parent is missing, create it
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("sect "+sectionPos+" missing parent. Creating at "+sectionPos.getParentPos());
}
parent = this._setRenderSectionInGridList(parentRingList, halfPos.x, halfPos.y, new LodRenderSection(sectionPos.getParentPos()));
}
parent.childCount++;
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("parent render section "+sectionPos.getParentPos()+" now has "+parent.childCount+" children.");
}
}
}
runValidations(renderSection);
}
// Final quick assert to insure section pos is correct.
if (renderSection != null)
{
LodUtil.assertTrue(renderSection.pos.sectionDetailLevel == sectionDetailLevel, "section.pos: " + renderSection.pos + " vs level: " + sectionDetailLevel);
LodUtil.assertTrue(renderSection.pos.sectionX == tree2dPos.x, "section.pos: " + renderSection.pos + " vs pos: " + tree2dPos);
LodUtil.assertTrue(renderSection.pos.sectionZ == tree2dPos.y, "section.pos: " + renderSection.pos + " vs pos: " + tree2dPos);
runValidations(renderSection);
}
});
}
// re-run validation, this must be done separately just in case a previously valid renderSection was modified after being validated
for (byte sectionDetailLevelIteration = TREE_LOWEST_DETAIL_LEVEL; sectionDetailLevelIteration < this.numbersOfSectionDetailLevels; sectionDetailLevelIteration++)
{
final MovableGridRingList<LodRenderSection> 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<LodRenderSection> rootNode, QuadNode<LodRenderSection> 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<LodRenderSection> ringList = this.renderSectionRingLists[sectLevel - TREE_LOWEST_DETAIL_LEVEL];
//final MovableGridRingList<LodRenderSection> 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<ColumnRenderSource> 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<LodRenderSection> 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<LodRenderSection> 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());
}
@@ -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<ColumnRenderSource> 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 +
@@ -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<LodRenderSection> 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<LodRenderSection> 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;
}
}