diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java index fa192dcfb..c2de2a567 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java @@ -111,7 +111,7 @@ public class ColumnRenderBufferBuilder /** @link adjData should be null for adjacent sections that cross detail level boundaries */ public static CompletableFuture uploadBuffersAsync( IDhClientLevel clientLevel, - ColumnRenderSource renderSource, + long pos, LodQuadBuilder quadBuilder ) { @@ -132,7 +132,7 @@ public class ColumnRenderBufferBuilder { try { - ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(renderSource.pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(renderSource.pos))); + ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(pos))); try { buffer.uploadBuffer(quadBuilder, GLProxy.getInstance().getGpuUploadMethod()); @@ -158,7 +158,7 @@ public class ColumnRenderBufferBuilder } catch (Throwable e3) { - LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: [" + e3.getMessage() + "].", e3); + LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(pos)+"], error: [" + e3.getMessage() + "].", e3); throw e3; } }, bufferUploaderExecutor); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java index d9f4dae45..4a8b96616 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java @@ -36,11 +36,11 @@ import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; +import com.seibel.distanthorizons.core.util.ListUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import it.unimi.dsi.fastutil.longs.LongArrayList; import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.Nullable; import javax.annotation.WillNotClose; import java.awt.*; @@ -48,7 +48,6 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; /** @@ -97,9 +96,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable private final ReentrantLock getRenderSourceLock = new ReentrantLock(); /** Stored as a class variable so we can reuse it's result across multiple LOD loads if necessary */ - private ReferencedFutureWrapper renderSourceLoadingRefFuture = null; - /** Stored as a class variable so we can decrement reference counts as each {@link LodRenderSection} finishes using them. */ - private ReferencedFutureWrapper[] adjacentLoadRefFutures; + private CompletableFuture renderSourceLoadingRefFuture = null; private boolean missingPositionsCalculated = false; /** should be an empty array if no positions need to be generated */ @@ -123,11 +120,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable - //===============================// - // render data loading/uploading // - //===============================// + //======================================// + // render data generation and uploading // + //======================================// - // TODO cleanup, there's a lot of nested futures and duplicate error handling here and it's hard to read public synchronized void uploadRenderDataToGpuAsync() { if (!GLProxy.hasInstance()) @@ -143,148 +139,167 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable return; } - - ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor(); if (executor == null || executor.isTerminated()) { return; } - this.buildAndUploadRenderDataToGpuFuture = CompletableFuture.runAsync(() -> + try { - //==================// - // load render data // - //==================// - - this.tryDecrementingLoadFutureArray(this.adjacentLoadRefFutures); - - ReferencedFutureWrapper thisRenderSourceLoadFuture = this.getRenderSourceAsync(); - ReferencedFutureWrapper[] adjRenderSourceLoadRefFutures = this.getNeighborRenderSourcesAsync(); - - - // wait for all futures to complete together, - // merging the futures makes loading significantly faster than loading this position then loading its neighbors - ArrayList> futureList = new ArrayList<>(); - futureList.add(thisRenderSourceLoadFuture.future); - for (ReferencedFutureWrapper refFuture : adjRenderSourceLoadRefFutures) - { - futureList.add(refFuture.future); - } - - CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).thenAccept((voidObj) -> + this.buildAndUploadRenderDataToGpuFuture = CompletableFuture.runAsync(() -> { try { - ColumnRenderSource renderSource = thisRenderSourceLoadFuture.future.get(); - if (renderSource == null || renderSource.isEmpty()) - { - thisRenderSourceLoadFuture.decrementRefCount(); - for (ReferencedFutureWrapper futureWrapper : adjRenderSourceLoadRefFutures) + this.loadRenderDataAsync() + .thenCompose((loadedRenderSources) -> { - futureWrapper.decrementRefCount(); - } - - // nothing needs to be rendered - this.canRender = false; - this.buildAndUploadRenderDataToGpuFuture = null; - this.bufferBuildFuture = null; - return; - } - - - - //=======================// - // build new render data // - //=======================// - - try - { - ColumnRenderBuffer previousBuffer = this.renderBuffer; - - ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length]; - boolean[] adjIsSameDetailLevel = new boolean[EDhDirection.ADJ_DIRECTIONS.length]; - for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++) - { - adjacentRenderSections[i] = adjRenderSourceLoadRefFutures[i].future.getNow(null); - - // if the adjacent position isn't the same detail level the buffer building logic - // will need to be slightly different in order to reduce holes in the LODs - EDhDirection direction = EDhDirection.ADJ_DIRECTIONS[i]; - adjIsSameDetailLevel[direction.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(direction); - } - - if (this.bufferBuildFuture != null) - { - // shouldn't normally happen, but just in case canceling the previous future - // prevents the CPU from working on something that won't be used - this.bufferBuildFuture.cancel(true); - } - this.bufferBuildFuture = ColumnRenderBufferBuilder.buildBuffersAsync(this.level, renderSource, adjacentRenderSections, adjIsSameDetailLevel); - this.bufferBuildFuture.thenAccept((lodQuadBuilder) -> - { - - - - //===================================// - // upload new render data to the GPU // - //===================================// - - if (this.bufferUploadFuture != null) + try { - // shouldn't normally happen, but just in case canceling the previous future - // prevents the CPU from working on something that won't be used - this.bufferUploadFuture.cancel(true); - } - this.bufferUploadFuture = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, renderSource, lodQuadBuilder); - this.bufferUploadFuture.thenAccept((buffer) -> - { - // upload complete, clean up the old data if - this.renderBuffer = buffer; - this.canRender = (buffer != null); - this.buildAndUploadRenderDataToGpuFuture = null; - this.bufferBuildFuture = null; - - - if (previousBuffer != null) + ColumnRenderSource thisRenderSource = loadedRenderSources.getThisRenderSource(); + if (thisRenderSource != null && !thisRenderSource.isEmpty()) { - previousBuffer.close(); + CompletableFuture buildDataFuture = this.buildNewRenderDataAsync(thisRenderSource, loadedRenderSources); + buildDataFuture.thenRun(() -> + { + ColumnRenderSource.DATA_SOURCE_POOL.returnPooledDataSource(thisRenderSource); + ArrayList adjacentSourceList = loadedRenderSources.getAdjacentRenderSourceList(); + for (int i = 0; i < adjacentSourceList.size(); i++) + { + ColumnRenderSource.DATA_SOURCE_POOL.returnPooledDataSource(adjacentSourceList.get(i)); + } + }); + return buildDataFuture; } - - thisRenderSourceLoadFuture.decrementRefCount(); - this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures); - this.adjacentLoadRefFutures = null; - }); + else + { + // nothing needs to be rendered + this.canRender = false; + this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; + return CompletableFuture.completedFuture(null); + } + } + catch (Exception e) + { + // exception handling is done here since attempting to do so in the final future's + // .exceptionally() block doesn't return the correct stack traces, making debugging impossible + this.handleException(e); + throw e; + } + }) + .thenCompose((lodQuadBuilder) -> + { + try + { + // can be null if there was a problem or if there's nothing to render + if (lodQuadBuilder != null) + { + return this.uploadToGpuAsync(lodQuadBuilder); + } + else + { + return CompletableFuture.completedFuture(null); + } + } + catch (Exception e) + { + this.handleException(e); + throw e; + } }); - } - catch (Exception e) - { - thisRenderSourceLoadFuture.decrementRefCount(); - this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures); - this.adjacentLoadRefFutures = null; - - LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e); - this.buildAndUploadRenderDataToGpuFuture = null; - this.bufferBuildFuture = null; - } } catch (Exception e) { - thisRenderSourceLoadFuture.decrementRefCount(); - this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures); - this.adjacentLoadRefFutures = null; - - LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e); - this.buildAndUploadRenderDataToGpuFuture = null; - this.bufferBuildFuture = null; + // this catch is just for the first loadRenderDataAsync(), + // each subsequent method has their own handleException() block. + this.handleException(e); } - }); - }, executor); + }, executor); + } + catch (RejectedExecutionException ignore) + { /* the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back */ } } - /** Should be called on the {@link ThreadPoolUtil#getFileHandlerExecutor()} */ - private ReferencedFutureWrapper[] getNeighborRenderSourcesAsync() + private CompletableFuture loadRenderDataAsync() { - ReferencedFutureWrapper[] futureArray = new ReferencedFutureWrapper[EDhDirection.ADJ_DIRECTIONS.length]; + CompletableFuture thisRenderSourceLoadFuture = this.getRenderSourceAsync(); + ArrayList> adjRenderSourceLoadRefFutures = this.getNeighborRenderSourcesAsync(); + + + // wait for all futures to complete together, + // merging the futures makes loading significantly faster than loading this position then loading its neighbors + ArrayList> futureList = new ArrayList<>(); + futureList.add(thisRenderSourceLoadFuture); + futureList.addAll(adjRenderSourceLoadRefFutures); + CompletableFuture allLoadedFuture = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])); + + return allLoadedFuture.thenApply((voidObj) -> new LoadedRenderSourcesFutureWrapper(allLoadedFuture, thisRenderSourceLoadFuture, adjRenderSourceLoadRefFutures)); + } + private CompletableFuture buildNewRenderDataAsync( + ColumnRenderSource thisRenderSource, + LoadedRenderSourcesFutureWrapper loadedRenderSources) + { + ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length]; + boolean[] adjIsSameDetailLevel = new boolean[EDhDirection.ADJ_DIRECTIONS.length]; + for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++) + { + adjacentRenderSections[i] = loadedRenderSources.getAdjacentRenderSource(i); + + // if the adjacent position isn't the same detail level the buffer building logic + // will need to be slightly different in order to reduce holes in the LODs + EDhDirection direction = EDhDirection.ADJ_DIRECTIONS[i]; + adjIsSameDetailLevel[direction.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(direction); + } + + if (this.bufferBuildFuture != null) + { + // shouldn't normally happen, but just in case canceling the previous future + // prevents the CPU from working on something that won't be used + this.bufferBuildFuture.cancel(true); + } + this.bufferBuildFuture = ColumnRenderBufferBuilder.buildBuffersAsync(this.level, thisRenderSource, adjacentRenderSections, adjIsSameDetailLevel); + return this.bufferBuildFuture; + } + private CompletableFuture uploadToGpuAsync(LodQuadBuilder lodQuadBuilder) + { + if (this.bufferUploadFuture != null) + { + // shouldn't normally happen, but just in case canceling the previous future + // prevents the CPU from working on something that won't be used + this.bufferUploadFuture.cancel(true); + } + + this.bufferUploadFuture = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, this.pos, lodQuadBuilder); + return this.bufferUploadFuture.thenCompose((buffer) -> + { + ColumnRenderBuffer previousBuffer = this.renderBuffer; + + // upload complete, clean up the old data if + this.renderBuffer = buffer; + this.canRender = (buffer != null); + this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; + + if (previousBuffer != null) + { + previousBuffer.close(); + } + + return null; + }); + } + + + + //=====================// + // render data helpers // + //=====================// + + /** Should be called on the {@link ThreadPoolUtil#getFileHandlerExecutor()} */ + private ArrayList> getNeighborRenderSourcesAsync() + { + ArrayList> futureList = ListUtil.createEmptyList(EDhDirection.ADJ_DIRECTIONS.length); + for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++) { EDhDirection direction = EDhDirection.ADJ_DIRECTIONS[i]; @@ -296,46 +311,34 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable LodRenderSection adjRenderSection = this.quadTree.getValue(adjPos); if (adjRenderSection != null) { - futureArray[arrayIndex] = adjRenderSection.getRenderSourceAsync(); + futureList.set(arrayIndex, adjRenderSection.getRenderSourceAsync()); } } catch (IndexOutOfBoundsException ignore) {} - if (futureArray[arrayIndex] == null) + if (futureList.get(arrayIndex) == null) { - futureArray[arrayIndex] = new ReferencedFutureWrapper(CompletableFuture.completedFuture(null)); + futureList.set(arrayIndex, CompletableFuture.completedFuture(null)); } } - this.adjacentLoadRefFutures = futureArray; - return futureArray; + return futureList; } + /** Will try to return the same {@link CompletableFuture} if multiple requests are made for the same position */ - private ReferencedFutureWrapper getRenderSourceAsync() + private CompletableFuture getRenderSourceAsync() { try { this.getRenderSourceLock.lock(); - - // if a load is already in progress, use that existing one - // (this reduces the number of duplicate loads that may happen when initially loading the world) - if (this.renderSourceLoadingRefFuture != null) - { - // increment the number of objects needing this future - this.renderSourceLoadingRefFuture.incrementRefCount(); - return this.renderSourceLoadingRefFuture; - } - - - ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor(); if (executor == null || executor.isTerminated()) { - return new ReferencedFutureWrapper(CompletableFuture.completedFuture(null)); + return CompletableFuture.completedFuture(null); } - this.renderSourceLoadingRefFuture = new ReferencedFutureWrapper(CompletableFuture.supplyAsync(() -> + this.renderSourceLoadingRefFuture = CompletableFuture.supplyAsync(() -> { try (FullDataSourceV2 fullDataSource = this.fullDataSourceProvider.get(this.pos)) { @@ -349,7 +352,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.renderSourceLoadingRefFuture = null; return null; } - }, executor)); + }, executor); return this.renderSourceLoadingRefFuture; } finally @@ -362,26 +365,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable long adjPos = DhSectionPos.getAdjacentPos(this.pos, direction); byte detailLevel = this.quadTree.calculateExpectedDetailLevel(new DhBlockPos2D(MC.getPlayerBlockPos()), adjPos); detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; - boolean adjacentIsSameDetailLevel = (detailLevel == DhSectionPos.getDetailLevel(this.pos)); - return adjacentIsSameDetailLevel; + return detailLevel == DhSectionPos.getDetailLevel(this.pos); } - - /** - * Note: can cause issues with neighboring LOD sections - * if only some (vs all) futures are canceled. - */ - public void cancelGpuUpload() + private void handleException(Throwable e) { - CompletableFuture future = this.buildAndUploadRenderDataToGpuFuture; + LOGGER.error("Unexpected error in LodRenderSection loading, Error: " + e.getMessage(), e); this.buildAndUploadRenderDataToGpuFuture = null; this.bufferBuildFuture = null; - if (future != null) - { - // interrupting the future speeds things up, but also causes - // some LODs to never load in properly - future.cancel(false); - } } @@ -430,7 +421,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable // full data retrieval (world gen) // //=================================// - public boolean isFullyGenerated() { return this.missingPositionsCalculated && this.missingGenerationPos.size() == 0; } + public boolean isFullyGenerated() { return this.missingPositionsCalculated && this.missingGenerationPos.isEmpty(); } public boolean missingPositionsCalculated() { return this.missingPositionsCalculated; } public int ungeneratedPositionCount() { return (this.missingGenerationPos != null) ? this.missingGenerationPos.size() : 0; } @@ -475,27 +466,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable - //=========// - // cleanup // - //=========// - - /** does nothing if the passed in value is null. */ - private void tryDecrementingLoadFutureArray(@Nullable ReferencedFutureWrapper[] refFutures) - { - if (refFutures != null) - { - for (ReferencedFutureWrapper futureWrapper : refFutures) - { - if (futureWrapper != null) - { - futureWrapper.decrementRefCount(); - } - } - } - } - - - //==============// // base methods // //==============// @@ -546,12 +516,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.bufferUploadFuture.cancel(true); } - // this render section won't be rendering, we don't need to load any data for it - this.tryDecrementingLoadFutureArray(this.adjacentLoadRefFutures); - if (this.renderSourceLoadingRefFuture != null) - { - this.renderSourceLoadingRefFuture.decrementRefCount(); - } // remove any active world gen requests that may be for this position @@ -596,44 +560,40 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable // helper classes // //================// - /** - * Used to keep track of whether a {@link ColumnRenderSource} {@link CompletableFuture} - * is in use or not, and if not in use cancels the future.

- * - * This helps speed up LOD loading by canceling loads that are no longer needed, - * IE out of range or in an unloaded dimension. - */ - private static class ReferencedFutureWrapper + /** Used to easily pass around the loaded {@link ColumnRenderSource}'s. */ + private static class LoadedRenderSourcesFutureWrapper { - public final CompletableFuture future; - // starts at 1 since the constructing method is referencing this future - private final AtomicInteger refCount = new AtomicInteger(1); + //private final CompletableFuture future; + private final CompletableFuture thisRenderSourceFuture; + private final ArrayList> adjacentRenderSourceFutures; - public ReferencedFutureWrapper(CompletableFuture future) { this.future = future; } - - public void incrementRefCount() { this.refCount.incrementAndGet(); } - public void decrementRefCount() + public LoadedRenderSourcesFutureWrapper(CompletableFuture future, CompletableFuture thisRenderSourceFuture, ArrayList> adjacentRenderSourceFutures) { - // automatically clean up this future if no one else is referencing it - if (this.refCount.decrementAndGet() <= 0) - { - if (this.future != null) - { - if (!this.future.isDone()) - { - this.future.cancel(true); - } - } - } + //this.future = future; + this.thisRenderSourceFuture = thisRenderSourceFuture; + this.adjacentRenderSourceFutures = adjacentRenderSourceFutures; } - @Override - public String toString() { return this.future.toString() + " - " + this.refCount.get(); } - + //public CompletableFuture getFuture() { return this.future; } + public ColumnRenderSource getThisRenderSource() { return this.thisRenderSourceFuture != null ? this.thisRenderSourceFuture.getNow(null) : null; } + public ColumnRenderSource getAdjacentRenderSource(int i) + { + CompletableFuture future = this.adjacentRenderSourceFutures.get(i); + return future != null ? future.getNow(null) : null; + } + public ArrayList getAdjacentRenderSourceList() + { + ArrayList list = new ArrayList<>(); + for (int i = 0; i < this.adjacentRenderSourceFutures.size(); i++) + { + list.add(this.getAdjacentRenderSource(i)); + } + return list; + } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/ListUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/ListUtil.java new file mode 100644 index 000000000..c3adaa220 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/ListUtil.java @@ -0,0 +1,19 @@ +package com.seibel.distanthorizons.core.util; + +import java.util.ArrayList; + +public class ListUtil +{ + /** Create list filled with null up to the size */ + public static ArrayList createEmptyList(int size) + { + ArrayList list = new ArrayList(); + for (int i = 0; i < size; i++) + { + list.add(null); + } + return list; + } + + +}