From 175581ca76d747b67e6e1cad7a622690a9bffd5f Mon Sep 17 00:00:00 2001 From: TomTheFurry Date: Thu, 15 Jun 2023 21:55:18 +0800 Subject: [PATCH] Refactor and cleanup render buffer management --- .../render/ColumnRenderSource.java | 105 +------ .../ColumnRenderBufferBuilder.java | 109 +++---- .../fullDatafile/FullDataFileHandler.java | 30 +- .../file/fullDatafile/FullDataMetaFile.java | 11 +- .../GeneratedFullDataFileHandler.java | 138 +++------ .../fullDatafile/IFullDataSourceProvider.java | 5 +- .../core/generation/WorldGenerationQueue.java | 8 +- .../tasks/IWorldGenTaskTracker.java | 3 +- .../tasks/SplitWorldGenTaskTracker.java | 57 ---- .../generation/tasks/WorldGenTaskGroup.java | 14 +- .../seibel/lod/core/render/LodQuadTree.java | 33 +-- .../lod/core/render/LodRenderSection.java | 266 +++++++++++------- .../lod/core/render/RenderBufferHandler.java | 58 +--- 13 files changed, 326 insertions(+), 511 deletions(-) delete mode 100644 core/src/main/java/com/seibel/lod/core/generation/tasks/SplitWorldGenTaskTracker.java diff --git a/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java b/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java index c2ca46944..62ee5395c 100644 --- a/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java @@ -22,7 +22,9 @@ import com.seibel.lod.core.util.LodUtil; import org.apache.logging.log4j.Logger; import java.io.*; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; /** @@ -62,20 +64,6 @@ public class ColumnRenderSource private boolean isEmpty = true; public EDhApiWorldGenerationStep worldGenStep; - private IDhClientLevel level = null; //FIXME: hack to pass level into tryBuildBuffer - - //FIXME: Temp Hack to prevent swapping buffers too quickly - private long lastNs = -1; - /** 2 sec */ - private static final long SWAP_TIMEOUT_IN_NS = 2_000000000L; - /** 1 sec */ - private static final long SWAP_BUSY_COLLISION_TIMEOUT_IN_NS = 1_000000000L; - - private CompletableFuture buildRenderBufferFuture = null; - private final Reference columnRenderBufferRef = new Reference<>(); - - - //==============// // constructors // //==============// @@ -342,94 +330,7 @@ public class ColumnRenderSource public byte getDetailOffset() { return SECTION_SIZE_OFFSET; } - - //================// - // Render Methods // - //================// - - // TODO return future? - private void tryBuildBuffer(IDhClientLevel level, ColumnRenderSource[] adjacentRenderSources) - { - if (this.buildRenderBufferFuture == null && !ColumnRenderBufferBuilder.isBusy() && !this.isEmpty) - { - this.buildRenderBufferFuture = ColumnRenderBufferBuilder.buildBuffers(level, this.columnRenderBufferRef, this, adjacentRenderSources); - } - } - - private void cancelBuildBuffer() - { - if (this.buildRenderBufferFuture != null) - { - //LOGGER.info("Cancelling build of render buffer for {}", sectionPos); - this.buildRenderBufferFuture.cancel(true); - this.buildRenderBufferFuture = null; - } - } - - public void allowRendering(IDhClientLevel level) { this.level = level; } - - public void disableRender() { this.cancelBuildBuffer(); } - - public void dispose() { this.cancelBuildBuffer(); } - - /** - * Try and swap in new render buffer for this section. Note that before this call, there should be no other - * places storing or referencing the render buffer. - * @param renderBufferRefToSwap The slot for swapping in the new buffer. - * @return True if the swap was successful. False if swap is not needed or if it is in progress. - */ - public boolean trySwapInNewlyBuiltRenderBuffer(AtomicReference renderBufferRefToSwap, ColumnRenderSource[] adjacentRenderSources) - { - // prevent swapping the buffer to quickly - if (this.lastNs != -1 && System.nanoTime() - this.lastNs < SWAP_TIMEOUT_IN_NS) - { - return false; - } - - - if (this.buildRenderBufferFuture != null) - { - if (this.buildRenderBufferFuture.isDone()) - { - this.lastNs = System.nanoTime(); - //LOGGER.info("Swapping render buffer for {}", sectionPos); - - - ColumnRenderBuffer newBuffer = this.buildRenderBufferFuture.join(); - LodUtil.assertTrue(newBuffer.buffersUploaded, "The buffer future for "+this.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.buffersUploaded = false; - } - - ColumnRenderBuffer swapped = this.columnRenderBufferRef.swap(oldBuffer); - LodUtil.assertTrue(swapped == null); - - - this.buildRenderBufferFuture = null; - return true; - } - } - else - { - if (!this.isEmpty) - { - if (ColumnRenderBufferBuilder.isBusy()) - { - this.lastNs += (long) (SWAP_BUSY_COLLISION_TIMEOUT_IN_NS * Math.random()); - } - else - { - this.tryBuildBuffer(this.level, adjacentRenderSources); - } - } - } - - return false; - } + public byte getRenderDataFormatVersion() { return DATA_FORMAT_VERSION; } diff --git a/core/src/main/java/com/seibel/lod/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java b/core/src/main/java/com/seibel/lod/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java index cd511585c..2c00037a9 100644 --- a/core/src/main/java/com/seibel/lod/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java +++ b/core/src/main/java/com/seibel/lod/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java @@ -14,6 +14,7 @@ import com.seibel.lod.core.logging.DhLoggerBuilder; import com.seibel.lod.core.pos.DhBlockPos; import com.seibel.lod.core.render.glObject.GLProxy; import com.seibel.lod.core.render.glObject.buffer.GLVertexBuffer; +import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.util.RenderDataPointUtil; import com.seibel.lod.core.util.ThreadUtil; import com.seibel.lod.core.util.objects.Reference; @@ -90,71 +91,71 @@ public class ColumnRenderBufferBuilder LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3); throw e3; } - }, bufferBuilderThreadPool) .thenApplyAsync((quadBuilder) -> + { + try { + EVENT_LOGGER.trace("RenderRegion start Upload @ "+renderSource.sectionPos); + GLProxy glProxy = GLProxy.getInstance(); + EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod(); + EGLProxyContext oldContext = glProxy.getGlContext(); + glProxy.setGlContext(EGLProxyContext.LOD_BUILDER); + ColumnRenderBuffer buffer = renderBufferRef.swap(null); + + if (buffer == null) + { + buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.sectionPos.getCorner().getCornerBlockPos(), clientLevel.getMinY()), renderSource.sectionPos); + } + try { - EVENT_LOGGER.trace("RenderRegion start Upload @ "+renderSource.sectionPos); - GLProxy glProxy = GLProxy.getInstance(); - EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod(); - EGLProxyContext oldContext = glProxy.getGlContext(); - glProxy.setGlContext(EGLProxyContext.LOD_BUILDER); - ColumnRenderBuffer buffer = renderBufferRef.swap(null); - - if (buffer == null) - { - buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.sectionPos.getCorner().getCornerBlockPos(), clientLevel.getMinY()), renderSource.sectionPos); - } - buffer.buffersUploaded = false; - - try - { - buffer.uploadBuffer(quadBuilder, method); - EVENT_LOGGER.trace("RenderRegion end Upload @ "+renderSource.sectionPos); - return buffer; - } - catch (Exception e) + buffer.uploadBuffer(quadBuilder, method); + LodUtil.assertTrue(buffer.buffersUploaded); + EVENT_LOGGER.trace("RenderRegion end Upload @ "+renderSource.sectionPos); + return buffer; + } + catch (Exception e) + { + buffer.close(); + throw e; + } + finally + { + glProxy.setGlContext(oldContext); + } + } + catch (InterruptedException e) + { + throw UncheckedInterruptedException.convert(e); + } + catch (Throwable e3) + { + LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3); + throw e3; + } + }, bufferUploaderThreadPool) + .handle((columnRenderBuffer, ex) -> + { + //LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos); + if (ex != null) + { + LOGGER.warn("Buffer building failed: "+ex.getMessage(), ex); + + if (!renderBufferRef.isEmpty()) { + ColumnRenderBuffer buffer = renderBufferRef.swap(null); buffer.close(); - throw e; - } - finally - { - glProxy.setGlContext(oldContext); } + + return null; } - catch (InterruptedException e) + else { - throw UncheckedInterruptedException.convert(e); + LodUtil.assertTrue(columnRenderBuffer.buffersUploaded); + return columnRenderBuffer; } - catch (Throwable e3) - { - LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3); - throw e3; - } - }, - bufferUploaderThreadPool).handle((columnRenderBuffer, ex) -> - { - //LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos); - if (ex != null) - { - LOGGER.warn("Buffer building failed: "+ex.getMessage(), ex); - - if (!renderBufferRef.isEmpty()) - { - ColumnRenderBuffer buffer = renderBufferRef.swap(null); - buffer.close(); - } - - return null; - } - else - { - return columnRenderBuffer; - } - }); + }); } private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, ColumnRenderSource[] adjRegions) { diff --git a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataFileHandler.java b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataFileHandler.java index ddd3acad5..d623c3e61 100644 --- a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataFileHandler.java +++ b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataFileHandler.java @@ -20,10 +20,7 @@ import org.apache.logging.log4j.Logger; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -314,7 +311,26 @@ public class FullDataFileHandler implements IFullDataSourceProvider } return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); } - + + @Override + public CompletableFuture flushAndSave(DhSectionPos sectionPos) + { + FullDataMetaFile metaFile = this.fileBySectionPos.get(sectionPos); + if (metaFile == null) + { + return CompletableFuture.completedFuture(null); + } + return metaFile.flushAndSaveAsync(); + } + + + private LinkedList> onUpdatedListeners = new LinkedList<>(); + @Override + public synchronized void addOnUpdatedListener(Consumer listener) + { + this.onUpdatedListeners.add(listener); + } + // @Override // public long getCacheVersion(DhSectionPos sectionPos) // { @@ -402,7 +418,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider } @Override - public IFullDataSource onDataFileLoaded(IFullDataSource source, BaseMetaData metaData, + public CompletableFuture onDataFileLoaded(IFullDataSource source, BaseMetaData metaData, Consumer onUpdated, Function updater, boolean justCreated) { boolean changed = updater.apply(source); @@ -422,7 +438,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider { onUpdated.accept(source); } - return source; + return CompletableFuture.completedFuture(source); } @Override public CompletableFuture onDataFileRefresh(IFullDataSource source, BaseMetaData metaData, Function updater, Consumer onUpdated) diff --git a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataMetaFile.java b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataMetaFile.java index 0e507c1dd..34d8c412f 100644 --- a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataMetaFile.java +++ b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataMetaFile.java @@ -163,7 +163,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile this.baseMetaData = this._makeBaseMetaData(fullDataSource); return fullDataSource; }) - .thenApply((fullDataSource) -> this.fullDataSourceProvider.onDataFileLoaded(fullDataSource, this.baseMetaData, this::_updateAndWriteDataSource, this::_applyWriteQueueToFullDataSource, true)) + .thenCompose((fullDataSource) -> this.fullDataSourceProvider.onDataFileLoaded(fullDataSource, this.baseMetaData, this::_updateAndWriteDataSource, this::_applyWriteQueueToFullDataSource, true)) .whenComplete((fullDataSource, exception) -> { if (exception != null) @@ -223,12 +223,10 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile LodUtil.assertTrue(this.inCacheWriteAccessFuture.get() == null, "No one should be writing to the cache while we are in the process of " + "loading one into the cache! Is this a deadlock?"); - - // fire the onDataLoaded method - fullDataSource = this.fullDataSourceProvider.onDataFileLoaded(fullDataSource, this.baseMetaData, this::_updateAndWriteDataSource, this::_applyWriteQueueToFullDataSource, false); + return fullDataSource; - }, executorService) + .thenCompose((fullDataSource) -> this.fullDataSourceProvider.onDataFileLoaded(fullDataSource, this.baseMetaData, this::_updateAndWriteDataSource, this::_applyWriteQueueToFullDataSource, false)) .exceptionally((ex) -> { if (ex instanceof InterruptedException) @@ -243,11 +241,8 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile //LOGGER.warn(FullDataMetaFile.class.getSimpleName()+" loadOrGetCachedAsync attempted to use a closed thread pool."); return null; } - - LOGGER.error("Error loading file "+this.file+": ", ex); this.cachedFullDataSource = new SoftReference<>(null); - future.completeExceptionally(ex); return null; // the return value here doesn't matter }) diff --git a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java index 7a3688c9c..52b0b6129 100644 --- a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java +++ b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java @@ -207,115 +207,44 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler public CompletableFuture onCreateDataFile(FullDataMetaFile file) { return this.spawnGenTasks(file, null); -/* DhSectionPos pos = file.pos; - - ArrayList existingFiles = new ArrayList<>(); - ArrayList missingPositions = new ArrayList<>(); - this.getDataFilesForPosition(pos, pos, existingFiles, missingPositions); - - // confirm the quad tree has at least one node in it - LodUtil.assertTrue(!missingPositions.isEmpty() || !existingFiles.isEmpty()); - - - // determine the type of dataSource that should be used for this position - IIncompleteFullDataSource incompleteFullDataSource; - if (pos.sectionDetailLevel <= HighDetailIncompleteFullDataSource.MAX_SECTION_DETAIL) - { - incompleteFullDataSource = HighDetailIncompleteFullDataSource.createEmpty(pos); - } - else - { - incompleteFullDataSource = LowDetailIncompleteFullDataSource.createEmpty(pos); - } - - - if (missingPositions.size() == 1 && existingFiles.isEmpty() && missingPositions.get(0).equals(pos)) - { - // No LOD data exists for this position yet - - WorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get(); - if (worldGenQueue != null) - { - this.incompleteSourceGenRequests.add(pos); - - // queue this section to be generated - GenTask genTask = new GenTask(pos, new WeakReference<>(incompleteFullDataSource)); - worldGenQueue.submitGenTask(new DhLodPos(pos), incompleteFullDataSource.getDataDetailLevel(), genTask) - .whenComplete((genTaskResult, ex) -> - { - this.onWorldGenTaskComplete(genTaskResult, ex, genTask, pos); - this.fireOnGenPosSuccessListeners(pos); - this.incompleteSourceGenRequests.remove(pos); - }); - } - - // return the empty dataSource (it will be populated later) - return CompletableFuture.completedFuture(incompleteFullDataSource); - } - else - { - // LOD data exists for this position - - // create the missing metaData files - for (DhSectionPos missingPos : missingPositions) - { - FullDataMetaFile newFile = this.getOrMakeFile(missingPos); - if (newFile != null) - { - existingFiles.add(newFile); - } - } - - // LOGGER.debug("Creating "+pos+" from sampling "+existingFiles.size()+" files: "+existingFiles); - - // read in the existing data - final ArrayList> loadDataFutures = new ArrayList<>(existingFiles.size()); - for (FullDataMetaFile existingFile : existingFiles) - { - loadDataFutures.add(existingFile.loadOrGetCachedDataSourceAsync() - .exceptionally((ex) -> *//*Ignore file read errors*//*null) - .thenAccept((fullDataSource) -> - { - if (fullDataSource == null) - { - return; - } - - this.checkIfSectionNeedsAdditionalGeneration(pos, fullDataSource); - - //LOGGER.info("Merging data from {} into {}", data.getSectionPos(), pos); - incompleteFullDataSource.sampleFrom(fullDataSource); - }) - ); - } - - return CompletableFuture.allOf(loadDataFutures.toArray(new CompletableFuture[0])) - .thenApply((voidValue) -> incompleteFullDataSource.tryPromotingToCompleteDataSource()); - }*/ } @Override - public IFullDataSource onDataFileLoaded(IFullDataSource source, BaseMetaData metaData, + public CompletableFuture onDataFileLoaded(IFullDataSource source, BaseMetaData metaData, Consumer onUpdated, Function updater, boolean justCreated) { - IFullDataSource fullDataSource = super.onDataFileLoaded(source, metaData, onUpdated, updater, justCreated); - if (fullDataSource instanceof CompleteFullDataSource || justCreated) { - return fullDataSource; + boolean changed = updater.apply(source); + + if (source instanceof IIncompleteFullDataSource) + { + IFullDataSource newSource = ((IIncompleteFullDataSource) source).tryPromotingToCompleteDataSource(); + changed |= newSource != source; + source = newSource; } - this.spawnGenTasks(getOrMakeFile(metaData.pos), (IIncompleteFullDataSource)fullDataSource); - //checkIfSectionNeedsAdditionalGeneration(metaData.pos, fullDataSource); - return fullDataSource; + if (source instanceof IIncompleteFullDataSource && !justCreated) { + return this.spawnGenTasks(getOrMakeFile(metaData.pos), (IIncompleteFullDataSource)source) + .thenApply((newSource) -> { + onUpdated.accept(newSource); + return newSource; + }); + } + else { + if (changed) + { + onUpdated.accept(source); + } + return CompletableFuture.completedFuture(source); + } } -/* @Override + @Override public CompletableFuture onDataFileRefresh(IFullDataSource source, BaseMetaData metaData, Function updater, Consumer onUpdated) { return super.onDataFileRefresh(source, metaData, updater, (IFullDataSource d) -> { - if (d instanceof CompleteFullDataSource) { - - } - } - }*/ + this.fireOnGenPosSuccessListeners(source.getSectionPos()); + onUpdated.accept(d); + }); + } /** * Checks if the given {@link IFullDataSource} is fully generated and @@ -417,12 +346,14 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler else if (genTaskResult.success) { // generation completed, update the files and listener(s) - this.fireOnGenPosSuccessListeners(pos); + this.flushAndSave(pos); + //this.fireOnGenPosSuccessListeners(pos); return; } else { // generation didn't complete + LOGGER.error("Gen Task Failed at " + pos + ": " + genTaskResult); } @@ -439,7 +370,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler { //LOGGER.info("gen task completed for pos: ["+pos+"]."); - // save the full data source +/* // save the full data source FullDataMetaFile file = this.fileBySectionPos.get(pos); if (file != null) { @@ -459,8 +390,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler // doesn't appear to cause issues, but probably isn't desired either //LOGGER.warn("fireOnGenPosSuccessListeners file null for pos: ["+pos+"]."); } - - + */ // fire the event listeners for (IOnWorldGenCompleteListener listener : this.onWorldGenTaskCompleteListeners) { @@ -501,7 +431,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler } @Override - public Consumer getOnGenTaskCompleteConsumer() + public Consumer getChunkDataConsumer() { if (this.loadedTargetFullDataSource == null) { @@ -521,7 +451,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler } }; } - + public void releaseStrongReference() { this.loadedTargetFullDataSource = null; } } diff --git a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/IFullDataSourceProvider.java b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/IFullDataSourceProvider.java index 8ac165f76..8443660cd 100644 --- a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/IFullDataSourceProvider.java +++ b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/IFullDataSourceProvider.java @@ -20,12 +20,15 @@ public interface IFullDataSourceProvider extends AutoCloseable CompletableFuture read(DhSectionPos pos); void write(DhSectionPos sectionPos, ChunkSizedFullDataAccessor chunkData); CompletableFuture flushAndSave(); + CompletableFuture flushAndSave(DhSectionPos sectionPos); + + void addOnUpdatedListener(Consumer listener); //long getCacheVersion(DhSectionPos sectionPos); //boolean isCacheVersionValid(DhSectionPos sectionPos, long cacheVersion); CompletableFuture onCreateDataFile(FullDataMetaFile file); - IFullDataSource onDataFileLoaded(IFullDataSource source, BaseMetaData metaData, Consumer onUpdated, Function updater, boolean justCreated); + CompletableFuture onDataFileLoaded(IFullDataSource source, BaseMetaData metaData, Consumer onUpdated, Function updater, boolean justCreated); CompletableFuture onDataFileRefresh(IFullDataSource source, BaseMetaData metaData, Function updater, Consumer onUpdated); File computeDataFilePath(DhSectionPos pos); ExecutorService getIOExecutor(); diff --git a/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java index 14588670a..08e6561a0 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java @@ -374,7 +374,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable // temporary solution to prevent generating the same section multiple times LOGGER.warn("Duplicate generation section " + taskPos + " with granularity [" + granularity + "] at " + chunkPosMin + ". Skipping..."); - StackTraceElement[] stackTrace = this.alreadyGeneratedPosHashSet.get(inProgressTaskGroup.group.pos); + //StackTraceElement[] stackTrace = this.alreadyGeneratedPosHashSet.get(inProgressTaskGroup.group.pos); // sending a success result is necessary to make sure the render sections are reloaded correctly inProgressTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(new DhSectionPos(granularity, taskPos)))); @@ -394,7 +394,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable //LOGGER.info("Generating section "+taskPos+" with granularity "+granularity+" at "+chunkPosMin); this.numberOfTasksQueued++; - inProgressTaskGroup.genFuture = this.startGenerationEvent(chunkPosMin, granularity, taskDetailLevel, inProgressTaskGroup.group::onGenerationComplete); + inProgressTaskGroup.genFuture = this.startGenerationEvent(chunkPosMin, granularity, taskDetailLevel, inProgressTaskGroup.group::consumeChunkData); inProgressTaskGroup.genFuture.whenComplete((voidObj, exception) -> { this.numberOfTasksQueued--; @@ -425,7 +425,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable private CompletableFuture startGenerationEvent( DhChunkPos chunkPosMin, byte granularity, byte targetDataDetail, - Consumer generationCompleteConsumer) + Consumer chunkDataConsumer) { EDhApiDistantGeneratorMode generatorMode = Config.Client.Advanced.WorldGenerator.distantGeneratorMode.get(); return this.generator.generateChunks(chunkPosMin.x, chunkPosMin.z, granularity, targetDataDetail, generatorMode, worldGeneratorThreadPool, (generatedObjectArray) -> @@ -433,7 +433,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable try { IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(generatedObjectArray); - generationCompleteConsumer.accept(LodDataBuilder.createChunkData(chunk)); + chunkDataConsumer.accept(LodDataBuilder.createChunkData(chunk)); } catch (ClassCastException e) { diff --git a/core/src/main/java/com/seibel/lod/core/generation/tasks/IWorldGenTaskTracker.java b/core/src/main/java/com/seibel/lod/core/generation/tasks/IWorldGenTaskTracker.java index 72af68113..52a52713d 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/tasks/IWorldGenTaskTracker.java +++ b/core/src/main/java/com/seibel/lod/core/generation/tasks/IWorldGenTaskTracker.java @@ -13,6 +13,5 @@ public interface IWorldGenTaskTracker /** Returns true if the task hasn't been garbage collected. */ boolean isMemoryAddressValid(); - Consumer getOnGenTaskCompleteConsumer(); - + Consumer getChunkDataConsumer(); } diff --git a/core/src/main/java/com/seibel/lod/core/generation/tasks/SplitWorldGenTaskTracker.java b/core/src/main/java/com/seibel/lod/core/generation/tasks/SplitWorldGenTaskTracker.java deleted file mode 100644 index 5602f6d98..000000000 --- a/core/src/main/java/com/seibel/lod/core/generation/tasks/SplitWorldGenTaskTracker.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.seibel.lod.core.generation.tasks; - -import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; - -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import com.seibel.lod.core.generation.WorldGenerationQueue; - -/** - * Used to synchronize {@link WorldGenerationQueue} {@link WorldGenTask}'s - * if the {@link WorldGenTask} needs to be split up. - * - * @author Leetom - * @version 2022-11-25 - */ -public class SplitWorldGenTaskTracker implements IWorldGenTaskTracker -{ - public final IWorldGenTaskTracker parentTracker; - public final CompletableFuture parentFuture; - - /** cached value to allow for quicker checking */ - public boolean isValid = true; - - - - public SplitWorldGenTaskTracker(IWorldGenTaskTracker parentTracker, CompletableFuture parentFuture) - { - this.parentTracker = parentTracker; - this.parentFuture = parentFuture; - } - - - - /** Recalculates and returns the new {@link SplitWorldGenTaskTracker#isValid} value */ - public boolean recalculateIsValid() - { - if (!this.isValid) - { - return false; - } - - this.isValid = this.parentTracker.isMemoryAddressValid(); - if (!this.isValid) - { - this.parentFuture.complete(false); - } - - return this.isValid; - } - - @Override - public boolean isMemoryAddressValid() { return this.isValid; } - - @Override - public Consumer getOnGenTaskCompleteConsumer() { return this.parentTracker.getOnGenTaskCompleteConsumer(); } - -} diff --git a/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTaskGroup.java b/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTaskGroup.java index bd6ee11c4..d9666902d 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTaskGroup.java +++ b/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTaskGroup.java @@ -25,27 +25,23 @@ public final class WorldGenTaskGroup this.pos = pos; this.dataDetail = dataDetail; } - - - - public void onGenerationComplete(ChunkSizedFullDataAccessor chunkSizedFullDataView) + + public void consumeChunkData(ChunkSizedFullDataAccessor chunkSizedFullDataView) { Iterator tasks = this.worldGenTasks.iterator(); while (tasks.hasNext()) { WorldGenTask task = tasks.next(); - Consumer onGenTaskCompleteConsumer = task.taskTracker.getOnGenTaskCompleteConsumer(); - if (onGenTaskCompleteConsumer == null) + Consumer chunkDataConsumer = task.taskTracker.getChunkDataConsumer(); + if (chunkDataConsumer == null) { tasks.remove(); task.future.complete(WorldGenResult.CreateFail()); } else { - // TODO why aren't we removing the task if it has a consumer? - onGenTaskCompleteConsumer.accept(chunkSizedFullDataView); + chunkDataConsumer.accept(chunkSizedFullDataView); } } } - } diff --git a/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java index b01cf9378..0b64eb085 100644 --- a/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java @@ -198,9 +198,7 @@ public class LodQuadTree extends QuadTree implements AutoClose if (sectionPos.sectionDetailLevel > expectedDetailLevel) { // section detail level too high // - - - boolean isThisPositionBeingRendered = renderSection.isRenderingEnabled(); + boolean canThisPosRender = renderSection.isRenderingEnabled(); boolean allChildrenSectionsAreLoaded = true; // recursively update all child render sections @@ -210,24 +208,20 @@ public class LodQuadTree extends QuadTree implements AutoClose DhSectionPos childPos = childPosIterator.next(); QuadNode childNode = rootNode.getNode(childPos); - boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, isThisPositionBeingRendered || parentRenderSectionIsEnabled); + boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, canThisPosRender || parentRenderSectionIsEnabled); allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded; } - - - + if (!allChildrenSectionsAreLoaded) { // not all child positions are loaded yet, or this section is out of render range - return isThisPositionBeingRendered; + return canThisPosRender; } else { // all child positions are loaded, disable this section and enable its children. - renderSection.disposeRenderData(); renderSection.disableRendering(); - - + renderSection.disposeRenderData(); // 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(); @@ -243,10 +237,9 @@ public class LodQuadTree extends QuadTree implements AutoClose { // FIXME having world generation enabled in a pre-generated world that doesn't have any DH data can cause this to happen // surprisingly reloadPos() doesn't appear to be the culprit, maybe there is an issue with reloading/changing the full data source? - LOGGER.debug("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+sectionPos); + LOGGER.warn("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+sectionPos); } - - + // this section is now being rendered via its children return true; } @@ -255,29 +248,25 @@ public class LodQuadTree extends QuadTree implements AutoClose else if (sectionPos.sectionDetailLevel == expectedDetailLevel || sectionPos.sectionDetailLevel == expectedDetailLevel-1) { // this is the detail level we want to render // - - // 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 (!parentRenderSectionIsEnabled && renderSection.canRenderNow()) { renderSection.enableRendering(); - - + // delete/disable children, all of them will be a lower detail level than requested quadNode.deleteAllChildren((childRenderSection) -> { if (childRenderSection != null) { - childRenderSection.disposeRenderData(); childRenderSection.disableRendering(); + childRenderSection.disposeRenderData(); } }); } - - return renderSection.isRenderDataLoaded(); + return renderSection.canRenderNow(); } else { diff --git a/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java index 6f8b108a2..e141836ad 100644 --- a/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java @@ -2,17 +2,25 @@ package com.seibel.lod.core.render; import com.seibel.lod.core.dataObjects.render.ColumnRenderSource; import com.seibel.lod.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer; +import com.seibel.lod.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder; +import com.seibel.lod.core.enums.ELodDirection; 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 com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.objects.Reference; +import com.seibel.lod.core.util.objects.quadTree.QuadTree; import org.apache.logging.log4j.Logger; import java.awt.*; import java.util.Objects; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; /** @@ -25,8 +33,6 @@ public class LodRenderSection implements IDebugRenderable public final DhSectionPos pos; - /** a reference is used so the render buffer can be swapped to and from the buffer builder */ - public final AtomicReference renderBufferRef = new AtomicReference<>(); private boolean isRenderingEnabled = false; /** @@ -34,12 +40,25 @@ public class LodRenderSection implements IDebugRenderable * a {@link ILodRenderSourceProvider} was already being loaded. */ private boolean reloadRenderSourceOnceLoaded = false; - - + private ILodRenderSourceProvider renderSourceProvider = null; private CompletableFuture renderSourceLoadFuture; private ColumnRenderSource renderSource; - + + private IDhClientLevel level = null; + + //FIXME: Temp Hack to prevent swapping buffers too quickly + private long lastNs = -1; + /** 2 sec */ + private static final long SWAP_TIMEOUT_IN_NS = 2_000000000L; + /** 1 sec */ + private static final long SWAP_BUSY_COLLISION_TIMEOUT_IN_NS = 1_000000000L; + + private CompletableFuture buildRenderBufferFuture = null; + private final Reference inactiveRenderBufferRef = new Reference<>(); + + /** a reference is used so the render buffer can be swapped to and from the buffer builder */ + public final AtomicReference activeRenderBufferRef = new AtomicReference<>(); public LodRenderSection(DhSectionPos pos) { @@ -58,13 +77,14 @@ public class LodRenderSection implements IDebugRenderable if (renderSource != null) { color = Color.blue; - if (isRenderingEnabled) color = Color.cyan; - if (isRenderingEnabled && isRenderDataLoaded()) color = Color.green; + if (buildRenderBufferFuture != null) color = Color.magenta; + if (canRenderNow()) color = Color.cyan; + if (canRenderNow() && isRenderingEnabled) color = Color.green; } - float yOffset = Objects.hashCode(this) / (float) Integer.MAX_VALUE * 16f; + float yOffset = Objects.hashCode(this) / (float) Integer.MAX_VALUE * 16f + 16f; - //r.renderBox(this.pos, yOffset, yOffset, 0.1f, color); + r.renderBox(this.pos, yOffset, yOffset, 0.1f, color); } @@ -73,44 +93,23 @@ public class LodRenderSection implements IDebugRenderable // rendering // //===========// - public void enableRendering() { this.isRenderingEnabled = true; } - public void disableRendering() { this.isRenderingEnabled = false; } - - + 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) - { - return; - } - - // don't re-load or double load the render source - if (this.renderSource != null || this.renderSourceLoadFuture != null) - { - return; - } - - - + + private void startLoadRenderSource() { this.renderSourceLoadFuture = this.renderSourceProvider.readAsync(this.pos); this.renderSourceLoadFuture.whenComplete((renderSource, ex) -> { this.renderSourceLoadFuture = null; - this.renderSource = renderSource; - if (this.renderSource != null) - { - this.renderSource.allowRendering(level); - } - - if (this.reloadRenderSourceOnceLoaded) { this.reloadRenderSourceOnceLoaded = false; @@ -119,6 +118,23 @@ public class LodRenderSection implements IDebugRenderable }); } + /** 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; + this.level = level; + if (this.renderSourceProvider == null) + { + return; + } + // don't re-load or double load the render source + if (this.renderSource != null || this.renderSourceLoadFuture != null) + { + return; + } + startLoadRenderSource(); + } + public void reload(ILodRenderSourceProvider renderDataProvider) { this.renderSourceProvider = renderDataProvider; @@ -126,85 +142,34 @@ public class LodRenderSection implements IDebugRenderable { return; } - - // don't accidentally enable rendering for a disabled section if (!this.isRenderingEnabled) { return; } - // wait for the current load future to finish before re-loading if (this.renderSourceLoadFuture != null) { reloadRenderSourceOnceLoaded = true; return; } - - - - this.renderSourceLoadFuture = this.renderSourceProvider.readAsync(this.pos); - this.renderSourceLoadFuture.whenComplete((renderSource, ex) -> - { - this.renderSourceLoadFuture = null; - - // swap in the new render source - ColumnRenderSource oldRenderSource = this.renderSource; - this.renderSource = renderSource; - if (oldRenderSource != null) - { - // only clear the render source after we have a new one, otherwise flickering can happen - oldRenderSource.dispose(); - } - - - if (this.reloadRenderSourceOnceLoaded) - { - this.reloadRenderSourceOnceLoaded = false; - reload(this.renderSourceProvider); - } - }); + startLoadRenderSource(); } - - public void disposeRenderData() - { - if (this.renderSource != null) - { - this.renderSource.disableRender(); - this.renderSource.dispose(); - this.renderSource = null; - } - - if (this.renderBufferRef.get() != null) - { - this.renderBufferRef.get().close(); - this.renderBufferRef.set(null); - } - - if (this.renderSourceLoadFuture != null) - { - this.renderSourceLoadFuture.cancel(true); - this.renderSourceLoadFuture = null; - } - this.renderSourceProvider = null; - } //========================// // getters and properties // //========================// - - /** @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 boolean isRenderDataLoaded() + public boolean canRenderNow() { return this.renderSource != null && @@ -217,14 +182,98 @@ public class LodRenderSection implements IDebugRenderable || ( // check if the buffers have been loaded - this.renderBufferRef.get() != null - && this.renderBufferRef.get().buffersUploaded + this.activeRenderBufferRef.get() != null ) ); } - - - + + //================// + // Render Methods // + //================// + private void cancelBuildBuffer() + { + if (this.buildRenderBufferFuture != null) + { + //LOGGER.info("Cancelling build of render buffer for {}", sectionPos); + this.buildRenderBufferFuture.cancel(true); + this.buildRenderBufferFuture = null; + } + } + + private boolean isInBuildBufferTimeout() { + if (this.lastNs == -1) return false; + return true; + + //boolean inTimeout = System.nanoTime() - this.lastNs < SWAP_TIMEOUT_IN_NS; + //if (!inTimeout && ColumnRenderBufferBuilder.isBusy()) { + // this.lastNs += (long) (SWAP_BUSY_COLLISION_TIMEOUT_IN_NS * Math.random()); + // inTimeout = true; + //} + //return inTimeout; + } + + /** @return true if this section is loaded and set to render */ + public boolean canBuildBuffer() { return this.renderSource != null && this.buildRenderBufferFuture == null && !isInBuildBufferTimeout() && !this.renderSource.isEmpty(); } + + /** @return true if this section is loaded and set to render */ + public boolean canSwapBuffer() { return this.buildRenderBufferFuture != null && this.buildRenderBufferFuture.isDone(); } + + + + /** + * Try and swap in new render buffer for this section. Note that before this call, there should be no other + * places storing or referencing the render buffer. + * @return True if the swap was successful. False if swap is not needed or if it is in progress. + */ + public boolean tryBuildAndSwapBuffer(QuadTree tree) + { + boolean didSwapped = false; + if (canBuildBuffer()) { + ColumnRenderSource[] adjacentSources = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length]; + for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS) { + try { + DhSectionPos adjPos = pos.getAdjacentPos(direction); + LodRenderSection adjRenderSection = tree.getValue(adjPos); + // adjacent render sources can be null + if (adjRenderSection != null) { + adjacentSources[direction.ordinal() - 2] = adjRenderSection.renderSource; // can be null + } + } catch (IndexOutOfBoundsException e) { + // adjacent positions can be out of bounds, in that case a null render source will be used + } + } + this.buildRenderBufferFuture = + ColumnRenderBufferBuilder.buildBuffers(level, this.inactiveRenderBufferRef, renderSource, adjacentSources); + } + if (canSwapBuffer()) { + this.lastNs = System.nanoTime(); + ColumnRenderBuffer newBuffer; + try { + newBuffer = this.buildRenderBufferFuture.getNow(null); + LodUtil.assertTrue(newBuffer != null && newBuffer.buffersUploaded, "The buffer future for "+pos+" returned an un-built buffer."); + this.buildRenderBufferFuture = null; + ColumnRenderBuffer oldBuffer = this.activeRenderBufferRef.getAndSet(newBuffer); + if (oldBuffer != null) + { + // the old buffer is now considered unloaded, it will need to be freshly re-loaded + oldBuffer.buffersUploaded = false; + } + ColumnRenderBuffer swapped = this.inactiveRenderBufferRef.swap(oldBuffer); + didSwapped = true; + LodUtil.assertTrue(swapped == null); + } + catch (CancellationException e1) { + // ignore. + this.buildRenderBufferFuture = null; + } + catch (CompletionException e) { + LOGGER.error("Unable to get render buffer for "+pos+".", e); + this.buildRenderBufferFuture = null; + } + } + return didSwapped; + } + //==============// // base methods // //==============// @@ -239,7 +288,28 @@ public class LodRenderSection implements IDebugRenderable } public void dispose() { - DebugRenderer.unregister(this); disposeRenderData(); + DebugRenderer.unregister(this); + } + + public void disposeRenderData() + { + disposeRenderBuffer(); + if (this.renderSourceLoadFuture != null) + { + this.renderSourceLoadFuture.cancel(true); + this.renderSourceLoadFuture = null; + } + this.renderSource = null; + } + + public void disposeRenderBuffer() + { + cancelBuildBuffer(); + if (this.activeRenderBufferRef.get() != null) + { + ColumnRenderBuffer buffer = this.activeRenderBufferRef.getAndSet(null); + buffer.close(); + } } } diff --git a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java index 4474a7b02..411e873cb 100644 --- a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java +++ b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java @@ -145,11 +145,12 @@ public class RenderBufferHandler DhSectionPos sectionPos = node.sectionPos; LodRenderSection renderSection = node.value; - if (renderSection != null && renderSection.shouldRender()) + if (renderSection != null && renderSection.isRenderingEnabled()) { - if (renderSection.renderBufferRef.get() != null && renderSection.renderBufferRef.get().buffersUploaded) + AbstractRenderBuffer buffer = renderSection.activeRenderBufferRef.get(); + if (buffer != null) { - this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(renderSection.renderBufferRef.get(), sectionPos)); + this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(buffer, sectionPos)); } } } @@ -168,48 +169,20 @@ public class RenderBufferHandler public void updateQuadTreeRenderSources() { - try + Iterator> nodeIterator = this.lodQuadTree.nodeIterator(); + while (nodeIterator.hasNext()) { - Iterator> nodeIterator = this.lodQuadTree.nodeIterator(); - while (nodeIterator.hasNext()) - { - LodRenderSection renderSection = nodeIterator.next().value; + LodRenderSection renderSection = nodeIterator.next().value; + try { if (renderSection != null) { - ColumnRenderSource sectionRenderSource = renderSection.getRenderSource(); - // if the render source is present, attempt to build it - if (sectionRenderSource != null) - { - ColumnRenderSource[] adjacentRenderSources = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length]; - for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS) - { - try - { - DhSectionPos adjPos = sectionRenderSource.sectionPos.getAdjacentPos(direction); - LodRenderSection adjRenderSection = this.lodQuadTree.getValue(adjPos); - // adjacent render sources can be null - if (adjRenderSection != null) - { - ColumnRenderSource adjRenderSource = adjRenderSection.getRenderSource(); - adjacentRenderSources[direction.ordinal() - 2] = adjRenderSource; - } - } - catch (IndexOutOfBoundsException e) - { - // adjacent positions can be out of bounds, in that case a null render source will be used - } - } - - - // 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.renderBufferRef, adjacentRenderSources); - } + renderSection.tryBuildAndSwapBuffer(lodQuadTree); } } - } - catch (Exception e) - { - LOGGER.error("Error updating QuadTree render sources. Error: "+e.getMessage(), e); + catch (Exception e) + { + LOGGER.error("Error updating QuadTree render source at "+renderSection.pos+".", e); + } } } @@ -219,10 +192,9 @@ public class RenderBufferHandler while (nodeIterator.hasNext()) { LodRenderSection renderSection = nodeIterator.next().value; - if (renderSection != null && renderSection.renderBufferRef.get() != null) + if (renderSection != null) { - renderSection.renderBufferRef.get().close(); - renderSection.renderBufferRef.set(null); + renderSection.dispose(); } } }