Continue work on buffer managements

This commit is contained in:
TomTheFurry
2022-05-11 17:19:13 +08:00
parent 786e00e88f
commit 0c52fe8364
11 changed files with 337 additions and 76 deletions
@@ -35,6 +35,10 @@ public class Pos2D {
public Pos2D subtract(Pos2D other) {
return new Pos2D(x - other.x, y - other.y);
}
public Pos2D subtract(int v) {
return new Pos2D(x - v, y - v);
}
public double dist(Pos2D other) {
return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(y - other.y, 2));
}
@@ -18,27 +18,30 @@ import com.seibel.lod.core.util.gridList.MovableGridRingList;
* -by adding data with the lodBuilder
*/
public abstract class LodQuadTree {
/**
* TODO add static configs here
* These configs are updated someway
* //TODO add static configs here
* //These configs are updated someway
* Comment: all config value should be via the class that extends this class, and
* by implementing different abstract methods - LeeTom
*/
public final int maxPossibleDetailLevel;
public final int numbersOfDetailLevels;
private final MovableGridRingList<LodSection>[] ringLists;
/**
* Constructor of the quadTree
* @param viewDistance
* @param initialPlayerX
* @param initialPlayerZ
* @param viewDistance View distance in blocks
* @param initialPlayerX player x coordinate
* @param initialPlayerZ player z coordinate
*/
public LodQuadTree(int viewDistance, int initialPlayerX, int initialPlayerZ) {
maxPossibleDetailLevel = DetailDistanceUtil.getDetailLevelFromDistance(viewDistance*Math.sqrt(2));
ringLists = new MovableGridRingList[maxPossibleDetailLevel];
numbersOfDetailLevels = DetailDistanceUtil.getDetailLevelFromDistance(viewDistance*Math.sqrt(2));
ringLists = new MovableGridRingList[numbersOfDetailLevels];
int size;
for (byte detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) {
for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) {
double distance = DetailDistanceUtil.getDrawDistanceFromDetail(detailLevel);
int sectionCount = LodUtil.ceilDiv((int) Math.ceil(distance),
DhSectionPos.getWidth(detailLevel).toBlock()) + 1; // +1 for the border during move
@@ -49,19 +52,37 @@ public abstract class LodQuadTree {
/**
* This method return the LodSection given the Section Pos
* @param pos
* @return
* @param pos the section positon
* @return the LodSection
*/
public LodSection getSection(DhSectionPos pos) {
return getSection(pos.detail, pos.x, pos.z);
}
/**
* 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<LodSection> getRingList(byte detailLevel) {
return ringLists[detailLevel];
}
/**
* This method returns the number of detail levels in the quadTree
* @return the number of detail levels
*/
public int getNumbersOfDetailLevels() {
return numbersOfDetailLevels;
}
/**
* This method return the LodSection at the given detail level and level coordinate x and z
* @param detailLevel
* @param x
* @param z
* @return
* @param detailLevel detail level of the section
* @param x x coordinate of the section
* @param z z coordinate of the section
* @return the LodSection
*/
public LodSection getSection(byte detailLevel, int x, int z) {
return ringLists[detailLevel].get(x, z);
@@ -71,8 +92,8 @@ public abstract class LodQuadTree {
/**
* This method will compute the detail level based on player position and section pos
* @param playerPos
* @param sectionPos
* @param playerPos player position as a reference for calculating the detail level
* @param sectionPos section position
* @return detail level of this section pos
*/
public byte calculateExpectedDetailLevel(DhBlockPos2D playerPos, DhSectionPos sectionPos) {
@@ -84,7 +105,7 @@ public abstract class LodQuadTree {
/**
* Given a section pos at level n this method returns the parent section at level n+1
* @param pos
* @param pos the section positon
* @return the parent LodSection
*/
public LodSection getParentSection(DhSectionPos pos) {
@@ -106,12 +127,12 @@ public abstract class LodQuadTree {
/**
* This function update the quadTree based on the playerPos and the current game configs (static and global)
* @param playerPos
* @param playerPos the reference position for the player
*/
public void tick(DhBlockPos2D playerPos) {
for (int detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) {
for (int detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) {
ringLists[detailLevel].move(playerPos.x >> detailLevel, playerPos.z >> detailLevel,
LodSection::immediateDispose);
LodSection::dispose);
}
// First tick pass: update all sections' childCount from bottom level to top level. Step:
@@ -133,12 +154,12 @@ public abstract class LodQuadTree {
// - set childCount to -1 (Signal that this section will be freed if not rescued)
// - If targetLevel <= detail && section == null:
// - Parent's childCount++ (Create parent if needed)
for (byte detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) {
for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) {
final MovableGridRingList<LodSection> ringList = ringLists[detailLevel];
final MovableGridRingList<LodSection> childRingList =
detailLevel == 0 ? null : ringLists[detailLevel - 1];
final MovableGridRingList<LodSection> parentRingList =
detailLevel == maxPossibleDetailLevel - 1 ? null : ringLists[detailLevel + 1];
detailLevel == numbersOfDetailLevels - 1 ? null : ringLists[detailLevel + 1];
final byte detail = detailLevel;
ringList.forEachPosOrdered((section, pos) -> {
if (detail == 0 && section != null) {
@@ -150,7 +171,7 @@ public abstract class LodQuadTree {
LodSection parent = parentRingList.get(pos.x >> 1, pos.y >> 1);
if (parent == null) {
parent = parentRingList.setChained(pos.x >> 1, pos.y >> 1,
new LodSection(section.pos.getParent()));
new LodSection(section.pos.getParent(), getRenderDataSource()));
parent.childCount++;
}
LodUtil.assertTrue(parent.childCount <= 4 && parent.childCount > 0);
@@ -159,7 +180,7 @@ public abstract class LodQuadTree {
LodSection child = ringList.get(childPos.x, childPos.z);
if (child == null) {
child = ringList.setChained(childPos.x, childPos.z,
new LodSection(childPos));
new LodSection(childPos, getRenderDataSource()));
child.childCount = 0;
} else if (child.childCount == -1) {
child.childCount = 0;
@@ -181,7 +202,7 @@ public abstract class LodQuadTree {
LodSection parent = parentRingList.get(pos.x >> 1, pos.y >> 1);
if (parent == null) {
parent = parentRingList.setChained(pos.x >> 1, pos.y >> 1,
new LodSection(sectPos.getParent()));
new LodSection(sectPos.getParent(), getRenderDataSource()));
}
parent.childCount++;
}
@@ -209,12 +230,12 @@ public abstract class LodQuadTree {
// if childCount == -1: // (section can be loaded or unloaded, due to fast movement)
// - 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 detailLevel = 0; detailLevel < maxPossibleDetailLevel; detailLevel++) {
for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) {
final MovableGridRingList<LodSection> ringList = ringLists[detailLevel];
final MovableGridRingList<LodSection> childRingList =
detailLevel == 0 ? null : ringLists[detailLevel - 1];
final MovableGridRingList<LodSection> parentRingList =
detailLevel == maxPossibleDetailLevel - 1 ? null : ringLists[detailLevel + 1];
detailLevel == numbersOfDetailLevels - 1 ? null : ringLists[detailLevel + 1];
ringList.forEachPosOrdered((section, pos) -> {
LodUtil.assertTrue(section.childCount == 4 || section.childCount == 0 || section.childCount == -1);
if (section.childCount == 4) LodUtil.assertTrue(
@@ -229,16 +250,12 @@ public abstract class LodQuadTree {
getChildSection(section.pos, 3) == null);
if (section.childCount == -1) LodUtil.assertTrue(
getParentSection(section.pos).childCount == 0);
if (section.childCount == 4 && section.isLoaded()) {
section.load(getRenderDataSource());
} else if (section.childCount == 0 && !section.isLoaded()) {
section.unload();
} else if (section.childCount == 0 && !section.isLoaded()) {
section.load();
} else if (section.childCount == -1) {
ringList.set(pos.x, pos.y, null);
if (section.isLoaded()) {
section.unload();
}
section.dispose();
}
});
@@ -14,43 +14,34 @@ public class LodSection {
// (Should always be 4 after tick() is done, or 0 only if this is an unloaded node)
public byte childCount = 0;
private RenderDataContainer levelContainer;
public RenderContainer renderContainer = null;
// TODO: Should I provide a way to change the render source?
private RenderContainer renderContainer;
// Create sub region
public LodSection(DhSectionPos pos) {
public LodSection(DhSectionPos pos, RenderDataSource renderSource) {
this.pos = pos;
levelContainer = null;
}
LodSection(DhSectionPos pos, RenderDataContainer levelContainer) {
this.pos = pos;
this.levelContainer = levelContainer;
this.renderContainer = renderSource.createRenderData(pos);
}
// Return null if data does not exist
public boolean load(RenderDataSource renderDataSource) {
if (isLoaded()) throw new IllegalStateException("LodSection is already loaded");
levelContainer = renderDataSource.createRenderData(pos);
return levelContainer != null;
public void load() {
LodUtil.assertTrue(!isLoaded());
renderContainer.load();
}
public void unload() {
if (!isLoaded()) throw new IllegalStateException("LodSection is not loaded");
if (renderContainer != null) renderContainer.notifyUnload();
levelContainer = null;
}
public void dispose() {
LodUtil.assertTrue(!isLoaded());
if (renderContainer != null) renderContainer.notifyDispose();
LodUtil.assertTrue(isLoaded());
renderContainer.unload();
}
public void immediateDispose() {
if (isLoaded()) unload();
if (renderContainer != null) renderContainer.notifyDispose();
public void dispose() {
if (renderContainer != null) renderContainer.dispose();
}
public boolean isLoaded() {
return levelContainer != null;
return renderContainer != null && renderContainer.isLoaded();
}
public RenderContainer getRenderContainer() {
return renderContainer;
}
}
@@ -1,7 +1,13 @@
package com.seibel.lod.core.objects.a7;
import com.seibel.lod.core.objects.a7.pos.DhSectionPos;
import com.seibel.lod.core.objects.a7.render.RenderContainer;
public interface RenderDataSource {
RenderDataContainer createRenderData(DhSectionPos pos);
/**
* Returns the render container for the given section.
* @param pos The section position.
* @return The render container. If there are no data, returns EmptyRenderContainer.
*/
RenderContainer createRenderData(DhSectionPos pos);
}
@@ -1,8 +1,9 @@
package com.seibel.lod.core.objects.a7.data;
import com.seibel.lod.core.objects.a7.RenderDataContainer;
import com.seibel.lod.core.objects.a7.RenderDataSource;
import com.seibel.lod.core.objects.a7.pos.DhSectionPos;
import com.seibel.lod.core.objects.a7.render.EmptyRenderContainer;
import com.seibel.lod.core.objects.a7.render.RenderContainer;
import java.io.File;
@@ -14,10 +15,8 @@ public class DataHandler implements RenderDataSource {
}
@Override
public RenderDataContainer createRenderData(DhSectionPos pos) {
public RenderContainer createRenderData(DhSectionPos pos) {
//TODO
return null;
return EmptyRenderContainer.INSTANCE;
}
}
@@ -5,6 +5,8 @@ import com.seibel.lod.core.objects.opengl.RenderBuffer;
import com.seibel.lod.core.objects.opengl.RenderRegion;
import com.seibel.lod.core.util.LodUtil;
import java.util.concurrent.atomic.AtomicReference;
public class ColumnRenderContainer extends RenderContainer {
public static final int columnWidth = DhSectionPos.DATA_WIDTH_PER_SECTION;
public static final int columnCount = LodUtil.pow2(DhSectionPos.DATA_WIDTH_PER_SECTION);
@@ -21,6 +23,26 @@ public class ColumnRenderContainer extends RenderContainer {
renderRegion = new RenderRegion();
}
@Override
public void notifyRenderable() {
}
@Override
public void notifyUnrenderable() {
}
@Override
public boolean isRenderable() {
return false;
}
@Override
public void notifyLoad() {
}
@Override
public void notifyUnload() {
@@ -32,8 +54,7 @@ public class ColumnRenderContainer extends RenderContainer {
}
@Override
public RenderRegion getRenderRegion() {
return null;
public boolean trySwapRenderBuffer(AtomicReference<RenderBuffer> referenceSlot) {
return false;
}
}
@@ -1,6 +1,32 @@
package com.seibel.lod.core.objects.a7.render;
import com.seibel.lod.core.objects.opengl.RenderBuffer;
import java.util.concurrent.atomic.AtomicReference;
public class EmptyRenderContainer extends RenderContainer {
public static final EmptyRenderContainer INSTANCE = new EmptyRenderContainer();
@Override
public void notifyRenderable() {
}
@Override
public void notifyUnrenderable() {
}
@Override
public boolean isRenderable() {
return false;
}
@Override
public void notifyLoad() {
}
@Override
public void notifyUnload() {
@@ -12,7 +38,7 @@ public class EmptyRenderContainer extends RenderContainer {
}
@Override
public boolean render() {
return true; //Always render successfully since there is nothing to render
public boolean trySwapRenderBuffer(AtomicReference<RenderBuffer> referenceSlot) {
return false; // no swap
}
}
@@ -0,0 +1,144 @@
package com.seibel.lod.core.objects.a7.render;
import com.seibel.lod.core.objects.Pos2D;
import com.seibel.lod.core.objects.a7.LodQuadTree;
import com.seibel.lod.core.objects.a7.LodSection;
import com.seibel.lod.core.objects.a7.pos.DhSectionPos;
import com.seibel.lod.core.objects.opengl.RenderBuffer;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.gridList.MovableGridRingList;
import java.util.concurrent.atomic.AtomicReference;
public class RenderBufferHandler {
public final LodQuadTree target;
private final MovableGridRingList<RenderBufferNode> renderBufferNodes;
class RenderBufferNode implements AutoCloseable {
public final DhSectionPos pos;
public volatile RenderBufferNode[] children = null;
public AtomicReference<RenderBuffer> renderBufferSlot = null;
public RenderBufferNode(DhSectionPos pos) {
this.pos = pos;
}
public void render(LodRenderProgram renderContext) {
RenderBuffer buff = renderBufferSlot.get();
if (buff != null) {
buff.render(renderContext);
} else {
RenderBufferNode[] childs = children;
if (childs != null) {
for (RenderBufferNode child : childs) {
child.render(renderContext);
}
}
}
}
//TODO: In the future make this logic a bit more complex so that when children are just created,
// the buffer is only unloaded if all children's buffers are ready. This will make the
// transition between buffers no longer causing any flicker.
public void update() {
LodSection section = target.getSection(pos);
// If this fails, there may be concurrent modification of the quad tree
// (as this update() should be called from the same thread that calls update() on the quad tree)
LodUtil.assertTrue(section != null);
RenderContainer container = section.getRenderContainer();
// Update self's render buffer state
boolean shouldRender = section.isLoaded();
if (!shouldRender) {
RenderBuffer buff = renderBufferSlot.getAndSet(null);
if (buff != null) {
buff.close();
}
} else {
container.trySwapRenderBuffer(renderBufferSlot);
}
// Update children's render buffer state
boolean shouldHaveChildren = !container.isLoaded();
if (shouldHaveChildren) {
if (children == null) {
RenderBufferNode[] childs = new RenderBufferNode[4];
for (int i = 0; i < 4; i++) {
childs[i] = new RenderBufferNode(pos.getChild(i));
}
children = childs;
}
for (RenderBufferNode child : children) {
child.update();
}
} else {
if (children != null) {
RenderBufferNode[] childs = children;
children = null;
for (RenderBufferNode child : childs) {
child.close();
}
}
}
}
@Override
public void close() {
if (children != null) {
for (RenderBufferNode child : children) {
child.close();
}
}
RenderBuffer buff = renderBufferSlot.getAndSet(null);
if (buff != null) {
buff.close();
}
}
}
public RenderBufferHandler(LodQuadTree target) {
this.target = target;
MovableGridRingList<LodSection> referenceList = target.getRingList((byte) (target.getNumbersOfDetailLevels() - 1));
Pos2D center = referenceList.getCenter();
renderBufferNodes = new MovableGridRingList<>(referenceList.getHalfSize(), center);
}
public void render(LodRenderProgram renderContext) {
//TODO: This might get locked by update() causing move() call. Is there a way to avoid this?
// Maybe dupe the base list and use atomic swap on render? Or is this not worth it?
renderBufferNodes.forEachOrdered(n -> n.render(renderContext));
}
public void update() {
byte topDetail = (byte) (target.getNumbersOfDetailLevels() - 1);
MovableGridRingList<LodSection> referenceList = target.getRingList(topDetail);
Pos2D center = referenceList.getCenter();
renderBufferNodes.move(center.x, center.y, RenderBufferNode::close); // Note: may lock the list
renderBufferNodes.forEachPosOrdered((node, pos) -> {
DhSectionPos sectPos = new DhSectionPos(topDetail, pos.x, pos.y);
LodSection section = target.getSection(sectPos);
if (section == null) {
// If section is null, but node exists, remove node
if (node != null) {
renderBufferNodes.remove(pos).close();
}
// If section is null, continue
return;
}
// If section is not null, but node does not exist, create node
if (node == null) {
node = renderBufferNodes.setChained(pos, new RenderBufferNode(sectPos));
}
// Node should be not null here
// Update node
node.update();
});
}
public void close() {
renderBufferNodes.clear(RenderBufferNode::close);
}
}
@@ -3,11 +3,39 @@ package com.seibel.lod.core.objects.a7.render;
import com.seibel.lod.core.objects.opengl.RenderBuffer;
import com.seibel.lod.core.objects.opengl.RenderRegion;
import java.util.concurrent.atomic.AtomicReference;
public abstract class RenderContainer {
public abstract void notifyUnload();
public abstract void notifyDispose();
private boolean isLoaded = false;
public final void load() {
isLoaded = true;
notifyLoad();
}
public final void unload() {
isLoaded = false;
notifyUnload();
}
public final void dispose() {
if (isLoaded) {
unload();
}
notifyDispose();
}
public abstract RenderRegion getRenderRegion();
public final boolean isLoaded() {
return isLoaded;
}
protected abstract void notifyLoad(); // notify the container that it is now loaded and therefore may be rendered
protected abstract void notifyUnload(); // notify the container that it is now unloaded and therefore will not be rendered
protected abstract void notifyDispose(); // notify the container that the parent lodSection is now disposed
/**
* Try and swap in new render buffer for this section. Note that before this call, there should be no other
* places storing or referencing the render buffer.
* @param referenceSlot The slot for swapping in the new buffer.
* @return True if the swap was successful. False if swap is not needed or if it is in progress.
*/
public abstract boolean trySwapRenderBuffer(AtomicReference<RenderBuffer> referenceSlot);
}
@@ -63,7 +63,8 @@ public class RenderRegion implements AutoCloseable
/** stores if the region at the given x and z index needs to be regenerated */
// Use int because I need Tri state:
private final AtomicInteger needRegen = new AtomicInteger(2);
private enum BackState {
Unused,
Building,
@@ -41,6 +41,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
private Pos2D[] ringIteratorList = null;
//TODO: Check if this needs to be synchronized
//FIXME: Make all usage of this class do stuff relative to the minPos instead of the center
private void buildRingIteratorList() {
ringIteratorList = null;
Pos2D[] list = new Pos2D[size*size];
@@ -67,6 +68,9 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
pos.set(new Pos2D(centerX-halfSize, centerY-halfSize));
clear();
}
public MovableGridRingList(int halfSize, Pos2D center) {
this(halfSize, center.x, center.y);
}
@Override
public void clear() {
@@ -173,6 +177,23 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
}
}
public T remove(int x, int y) {
return swap(x, y, null);
}
public T get(Pos2D p) {
return get(p.x, p.y);
}
public boolean set(Pos2D p, T t) {
return set(p.x, p.y, t);
}
public T swap(Pos2D p, T t) {
return swap(p.x, p.y, t);
}
public T remove(Pos2D p) {
return remove(p.x, p.y);
}
// TODO: Impl this
/*
// do a compare and set
@@ -196,6 +217,9 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
public T setChained(int x, int y, T t) {
return set(x,y,t) ? t : null;
}
public T setChained(Pos2D p, T t) {
return setChained(p.x, p.y, t);
}
// Return false if haven't changed. Return true if it did
public boolean move(int newCenterX, int newCenterY) {