This commit is contained in:
James Seibel
2023-06-11 15:53:53 -05:00
13 changed files with 202 additions and 69 deletions
@@ -65,6 +65,7 @@ public class HighDetailIncompleteFullDataSource implements IIncompleteFullDataSo
public final int dataPointsPerSection;
public boolean isEmpty = true;
public EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.EMPTY;
private boolean isPromoted = false;
@@ -614,10 +615,15 @@ public class HighDetailIncompleteFullDataSource implements IIncompleteFullDataSo
return this;
}
}
isPromoted = true;
CompleteFullDataSource fullDataSource = CompleteFullDataSource.createEmpty(this.sectionPos);
this.applyToFullDataSource(fullDataSource);
return fullDataSource;
}
@Override
public boolean hasBeenPromoted() {
return isPromoted;
}
}
@@ -52,6 +52,7 @@ public class LowDetailIncompleteFullDataSource extends FullDataArrayAccessor imp
private boolean isEmpty = true;
public EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.EMPTY;
private boolean isPromoted = false;
@@ -514,12 +515,16 @@ public class LowDetailIncompleteFullDataSource extends FullDataArrayAccessor imp
{
return this;
}
isPromoted = true;
return new CompleteFullDataSource(this.sectionPos, this.mapping, this.dataArrays);
}
@Override
public boolean hasBeenPromoted() {
return isPromoted;
}
//================//
// helper classes //
//================//
@@ -17,5 +17,6 @@ public interface IIncompleteFullDataSource extends IFullDataSource
* @return a new {@link CompleteFullDataSource} if successful, this if the promotion failed, .
*/
IFullDataSource tryPromotingToCompleteDataSource();
boolean hasBeenPromoted();
}
@@ -3,6 +3,7 @@ package com.seibel.lod.core.dataObjects.render.bufferBuilding;
import com.seibel.lod.core.pos.DhBlockPos2D;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.render.renderer.DebugRenderer;
import com.seibel.lod.core.render.renderer.IDebugRenderable;
import com.seibel.lod.core.render.renderer.LodRenderer;
import com.seibel.lod.core.render.AbstractRenderBuffer;
import com.seibel.lod.core.config.Config;
@@ -27,7 +28,7 @@ import static com.seibel.lod.core.render.glObject.GLProxy.GL_LOGGER;
*
* @see ColumnRenderBufferBuilder
*/
public class ColumnRenderBuffer extends AbstractRenderBuffer
public class ColumnRenderBuffer extends AbstractRenderBuffer implements IDebugRenderable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -55,14 +56,16 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
this.debugPos = debugPos;
vbos = new GLVertexBuffer[0];
vbosTransparent = new GLVertexBuffer[0];
DebugRenderer.register(this, (r) -> {
if (closed || this.vbos == null) {
return;
}
DebugRenderer.register(this);
}
Color c = Color.green;
r.renderBox(debugPos, -32, 32, c);
});
public void debugRender(DebugRenderer r)
{
if (closed || vbos == null) {
return;
}
Color c = Color.green;
r.renderBox(debugPos, 128, 128, 0.05f, c);
}
@@ -178,7 +178,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
while (--sectionDetail >= this.minDetailLevel)
{
DhLodPos minPos = pos.getCorner().getCornerLodPos(sectionDetail);
int count = pos.getSectionBBoxPos().getBlockWidth(sectionDetail);
int count = pos.getSectionBBoxPos().getWidthAtDetail(sectionDetail);
for (int xOffset = 0; xOffset < count; xOffset++)
{
@@ -1,10 +1,12 @@
package com.seibel.lod.core.file.fullDatafile;
import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.lod.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.lod.core.dataObjects.fullData.sources.HighDetailIncompleteFullDataSource;
import com.seibel.lod.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.lod.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource;
import com.seibel.lod.core.dataObjects.fullData.sources.LowDetailIncompleteFullDataSource;
import com.seibel.lod.core.file.metaData.BaseMetaData;
import com.seibel.lod.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.lod.core.generation.WorldGenerationQueue;
import com.seibel.lod.core.generation.tasks.WorldGenResult;
@@ -21,6 +23,7 @@ import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
public class GeneratedFullDataFileHandler extends FullDataFileHandler
{
@@ -173,7 +176,17 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
.thenApply((voidValue) -> incompleteFullDataSource.tryPromotingToCompleteDataSource());
}
}
/* @Override
public CompletableFuture<IFullDataSource> onDataFileRefresh(IFullDataSource source, BaseMetaData metaData, Function<IFullDataSource, Boolean> updater, Consumer<IFullDataSource> onUpdated)
{
return super.onDataFileRefresh(source, metaData, updater, (IFullDataSource d) -> {
if (d instanceof CompleteFullDataSource) {
}
}
}*/
/**
* Checks if the given {@link IFullDataSource} is fully generated and
* if it isn't, creates the necessary world gen request(s) to finish it. <br>
@@ -351,7 +364,10 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
@Override
public boolean isMemoryAddressValid() { return this.targetFullDataSourceRef.get() != null; }
public boolean isMemoryAddressValid() {
IFullDataSource ref = this.targetFullDataSourceRef.get();
return ref != null && !((IIncompleteFullDataSource)ref).hasBeenPromoted();
}
@Override
public Consumer<ChunkSizedFullDataAccessor> getOnGenTaskCompleteConsumer()
@@ -10,6 +10,8 @@ import com.seibel.lod.core.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.file.fullDatafile.FullDataFileHandler;
import com.seibel.lod.core.generation.tasks.*;
import com.seibel.lod.core.pos.*;
import com.seibel.lod.core.render.renderer.DebugRenderer;
import com.seibel.lod.core.render.renderer.IDebugRenderable;
import com.seibel.lod.core.util.ThreadUtil;
import com.seibel.lod.core.util.objects.quadTree.QuadNode;
import com.seibel.lod.core.util.objects.quadTree.QuadTree;
@@ -20,12 +22,13 @@ import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.io.Closeable;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
public class WorldGenerationQueue implements Closeable
public class WorldGenerationQueue implements Closeable, IDebugRenderable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -95,6 +98,7 @@ public class WorldGenerationQueue implements Closeable
{
throw new IllegalArgumentException(IDhApiWorldGenerator.class.getSimpleName() + ": max granularity smaller than min granularity!");
}
DebugRenderer.register(this);
}
@@ -242,7 +246,9 @@ public class WorldGenerationQueue implements Closeable
// }
// }
// }
private final Set<WorldGenTask> CheckingTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
/**
* @param targetPos the position to center the generation around
* @return false if no tasks were found to generate
@@ -252,6 +258,7 @@ public class WorldGenerationQueue implements Closeable
long closestGenDist = Long.MAX_VALUE;
WorldGenTask closestTask = null;
CheckingTasks.clear();
// TODO improve, having to go over every node isn't super efficient, removing null nodes from the tree would help
Iterator<QuadNode<WorldGenTask>> nodeIterator = this.waitingTaskQuadTree.nodeIterator();
@@ -263,10 +270,11 @@ public class WorldGenerationQueue implements Closeable
if (newGenTask != null) // TODO add an option to skip leaves with null values and potentially auto-prune them
{
CheckingTasks.add(newGenTask);
// TODO this isn't a long term fix, in the long term the tree should automatically remove out of bound nodes when moved
if (!this.waitingTaskQuadTree.isSectionPosInBounds(taskSectionPos))
if (!this.waitingTaskQuadTree.isSectionPosInBounds(taskSectionPos) || !newGenTask.StillValid())
{
// skip and remove out-of-bound tasks
// skip and remove out-of-bound tasks or tasks that are no longer valid
taskNode.value = null;
continue;
}
@@ -289,14 +297,10 @@ public class WorldGenerationQueue implements Closeable
return false;
}
// remove the task we found, we are going to start it and don't want to run it multiple times
// TODO the setValue can fail if the user is moving and the task that was once in range is no longer in range
WorldGenTask removedWorldGenTask = this.waitingTaskQuadTree.setValue(new DhSectionPos(closestTask.pos.detailLevel, closestTask.pos.x, closestTask.pos.z), null);
// do we need to modify this task to generate it?
if(this.canGeneratePos((byte) 0, closestTask.pos)) // TODO should detail level 0 be replaced?
{
@@ -371,7 +375,6 @@ public class WorldGenerationQueue implements Closeable
DhChunkPos chunkPosMin = new DhChunkPos(taskPos.getCornerBlockPos());
// check if this is a duplicate generation task
if (this.alreadyGeneratedPosHashSet.contains(inProgressTaskGroup.group.pos))
{
@@ -578,6 +581,8 @@ public class WorldGenerationQueue implements Closeable
LOGGER.info("Finished closing "+WorldGenerationQueue.class.getSimpleName());
DebugRenderer.unregister(this);
}
@@ -616,6 +621,16 @@ public class WorldGenerationQueue implements Closeable
return index;
}
@Override
public void debugRender(DebugRenderer r) {
CheckingTasks.forEach((t) -> {
DhLodPos pos = t.pos;
r.renderBox(pos, -32f, 64f, 0.05f, Color.blue);
});
this.inProgressGenTasksByLodPos.forEach((pos, t) -> {
r.renderBox(pos, -30f, 64f, 0.05f, Color.red);
});
}
}
@@ -25,5 +25,8 @@ public final class WorldGenTask
this.taskTracker = taskTracker;
this.future = future;
}
public boolean StillValid() {
return taskTracker.isMemoryAddressValid();
}
}
@@ -39,19 +39,22 @@ public class DhLodPos implements Comparable<DhLodPos>
public DhLodUnit getX() { return new DhLodUnit(this.detailLevel, this.x); }
public DhLodUnit getZ() { return new DhLodUnit(this.detailLevel, this.z); }
public int getBlockWidth() { return this.getBlockWidth(this.detailLevel); }
public int getBlockWidth(byte detailLevel) // TODO this needs some documentation or a better name describing what is happening, why is there an assert here?
// Get the width of this pos, measured in the mc block unit. (i.e. detail 0)
public int getBlockWidth() { return this.getWidthAtDetail((byte)0); }
// Get the width of this pos, measured in the target detail level.
public int getWidthAtDetail(byte targetLevel)
{
LodUtil.assertTrue(detailLevel <= this.detailLevel);
return BitShiftUtil.powerOfTwo(this.detailLevel - detailLevel);
LodUtil.assertTrue(targetLevel <= this.detailLevel);
return BitShiftUtil.powerOfTwo(this.detailLevel - targetLevel);
}
public DhBlockPos2D getCenterBlockPos()
{
return new DhBlockPos2D(
this.getX().toBlockWidth() + BitShiftUtil.half(this.getBlockWidth()),
this.getZ().toBlockWidth() + BitShiftUtil.half(this.getBlockWidth()));
this.getZ().toBlockWidth() + BitShiftUtil.half(this.getBlockWidth()));
}
public DhBlockPos2D getCornerBlockPos() { return new DhBlockPos2D(this.getX().toBlockWidth(), this.getZ().toBlockWidth()); }
@@ -16,6 +16,7 @@ import org.apache.logging.log4j.Logger;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* This quadTree structure is our core data structure and holds
@@ -32,9 +33,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
/**
* This holds every {@link DhSectionPos} that should be reloaded next tick. <br>
* This is a {@link ConcurrentHashMap} because new sections can be added to this list via the world generator threads.
* This is a {@link ConcurrentLinkedQueue} because new sections can be added to this list via the world generator threads.
*/
private final ConcurrentHashMap<DhSectionPos, Boolean> sectionsToReload = new ConcurrentHashMap<>();
private final ConcurrentLinkedQueue<DhSectionPos> sectionsToReload = new ConcurrentLinkedQueue<>();
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference!
@@ -80,7 +81,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
try
{
// recenter if necessary, removing out of bounds sections
this.setCenterBlockPos(playerPos, LodRenderSection::disposeRenderData);
this.setCenterBlockPos(playerPos, LodRenderSection::dispose);
updateAllRenderSections(playerPos);
}
@@ -92,12 +93,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
private void updateAllRenderSections(DhBlockPos2D playerPos)
{
// reload any sections that need it
DhSectionPos[] reloadSectionArray = this.sectionsToReload.keySet().toArray(new DhSectionPos[0]);
this.sectionsToReload.clear();
for (DhSectionPos pos : reloadSectionArray)
DhSectionPos pos;
while ((pos = this.sectionsToReload.poll()) != null)
{
// walk up the tree until we hit the root node
// this is done so any high detail changes flow up to the lower detail render sections as well
// this is done so any high detail changes flow up to the lower detail render sections as well
while (pos.sectionDetailLevel <= this.treeMaxDetailLevel)
{
try
@@ -110,12 +110,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
}
catch (IndexOutOfBoundsException e)
{ /* the section is now out of bounds, it doesn't need to be reloaded */ }
pos = pos.getParentPos();
}
}
// walk through each root node
Iterator<DhSectionPos> rootPosIterator = this.rootNodePosIterator();
while (rootPosIterator.hasNext())
@@ -352,7 +351,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
}
//LOGGER.info("LodQuadTree reloadPos ["+pos+"].");
this.sectionsToReload.put(pos, true);
this.sectionsToReload.add(pos);
}
@@ -6,8 +6,12 @@ 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.render.renderer.DebugRenderer;
import com.seibel.lod.core.render.renderer.IDebugRenderable;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
@@ -15,7 +19,7 @@ import java.util.concurrent.atomic.AtomicReference;
* A render section represents an area that could be rendered.
* For more information see {@link LodQuadTree}.
*/
public class LodRenderSection
public class LodRenderSection implements IDebugRenderable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -38,7 +42,30 @@ public class LodRenderSection
public LodRenderSection(DhSectionPos pos) { this.pos = pos; }
public LodRenderSection(DhSectionPos pos) {
this.pos = pos;
DebugRenderer.register(this);
}
public void debugRender(DebugRenderer r)
{
Color color = Color.red;
if (this.renderSourceProvider == null) color = Color.black;
if (this.renderSourceLoadFuture != null) color = Color.yellow;
if (renderSource != null) {
color = Color.blue;
if (isRenderingEnabled) color = Color.cyan;
if (isRenderingEnabled && isRenderDataLoaded()) color = Color.green;
}
float yOffset = Objects.hashCode(this) / (float) Integer.MAX_VALUE * 16f;
r.renderBox(this.pos, yOffset, yOffset, 0.1f, color);
}
@@ -160,6 +187,8 @@ public class LodRenderSection
this.renderSourceLoadFuture.cancel(true);
this.renderSourceLoadFuture = null;
}
this.renderSourceProvider = null;
}
@@ -208,5 +237,9 @@ public class LodRenderSection
", isRenderEnabled=" + this.isRenderingEnabled +
'}';
}
public void dispose() {
DebugRenderer.unregister(this);
disposeRenderData();
}
}
@@ -7,8 +7,8 @@ import com.seibel.lod.core.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.logging.ConfigBasedSpamLogger;
import com.seibel.lod.core.pos.DhBlockPos2D;
import com.seibel.lod.core.pos.DhLodPos;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.render.glObject.GLProxy;
import com.seibel.lod.core.render.glObject.GLState;
import com.seibel.lod.core.render.glObject.buffer.GLElementBuffer;
import com.seibel.lod.core.render.glObject.buffer.GLVertexBuffer;
@@ -22,11 +22,10 @@ import org.apache.logging.log4j.LogManager;
import org.lwjgl.opengl.GL32;
import java.awt.*;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.*;
public class DebugRenderer {
public static DebugRenderer INSTANCE = new DebugRenderer();
@@ -74,12 +73,34 @@ public class DebugRenderer {
VertexAttribute va;
boolean init = false;
public static void unregister(IDebugRenderable r) {
if (INSTANCE == null) return;
INSTANCE.removeRenderer(r);
}
private void removeRenderer(IDebugRenderable r) {
synchronized (renderers) {
Iterator<WeakReference<IDebugRenderable>> it = renderers.iterator();
while (it.hasNext()) {
WeakReference<IDebugRenderable> ref = it.next();
if (ref.get() == null) {
it.remove();
continue;
}
if (ref.get() == r) {
it.remove();
return;
}
}
}
}
public void init() {
if (init) return;
init = true;
va = VertexAttribute.create();
va.bind();
// Pos
// Pos\
va.setVertexAttribute(0, 0, VertexAttribute.VertexPointer.addVec3Pointer(false));
va.completeAndCheck(Float.BYTES * 3);
basicShader = new ShaderProgram("shaders/debug/vert.vert", "shaders/debug/frag.frag",
@@ -105,30 +126,40 @@ public class DebugRenderer {
boxOutlineBuffer.uploadBuffer(buffer, EGpuUploadMethod.DATA, box_outline_indices.length * Integer.BYTES, GL32.GL_STATIC_DRAW);
}
@FunctionalInterface
public interface IDebugRender {
void render(DebugRenderer r);
}
private final LinkedList<WeakReference<IDebugRenderable>> renderers = new LinkedList<>();
private final Map<Object, IDebugRender> renderers = Collections.synchronizedMap(new WeakHashMap<>());
public void addRenderer(Object o, IDebugRender r) {
public void addRenderer(IDebugRenderable r) {
if (!Config.Client.Advanced.Debugging.debugWireframeRendering.get()) return;
renderers.put(o, r);
synchronized (renderers) {
renderers.add(new WeakReference<>(r));
}
}
public static void register(Object o, IDebugRender r) {
public static void register(IDebugRenderable r) {
if (INSTANCE == null) return;
INSTANCE.addRenderer(o, r);
INSTANCE.addRenderer(r);
}
private Mat4f transform_this_frame;
private Vec3f camf;
public void renderBox(DhLodPos pos, float minY, float maxY, float marginPercent, Color color) {
DhBlockPos2D blockMin = pos.getCornerBlockPos();
DhBlockPos2D blockMax = blockMin.add(pos.getBlockWidth(), pos.getBlockWidth());
float edge = pos.getBlockWidth() * marginPercent;
renderBox(blockMin.x + edge, minY, blockMin.z + edge, blockMax.x - edge, maxY, blockMax.z - edge, color);
}
public void renderBox(DhLodPos pos, float minY, float maxY, Color color) {
renderBox(pos, minY, maxY, 0, color);
}
public void renderBox(DhSectionPos sectPos, float minY, float maxY, Color color) {
DhBlockPos2D blockMin = sectPos.getCorner().getCornerBlockPos();
DhBlockPos2D blockMax = blockMin.add(sectPos.getWidth().toBlockWidth(), sectPos.getWidth().toBlockWidth());
renderBox(blockMin.x, minY, blockMin.z, blockMax.x, maxY, blockMax.z, color);
renderBox(sectPos.getSectionBBoxPos(), minY, maxY, 0, color);
}
public void renderBox(DhSectionPos sectPos, float minY, float maxY, float marginPercent, Color color) {
renderBox(sectPos.getSectionBBoxPos(), minY, maxY, marginPercent, color);
}
public void renderBox(float x, float y, float z, float x2, float y2, float z2, Color color) {
@@ -165,9 +196,22 @@ public class DebugRenderer {
basicShader.bind();
va.bind();
va.bindBufferToAllBindingPoint(boxBuffer.getId());
boxOutlineBuffer.bind();
renderers.forEach((o, r) -> r.render(this));
synchronized (renderers)
{
Iterator<WeakReference<IDebugRenderable>> it = renderers.iterator();
while (it.hasNext()) {
WeakReference<IDebugRenderable> ref = it.next();
IDebugRenderable r = ref.get();
if (r == null) {
it.remove();
continue;
}
r.debugRender(this);
}
}
state.restore();
}
@@ -0,0 +1,5 @@
package com.seibel.lod.core.render.renderer;
public interface IDebugRenderable {
void debugRender(DebugRenderer r);
}