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 4a8b96616..45800d71e 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 @@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuad import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.enums.EDhDirection; +import com.seibel.distanthorizons.core.file.DataSourcePool; import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; @@ -37,6 +38,7 @@ 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.LodUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import it.unimi.dsi.fastutil.longs.LongArrayList; @@ -48,6 +50,7 @@ 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; /** @@ -96,7 +99,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 CompletableFuture renderSourceLoadingRefFuture = null; + private ReferencedRenderSourceFutureWrapper renderSourceLoadingRefFuture = null; private boolean missingPositionsCalculated = false; /** should be an empty array if no positions need to be generated */ @@ -160,15 +163,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable if (thisRenderSource != null && !thisRenderSource.isEmpty()) { 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)); - } - }); + buildDataFuture.thenRun(loadedRenderSources::decrementRefCounts); return buildDataFuture; } else @@ -222,18 +217,21 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable } private CompletableFuture loadRenderDataAsync() { - CompletableFuture thisRenderSourceLoadFuture = this.getRenderSourceAsync(); - ArrayList> adjRenderSourceLoadRefFutures = this.getNeighborRenderSourcesAsync(); + ReferencedRenderSourceFutureWrapper 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); + futureList.add(thisRenderSourceLoadFuture.future); + for(ReferencedRenderSourceFutureWrapper futureWrapper : adjRenderSourceLoadRefFutures) + { + futureList.add(futureWrapper.future); + } CompletableFuture allLoadedFuture = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])); - return allLoadedFuture.thenApply((voidObj) -> new LoadedRenderSourcesFutureWrapper(allLoadedFuture, thisRenderSourceLoadFuture, adjRenderSourceLoadRefFutures)); + return allLoadedFuture.thenApply((voidObj) -> new LoadedRenderSourcesFutureWrapper(thisRenderSourceLoadFuture, adjRenderSourceLoadRefFutures)); } private CompletableFuture buildNewRenderDataAsync( ColumnRenderSource thisRenderSource, @@ -296,9 +294,9 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable //=====================// /** Should be called on the {@link ThreadPoolUtil#getFileHandlerExecutor()} */ - private ArrayList> getNeighborRenderSourcesAsync() + private ArrayList getNeighborRenderSourcesAsync() { - ArrayList> futureList = ListUtil.createEmptyList(EDhDirection.ADJ_DIRECTIONS.length); + ArrayList futureList = ListUtil.createEmptyList(EDhDirection.ADJ_DIRECTIONS.length); for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++) { @@ -318,7 +316,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable if (futureList.get(arrayIndex) == null) { - futureList.set(arrayIndex, CompletableFuture.completedFuture(null)); + futureList.set(arrayIndex, new ReferencedRenderSourceFutureWrapper(CompletableFuture.completedFuture(null))); } } @@ -326,19 +324,28 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable } /** Will try to return the same {@link CompletableFuture} if multiple requests are made for the same position */ - private CompletableFuture getRenderSourceAsync() + private ReferencedRenderSourceFutureWrapper getRenderSourceAsync() { try { this.getRenderSourceLock.lock(); + // use the already loading future if one is present + ReferencedRenderSourceFutureWrapper oldFuture = this.renderSourceLoadingRefFuture; + if (oldFuture != null) + { + oldFuture.incrementRefCount(); + return oldFuture; + } + + ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor(); if (executor == null || executor.isTerminated()) { - return CompletableFuture.completedFuture(null); + return new ReferencedRenderSourceFutureWrapper(CompletableFuture.completedFuture(null)); } - this.renderSourceLoadingRefFuture = CompletableFuture.supplyAsync(() -> + this.renderSourceLoadingRefFuture = new ReferencedRenderSourceFutureWrapper(CompletableFuture.supplyAsync(() -> { try (FullDataSourceV2 fullDataSource = this.fullDataSourceProvider.get(this.pos)) { @@ -352,7 +359,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.renderSourceLoadingRefFuture = null; return null; } - }, executor); + }, executor)); return this.renderSourceLoadingRefFuture; } finally @@ -563,37 +570,81 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable /** Used to easily pass around the loaded {@link ColumnRenderSource}'s. */ private static class LoadedRenderSourcesFutureWrapper { - //private final CompletableFuture future; - private final CompletableFuture thisRenderSourceFuture; - private final ArrayList> adjacentRenderSourceFutures; + private final ReferencedRenderSourceFutureWrapper thisRenderSourceFutureRef; + private final ArrayList adjacentRenderSourceFutureRefList; - public LoadedRenderSourcesFutureWrapper(CompletableFuture future, CompletableFuture thisRenderSourceFuture, ArrayList> adjacentRenderSourceFutures) + public LoadedRenderSourcesFutureWrapper(ReferencedRenderSourceFutureWrapper thisRenderSourceFutureRef, ArrayList adjacentRenderSourceFutureRefList) { - //this.future = future; - this.thisRenderSourceFuture = thisRenderSourceFuture; - this.adjacentRenderSourceFutures = adjacentRenderSourceFutures; + this.thisRenderSourceFutureRef = thisRenderSourceFutureRef; + this.adjacentRenderSourceFutureRefList = adjacentRenderSourceFutureRefList; } - //public CompletableFuture getFuture() { return this.future; } - public ColumnRenderSource getThisRenderSource() { return this.thisRenderSourceFuture != null ? this.thisRenderSourceFuture.getNow(null) : null; } + public ColumnRenderSource getThisRenderSource() { return (this.thisRenderSourceFutureRef != null && this.thisRenderSourceFutureRef.future != null) ? this.thisRenderSourceFutureRef.future.getNow(null) : null; } public ColumnRenderSource getAdjacentRenderSource(int i) { - CompletableFuture future = this.adjacentRenderSourceFutures.get(i); - return future != null ? future.getNow(null) : null; + ReferencedRenderSourceFutureWrapper futureWrapper = this.adjacentRenderSourceFutureRefList.get(i); + return (futureWrapper != null && futureWrapper.future != null) ? futureWrapper.future.getNow(null) : null; } - public ArrayList getAdjacentRenderSourceList() + + public void decrementRefCounts() { - ArrayList list = new ArrayList<>(); - for (int i = 0; i < this.adjacentRenderSourceFutures.size(); i++) + this.thisRenderSourceFutureRef.decrementRefCount(); + + for (int i = 0; i < this.adjacentRenderSourceFutureRefList.size(); i++) { - list.add(this.getAdjacentRenderSource(i)); - } - return list; + this.adjacentRenderSourceFutureRefList.get(i).decrementRefCount(); + } } + } + /** + * Used to keep track of whether a {@link ColumnRenderSource} {@link CompletableFuture} + * is in use or not, and if not in use returns the data source to the {@link DataSourcePool}.

+ * + * This reduces GC overhead by pooling shared {@link ColumnRenderSource}. + */ + private static class ReferencedRenderSourceFutureWrapper + { + public final CompletableFuture future; + // starts at 1 since the constructing method is referencing this future + private final AtomicInteger refCount = new AtomicInteger(1); + + + + public ReferencedRenderSourceFutureWrapper(CompletableFuture future) { this.future = future; } + + public void incrementRefCount() { this.refCount.incrementAndGet(); } + public void decrementRefCount() + { + int refCount = this.refCount.decrementAndGet(); + if (refCount <= 0) + { + // this render section should only be released once + if (refCount < 0) + { + LodUtil.assertNotReach("ReferencedRenderSourceFutureWrapper was released more than once! Ref Count ["+refCount+"]."); + } + + // return data source to the pool + ColumnRenderSource source = this.future.getNow(null); + if (source != null) + { + ColumnRenderSource.DATA_SOURCE_POOL.returnPooledDataSource(source); + } + } + } + + + + @Override + public String toString() { return this.future.toString() + " - " + this.refCount.get(); } + + } + + }