Refactor LodQuadTree

This commit is contained in:
James Seibel
2023-02-04 22:01:47 -06:00
parent e3caab440a
commit d5a8e1eb1b
8 changed files with 798 additions and 496 deletions
@@ -32,6 +32,15 @@ public class BitShiftUtil
*/
public static int half(int value) { return value >> 1; }
/**
* Equivalent to: <br>
* value >> power, <br>
* value / 2^power <br><br>
*
* Note: value / 2^power isn't identical for negative values
*/
public static int divideByPowerOfTwo(int value, int power) { return value >> power; }
/**
* Equivalent to: <br>
* value << 1, <br>
@@ -96,7 +96,7 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel
if (rs == null)
return;
if (rs.tree.viewDistance != Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH)
if (rs.tree.blockViewDistance != Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH)
{
if (!this.renderState.compareAndSet(rs, null))
return; //If we fail, we'll just wait for the next tick
@@ -9,90 +9,129 @@ import java.util.LinkedList;
import java.util.List;
import java.util.function.Supplier;
public class F3Screen {
public static boolean renderCustomF3 = true;
private static final String[] DEFAULT_STR = {
"",
ModInfo.READABLE_NAME + " version: " + ModInfo.VERSION
};
private static final LinkedList<WeakReference<Message>> selfUpdateMessages = new LinkedList<>();
public static void addStringToDisplay(List<String> list) {
list.addAll(Arrays.asList(DEFAULT_STR));
Iterator<WeakReference<Message>> it = selfUpdateMessages.iterator();
while (it.hasNext()) {
WeakReference<Message> ref = it.next();
Message msg = ref.get();
if (msg == null) {
it.remove();
} else {
msg.printTo(list);
}
}
}
@SuppressWarnings("unused")
public static abstract class Message {
protected Message() {
selfUpdateMessages.add(new WeakReference<>(this));
}
public abstract void printTo(List<String> output);
}
@SuppressWarnings("unused")
public static class StaticMessage extends Message {
private final String[] lines;
public StaticMessage(String... lines) {
this.lines = lines;
}
@Override
public void printTo(List<String> output) {
output.addAll(Arrays.asList(lines));
}
}
@SuppressWarnings("unused")
public static class DynamicMessage extends Message {
private final Supplier<String> supplier;
public DynamicMessage(Supplier<String> message) {
this.supplier = message;
}
public void printTo(List<String> list) {
String msg = supplier.get();
if (msg != null) {
list.add(msg);
}
}
}
@SuppressWarnings("unused")
public static class MultiDynamicMessage extends Message {
private final Supplier<String>[] supplier;
@SafeVarargs
public MultiDynamicMessage(Supplier<String>... messages) {
this.supplier = messages;
}
public void printTo(List<String> list) {
for (Supplier<String> s : supplier) {
String msg = s.get();
if (msg != null) {
list.add(msg);
}
}
}
}
public static class NestedMessage extends Message {
private final Supplier<String[]> supplier;
public NestedMessage(Supplier<String[]> message) {
this.supplier = message;
}
public void printTo(List<String> list) {
String[] msg = supplier.get();
if (msg != null) {
list.addAll(Arrays.asList(msg));
}
}
}
public class F3Screen
{
public static boolean renderCustomF3 = true;
private static final String[] DEFAULT_STR = {
"",
ModInfo.READABLE_NAME + " version: " + ModInfo.VERSION
};
private static final LinkedList<WeakReference<Message>> selfUpdateMessages = new LinkedList<>();
public static void addStringToDisplay(List<String> list)
{
list.addAll(Arrays.asList(DEFAULT_STR));
Iterator<WeakReference<Message>> iterator = selfUpdateMessages.iterator();
while (iterator.hasNext())
{
WeakReference<Message> ref = iterator.next();
Message message = ref.get();
if (message == null)
{
iterator.remove();
}
else
{
message.printTo(list);
}
}
}
//================//
// helper classes //
//================//
public static abstract class Message
{
protected Message()
{
selfUpdateMessages.add(new WeakReference<>(this));
}
public abstract void printTo(List<String> output);
}
@SuppressWarnings("unused")
public static class StaticMessage extends Message
{
private final String[] lines;
public StaticMessage(String... lines)
{
this.lines = lines;
}
@Override
public void printTo(List<String> output)
{
output.addAll(Arrays.asList(lines));
}
}
@SuppressWarnings("unused")
public static class DynamicMessage extends Message
{
private final Supplier<String> supplier;
public DynamicMessage(Supplier<String> message)
{
this.supplier = message;
}
public void printTo(List<String> list)
{
String msg = supplier.get();
if (msg != null)
{
list.add(msg);
}
}
}
@SuppressWarnings("unused")
public static class MultiDynamicMessage extends Message
{
private final Supplier<String>[] supplier;
@SafeVarargs
public MultiDynamicMessage(Supplier<String>... messages)
{
this.supplier = messages;
}
public void printTo(List<String> list)
{
for (Supplier<String> s : supplier)
{
String msg = s.get();
if (msg != null)
{
list.add(msg);
}
}
}
}
public static class NestedMessage extends Message
{
private final Supplier<String[]> supplier;
public NestedMessage(Supplier<String[]> message)
{
this.supplier = message;
}
public void printTo(List<String> list)
{
String[] msg = supplier.get();
if (msg != null)
{
list.addAll(Arrays.asList(msg));
}
}
}
}
@@ -7,125 +7,132 @@ import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.file.renderfile.IRenderSourceProvider;
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 org.apache.logging.log4j.Logger;
// QuadTree built from several layers of 2d ring buffers
/**
* This quadTree structure is the core of the DH mod.
* This class represent a circular quadTree of lodSection
*
* Each section at level n is populated in one (sometimes more than one) ways:
* -by constructing it from the data of all the children sections (lower levels)
* -by loading from file
* -by adding data with the lodBuilder
* This quadTree structure is the core of the DH mod. <br><br>
*
* This class represent a circular quadTree of lodSections. <br>
* Each section at level n is populated in one or more ways: <br>
* -by constructing it from the data of all the children sections (lower levels) <br>
* -by loading from file <br>
* -by adding data with the lodBuilder <br>
* <br><br>
* The QuadTree is built from several layers of 2d ring buffers.
*/
public class LodQuadTree implements AutoCloseable {
public class LodQuadTree implements AutoCloseable
{
/**
* Note: all config value should be via the class that extends this class, and
* Note: all config values should be via the class that extends this class, and
* by implementing different abstract methods
*/
private static final byte LAYER_BEGINNING_OFFSET = ColumnRenderSource.SECTION_SIZE_OFFSET;
private static final byte TREE_LOWEST_DETAIL_LEVEL = ColumnRenderSource.SECTION_SIZE_OFFSET;
private static final boolean SUPER_VERBOSE_LOGGING = false;
public final byte getLayerDataDetailOffset(byte sectionDetail) {
return ColumnRenderSource.SECTION_SIZE_OFFSET;
}
public final byte getLayerSectionDetailOffset(byte dataDetail) {
return ColumnRenderSource.SECTION_SIZE_OFFSET;
}
public final byte getLayerDataDetail(byte sectionDetail) {
return (byte) (sectionDetail - getLayerDataDetailOffset(sectionDetail));
}
public final byte getLayerSectionDetail(byte dataDetail) {
return (byte) (dataDetail + getLayerSectionDetailOffset(dataDetail));
}
private static final Logger LOGGER = DhLoggerBuilder.getLogger("LodQuadTree");
public final byte numbersOfSectionLevels;
private final MovableGridRingList<LodRenderSection>[] ringLists;
/** measured in blocks */
public final int viewDistance;
public final byte getLayerDataDetailOffset() { return ColumnRenderSource.SECTION_SIZE_OFFSET; }
public final byte getLayerDataDetail(byte sectionDetailLevel) { return (byte) (sectionDetailLevel - this.getLayerDataDetailOffset()); }
public final byte getLayerSectionDetailOffset() { return ColumnRenderSource.SECTION_SIZE_OFFSET; }
public final byte getLayerSectionDetail(byte dataDetail) { return (byte) (dataDetail + this.getLayerSectionDetailOffset()); }
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/** AKA number of section layers? - TODO James */
public final byte numbersOfSectionDetailLevels;
private final MovableGridRingList<LodRenderSection>[] renderSectionRingLists;
public final int blockViewDistance;
private final IRenderSourceProvider renderSourceProvider;
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference!
/**
* Constructor of the quadTree
* @param viewDistance View distance in blocks
* @param initialPlayerX player x coordinate
* @param initialPlayerZ player z coordinate
* @param initialPlayerX player x block coordinate
* @param initialPlayerZ player z block coordinate
*/
public LodQuadTree(IDhClientLevel level, int viewDistance, int initialPlayerX, int initialPlayerZ, IRenderSourceProvider provider) {
public LodQuadTree(
IDhClientLevel level, int viewDistance,
int initialPlayerX, int initialPlayerZ,
IRenderSourceProvider provider)
{
DetailDistanceUtil.updateSettings(); //TODO: Move this to somewhere else
this.level = level;
renderSourceProvider = provider;
this.viewDistance = viewDistance;
{ // Calculate the max section detail
byte maxDataDetailLevel = getMaxDetailInRange(viewDistance * Math.sqrt(2));
byte topSectionLevel = getLayerSectionDetail(maxDataDetailLevel);
numbersOfSectionLevels = (byte) (topSectionLevel + 1);
ringLists = new MovableGridRingList[numbersOfSectionLevels - LAYER_BEGINNING_OFFSET];
}
{ // Construct the ringLists
LOGGER.info("Creating ringLists with player center at {}", new Pos2D(initialPlayerX, initialPlayerZ));
for (byte i = LAYER_BEGINNING_OFFSET; i < numbersOfSectionLevels; i++) {
byte targetDataDetail = getLayerDataDetail(i);
int maxDist = getFurthestDistance(targetDataDetail);
int halfSize = MathUtil.ceilDiv(maxDist, (1 << i)) + 8; // +8 to make sure the section is fully contained in the ringList
{
DhSectionPos checkerPos = new DhSectionPos(i, halfSize, halfSize);
byte checkedDetail = calculateExpectedDetailLevel(new DhBlockPos2D(initialPlayerX, initialPlayerZ),checkerPos);
LodUtil.assertTrue(checkedDetail > targetDataDetail,
"in {}, getFuthestDistance return {} which would be contained in range {}, but calculateExpectedDetailLevel at {} is {} <= {}",
i, maxDist, halfSize - 2, checkerPos, checkedDetail, targetDataDetail);
}
LOGGER.info("ringlist centered in {} with halfSize {} (maxDist {}, dataDetail {})", new Pos2D(initialPlayerX >> i, initialPlayerZ >> i), halfSize, maxDist, targetDataDetail);
ringLists[i - LAYER_BEGINNING_OFFSET] = new MovableGridRingList<>(halfSize,
initialPlayerX >> i, initialPlayerZ >> i);
LOGGER.info("Creating ringList {}: {}", i, ringLists[i - LAYER_BEGINNING_OFFSET].toString());
}
}
this.renderSourceProvider = provider;
this.blockViewDistance = viewDistance;
// Calculate the max section detail level //
byte maxDetailLevel = this.getMaxDetailInRange(viewDistance * Math.sqrt(2));
byte topSectionDetailLevel = this.getLayerSectionDetail(maxDetailLevel);
this.numbersOfSectionDetailLevels = (byte) (topSectionDetailLevel + 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);
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);
}
}
/**
* This method return the LodSection given the Section Pos
* @param pos the section positon.
* @param pos the section position.
* @return the LodSection
*/
public LodRenderSection getSection(DhSectionPos pos) {
return getSection(pos.sectionDetailLevel, pos.sectionX, pos.sectionZ);
}
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!
* @param detailLevel the detail level
* @return the RingList
*/
public MovableGridRingList<LodRenderSection> getRingList(byte detailLevel) {
return ringLists[detailLevel - LAYER_BEGINNING_OFFSET];
}
public MovableGridRingList<LodRenderSection> getRingList(byte detailLevel) { return this.renderSectionRingLists[detailLevel - TREE_LOWEST_DETAIL_LEVEL]; }
/**
* This method returns the number of detail levels in the quadTree
* @return the number of detail levels
*/
public byte getNumbersOfSectionLevels() {
return numbersOfSectionLevels;
}
public byte getNumbersOfSectionDetailLevels() { return this.numbersOfSectionDetailLevels; }
public byte getStartingSectionLevel() {
return LAYER_BEGINNING_OFFSET;
}
public byte getStartingSectionLevel() { return TREE_LOWEST_DETAIL_LEVEL; }
/**
* This method return the LodSection at the given detail level and level coordinate x and z
@@ -134,10 +141,11 @@ 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 ringLists[detailLevel - LAYER_BEGINNING_OFFSET].get(x, z);
}
public LodRenderSection getSection(byte detailLevel, int x, int z) { return this.renderSectionRingLists[detailLevel - TREE_LOWEST_DETAIL_LEVEL].get(x, z); }
/**
* This method will compute the detail level based on player position and section pos
@@ -146,7 +154,8 @@ public class LodQuadTree implements AutoCloseable {
* @param sectionPos section position
* @return detail level of this section pos
*/
public byte calculateExpectedDetailLevel(DhBlockPos2D playerPos, DhSectionPos sectionPos) {
public byte calculateExpectedDetailLevel(DhBlockPos2D playerPos, DhSectionPos sectionPos)
{
return DetailDistanceUtil.getDetailLevelFromDistance(
playerPos.dist(sectionPos.getCenter().getCenterBlockPos()));
}
@@ -159,9 +168,7 @@ public class LodQuadTree implements AutoCloseable {
* @param distance the circle radius
* @return the highest detail level in the circle
*/
public byte getMaxDetailInRange(double distance) {
return DetailDistanceUtil.getDetailLevelFromDistance(distance);
}
public byte getMaxDetailInRange(double distance) { return DetailDistanceUtil.getDetailLevelFromDistance(distance); }
/**
* The method will return the furthest distance to the center for the given detail level
@@ -171,88 +178,72 @@ public class LodQuadTree implements AutoCloseable {
* @param detailLevel detail level
* @return the furthest distance to the center, in blocks
*/
public int getFurthestDistance(byte detailLevel) {
public int getFurthestDistance(byte detailLevel)
{
return (int)Math.ceil(DetailDistanceUtil.getDrawDistanceFromDetail(detailLevel + 1));
// +1 because that's the border to the next detail level, and we want to include up to it.
}
/**
* Given a section pos at level n this method returns the parent section at level n+1
* @param pos the section positon
* @param pos the section position
* @return the parent LodSection
*/
public LodRenderSection getParentSection(DhSectionPos pos) {
return getSection(pos.getParentPos());
}
public LodRenderSection getParentSection(DhSectionPos pos) { return this.getSection(pos.getParentPos()); }
/**
* Given a section pos at level n and a child index this method return the
* child section at level n-1
* @param pos
* @param child0to3 since there are 4 possible children this index identify which one we are getting
* @return one of the child LodSection
*/
public LodRenderSection getChildSection(DhSectionPos pos, int child0to3) {
return getSection(pos.getChildByIndex(child0to3));
}
private LodRenderSection _set(MovableGridRingList<LodRenderSection> list, int x, int z, LodRenderSection t) {
LodUtil.assertTrue(t != null, "setting null at [{},{}] in {}", x, z, list.toString());
LodUtil.assertTrue(t.pos.sectionX == x && t.pos.sectionZ == z, "pos {} != [{},{}] in {}", t.pos, x, z, list.toString());
LodRenderSection s = list.setChained(x,z,t);
LodUtil.assertTrue(s != null, "returned null at [{},{}]: {}", x, z, list.toString());
LodUtil.assertTrue(s == t,"{} != {} in {}",s,t, list.toString());
return s;
}
private LodRenderSection _getNotNull(MovableGridRingList<LodRenderSection> list, int x, int z) {
LodUtil.assertTrue(list.inRange(x,z), "[{},{}] not in range of {}", x, z, list.toString());
LodRenderSection s = list.get(x,z);
LodUtil.assertTrue(s != null, "getting null at [{},{}] in {}", x, z, list.toString());
LodUtil.assertTrue(s.pos.sectionX == x && s.pos.sectionZ == z, "obj {} != [{},{}] in {}", s, x, z, list.toString());
return s;
}
private LodRenderSection _get(MovableGridRingList<LodRenderSection> list, int x, int z) {
LodRenderSection s = list.get(x,z);
LodUtil.assertTrue(s == null || (s.pos.sectionX == x && s.pos.sectionZ == z), "obj {} != [{},{}] in {}", s, x, z, list.toString());
return s;
}
public LodRenderSection getChildSection(DhSectionPos pos, int child0to3) { return this.getSection(pos.getChildByIndex(child0to3)); }
// tick //
/**
* This function update the quadTree based on the playerPos and the current game configs (static and global)
* This function updates the quadTree based on the playerPos and the current game configs (static and global)
* @param playerPos the reference position for the player
*/
public void tick(DhBlockPos2D playerPos) {
for (int sectLevel = LAYER_BEGINNING_OFFSET; sectLevel < numbersOfSectionLevels; sectLevel++) {
if (!ringLists[sectLevel - LAYER_BEGINNING_OFFSET].getCenter().equals(
new Pos2D(playerPos.x >> sectLevel, playerPos.z >> sectLevel))) {
LOGGER.info("TreeTick: Moving ring list {} from {} to {}", sectLevel,
ringLists[sectLevel - LAYER_BEGINNING_OFFSET].getCenter(),
new Pos2D(playerPos.x >> sectLevel, playerPos.z >> sectLevel));
ringLists[sectLevel - LAYER_BEGINNING_OFFSET]
.move(playerPos.x >> sectLevel, playerPos.z >> sectLevel,
LodRenderSection::dispose);
}
}
// First tick pass: update all sections' childCount from bottom level to top level. Step:
// If sectLevel is bottom && section != null:
// - set childCount to 0
// If section != null && child != 0: //TODO: Should I move this createChild steps to Second tick pass?
// - // Section will be in the unloaded state.
// - create parent if not at final level and if it doesn't exist, with childCount = 1
// - for each child:
// - if null, create new with childCount = 0 (force load due to neighboring issues)
// - else if childCount == -1, set childCount = 0 (rescue it)
// - set childCount to 4
// Else:
// - Calculate targetLevel at that section
// - If sectLevel == numberOfSectionLevels - 1:
// - // Section is the top level.
// - If targetLevel > dataLevel@sectLevel && section != null:
// - set childCount to -1 (Signal that section is to be freed) (this prob not be rescued as it is the top level)
// - If targetLevel <= dataLevel@sectLevel && section == null: (direct use the current sectLevel's dataLevel)
// - create new section with childCount = 0
// - Else:
public void tick(DhBlockPos2D playerPos)
{
// recenter the grid lists if necessary
for (int sectionDetailLevel = TREE_LOWEST_DETAIL_LEVEL; sectionDetailLevel < this.numbersOfSectionDetailLevels; 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::dispose);
}
}
// 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)
// First tick pass: update all sections' childCount from bottom level to top level. Step:
// If sectLevel is bottom && section != null:
// - set childCount to 0
// If section != null && child != 0: //TODO: Should I move this createChild steps to Second tick pass?
// - // Section will be in the unloaded state.
// - create parent if not at final level and if it doesn't exist, with childCount = 1
// - for each child:
// - if null, create new with childCount = 0 (force load due to neighboring issues)
// - else if childCount == -1, set childCount = 0 (rescue it)
// - set childCount to 4
// Else:
// - Calculate targetLevel at that section
// - If sectLevel == numberOfSectionLevels - 1:
// - // Section is the top level.
// - If targetLevel > dataLevel@sectLevel && section != null:
// - set childCount to -1 (Signal that section is to be freed) (this prob not be rescued as it is the top level)
// - If targetLevel <= dataLevel@sectLevel && section == null: (direct use the current sectLevel's dataLevel)
// - create new section with childCount = 0
// - Else:
// - // Section is not the top level. So we also need to consider the parent.
// - If targetLevel >= dataLevel@(sectLevel+1) && section != null: (use the next level's dataLevel)
// - Parent's childCount-- (Assert parent != null && childCount > 0 before decrementing)
@@ -263,127 +254,268 @@ public class LodQuadTree implements AutoCloseable {
// - If targetLevel < dataLevel@(sectLevel+1) && section == null: (use the next level's dataLevel)
// - create new section with childCount = 0
// - Parent's childCount++ (Create parent if needed)
for (byte sectLevel = LAYER_BEGINNING_OFFSET; sectLevel < numbersOfSectionLevels; sectLevel++) {
final MovableGridRingList<LodRenderSection> ringList = ringLists[sectLevel - LAYER_BEGINNING_OFFSET];
final MovableGridRingList<LodRenderSection> childRingList =
sectLevel == LAYER_BEGINNING_OFFSET ? null : ringLists[sectLevel - LAYER_BEGINNING_OFFSET - 1];
final MovableGridRingList<LodRenderSection> parentRingList =
sectLevel == numbersOfSectionLevels - 1 ? null : ringLists[sectLevel - LAYER_BEGINNING_OFFSET + 1];
final byte f_sectLevel = sectLevel;
ringList.forEachPosOrdered((section, pos) -> {
if (f_sectLevel == LAYER_BEGINNING_OFFSET && section != null) {
section.childCount = 0;
//LOGGER.info("sect {} in first layer with non-null. Reset childCount", section.pos);
}
if (section != null && section.childCount != 0) {
// Section will be in the unloaded state.
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} has child", section.pos);
if (parentRingList != null) {
LodRenderSection parent = _get(parentRingList, pos.x >> 1, pos.y >> 1);
if (parent == null) {
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} missing parent. Creating at {}", section.pos, section.pos.getParentPos());
parent = _set(parentRingList, pos.x >> 1, pos.y >> 1, new LodRenderSection(section.pos.getParentPos()));
parent.childCount++;
if (SUPER_VERBOSE_LOGGING) LOGGER.info("parent sect {} now has {} childs.", section.pos.getParentPos(), parent.childCount);
}
LodUtil.assertTrue(parent.childCount <= 4 && parent.childCount > 0);
}
for (byte i = 0; i < 4; i++) {
DhSectionPos childPos = section.pos.getChildByIndex(i);
LodUtil.assertTrue(childRingList != null);
LodRenderSection child = _get(childRingList, childPos.sectionX, childPos.sectionZ);
if (child == null) {
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} missing child at {}. Creating.", section.pos, childPos);
child = _set(childRingList, childPos.sectionX, childPos.sectionZ, new LodRenderSection(childPos));
child.childCount = 0;
} else if (child.childCount == -1) {
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} rescued child at {}.", section.pos, childPos);
child.childCount = 0;
}
}
section.childCount = 4;
} else {
final DhSectionPos sectPos = section != null ? section.pos : new DhSectionPos(f_sectLevel, pos.x, pos.y);
LodUtil.assertTrue(sectPos.sectionDetailLevel == f_sectLevel
&& sectPos.sectionX == pos.x && sectPos.sectionZ == pos.y,
"sectPos {} != {} @ {}", sectPos, pos, f_sectLevel);
byte targetLevel = calculateExpectedDetailLevel(playerPos, sectPos);
if (SUPER_VERBOSE_LOGGING) LOGGER.info("0 child sect {}(null?{}) - target:{}/{} (parent:{})", sectPos, section == null,
targetLevel, getLayerDataDetail(f_sectLevel),
f_sectLevel == numbersOfSectionLevels-1 ? "N/A" : getLayerDataDetail((byte) (f_sectLevel+1)));
if (f_sectLevel == numbersOfSectionLevels -1) {
// Section is in the top level.
if (targetLevel > getLayerDataDetail(f_sectLevel) && section != null) {
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} in top & target>current. Mark as free.", sectPos);
section.childCount = -1;
}
if (targetLevel <= getLayerDataDetail(f_sectLevel) && section == null) {
if (SUPER_VERBOSE_LOGGING) LOGGER.info("null sect {} in top & target<=current. Creating.", sectPos);
section = _set(ringList, pos.x, pos.y, new LodRenderSection(sectPos));
}
} else {
// Section is not the top level. So we also need to consider the parent.
if (targetLevel >= getLayerDataDetail((byte) (f_sectLevel+1)) && section != null) {
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} target>=nextLevel. Mark as free.", sectPos);
LodUtil.assertTrue(parentRingList != null);
LodRenderSection parent = _getNotNull(parentRingList, pos.x >> 1, pos.y >> 1);
LodUtil.assertTrue(parent.childCount <= 4 && parent.childCount > 0);
parent.childCount--;
if (SUPER_VERBOSE_LOGGING) LOGGER.info("parent sect {} now has {} child.", sectPos, parent.childCount);
section.childCount = -1;
}
if (targetLevel < getLayerDataDetail((byte) (f_sectLevel+1)) && section == null) {
if (SUPER_VERBOSE_LOGGING) LOGGER.info("null sect {} target<nextLevel. Creating.", sectPos);
section = _set(ringList, pos.x, pos.y, new LodRenderSection(sectPos));
LodUtil.assertTrue(parentRingList != null);
LodRenderSection parent = _get(parentRingList, pos.x >> 1, pos.y >> 1);
if (parent == null) {
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} missing parent. Creating at {}", sectPos, sectPos.getParentPos());
parent = _set(parentRingList, pos.x >> 1, pos.y >> 1, new LodRenderSection(sectPos.getParentPos()));
}
parent.childCount++;
if (SUPER_VERBOSE_LOGGING) LOGGER.info("parent sect {} now has {} childs.", sectPos.getParentPos(), parent.childCount);
}
}
}
// Final quick assert to insure section pos is correct.
if (section != null) {
LodUtil.assertTrue(section.pos.sectionDetailLevel == f_sectLevel, "section.pos: " + section.pos + " vs level: " + f_sectLevel);
LodUtil.assertTrue(section.pos.sectionX == pos.x, "section.pos: " + section.pos + " vs pos: " + pos);
LodUtil.assertTrue(section.pos.sectionZ == pos.y, "section.pos: " + section.pos + " vs pos: " + pos);
}
});
}
for (byte sectionDetailLevelIteration = TREE_LOWEST_DETAIL_LEVEL; sectionDetailLevelIteration < this.numbersOfSectionDetailLevels; sectionDetailLevelIteration++)
{
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.numbersOfSectionDetailLevels-1) ? null : this.renderSectionRingLists[sectionDetailLevel- TREE_LOWEST_DETAIL_LEVEL +1];
ringList.forEachPosOrdered((section, pos) ->
{
// TODO why do we need to use the halfPos to get sections?
final Pos2D halfPos = new Pos2D(BitShiftUtil.half(pos.x), BitShiftUtil.half(pos.y));
if (section != null && sectionDetailLevel == TREE_LOWEST_DETAIL_LEVEL)
{
// this section is a leaf node, set its children to 0
section.childCount = 0;
//LOGGER.info("sect {} in first layer with non-null. Reset childCount", section.pos);
}
if (section != null && section.childCount != 0)
{
// Section will be in the unloaded state, with 1-3 children
// load its parent and children
//TODO: Should I move this createChild steps to the Second tick pass?
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("sect "+section.pos+" has child");
}
// if this isn't the top detail level, make sure it has a valid parent
if (parentRingList != null)
{
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 "+section.pos+" missing parent. Creating at "+section.pos.getParentPos());
}
parentSection = this._setRenderSectionInGridList(parentRingList, halfPos.x, halfPos.y, new LodRenderSection(section.pos.getParentPos()));
parentSection.childCount++;
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("parent sect "+section.pos.getParentPos()+" now has "+parentSection.childCount+" children.");
}
}
LodUtil.assertTrue(parentSection.childCount > 0 && parentSection.childCount <= 4);
}
// load this section's children
LodUtil.assertTrue(childRingList != null); // this shouldn't be a leaf node
for (int childIndex = 0; childIndex < 4; childIndex++)
{
DhSectionPos childPos = section.pos.getChildByIndex(childIndex);
LodRenderSection child = this._getRenderSectionFromGridList(childRingList, childPos.sectionX, childPos.sectionZ);
if (child == null)
{
// no child exists, create one
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("sect "+section.pos+" missing child at "+childPos+". Creating.");
}
child = this._setRenderSectionInGridList(childRingList, childPos.sectionX, childPos.sectionZ, new LodRenderSection(childPos));
child.childCount = 0;
}
else if (child.childCount == -1)
{
// a child existed but was marked for deletion,
// rescue (reuse) it
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("sect "+section.pos+" rescued child at "+childPos+".");
}
child.childCount = 0;
}
}
// the section is now fully loaded
section.childCount = 4;
}
else
{
// this render section is a fully loaded leaf node,
// TODO now what?
final DhSectionPos sectPos = (section != null) ? section.pos : new DhSectionPos(sectionDetailLevel, pos.x, pos.y);
// confirm the sectPos is correct
LodUtil.assertTrue(sectPos.sectionDetailLevel == sectionDetailLevel
&& sectPos.sectionX == pos.x
&& sectPos.sectionZ == pos.y,
"sectPos "+sectPos+" != "+pos+" @ "+sectionDetailLevel);
byte targetDetailLevel = this.calculateExpectedDetailLevel(playerPos, sectPos);
if (SUPER_VERBOSE_LOGGING)
{
String layerDetailLevel = (sectionDetailLevel == this.numbersOfSectionDetailLevels-1) ? "N/A" : this.getLayerDataDetail((byte) (sectionDetailLevel+1))+"";
LOGGER.info("0 child sect "+sectPos+"(null?"+(section==null)+") - target:"+targetDetailLevel+"/"+this.getLayerDataDetail(sectionDetailLevel)+" (parent:"+layerDetailLevel+")");
}
if (sectionDetailLevel == this.numbersOfSectionDetailLevels-1) // TODO equivalent to ... == treeMaxDetailLevel
{
// Render Section is at the top detail level.
// TODO what does any of this mean? shouldn't both values in this if statement be independent of the section?
if (section != null && targetDetailLevel > this.getLayerDataDetail(sectionDetailLevel))
{
// this section is a higher detail level than we want, mark it for deletion
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("sect "+sectPos+" in top detail level & target>current. Mark as free.");
}
section.childCount = -1;
}
if (section == null && targetDetailLevel <= this.getLayerDataDetail(sectionDetailLevel))
{
// the render section for this detail level is missing, create it
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("null sect "+sectPos+" in top & target<=current. Creating.");
}
section = this._setRenderSectionInGridList(ringList, pos.x, pos.y, new LodRenderSection(sectPos));
}
}
else
{
// Section is not in the top detail level,
// so we also need to consider its parent.
if (section != null && targetDetailLevel >= this.getLayerDataDetail((byte) (sectionDetailLevel + 1)))
{
// this section is a higher detail level than what we want, mark it for deletion
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("sect "+sectPos+" target>=nextLevel. Mark as free.");
}
LodUtil.assertTrue(parentRingList != null);
LodRenderSection parent = this._getNotNull(parentRingList, halfPos.x, halfPos.y);
LodUtil.assertTrue(parent.childCount > 0 && parent.childCount <= 4);
parent.childCount--;
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("parent sect "+sectPos+" now has "+parent.childCount+" child.");
}
// mark this section for deletion
section.childCount = -1;
// Note that this doesn't necessarily mean this section will be freed as it may be rescued later
// due to neighboring quadrants not able to be freed (they pass targetLevel checks or has children)
// or due to parent's layer is in the Always Cascade mode. (containerType == null)
}
if (section == null && targetDetailLevel < this.getLayerDataDetail((byte) (sectionDetailLevel + 1)))
{
// the render section for this detail level is missing, create it
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("null sect "+sectPos+" target<nextLevel. Creating.");
}
section = this._setRenderSectionInGridList(ringList, pos.x, pos.y, new LodRenderSection(sectPos));
LodUtil.assertTrue(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 "+sectPos+" missing parent. Creating at "+sectPos.getParentPos());
}
parent = this._setRenderSectionInGridList(parentRingList, halfPos.x, halfPos.y, new LodRenderSection(sectPos.getParentPos()));
}
parent.childCount++;
if (SUPER_VERBOSE_LOGGING)
{
LOGGER.info("parent render section "+sectPos.getParentPos()+" now has "+parent.childCount+" children.");
}
}
}
}
// Final quick assert to insure section pos is correct.
if (section != null)
{
LodUtil.assertTrue(section.pos.sectionDetailLevel == sectionDetailLevel, "section.pos: " + section.pos + " vs level: " + sectionDetailLevel);
LodUtil.assertTrue(section.pos.sectionX == pos.x, "section.pos: " + section.pos + " vs pos: " + pos);
LodUtil.assertTrue(section.pos.sectionZ == pos.y, "section.pos: " + section.pos + " vs pos: " + pos);
}
});
}
// 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). Step:
// ===Assertion steps===
// 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 can be loaded or unloaded, due to fast movement)
// 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
for (byte sectLevel = (byte) (numbersOfSectionLevels - 1); sectLevel >= LAYER_BEGINNING_OFFSET; sectLevel--) {
final MovableGridRingList<LodRenderSection> ringList = ringLists[sectLevel - LAYER_BEGINNING_OFFSET];
final MovableGridRingList<LodRenderSection> childRingList =
sectLevel == LAYER_BEGINNING_OFFSET ? null : ringLists[sectLevel - LAYER_BEGINNING_OFFSET - 1];
final boolean doCascade = false; // TODO: Utilize this cascade mode or at least expose this option
ringList.forEachPosOrdered((section, pos) -> {
if (section == null) return;
// Cascade layers
for (byte sectLevel = (byte) (this.numbersOfSectionDetailLevels - 1); sectLevel >= TREE_LOWEST_DETAIL_LEVEL; sectLevel--)
{
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 (section == null)
{
return;
}
// Cascade layers
// if (doCascade && section.childCount == 0) {
// LodUtil.assertTrue(childRingList != null);
// // Create childs to cascade the layer.
// // 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);
@@ -399,71 +531,156 @@ public class LodQuadTree implements AutoCloseable {
// }
// section.childCount = 4;
// }
// Call load on new sections, and tick on existing ones, and dispose old sections
if (section.childCount == -1) {
if (section.pos.sectionDetailLevel < numbersOfSectionLevels-1)
LodUtil.assertTrue(getParentSection(section.pos).childCount == 0);
ringList.set(pos.x, pos.y, null);
section.dispose();
return;
} else {
if (!section.isLoaded() && !section.isLoading()) {
section.load(renderSourceProvider);
} else if (section.isOutdated()) {
section.reload(renderSourceProvider);
}
if (section.childCount == 4) section.disableRender();
if (section.childCount == 0) section.enableRender(level, this);
section.tick(this, level);
}
// Assertion steps
LodUtil.assertTrue(section.childCount == 4 || section.childCount == 0);
if (section.pos.sectionDetailLevel == LAYER_BEGINNING_OFFSET) LodUtil.assertTrue(section.childCount == 0);
if (section.pos.sectionDetailLevel != LAYER_BEGINNING_OFFSET) {
LodRenderSection child0 = getChildSection(section.pos, 0);
LodRenderSection child1 = getChildSection(section.pos, 1);
LodRenderSection child2 = getChildSection(section.pos, 2);
LodRenderSection child3 = 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 {} child count 4 but child has null or is being disposed: {} {} {} {}",
section.pos, child0, child1, child2, child3);
if (section.childCount == 0) LodUtil.assertTrue(
(child0 == null || child0.childCount == -1) &&
(child1 == null || child1.childCount == -1) &&
(child2 == null || child2.childCount == -1) &&
(child3 == null || child3.childCount == -1),
"Sect {} child count 0 but child is neither null or being disposed: {} {} {} {}",
section.pos, child0, child1, child2, child3);
}
});
}
}
public String getDebugString() {
StringBuilder sb = new StringBuilder();
for (byte i = 0; i < ringLists.length; i++) {
sb.append("Layer ").append(i + LAYER_BEGINNING_OFFSET).append(":\n");
sb.append(ringLists[i].toDetailString());
sb.append("\n");
sb.append("\n");
}
return sb.toString();
// Call load on new sections, tick on existing ones, and dispose old sections
if (section.childCount == -1)
{
// dispose the old section
if (section.pos.sectionDetailLevel < this.numbersOfSectionDetailLevels-1)
{
LodUtil.assertTrue(this.getParentSection(section.pos).childCount == 0);
}
ringList.remove(pos.x, pos.y);
section.dispose();
return;
}
else
{
if (!section.isLoaded() && !section.isLoading())
{
// load in the new section
section.load(this.renderSourceProvider);
}
else if (section.isOutdated())
{
// replace the out of date data
section.reload(this.renderSourceProvider);
}
// TODO is this right? - enable rendering if this section is a leaf node in the tree, otherwise disable rendering
if (section.childCount == 4)
{
section.disableRender();
}
if (section.childCount == 0)
{
section.enableRender(this.level, this);
}
// update the section
section.tick(this, this.level);
}
// section validation
LodUtil.assertTrue(section.childCount == 4 || section.childCount == 0);
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+" child count 4 but child has null or is being disposed: {} {} {} {}", child0, child1, child2, child3);
}
else if (section.childCount == 0)
{
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+" child count 0 but child is neither null or being disposed: {} {} {} {}",
child0, child1, child2, child3);
}
}
});
}
}
//=========================//
// internal helper methods //
//=========================//
private LodRenderSection _setRenderSectionInGridList(MovableGridRingList<LodRenderSection> list, int x, int z, LodRenderSection renderSection)
{
LodUtil.assertTrue(renderSection != null, "setting null at [{},{}] in {}", x, z, list.toString());
LodUtil.assertTrue(renderSection.pos.sectionX == x && renderSection.pos.sectionZ == z, "pos {} != [{},{}] in {}", renderSection.pos, x, z, list.toString());
LodRenderSection section = list.setChained(x,z,renderSection);
LodUtil.assertTrue(section != null, "returned null at [{},{}]: {}", x, z, list.toString());
LodUtil.assertTrue(section == renderSection,"{} != {} in {}",section,renderSection, list.toString());
return section;
}
private LodRenderSection _getNotNull(MovableGridRingList<LodRenderSection> list, int x, int z)
{
LodUtil.assertTrue(list.inRange(x,z), "[{},{}] not in range of {}", x, z, list.toString());
LodRenderSection section = list.get(x,z);
LodUtil.assertTrue(section != null, "getting null at [{},{}] in {}", x, z, list.toString());
LodUtil.assertTrue(section.pos.sectionX == x && section.pos.sectionZ == z, "obj {} != [{},{}] in {}", section, x, z, list.toString());
return section;
}
private LodRenderSection _getRenderSectionFromGridList(MovableGridRingList<LodRenderSection> list, int x, int z)
{
LodRenderSection section = list.get(x,z);
LodUtil.assertTrue(section == null || (section.pos.sectionX == x && section.pos.sectionZ == z), "obj {} != [{},{}] in {}", section, x, z, list.toString());
return section;
}
//==============//
// 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();
}
@Override
public void close() {
for (MovableGridRingList<LodRenderSection> ringList : ringLists) {
ringList.forEach((section) -> {
if (section != null) section.dispose();
});
}
}
public void close()
{
for (MovableGridRingList<LodRenderSection> ringList : this.renderSectionRingLists)
{
ringList.forEach((section) ->
{
if (section != null)
{
section.dispose();
}
});
}
}
}
@@ -12,66 +12,108 @@ import java.util.concurrent.CompletableFuture;
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: 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;
// TODO: Should I provide a way to change the render source?
private ILodRenderSource lodRenderSource;
private CompletableFuture<ILodRenderSource> loadFuture;
private boolean isRenderEnabled = false;
private IRenderSourceProvider provider = null;
// Create sub region
public LodRenderSection(DhSectionPos pos) {
this.pos = pos;
// TODO: Should I provide a way to change the render source?
private ILodRenderSource lodRenderSource;
private IRenderSourceProvider renderSourceProvider = null; // TODO: rename these two interfaces to make it more obvious what each one does
private EVerticalQuality previousVerticalQualitySetting = null;
// Create sub region
public LodRenderSection(DhSectionPos pos) { this.pos = pos; }
//===========//
// rendering //
//===========//
public void enableRender(IDhClientLevel level, LodQuadTree quadTree)
{
if (this.isRenderEnabled)
{
return;
}
this.loadFuture = this.renderSourceProvider.read(this.pos);
this.isRenderEnabled = true;
}
public void enableRender(IDhClientLevel level, LodQuadTree quadTree) {
if (isRenderEnabled) return;
loadFuture = provider.read(pos);
isRenderEnabled = true;
}
public void disableRender() {
if (!isRenderEnabled) return;
if (lodRenderSource != null) {
lodRenderSource.disableRender();
lodRenderSource.dispose();
lodRenderSource = null;
public void disableRender()
{
if (!this.isRenderEnabled)
{
return;
}
if (this.lodRenderSource != null)
{
this.lodRenderSource.disableRender();
this.lodRenderSource.dispose();
this.lodRenderSource = null;
}
if (loadFuture != null) {
loadFuture.cancel(true);
loadFuture = null;
if (this.loadFuture != null)
{
this.loadFuture.cancel(true);
this.loadFuture = null;
}
isRenderEnabled = false;
this.isRenderEnabled = false;
}
//
//
//
public void load(IRenderSourceProvider renderDataProvider)
{
provider = renderDataProvider;
this.previousQualitySetting = Config.Client.Graphics.Quality.verticalQuality.get();
this.renderSourceProvider = renderDataProvider;
this.previousVerticalQualitySetting = Config.Client.Graphics.Quality.verticalQuality.get();
}
public void reload(IRenderSourceProvider renderDataProvider) {
if (loadFuture != null) {
loadFuture.cancel(true);
loadFuture = null;
public void reload(IRenderSourceProvider renderDataProvider)
{
if (this.loadFuture != null)
{
this.loadFuture.cancel(true);
this.loadFuture = null;
}
if (lodRenderSource != null) {
lodRenderSource.dispose();
lodRenderSource = null;
if (this.lodRenderSource != null)
{
this.lodRenderSource.dispose();
this.lodRenderSource = null;
}
loadFuture = renderDataProvider.read(pos);
this.previousQualitySetting = Config.Client.Graphics.Quality.verticalQuality.get();
this.loadFuture = renderDataProvider.read(this.pos);
this.previousVerticalQualitySetting = Config.Client.Graphics.Quality.verticalQuality.get();
}
public void tick(LodQuadTree quadTree, IDhClientLevel level)
{
if (this.loadFuture != null && this.loadFuture.isDone())
{
this.lodRenderSource = this.loadFuture.join();
this.loadFuture = null;
if (this.isRenderEnabled)
{
this.lodRenderSource.enableRender(level, quadTree);
@@ -80,54 +122,49 @@ public class LodRenderSection
if (this.lodRenderSource != null)
{
this.provider.refreshRenderSource(this.lodRenderSource);
this.renderSourceProvider.refreshRenderSource(this.lodRenderSource);
}
}
public void dispose() {
if (lodRenderSource != null) {
lodRenderSource.dispose();
} else if (loadFuture != null) {
loadFuture.cancel(true);
}
}
public boolean canRender() {
return isLoaded() && isRenderEnabled && lodRenderSource != null;
}
public boolean isLoaded() {
return provider != null;
}
//FIXME: Used by RenderBufferHandler
public int FIXME_BYPASS_DONT_USE_getChildCount() {
return childCount;
}
public boolean isLoading() {
return false;
}
private EVerticalQuality previousQualitySetting = null;
public boolean isOutdated()
public void dispose()
{
return this.previousQualitySetting != Config.Client.Graphics.Quality.verticalQuality.get() || (lodRenderSource != null && !lodRenderSource.isValid());
}
if (this.lodRenderSource != null)
{
this.lodRenderSource.dispose();
}
else if (this.loadFuture != null)
{
this.loadFuture.cancel(true);
}
}
public ILodRenderSource getRenderSource() {
return lodRenderSource;
}
public boolean canRender() { return this.isLoaded() && this.isRenderEnabled && this.lodRenderSource != null; }
public boolean isLoaded() { return this.renderSourceProvider != null; }
public boolean isLoading() { return false; }
//FIXME: Used by RenderBufferHandler
public int FIXME_BYPASS_DONT_USE_getChildCount() { return this.childCount; }
public boolean isOutdated() { return this.previousVerticalQualitySetting != Config.Client.Graphics.Quality.verticalQuality.get() || (this.lodRenderSource != null && !this.lodRenderSource.isValid()); }
public ILodRenderSource getRenderSource() { return this.lodRenderSource; }
public String toString() {
return "LodRenderSection{" +
"pos=" + pos +
", childCount=" + childCount +
", lodRenderSource=" + lodRenderSource +
", loadFuture=" + loadFuture +
", isRenderEnabled=" + isRenderEnabled +
"pos=" + this.pos +
", childCount=" + this.childCount +
", lodRenderSource=" + this.lodRenderSource +
", loadFuture=" + this.loadFuture +
", isRenderEnabled=" + this.isRenderEnabled +
'}';
}
}
@@ -194,7 +194,7 @@ public class RenderBufferHandler {
public RenderBufferHandler(LodQuadTree target) {
this.target = target;
MovableGridRingList<LodRenderSection> referenceList = target.getRingList((byte) (target.getNumbersOfSectionLevels() - 1));
MovableGridRingList<LodRenderSection> referenceList = target.getRingList((byte) (target.getNumbersOfSectionDetailLevels() - 1));
Pos2D center = referenceList.getCenter();
renderBufferNodes = new MovableGridRingList<>(referenceList.getHalfSize(), center);
}
@@ -215,7 +215,7 @@ public class RenderBufferHandler {
}
public void update() {
byte topDetail = (byte) (target.getNumbersOfSectionLevels() - 1);
byte topDetail = (byte) (target.getNumbersOfSectionDetailLevels() - 1);
MovableGridRingList<LodRenderSection> referenceList = target.getRingList(topDetail);
Pos2D center = referenceList.getCenter();
//boolean moved = renderBufferNodes.getCenterBlockPos().x != center.x || renderBufferNodes.getCenterBlockPos().y != center.y;
@@ -358,7 +358,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
/**
* TODO: Use MutablePos2D in the future
* TODO: Use MutablePos2D in the future <br>
* Will pass in null entries
*/
public void forEachPos(BiConsumer<? super T, Pos2D> consumer)
@@ -383,7 +383,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
}
/**
* TODO: Use MutablePos2D in the future
* TODO: Use MutablePos2D in the future <br>
* Will skip null entries
*/
public void forEachOrdered(Consumer<? super T> consumer)
@@ -414,7 +414,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
}
/**
* TODO: Use MutablePos2D in the future
* TODO: Use MutablePos2D in the future <br>
* Will pass in null entries
*/
public void forEachPosOrdered(BiConsumer<? super T, Pos2D> consumer)
@@ -432,8 +432,8 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
for (Pos2D offset : this.ringIteratorList)
{
LodUtil.assertTrue(this._inRangeAcquired(min.x + offset.x, min.y + offset.y, min));
T t = this._getUnsafe(min.x + offset.x, min.y + offset.y);
consumer.accept(t, new Pos2D(min.x + offset.x, min.y + offset.y));
T item = this._getUnsafe(min.x + offset.x, min.y + offset.y);
consumer.accept(item, new Pos2D(min.x + offset.x, min.y + offset.y));
}
}
finally
@@ -63,11 +63,11 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
}
private void _clientTick() {
int newViewDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * 16;
int newViewDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH;
Iterator<DhClientLevel> iterator = levels.values().iterator();
while (iterator.hasNext()) {
DhClientLevel level = iterator.next();
if (level.tree.viewDistance != newViewDistance) {
if (level.tree.blockViewDistance != newViewDistance) {
level.close();
iterator.remove();
}