Fix RenderSection overlap and holes
This commit is contained in:
@@ -250,7 +250,6 @@ public class ColumnRenderSource
|
||||
// the source is empty, don't attempt to update anything
|
||||
return;
|
||||
}
|
||||
|
||||
// the source isn't empty, this object won't be empty after the method finishes
|
||||
this.isEmpty = false;
|
||||
|
||||
@@ -366,10 +365,7 @@ public class ColumnRenderSource
|
||||
}
|
||||
}
|
||||
|
||||
public void enableRender(IDhClientLevel level)
|
||||
{
|
||||
this.level = level;
|
||||
}
|
||||
public void allowRendering(IDhClientLevel level) { this.level = level; }
|
||||
|
||||
public void disableRender() { this.cancelBuildBuffer(); }
|
||||
|
||||
@@ -397,8 +393,16 @@ public class ColumnRenderSource
|
||||
this.lastNs = System.nanoTime();
|
||||
//LOGGER.info("Swapping render buffer for {}", sectionPos);
|
||||
|
||||
|
||||
ColumnRenderBuffer newBuffer = this.buildRenderBufferFuture.join();
|
||||
LodUtil.assertTrue(newBuffer.areBuffersUploaded(), "The buffer future for "+renderSource.sectionPos+" returned an un-built buffer.");
|
||||
|
||||
ColumnRenderBuffer oldBuffer = renderBufferRefToSwap.getAndSet(newBuffer);
|
||||
if (oldBuffer != null)
|
||||
{
|
||||
// the old buffer is now considered unloaded, it will need to be freshly re-loaded
|
||||
oldBuffer.setBuffersUploaded(false);
|
||||
}
|
||||
|
||||
ColumnRenderBuffer swapped = this.columnRenderBufferRef.swap(oldBuffer);
|
||||
LodUtil.assertTrue(swapped == null);
|
||||
|
||||
+2
@@ -2,6 +2,7 @@ package com.seibel.lod.core.dataObjects.render.bufferBuilding;
|
||||
|
||||
import com.seibel.lod.core.dataObjects.render.ColumnRenderSource;
|
||||
import com.seibel.lod.core.dataObjects.render.columnViews.ColumnArrayView;
|
||||
import com.seibel.lod.core.pos.DhSectionPos;
|
||||
import com.seibel.lod.core.util.RenderDataPointUtil;
|
||||
import com.seibel.lod.core.level.IDhClientLevel;
|
||||
import com.seibel.lod.core.render.renderer.LodRenderer;
|
||||
@@ -591,6 +592,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
// getters //
|
||||
//=========//
|
||||
|
||||
public void setBuffersUploaded(boolean value) { this.buffersUploaded = value; }
|
||||
public boolean areBuffersUploaded() { return this.buffersUploaded; }
|
||||
|
||||
// TODO move static methods to their own class to avoid confusion
|
||||
|
||||
+3
-3
@@ -16,10 +16,10 @@ import java.util.concurrent.CompletableFuture;
|
||||
*/
|
||||
public interface ILodRenderSourceProvider extends AutoCloseable
|
||||
{
|
||||
CompletableFuture<ColumnRenderSource> read(DhSectionPos pos);
|
||||
CompletableFuture<ColumnRenderSource> readAsync(DhSectionPos pos);
|
||||
void addScannedFile(Collection<File> detectedFiles);
|
||||
void write(DhSectionPos sectionPos, ChunkSizedFullDataSource chunkData);
|
||||
CompletableFuture<Void> flushAndSave();
|
||||
void writeChunkDataToFile(DhSectionPos sectionPos, ChunkSizedFullDataSource chunkData);
|
||||
CompletableFuture<Void> flushAndSaveAsync();
|
||||
|
||||
/** Returns true if the data was refreshed, false otherwise */
|
||||
boolean refreshRenderSource(ColumnRenderSource source);
|
||||
|
||||
@@ -117,8 +117,9 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile
|
||||
Object inner = ((SoftReference<?>) obj).get();
|
||||
if (inner != null)
|
||||
{
|
||||
fileHandler.onReadRenderSourceFromCache(this, (ColumnRenderSource) inner);
|
||||
return CompletableFuture.completedFuture((ColumnRenderSource) inner);
|
||||
return fileHandler.onReadRenderSourceLoadedFromCacheAsync(this, (ColumnRenderSource) inner)
|
||||
// wait for the handler to finish before returning the renderSource
|
||||
.handle((voidObj, ex) -> (ColumnRenderSource) inner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +160,7 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile
|
||||
// After cas. We are in exclusive control.
|
||||
if (!this.doesFileExist)
|
||||
{
|
||||
this.fileHandler.onCreateRenderFile(this)
|
||||
this.fileHandler.onCreateRenderFileAsync(this)
|
||||
.thenApply((data) ->
|
||||
{
|
||||
this.metaData = makeMetaData(data);
|
||||
|
||||
+116
-80
@@ -54,12 +54,16 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Caller must ensure that this method is called only once,
|
||||
* and that the given files are not used before this method is called.
|
||||
*/
|
||||
@Override
|
||||
public void addScannedFile(Collection<File> newRenderFiles)
|
||||
//===============//
|
||||
// file handling //
|
||||
//===============//
|
||||
|
||||
/**
|
||||
* Caller must ensure that this method is called only once,
|
||||
* and that the given files are not used before this method is called.
|
||||
*/
|
||||
@Override
|
||||
public void addScannedFile(Collection<File> newRenderFiles)
|
||||
{
|
||||
HashMultimap<DhSectionPos, RenderMetaDataFile> filesByPos = HashMultimap.create();
|
||||
|
||||
@@ -90,7 +94,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
//fileToUse = metaFiles.stream().findFirst().orElse(null); // use the first file in the list
|
||||
|
||||
// use the file's last modified date
|
||||
fileToUse = Collections.max(metaFiles, Comparator.comparingLong(renderMetaDataFile ->
|
||||
fileToUse = Collections.max(metaFiles, Comparator.comparingLong(renderMetaDataFile ->
|
||||
renderMetaDataFile.file.lastModified()));
|
||||
|
||||
// fileToUse = Collections.max(metaFiles, Comparator.comparingLong(renderMetaDataFile ->
|
||||
@@ -111,7 +115,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
sb.append("\n");
|
||||
sb.append("(Other files will be renamed by appending \".old\" to their name.)");
|
||||
LOGGER.warn(sb.toString());
|
||||
|
||||
|
||||
// Rename all other files with the same pos to .old
|
||||
for (RenderMetaDataFile metaFile : metaFiles)
|
||||
{
|
||||
@@ -143,15 +147,9 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// file handling //
|
||||
//===============//
|
||||
|
||||
/** This call is concurrent. I.e. it supports multiple threads calling this method at the same time. */
|
||||
/** This call is concurrent. I.e. it supports multiple threads calling this method at the same time. */
|
||||
@Override
|
||||
public CompletableFuture<ColumnRenderSource> read(DhSectionPos pos)
|
||||
public CompletableFuture<ColumnRenderSource> readAsync(DhSectionPos pos)
|
||||
{
|
||||
RenderMetaDataFile metaFile = this.filesBySectionPos.get(pos);
|
||||
if (metaFile == null)
|
||||
@@ -196,14 +194,32 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
});
|
||||
}
|
||||
|
||||
/* This call is concurrent. I.e. it supports multiple threads calling this method at the same time. */
|
||||
@Override
|
||||
public void write(DhSectionPos sectionPos, ChunkSizedFullDataSource chunkData)
|
||||
public CompletableFuture<ColumnRenderSource> onCreateRenderFileAsync(RenderMetaDataFile file)
|
||||
{
|
||||
this.writeRecursively(sectionPos,chunkData);
|
||||
this.fullDataSourceProvider.write(sectionPos, chunkData); // TODO why is there fullData handling in the render file handler?
|
||||
final int vertSize = Config.Client.Graphics.Quality.verticalQuality.get()
|
||||
.calculateMaxVerticalData((byte) (file.pos.sectionDetailLevel - ColumnRenderSource.SECTION_SIZE_OFFSET));
|
||||
|
||||
return CompletableFuture.completedFuture(
|
||||
new ColumnRenderSource(file.pos, vertSize, this.level.getMinY()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// data saving //
|
||||
//=============//
|
||||
|
||||
/**
|
||||
* This call is concurrent. I.e. it supports multiple threads calling this method at the same time. <br>
|
||||
* TODO why is there fullData handling in the render file handler?
|
||||
*/
|
||||
@Override
|
||||
public void writeChunkDataToFile(DhSectionPos sectionPos, ChunkSizedFullDataSource chunkData)
|
||||
{
|
||||
this.writeChunkDataToFileRecursively(sectionPos,chunkData);
|
||||
this.fullDataSourceProvider.write(sectionPos, chunkData);
|
||||
}
|
||||
private void writeRecursively(DhSectionPos sectPos, ChunkSizedFullDataSource chunkData)
|
||||
private void writeChunkDataToFileRecursively(DhSectionPos sectPos, ChunkSizedFullDataSource chunkData)
|
||||
{
|
||||
if (!sectPos.getSectionBBoxPos().overlapsExactly(new DhLodPos((byte) (4 + chunkData.dataDetail), chunkData.x, chunkData.z)))
|
||||
{
|
||||
@@ -213,10 +229,10 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
|
||||
if (sectPos.sectionDetailLevel > ColumnRenderSource.SECTION_SIZE_OFFSET)
|
||||
{
|
||||
this.writeRecursively(sectPos.getChildByIndex(0), chunkData);
|
||||
this.writeRecursively(sectPos.getChildByIndex(1), chunkData);
|
||||
this.writeRecursively(sectPos.getChildByIndex(2), chunkData);
|
||||
this.writeRecursively(sectPos.getChildByIndex(3), chunkData);
|
||||
this.writeChunkDataToFileRecursively(sectPos.getChildByIndex(0), chunkData);
|
||||
this.writeChunkDataToFileRecursively(sectPos.getChildByIndex(1), chunkData);
|
||||
this.writeChunkDataToFileRecursively(sectPos.getChildByIndex(2), chunkData);
|
||||
this.writeChunkDataToFileRecursively(sectPos.getChildByIndex(3), chunkData);
|
||||
}
|
||||
|
||||
RenderMetaDataFile metaFile = this.filesBySectionPos.get(sectPos);
|
||||
@@ -227,9 +243,10 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** This call is concurrent. I.e. it supports multiple threads calling this method at the same time. */
|
||||
@Override
|
||||
public CompletableFuture<Void> flushAndSave()
|
||||
public CompletableFuture<Void> flushAndSaveAsync()
|
||||
{
|
||||
LOGGER.info("Shutting down "+ RenderSourceFileHandler.class.getSimpleName()+"...");
|
||||
|
||||
@@ -242,36 +259,21 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.whenComplete((voidObj, exception) -> LOGGER.info("Finished shutting down "+ RenderSourceFileHandler.class.getSimpleName()) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
for (RenderMetaDataFile metaFile : this.filesBySectionPos.values())
|
||||
{
|
||||
futures.add(metaFile.flushAndSave(this.renderCacheThread));
|
||||
}
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
}
|
||||
|
||||
public File computeRenderFilePath(DhSectionPos pos) { return new File(this.saveDir, pos.serialize() + RENDER_FILE_EXTENSION);}
|
||||
|
||||
public CompletableFuture<ColumnRenderSource> onCreateRenderFile(RenderMetaDataFile file)
|
||||
{
|
||||
final int vertSize = Config.Client.Graphics.Quality.verticalQuality.get()
|
||||
.calculateMaxVerticalData((byte) (file.pos.sectionDetailLevel - ColumnRenderSource.SECTION_SIZE_OFFSET));
|
||||
|
||||
return CompletableFuture.completedFuture(
|
||||
new ColumnRenderSource(file.pos, vertSize, this.level.getMinY()));
|
||||
}
|
||||
|
||||
private void updateCache(ColumnRenderSource renderSource, RenderMetaDataFile file)
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// cache updating //
|
||||
//================//
|
||||
|
||||
private CompletableFuture<Void> updateCacheAsync(ColumnRenderSource renderSource, RenderMetaDataFile file)
|
||||
{
|
||||
if (this.cacheUpdateLockBySectionPos.putIfAbsent(file.pos, new Object()) != null)
|
||||
{
|
||||
return;
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
// get the full data source loading future
|
||||
final WeakReference<ColumnRenderSource> renderSourceReference = new WeakReference<>(renderSource); // TODO why is this a week reference?
|
||||
CompletableFuture<IFullDataSource> fullDataSourceFuture = this.fullDataSourceProvider.read(renderSource.getSectionPos());
|
||||
fullDataSourceFuture = fullDataSourceFuture.thenApply((fullDataSource) ->
|
||||
@@ -289,51 +291,59 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
return null;
|
||||
});
|
||||
|
||||
|
||||
// future returned
|
||||
CompletableFuture<Void> transformationCompleteFuture = new CompletableFuture<>();
|
||||
|
||||
// convert the full data source into a render source
|
||||
//LOGGER.info("Recreating cache for {}", data.getSectionPos());
|
||||
DataRenderTransformer.transformDataSourceAsync(fullDataSourceFuture, this.level)
|
||||
.thenAccept((newRenderSource) -> this.write(renderSourceReference.get(), file, newRenderSource))
|
||||
.exceptionally((ex) ->
|
||||
.whenComplete((newRenderSource, ex) ->
|
||||
{
|
||||
if (ex instanceof InterruptedException)
|
||||
if (ex == null)
|
||||
{
|
||||
// expected if the transformer is shut down, the exception can be ignored
|
||||
// LOGGER.warn("RenderSource file transforming interrupted.");
|
||||
this.writeRenderSourceToFile(renderSourceReference.get(), file, newRenderSource);
|
||||
}
|
||||
else if (ex instanceof RejectedExecutionException || ex.getCause() instanceof RejectedExecutionException)
|
||||
else
|
||||
{
|
||||
// expected if the transformer was already shut down, the exception can be ignored
|
||||
// LOGGER.warn("RenderSource file transforming interrupted.");
|
||||
}
|
||||
else if (!UncheckedInterruptedException.isThrowableInterruption(ex))
|
||||
{
|
||||
LOGGER.error("Exception when updating render file using data source: ", ex);
|
||||
if (ex instanceof InterruptedException)
|
||||
{
|
||||
// expected if the transformer is shut down, the exception can be ignored
|
||||
// LOGGER.warn("RenderSource file transforming interrupted.");
|
||||
|
||||
int ignoreEmptyWarning = 0; // explicitly handling these exceptions is important so we know where they are going and if there is an issue we can easily re-enable the logging
|
||||
}
|
||||
else if (ex instanceof RejectedExecutionException || ex.getCause() instanceof RejectedExecutionException)
|
||||
{
|
||||
// expected if the transformer was already shut down, the exception can be ignored
|
||||
// LOGGER.warn("RenderSource file transforming interrupted.");
|
||||
|
||||
int ignoreEmptyWarning = 0;
|
||||
}
|
||||
else if (!UncheckedInterruptedException.isThrowableInterruption(ex))
|
||||
{
|
||||
LOGGER.error("Exception when updating render file using data source: ", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
transformationCompleteFuture.complete(null);
|
||||
})
|
||||
.thenRun(() -> this.cacheUpdateLockBySectionPos.remove(file.pos));
|
||||
|
||||
|
||||
return transformationCompleteFuture;
|
||||
}
|
||||
|
||||
/** TODO at some point this method may need to be made "async" like {@link RenderSourceFileHandler#onReadRenderSourceLoadedFromCacheAsync} since the insides are async */
|
||||
public ColumnRenderSource onRenderFileLoaded(ColumnRenderSource renderSource, RenderMetaDataFile file)
|
||||
{
|
||||
// if (!this.fullDataSourceProvider.isCacheVersionValid(file.pos, file.metaData.dataVersion.get()))
|
||||
// {
|
||||
this.updateCache(renderSource, file);
|
||||
// }
|
||||
|
||||
this.updateCacheAsync(renderSource, file).join();
|
||||
return renderSource;
|
||||
}
|
||||
|
||||
public void onReadRenderSourceFromCache(RenderMetaDataFile file, ColumnRenderSource data)
|
||||
{
|
||||
// if (!this.fullDataSourceProvider.isCacheVersionValid(file.pos, file.metaData.dataVersion.get()))
|
||||
// {
|
||||
this.updateCache(data, file);
|
||||
// }
|
||||
}
|
||||
public CompletableFuture<Void> onReadRenderSourceLoadedFromCacheAsync(RenderMetaDataFile file, ColumnRenderSource data) { return this.updateCacheAsync(data, file); }
|
||||
|
||||
private void write(ColumnRenderSource currentRenderSource, RenderMetaDataFile file,
|
||||
ColumnRenderSource newRenderSource)
|
||||
private void writeRenderSourceToFile(ColumnRenderSource currentRenderSource, RenderMetaDataFile file, ColumnRenderSource newRenderSource)
|
||||
{
|
||||
if (currentRenderSource == null || newRenderSource == null)
|
||||
{
|
||||
@@ -364,7 +374,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
LodUtil.assertTrue(file.metaData != null);
|
||||
// if (!this.fullDataSourceProvider.isCacheVersionValid(file.pos, file.metaData.dataVersion.get()))
|
||||
// {
|
||||
this.updateCache(renderSource, file);
|
||||
this.updateCacheAsync(renderSource, file).join();
|
||||
return true;
|
||||
// }
|
||||
|
||||
@@ -372,6 +382,23 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// clearing / shutdown //
|
||||
//=====================//
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
for (RenderMetaDataFile metaFile : this.filesBySectionPos.values())
|
||||
{
|
||||
futures.add(metaFile.flushAndSave(this.renderCacheThread));
|
||||
}
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
}
|
||||
|
||||
|
||||
public void deleteRenderCache()
|
||||
{
|
||||
// delete each file in the cache directory
|
||||
@@ -391,4 +418,13 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
this.filesBySectionPos.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
public File computeRenderFilePath(DhSectionPos pos) { return new File(this.saveDir, pos.serialize() + RENDER_FILE_EXTENSION);}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import com.seibel.lod.core.file.fullDatafile.RemoteFullDataFileHandler;
|
||||
import com.seibel.lod.core.file.structure.AbstractSaveStructure;
|
||||
import com.seibel.lod.core.level.states.ClientRenderState;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.logging.f3.F3Screen;
|
||||
import com.seibel.lod.core.pos.DhBlockPos2D;
|
||||
import com.seibel.lod.core.pos.DhLodPos;
|
||||
import com.seibel.lod.core.pos.DhSectionPos;
|
||||
@@ -21,7 +20,6 @@ import com.seibel.lod.core.util.math.Mat4f;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -104,7 +102,7 @@ public abstract class AbstractDhClientLevel implements IDhClientLevel
|
||||
}
|
||||
|
||||
clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
|
||||
clientRenderState.renderer.bufferHandler.update();
|
||||
clientRenderState.renderer.bufferHandler.updateQuadTreeRenderSources();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -186,7 +184,7 @@ public abstract class AbstractDhClientLevel implements IDhClientLevel
|
||||
DhLodPos pos = data.getBBoxLodPos().convertToDetailLevel(FullDataSource.SECTION_SIZE_OFFSET);
|
||||
if (ClientRenderState != null)
|
||||
{
|
||||
ClientRenderState.renderSourceFileHandler.write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data);
|
||||
ClientRenderState.renderSourceFileHandler.writeChunkDataToFile(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -200,7 +198,7 @@ public abstract class AbstractDhClientLevel implements IDhClientLevel
|
||||
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
|
||||
if (ClientRenderState != null)
|
||||
{
|
||||
return ClientRenderState.renderSourceFileHandler.flushAndSave().thenCombine(this.fullDataFileHandler.flushAndSave(), (voidA, voidB) -> null);
|
||||
return ClientRenderState.renderSourceFileHandler.flushAndSaveAsync().thenCombine(this.fullDataFileHandler.flushAndSave(), (voidA, voidB) -> null);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ClientRenderState
|
||||
|
||||
this.renderer.close();
|
||||
this.quadtree.close();
|
||||
return this.renderSourceFileHandler.flushAndSave();
|
||||
return this.renderSourceFileHandler.flushAndSaveAsync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.seibel.lod.core.pos.DhSectionPos;
|
||||
import com.seibel.lod.core.file.renderfile.ILodRenderSourceProvider;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.objects.quadTree.QuadNode;
|
||||
import com.seibel.lod.core.util.objects.quadTree.QuadTree;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -15,47 +16,17 @@ import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* This quadTree structure is our core data structure and holds
|
||||
* all rendering data. <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.
|
||||
* all rendering data.
|
||||
*/
|
||||
public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoCloseable
|
||||
{
|
||||
/**
|
||||
* Note: all config values should be via the class that extends this class, and
|
||||
* by implementing different abstract methods
|
||||
*/
|
||||
public static final byte TREE_LOWEST_DETAIL_LEVEL = ColumnRenderSource.SECTION_SIZE_OFFSET;
|
||||
|
||||
private static final boolean SUPER_VERBOSE_LOGGING = false;
|
||||
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
|
||||
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()); }
|
||||
|
||||
|
||||
public final int blockRenderDistance;
|
||||
private final ILodRenderSourceProvider renderSourceProvider;
|
||||
|
||||
/** How many {@link LodRenderSection}'s are currently loading */
|
||||
private int numberOfRenderSectionsLoading = 0;
|
||||
/**
|
||||
* Indicates how many {@link LodRenderSection}'s can load concurrently. <br>
|
||||
* Prevents large number of {@link ILodRenderSourceProvider} tasks from building up when initially loading.
|
||||
*/
|
||||
private static final int MAX_NUMBER_OF_LOADING_RENDER_SECTIONS = 2;
|
||||
|
||||
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference!
|
||||
|
||||
|
||||
@@ -76,73 +47,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This method return the LodSection at the given detail level and level coordinate x and z
|
||||
* @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 LodRenderSection getSection(byte detailLevel, int x, int z) { return this.getValue(new DhSectionPos(detailLevel, x, z)); }
|
||||
public LodRenderSection getSection(DhSectionPos pos) { return this.getValue(pos); }
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This method will compute the detail level based on player position and section pos
|
||||
* Override this method if you want to use a different algorithm
|
||||
* @param playerPos player position as a reference for calculating the detail level
|
||||
* @param sectionPos section position
|
||||
* @return detail level of this section pos
|
||||
*/
|
||||
public byte calculateExpectedDetailLevel(DhBlockPos2D playerPos, DhSectionPos sectionPos)
|
||||
{
|
||||
return DetailDistanceUtil.getDetailLevelFromDistance(playerPos.dist(sectionPos.getCenter().getCenterBlockPos()));
|
||||
}
|
||||
|
||||
/**
|
||||
* The method will return the highest detail level in a circle around the center
|
||||
* Override this method if you want to use a different algorithm
|
||||
* Note: the returned distance should always be the ceiling estimation of the distance
|
||||
* //TODO: Make this input a bbox or a circle or something....
|
||||
* @param distance the circle radius
|
||||
* @return the highest detail level in the circle
|
||||
*/
|
||||
public byte getMaxDetailInRange(double distance) { return DetailDistanceUtil.getDetailLevelFromDistance(distance); }
|
||||
|
||||
/**
|
||||
* The method will return the furthest distance to the center for the given detail level
|
||||
* Override this method if you want to use a different algorithm
|
||||
* Note: the returned distance should always be the ceiling estimation of the distance
|
||||
* //TODO: Make this return a bbox instead of a distance in circle
|
||||
* @param detailLevel detail level
|
||||
* @return the furthest distance to the center, in blocks
|
||||
*/
|
||||
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 position
|
||||
* @return the parent LodSection
|
||||
*/
|
||||
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 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 this.getSection(pos.getChildByIndex(child0to3)); }
|
||||
|
||||
|
||||
|
||||
// tick //
|
||||
//=============//
|
||||
// tick update //
|
||||
//=============//
|
||||
|
||||
/**
|
||||
* This function updates the quadTree based on the playerPos and the current game configs (static and global)
|
||||
@@ -150,60 +57,81 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
*/
|
||||
public void tick(DhBlockPos2D playerPos)
|
||||
{
|
||||
if (this.level == null)
|
||||
{
|
||||
// the level hasn't finished loading yet
|
||||
// TODO sometimes null pointers still happen, when logging back into a world (maybe the old level isn't null but isn't valid either?)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// recenter if necessary
|
||||
// recenter if necessary, removing out of bounds sections
|
||||
this.setCenterBlockPos(playerPos, LodRenderSection::disposeRenderData);
|
||||
|
||||
updateAllRenderSections(playerPos);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// TODO when we are stable this shouldn't be necessary
|
||||
LOGGER.error("Quad Tree tick exception for dimension: "+this.level.getClientLevelWrapper().getDimensionType().getDimensionName()+", exception: "+e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
private void updateAllRenderSections(DhBlockPos2D playerPos)
|
||||
{
|
||||
// make sure all root nodes are created
|
||||
// walk through each root node
|
||||
Iterator<DhSectionPos> rootPosIterator = this.rootNodePosIterator();
|
||||
while (rootPosIterator.hasNext())
|
||||
{
|
||||
DhSectionPos rootSectionPos = rootPosIterator.next();
|
||||
if (this.getNode(rootSectionPos) == null)
|
||||
// make sure all root nodes have been created
|
||||
DhSectionPos rootPos = rootPosIterator.next();
|
||||
if (this.getNode(rootPos) == null)
|
||||
{
|
||||
LodRenderSection newRenderSection = new LodRenderSection(rootSectionPos);
|
||||
this.setValue(rootSectionPos, newRenderSection);
|
||||
this.setValue(rootPos, new LodRenderSection(rootPos));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update all nodes in the tree
|
||||
Iterator<DhSectionPos> rootNodeIterator = this.rootNodePosIterator();
|
||||
while (rootNodeIterator.hasNext())
|
||||
{
|
||||
DhSectionPos rootPos = rootNodeIterator.next();
|
||||
QuadNode<LodRenderSection> rootNode = this.getNode(rootPos); // should never be null
|
||||
|
||||
// iterate over nodes in this root
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = rootNode.getNodeIterator();
|
||||
while (nodeIterator.hasNext())
|
||||
{
|
||||
QuadNode<LodRenderSection> quadNode = nodeIterator.next();
|
||||
recursivelyUpdateRenderSectionNode(playerPos, rootNode, quadNode, quadNode.sectionPos);
|
||||
}
|
||||
QuadNode<LodRenderSection> rootNode = this.getNode(rootPos);
|
||||
recursivelyUpdateRenderSectionNode(playerPos, rootNode, rootNode, rootNode.sectionPos, false);
|
||||
}
|
||||
}
|
||||
private void recursivelyUpdateRenderSectionNode(DhBlockPos2D playerPos, QuadNode<LodRenderSection> rootNode, QuadNode<LodRenderSection> nullableQuadNode, DhSectionPos sectionPos)
|
||||
/** @return whether the current position is able to render (note: not if it IS rendering, just if it is ABLE to.) */
|
||||
private boolean recursivelyUpdateRenderSectionNode(DhBlockPos2D playerPos, QuadNode<LodRenderSection> rootNode, QuadNode<LodRenderSection> quadNode, DhSectionPos sectionPos, boolean parentRenderSectionIsEnabled)
|
||||
{
|
||||
LodRenderSection nullableRenderSection = null;
|
||||
if (nullableQuadNode != null)
|
||||
//===============================//
|
||||
// node and render section setup //
|
||||
//===============================//
|
||||
|
||||
// make sure the node is created
|
||||
if (quadNode == null && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance
|
||||
{
|
||||
nullableRenderSection = nullableQuadNode.value;
|
||||
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos));
|
||||
quadNode = rootNode.getNode(sectionPos);
|
||||
}
|
||||
if (quadNode == null)
|
||||
{
|
||||
// this node must be out of bounds, or there was an issue adding it to the tree
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure the render section is created
|
||||
LodRenderSection renderSection = quadNode.value;
|
||||
// create a new render section if missing
|
||||
if (renderSection == null)
|
||||
{
|
||||
LodRenderSection newRenderSection = new LodRenderSection(sectionPos);
|
||||
rootNode.setValue(sectionPos, newRenderSection);
|
||||
|
||||
renderSection = newRenderSection;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//===============================//
|
||||
// handle enabling, loading, //
|
||||
// and disabling render sections //
|
||||
//===============================//
|
||||
|
||||
// byte expectedDetailLevel = 6; // can be used instead of the following logic for testing
|
||||
byte expectedDetailLevel = calculateExpectedDetailLevel(playerPos, sectionPos);
|
||||
expectedDetailLevel += DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
|
||||
@@ -211,151 +139,129 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
|
||||
if (sectionPos.sectionDetailLevel > expectedDetailLevel)
|
||||
{
|
||||
// section detail level too high...
|
||||
// section detail level too high //
|
||||
|
||||
if (nullableRenderSection != null)
|
||||
|
||||
boolean isThisPositionBeingRendered = renderSection.isRenderingEnabled();
|
||||
boolean allChildrenSectionsAreLoaded = true;
|
||||
|
||||
// recursively update all child render sections
|
||||
Iterator<DhSectionPos> childPosIterator = quadNode.getChildPosIterator();
|
||||
while (childPosIterator.hasNext())
|
||||
{
|
||||
if (areChildRenderSectionsEnabled(nullableRenderSection))
|
||||
{
|
||||
nullableRenderSection.disableAndDisposeRendering();
|
||||
}
|
||||
DhSectionPos childPos = childPosIterator.next();
|
||||
QuadNode<LodRenderSection> childNode = rootNode.getNode(childPos);
|
||||
|
||||
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, isThisPositionBeingRendered || parentRenderSectionIsEnabled);
|
||||
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
|
||||
}
|
||||
|
||||
if (nullableQuadNode == null)
|
||||
|
||||
|
||||
if (!allChildrenSectionsAreLoaded)
|
||||
{
|
||||
// ...create self
|
||||
if (this.isSectionPosInBounds(sectionPos)) // this should only fail when at the edge of the user's render distance
|
||||
{
|
||||
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos));
|
||||
}
|
||||
// not all child positions are loaded yet, or this section is out of render range
|
||||
return isThisPositionBeingRendered;
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<DhSectionPos> childPosIterator = nullableQuadNode.getChildPosIterator();
|
||||
// all child positions are loaded, disable this section and enable the children.
|
||||
renderSection.disposeRenderData();
|
||||
renderSection.disableRendering();
|
||||
|
||||
|
||||
|
||||
// walk back down the tree and enable the child sections //TODO there are probably more efficient ways of doing this, but this will work for now
|
||||
childPosIterator = quadNode.getChildPosIterator();
|
||||
while (childPosIterator.hasNext())
|
||||
{
|
||||
DhSectionPos childPos = childPosIterator.next();
|
||||
QuadNode<LodRenderSection> childNode = rootNode.getNode(childPos);
|
||||
|
||||
recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos);
|
||||
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, parentRenderSectionIsEnabled);
|
||||
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
|
||||
}
|
||||
LodUtil.assertTrue(allChildrenSectionsAreLoaded, "Potential QuadTree concurrency issue. All child sections should be enabled and ready to render.");
|
||||
|
||||
|
||||
// this section is now being rendered via its children
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// TODO this should only equal the expected detail level, the (expectedDetailLevel-1) is a temporary fix to prevent corners from being cut out
|
||||
else if (sectionPos.sectionDetailLevel == expectedDetailLevel || sectionPos.sectionDetailLevel == expectedDetailLevel-1)
|
||||
{
|
||||
// this is the correct detail level and should be rendered
|
||||
// this is the detail level we want to render //
|
||||
|
||||
if (nullableQuadNode == null)
|
||||
|
||||
// prepare this section for rendering
|
||||
renderSection.loadRenderSource(this.renderSourceProvider, this.level);
|
||||
|
||||
// wait for the parent to disable before enabling this section, so we don't overdraw/overlap render sections
|
||||
if (!parentRenderSectionIsEnabled && renderSection.isRenderDataLoaded())
|
||||
{
|
||||
if (this.isSectionPosInBounds(sectionPos))
|
||||
{
|
||||
// create new value and update next tick
|
||||
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos));
|
||||
}
|
||||
|
||||
nullableQuadNode = rootNode.getNode(sectionPos);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (nullableQuadNode != null)
|
||||
{
|
||||
// create a new render section if missing
|
||||
if (nullableRenderSection == null)
|
||||
{
|
||||
LodRenderSection newRenderSection = new LodRenderSection(sectionPos);
|
||||
rootNode.setValue(sectionPos, newRenderSection);
|
||||
|
||||
nullableRenderSection = newRenderSection;
|
||||
}
|
||||
|
||||
//if (!areParentRenderSectionsLoaded(sectionPos)) // TODO not functional yet
|
||||
{
|
||||
// enable the render section
|
||||
nullableRenderSection.loadRenderSource(this.renderSourceProvider);
|
||||
|
||||
// determine if the section has loaded yet // TODO rename "tick" to check loading future or something?
|
||||
nullableRenderSection.enableRendering(this.level);
|
||||
}
|
||||
renderSection.enableRendering();
|
||||
|
||||
|
||||
// delete/disable children
|
||||
if (isSectionLoaded(nullableRenderSection))
|
||||
// delete/disable children, all of them will be a lower detail level than requested
|
||||
quadNode.deleteAllChildren((childRenderSection) ->
|
||||
{
|
||||
nullableQuadNode.deleteAllChildren((renderSection) ->
|
||||
if (childRenderSection != null)
|
||||
{
|
||||
if (renderSection != null)
|
||||
{
|
||||
renderSection.disableAndDisposeRendering();
|
||||
}
|
||||
});
|
||||
}
|
||||
childRenderSection.disposeRenderData();
|
||||
childRenderSection.disableRendering();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine if a section can unload or not.
|
||||
* If this returns true, that means there are child render sections ready to render,
|
||||
* so there won't be any holes in the world by disabling the parent.
|
||||
* <br><Br>
|
||||
* FIXME sometimes sections will render on top of each other
|
||||
*/
|
||||
private boolean areChildRenderSectionsEnabled(LodRenderSection renderSection)
|
||||
{
|
||||
if (renderSection == null)
|
||||
{
|
||||
// this section isn't loaded
|
||||
return false;
|
||||
}
|
||||
if (renderSection.pos.sectionDetailLevel == TREE_LOWEST_DETAIL_LEVEL)
|
||||
{
|
||||
// this section is at the bottom detail level and has no children
|
||||
return isSectionEnabled(renderSection);
|
||||
|
||||
return renderSection.isRenderDataLoaded();
|
||||
}
|
||||
else
|
||||
{
|
||||
// recursively check if all children are loaded
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
DhSectionPos childPos = renderSection.pos.getChildByIndex(i);
|
||||
// if a section is out of bounds, act like it is loaded
|
||||
if (this.isSectionPosInBounds(childPos))
|
||||
{
|
||||
LodRenderSection child = this.getChildSection(renderSection.pos, i);
|
||||
// check if either this child or all of its children are loaded
|
||||
boolean childLoaded = isSectionEnabled(child) || areChildRenderSectionsEnabled(child);
|
||||
if (!childLoaded)
|
||||
{
|
||||
// at least one child isn't loaded
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all children are loaded
|
||||
return true;
|
||||
throw new IllegalStateException("LodQuadTree shouldn't be updating renderSections below the expected detail level: ["+expectedDetailLevel+"].");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSectionEnabled(LodRenderSection renderSection)
|
||||
|
||||
|
||||
//====================//
|
||||
// detail level logic //
|
||||
//====================//
|
||||
|
||||
/**
|
||||
* This method will compute the detail level based on player position and section pos
|
||||
* Override this method if you want to use a different algorithm
|
||||
* @param playerPos player position as a reference for calculating the detail level
|
||||
* @param sectionPos section position
|
||||
* @return detail level of this section pos
|
||||
*/
|
||||
public byte calculateExpectedDetailLevel(DhBlockPos2D playerPos, DhSectionPos sectionPos)
|
||||
{
|
||||
return isSectionLoaded(renderSection)
|
||||
&& renderSection.isRenderingEnabled()
|
||||
|
||||
&& renderSection.renderBufferRef.get() != null
|
||||
&& renderSection.renderBufferRef.get().areBuffersUploaded();
|
||||
return DetailDistanceUtil.getDetailLevelFromDistance(playerPos.dist(sectionPos.getCenter().getCenterBlockPos()));
|
||||
}
|
||||
|
||||
private static boolean isSectionLoaded(LodRenderSection renderSection)
|
||||
/**
|
||||
* The method will return the highest detail level in a circle around the center
|
||||
* Override this method if you want to use a different algorithm
|
||||
* Note: the returned distance should always be the ceiling estimation of the distance
|
||||
* //TODO: Make this input a bbox or a circle or something....
|
||||
* @param distance the circle radius
|
||||
* @return the highest detail level in the circle
|
||||
*/
|
||||
public byte getMaxDetailInRange(double distance) { return DetailDistanceUtil.getDetailLevelFromDistance(distance); }
|
||||
|
||||
/**
|
||||
* The method will return the furthest distance to the center for the given detail level
|
||||
* Override this method if you want to use a different algorithm
|
||||
* Note: the returned distance should always be the ceiling estimation of the distance
|
||||
* //TODO: Make this return a bbox instead of a distance in circle
|
||||
* @param detailLevel detail level
|
||||
* @return the furthest distance to the center, in blocks
|
||||
*/
|
||||
public int getFurthestDistance(byte detailLevel)
|
||||
{
|
||||
return renderSection != null
|
||||
&& renderSection.isLoaded()
|
||||
|
||||
&& renderSection.getRenderSource() != null
|
||||
&& !renderSection.getRenderSource().isEmpty();
|
||||
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.
|
||||
}
|
||||
|
||||
|
||||
@@ -370,6 +276,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
*/
|
||||
public void clearRenderDataCache()
|
||||
{
|
||||
// TODO this causes some (harmless) file errors when called
|
||||
LOGGER.info("Clearing render cache...");
|
||||
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
|
||||
@@ -395,7 +302,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
*/
|
||||
public void reloadPos(DhSectionPos pos)
|
||||
{
|
||||
LodRenderSection renderSection = this.getSection(pos);
|
||||
LodRenderSection renderSection = this.getValue(pos);
|
||||
if (renderSection != null)
|
||||
{
|
||||
renderSection.reload(this.renderSourceProvider);
|
||||
|
||||
@@ -11,20 +11,25 @@ import org.apache.logging.log4j.Logger;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
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
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
|
||||
public final DhSectionPos pos;
|
||||
|
||||
private CompletableFuture<ColumnRenderSource> loadFuture;
|
||||
private boolean isRenderEnabled = false;
|
||||
|
||||
private ColumnRenderSource renderSource;
|
||||
private ILodRenderSourceProvider renderSourceProvider = null;
|
||||
|
||||
/** a reference is used so the render buffer can be swapped to and from the buffer builder */
|
||||
public final AtomicReference<ColumnRenderBuffer> renderBufferRef = new AtomicReference<>();
|
||||
|
||||
private boolean isRenderingEnabled = false;
|
||||
|
||||
private ILodRenderSourceProvider renderSourceProvider = null;
|
||||
private CompletableFuture<ColumnRenderSource> renderSourceLoadFuture;
|
||||
private ColumnRenderSource renderSource;
|
||||
|
||||
|
||||
|
||||
public LodRenderSection(DhSectionPos pos) { this.pos = pos; }
|
||||
@@ -35,7 +40,17 @@ public class LodRenderSection
|
||||
// rendering //
|
||||
//===========//
|
||||
|
||||
public void loadRenderSource(ILodRenderSourceProvider renderDataProvider)
|
||||
public void enableRendering() { this.isRenderingEnabled = true; }
|
||||
public void disableRendering() { this.isRenderingEnabled = false; }
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// render data //
|
||||
//=============//
|
||||
|
||||
/** does nothing if a render source is already loaded or in the process of loading */
|
||||
public void loadRenderSource(ILodRenderSourceProvider renderDataProvider, IDhClientLevel level)
|
||||
{
|
||||
this.renderSourceProvider = renderDataProvider;
|
||||
if (this.renderSourceProvider == null)
|
||||
@@ -43,48 +58,26 @@ public class LodRenderSection
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.renderSource == null && this.loadFuture == null)
|
||||
if (this.renderSource == null && this.renderSourceLoadFuture == null)
|
||||
{
|
||||
this.loadFuture = this.renderSourceProvider.read(this.pos);
|
||||
this.loadFuture.whenComplete((renderSource, ex) ->
|
||||
this.renderSourceLoadFuture = this.renderSourceProvider.readAsync(this.pos);
|
||||
this.renderSourceLoadFuture.whenComplete((renderSource, ex) ->
|
||||
{
|
||||
this.renderSource = renderSource;
|
||||
this.loadFuture = null;
|
||||
this.renderSourceLoadFuture = null;
|
||||
|
||||
if (this.renderSource != null)
|
||||
{
|
||||
this.renderSource.allowRendering(level);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
public void enableRendering(IDhClientLevel level)
|
||||
{
|
||||
this.isRenderEnabled = true;
|
||||
|
||||
if (this.renderSource != null)
|
||||
{
|
||||
this.renderSource.enableRender(level);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void disableAndDisposeRendering()
|
||||
{
|
||||
if (!this.isRenderEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.disposeRenderData();
|
||||
this.isRenderEnabled = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//========================//
|
||||
// render source provider //
|
||||
//========================//
|
||||
|
||||
public void reload(ILodRenderSourceProvider renderDataProvider)
|
||||
{
|
||||
// don't accidentally enable rendering for a disabled section
|
||||
if (!this.isRenderEnabled)
|
||||
if (!this.isRenderingEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -92,10 +85,10 @@ public class LodRenderSection
|
||||
|
||||
this.renderSourceProvider = renderDataProvider;
|
||||
|
||||
if (this.loadFuture != null)
|
||||
if (this.renderSourceLoadFuture != null)
|
||||
{
|
||||
this.loadFuture.cancel(true);
|
||||
this.loadFuture = null;
|
||||
this.renderSourceLoadFuture.cancel(true);
|
||||
this.renderSourceLoadFuture = null;
|
||||
}
|
||||
|
||||
if (this.renderSource != null)
|
||||
@@ -104,15 +97,10 @@ public class LodRenderSection
|
||||
this.renderSource = null;
|
||||
}
|
||||
|
||||
this.loadFuture = this.renderSourceProvider.read(this.pos);
|
||||
this.renderSourceLoadFuture = this.renderSourceProvider.readAsync(this.pos);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// update methods //
|
||||
//================//
|
||||
|
||||
public void disposeRenderData()
|
||||
{
|
||||
if (this.renderSource != null)
|
||||
@@ -128,28 +116,45 @@ public class LodRenderSection
|
||||
this.renderBufferRef.set(null);
|
||||
}
|
||||
|
||||
if (this.loadFuture != null)
|
||||
if (this.renderSourceLoadFuture != null)
|
||||
{
|
||||
this.loadFuture.cancel(true);
|
||||
this.loadFuture = null;
|
||||
this.renderSourceLoadFuture.cancel(true);
|
||||
this.renderSourceLoadFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//========================//
|
||||
// getters and properties //
|
||||
//========================//
|
||||
|
||||
public boolean shouldRender() { return this.isLoaded() && this.isRenderEnabled; }
|
||||
|
||||
public boolean isRenderingEnabled() { return this.isRenderEnabled; }
|
||||
public boolean isLoaded() { return this.renderSource != null; }
|
||||
public boolean isLoading() { return this.loadFuture != null; }
|
||||
public boolean isOutdated() { return this.renderSource != null && !this.renderSource.isValid(); }
|
||||
/** @return true if this section is loaded and set to render */
|
||||
public boolean shouldRender() { return this.isRenderingEnabled && this.isRenderDataLoaded(); }
|
||||
/** This can return true before the render data is loaded */
|
||||
public boolean isRenderingEnabled() { return this.isRenderingEnabled; }
|
||||
|
||||
public ColumnRenderSource getRenderSource() { return this.renderSource; }
|
||||
public CompletableFuture<ColumnRenderSource> getRenderSourceLoadingFuture() { return this.loadFuture; }
|
||||
|
||||
public boolean isRenderDataLoaded()
|
||||
{
|
||||
return this.renderSource != null
|
||||
&&
|
||||
(
|
||||
(
|
||||
// if true; either this section represents empty chunks or un-generated chunks.
|
||||
// Either way, there isn't any data to render, but this should be considered "loaded"
|
||||
this.renderSource.isEmpty()
|
||||
)
|
||||
||
|
||||
(
|
||||
// check if the buffers have been loaded
|
||||
this.renderBufferRef.get() != null
|
||||
&& this.renderBufferRef.get().areBuffersUploaded()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
@@ -160,8 +165,8 @@ public class LodRenderSection
|
||||
return "LodRenderSection{" +
|
||||
"pos=" + this.pos +
|
||||
", lodRenderSource=" + this.renderSource +
|
||||
", loadFuture=" + this.loadFuture +
|
||||
", isRenderEnabled=" + this.isRenderEnabled +
|
||||
", loadFuture=" + this.renderSourceLoadFuture +
|
||||
", isRenderEnabled=" + this.isRenderingEnabled +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.pos.Pos2D;
|
||||
import com.seibel.lod.core.pos.DhSectionPos;
|
||||
import com.seibel.lod.core.render.renderer.LodRenderer;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.math.Vec3f;
|
||||
import com.seibel.lod.core.util.objects.SortedArraySet;
|
||||
import com.seibel.lod.core.util.objects.quadTree.QuadNode;
|
||||
@@ -24,14 +23,14 @@ public class RenderBufferHandler
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
/** contains all relevant data */
|
||||
public final LodQuadTree quadTree;
|
||||
public final LodQuadTree lodQuadTree;
|
||||
|
||||
// TODO: Make sorting go into the update loop instead of the render loop as it doesn't need to be done every frame
|
||||
private SortedArraySet<LoadedRenderBuffer> loadedNearToFarBuffers = null;
|
||||
|
||||
|
||||
|
||||
public RenderBufferHandler(LodQuadTree lodQuadTree) { this.quadTree = lodQuadTree; }
|
||||
public RenderBufferHandler(LodQuadTree lodQuadTree) { this.lodQuadTree = lodQuadTree; }
|
||||
|
||||
|
||||
|
||||
@@ -138,7 +137,7 @@ public class RenderBufferHandler
|
||||
// Build the sorted list
|
||||
this.loadedNearToFarBuffers = new SortedArraySet<>((a, b) -> -farToNearComparator.compare(a, b)); // TODO is the comparator named wrong?
|
||||
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.quadTree.nodeIterator();
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.lodQuadTree.nodeIterator();
|
||||
while (nodeIterator.hasNext())
|
||||
{
|
||||
QuadNode<LodRenderSection> node = nodeIterator.next();
|
||||
@@ -167,30 +166,20 @@ public class RenderBufferHandler
|
||||
this.loadedNearToFarBuffers.forEach(loadedBuffer -> loadedBuffer.buffer.renderTransparent(renderContext));
|
||||
}
|
||||
|
||||
public void update()
|
||||
public void updateQuadTreeRenderSources()
|
||||
{
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.quadTree.nodeIterator();
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.lodQuadTree.nodeIterator();
|
||||
while (nodeIterator.hasNext())
|
||||
{
|
||||
LodRenderSection renderSection = nodeIterator.next().value;
|
||||
if (renderSection != null)
|
||||
{
|
||||
ColumnRenderSource currentRenderSource = renderSection.getRenderSource();
|
||||
|
||||
// Update self's render buffer state
|
||||
if (!renderSection.shouldRender())
|
||||
ColumnRenderSource sectionRenderSource = renderSection.getRenderSource();
|
||||
// if the render source is present, attempt to load it
|
||||
if (sectionRenderSource != null)
|
||||
{
|
||||
//TODO: Does this really need to force the old buffer to not be rendered?
|
||||
AbstractRenderBuffer previousRenderBuffer = renderSection.renderBufferRef.getAndSet(null);
|
||||
if (previousRenderBuffer != null)
|
||||
{
|
||||
previousRenderBuffer.close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LodUtil.assertTrue(currentRenderSource != null); // section.shouldRender() should have ensured this
|
||||
currentRenderSource.trySwapInNewlyBuiltRenderBuffer(renderSection.getRenderSource(), renderSection.renderBufferRef);
|
||||
// TODO why are we always trying to swap the buffers? shouldn't we only swap them when a new buffer has been built? we have a future object specifically for that in ColumnRenderSource
|
||||
sectionRenderSource.trySwapInNewlyBuiltRenderBuffer(renderSection.getRenderSource(), renderSection.renderBufferRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +187,7 @@ public class RenderBufferHandler
|
||||
|
||||
public void close()
|
||||
{
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.quadTree.nodeIterator();
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.lodQuadTree.nodeIterator();
|
||||
while (nodeIterator.hasNext())
|
||||
{
|
||||
LodRenderSection renderSection = nodeIterator.next().value;
|
||||
|
||||
Reference in New Issue
Block a user