From 800ffc5611d4cccdaa5f62d19a90bebc6d2c244a Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 3 Sep 2023 16:55:52 -0500 Subject: [PATCH] Move RenderSourceFileHandler code into RenderMetaDataFile --- .../file/renderfile/RenderMetaDataFile.java | 558 +++++++++++------- .../renderfile/RenderSourceFileHandler.java | 133 +---- .../core/util/AtomicsUtil.java | 14 +- 3 files changed, 372 insertions(+), 333 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderMetaDataFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderMetaDataFile.java index 60eed59b2..764cd00d3 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderMetaDataFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderMetaDataFile.java @@ -21,6 +21,10 @@ package com.seibel.distanthorizons.core.file.renderfile; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; +import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer; +import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile; +import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.metaData.AbstractMetaDataContainerFile; import com.seibel.distanthorizons.core.file.metaData.BaseMetaData; import com.seibel.distanthorizons.core.level.IDhLevel; @@ -34,6 +38,7 @@ import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.util.AtomicsUtil; import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.util.objects.Reference; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; @@ -63,37 +68,7 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements private final RenderSourceFileHandler fileHandler; private boolean doesFileExist; - private static final class CacheQueryResult - { - public final CompletableFuture future; - public final boolean needsLoad; - public CacheQueryResult(CompletableFuture future, boolean needsLoad) - { - this.future = future; - this.needsLoad = needsLoad; - } - - } - @Override - public void debugRender(DebugRenderer r) - { - ColumnRenderSource cached = cachedRenderDataSource.get(); - Color c = Color.black; - if (cached != null) - { - c = Color.GREEN; - } - else if (renderSourceLoadFutureRef.get() != null) - { - c = Color.BLUE; - } - else if (doesFileExist) - { - c = Color.RED; - } - r.renderBox(new DebugRenderer.Box(pos, 64, 72, 0.05f, c)); - } //=============// // constructor // @@ -135,6 +110,7 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements DebugRenderer.register(this); } + /** * NOTE: should only be used if there IS an existing file. * @@ -144,7 +120,6 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements { return new RenderMetaDataFile(fileHandler, path); } - private RenderMetaDataFile(RenderSourceFileHandler fileHandler, File path) throws IOException { super(path); @@ -156,19 +131,30 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements DebugRenderer.register(this); } - public void updateChunkIfSourceExists(ChunkSizedFullDataAccessor chunkDataView, IDhClientLevel level) + + + //=============// + // data update // + //=============// + + public void updateChunkIfSourceExistsAsync(ChunkSizedFullDataAccessor chunkDataView, IDhClientLevel level) { DhLodPos chunkPos = chunkDataView.getLodPos(); LodUtil.assertTrue(this.pos.getSectionBBoxPos().overlapsExactly(chunkPos), "Chunk pos " + chunkPos + " doesn't overlap with section " + this.pos); // update the render source if one exists - CompletableFuture renderSourceLoadFuture = getCachedDataSourceAsync(false); - if (renderSourceLoadFuture == null) return; + CompletableFuture renderSourceLoadFuture = this.getCachedDataSourceAsync(false); + if (renderSourceLoadFuture == null) + { + return; + } - renderSourceLoadFuture.thenAccept((renderSource) -> { + + renderSourceLoadFuture.thenAccept((renderSource) -> + { boolean dataUpdated = renderSource.updateWithChunkData(chunkDataView, level); - //if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL+5) { + // add a debug renderer float offset = new Random(System.nanoTime() ^ Thread.currentThread().getId()).nextFloat() * 16f; Color debugColor = dataUpdated ? Color.blue : Color.red; DebugRenderer.makeParticle( @@ -177,55 +163,318 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements 2.0, 16f ) ); - //} }); } - public CompletableFuture flushAndSaveAsync(ExecutorService renderCacheThread) + + + //======================// + // render source getter // + //======================// + + public CompletableFuture getOrLoadCachedDataSourceAsync(Executor fileReaderThreads, IDhLevel level) + { + CacheQueryResult cacheQueryResult = this.getOrStartCachedDataSourceAsync(); + if (cacheQueryResult.cacheQueryAlreadyInProcess) + { + // return the in-process future + return cacheQueryResult.future; + } + + + + CompletableFuture getSourceFuture = cacheQueryResult.future; + if (!this.doesFileExist) + { + // create a new Meta file and render source + + + // create an empty render source + byte dataDetailLevel = (byte) (this.pos.sectionDetailLevel - ColumnRenderSource.SECTION_SIZE_OFFSET); + int verticalSize = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(dataDetailLevel); + ColumnRenderSource newColumnRenderSource = new ColumnRenderSource(this.pos, verticalSize, level.getMinY()); + + this.baseMetaData = new BaseMetaData( + newColumnRenderSource.getSectionPos(), -1, newColumnRenderSource.getDataDetail(), + newColumnRenderSource.worldGenStep, RenderSourceFileHandler.RENDER_SOURCE_TYPE_ID, + newColumnRenderSource.getRenderDataFormatVersion(), Long.MAX_VALUE); + + this.fileHandler.onRenderFileLoadedAsync(newColumnRenderSource, this) // TODO just calls // metaFile.updateRenderCacheAsync() + // wait for the file handler to finish before returning the render source + .whenComplete((renderSource, ex) -> + { + this.cachedRenderDataSource = new SoftReference<>(renderSource); + + this.renderSourceLoadFutureRef.set(null); + getSourceFuture.complete(renderSource); + }); + } + else + { + CompletableFuture.supplyAsync(() -> + { + if (this.baseMetaData == null) + { + throw new IllegalStateException("Meta data not loaded!"); + } + + // Load the render source file. + ColumnRenderSource renderSource; + try (FileInputStream fileInputStream = this.getFileInputStream(); // throws IoException + DhDataInputStream compressedInputStream = new DhDataInputStream(fileInputStream)) + { + renderSource = ColumnRenderLoader.INSTANCE.loadRenderSource(this, compressedInputStream, level); + } + catch (IOException ex) + { + throw new CompletionException(ex); + } + + return renderSource; + }, fileReaderThreads) + // TODO: Check for file version and only update if needed. + .thenCompose((renderSource) -> this.fileHandler.onRenderFileLoadedAsync(renderSource, this)) // TODO just calls // metaFile.updateRenderCacheAsync() + .whenComplete((renderSource, ex) -> + { + if (ex != null) + { + if (!LodUtil.isInterruptOrReject(ex)) + { + LOGGER.error("Error loading file "+this.file+": ", ex); + } + + // set the render source to null to prevent instances where a corrupt or incomplete render source is returned + renderSource = null; + } + + this.renderSourceLoadFutureRef.set(null); + + this.cachedRenderDataSource = new SoftReference<>(renderSource); + getSourceFuture.complete(renderSource); + }); + } + + return getSourceFuture; + } + // TODO why is this being used vs just // this.getCachedDataSourceAsync(true); ? + private CacheQueryResult getOrStartCachedDataSourceAsync() + { + CompletableFuture renderSourceLoadFuture = this.getCachedDataSourceAsync(true); + if (renderSourceLoadFuture != null) + { + // return the existing load future + return new CacheQueryResult(renderSourceLoadFuture, true); + } + else + { + // Create a new future if one doesn't already exist + CompletableFuture newFuture = new CompletableFuture<>(); + CompletableFuture oldFuture = AtomicsUtil.compareAndExchange(this.renderSourceLoadFutureRef, null, newFuture); + + CompletableFuture activeFuture = (oldFuture == null) ? newFuture : oldFuture; + // if a loading future is already active we don't need to trigger another load + boolean cacheQueryAlreadyInProcess = (oldFuture != null); + return new CacheQueryResult(activeFuture, cacheQueryAlreadyInProcess); + } + } + private FileInputStream getFileInputStream() throws IOException + { + FileInputStream inputStream = new FileInputStream(this.file); + int toSkip = METADATA_SIZE_IN_BYTES; + while (toSkip > 0) + { + long skipped = inputStream.skip(toSkip); + if (skipped == 0) + { + throw new IOException("Invalid file: Failed to skip metadata."); + } + toSkip -= skipped; + } + + if (toSkip != 0) + { + throw new IOException("File IO Error: Failed to skip metadata."); + } + else + { + return inputStream; + } + } + + + + //===============// + // cache handler // + //===============// + + public CompletableFuture updateRenderCacheAsync(ColumnRenderSource renderSource, IFullDataSourceProvider fullDataSourceProvider, IDhClientLevel clientLevel) + { + DebugRenderer.BoxWithLife debugBox = new DebugRenderer.BoxWithLife(new DebugRenderer.Box(renderSource.sectionPos, 74f, 86f, 0.1f, Color.red), 1.0, 32f, Color.green.darker()); + + + // Skip updating the cache if the data file is already up-to-date + FullDataMetaFile dataFile = fullDataSourceProvider.getFileIfExist(this.pos); + if (!RenderSourceFileHandler.ALWAYS_INVALIDATE_CACHE && dataFile != null && dataFile.baseMetaData != null && dataFile.baseMetaData.checksum == this.baseMetaData.dataVersion.get()) // TODO can we make it so the version comparisons either both use the checksum or the dataVersion? Comparing checksum and dataVersion is kinda confusing + { + LOGGER.debug("Skipping render cache update for " + this.pos); + renderSource.localVersion.incrementAndGet(); + return CompletableFuture.completedFuture(null); + } + + + + final Reference renderDataVersionRef = new Reference<>(Integer.MAX_VALUE); + + // get the full data source + CompletableFuture fullDataSourceFuture = + fullDataSourceProvider.readAsync(renderSource.getSectionPos()) + .thenApply((fullDataSource) -> + { + debugBox.box.color = Color.yellow.darker(); + + // get the metaFile's version + FullDataMetaFile renderSourceMetaFile = fullDataSourceProvider.getFileIfExist(this.pos); + if (renderSourceMetaFile != null) + { + renderDataVersionRef.value = renderSourceMetaFile.baseMetaData.checksum; + } + + return fullDataSource; + }).exceptionally((ex) -> + { + LOGGER.error("Exception when getting data for updateCache()", ex); + return null; + }); + + + + // convert the full data source into a render source + CompletableFuture transformFuture = FullDataToRenderDataTransformer.transformFullDataToRenderSourceAsync(fullDataSourceFuture, clientLevel) + .handle((newRenderSource, ex) -> + { + if (ex == null) + { + try + { + renderSource.updateFromRenderSource(newRenderSource); + + // update the meta data + this.baseMetaData.dataVersion.set(renderDataVersionRef.value); + this.baseMetaData.dataLevel = renderSource.getDataDetail(); + this.baseMetaData.dataTypeId = RenderSourceFileHandler.RENDER_SOURCE_TYPE_ID; + this.baseMetaData.binaryDataFormatVersion = renderSource.getRenderDataFormatVersion(); + + // save to file + this.save(renderSource); + } + catch (Throwable e) + { + LOGGER.error("Exception when writing render data to file: ", e); + } + } + else if (!LodUtil.isInterruptOrReject(ex)) + { + LOGGER.error("Exception when updating render file using data source: ", ex); + } + + debugBox.close(); + return null; + }); + return transformFuture; + } + + + + //===============// + // file handling // + //===============// + + public CompletableFuture flushAndSaveAsync() { if (!this.file.exists()) { return CompletableFuture.completedFuture(null); // No need to save if the file doesn't exist. } + // FIXME: TODO: Change doTriggerUpdate to true. Currently is false cause a dead future making render handler hang, // and that render cache aren't actually used really yet due to missing versioning atm. So disabling for now. - CompletableFuture source = getCachedDataSourceAsync(false); - if (source == null) + CompletableFuture getSourceFuture = this.getCachedDataSourceAsync(false); + if (getSourceFuture == null) { return CompletableFuture.completedFuture(null); // If there is no cached data, there is no need to save. } - return source.handle((columnRenderSource, ex) -> { - if (ex != null && !LodUtil.isInterruptOrReject(ex)) - LOGGER.error("Failed to load render source for " + this.pos + " for flush and saving", ex); - return null; - }); // Otherwise, wait for the data to be read (which also flushes changes to the file). + + // Wait for the data to be read, which also flushes changes to the file. + return getSourceFuture.thenAccept((columnRenderSource) -> { /* discard the render source, it doesn't need to be returned */ }); } - private CacheQueryResult getOrStartCachedDataSourceAsync() + + /** writes the given {@link ColumnRenderSource} to file */ + private void save(ColumnRenderSource renderSource) { - // use the existing future - CompletableFuture renderSourceLoadFuture = getCachedDataSourceAsync(true); - if (renderSourceLoadFuture == null) + if (renderSource.isEmpty()) { - // Make a new future, and CAS it, or return the existing future - CompletableFuture newFuture = new CompletableFuture<>(); - CompletableFuture cas = AtomicsUtil.compareAndExchange(renderSourceLoadFutureRef, null, newFuture); - if (cas == null) + if (this.file.exists()) { - return new CacheQueryResult(newFuture, true); - } - else - { - return new CacheQueryResult(cas, false); + // attempt to remove the empty render source + if (!this.file.delete()) + { + LOGGER.warn("Failed to delete render file at " + this.file); + } } + + this.doesFileExist = false; } else { - return new CacheQueryResult(renderSourceLoadFuture, false); + //LOGGER.info("Saving updated render file v[{}] at sect {}", this.metaData.dataVersion.get(), this.pos); + try + { + super.writeData((dhDataOutputStream) -> renderSource.writeData(dhDataOutputStream)); + this.doesFileExist = true; + } + catch (IOException e) + { + LOGGER.error("Failed to save updated render file at {} for sect {}", this.file, this.pos, e); + } } } + + + //=======// + // debug // + //=======// + + @Override + public void debugRender(DebugRenderer debugRenderer) + { + Color color = Color.black; + + ColumnRenderSource cached = this.cachedRenderDataSource.get(); + if (cached != null) + { + color = Color.GREEN; + } + else if (this.renderSourceLoadFutureRef.get() != null) + { + color = Color.BLUE; + } + else if (this.doesFileExist) + { + color = Color.RED; + } + + debugRenderer.renderBox(new DebugRenderer.Box(this.pos, 64, 72, 0.05f, color)); + } + + + + //================// + // helper methods // + //================// + @Nullable - private CompletableFuture getCachedDataSourceAsync(boolean doTriggerUpdate) + private CompletableFuture getCachedDataSourceAsync(boolean triggerAndWaitForListener) { // use the existing future CompletableFuture renderSourceLoadFuture = this.renderSourceLoadFutureRef.get(); @@ -243,173 +492,56 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements } else { - if (!doTriggerUpdate) return CompletableFuture.completedFuture(cachedRenderDataSource); - - // Make a new future, and CAS it, or return the existing future - CompletableFuture newFuture = new CompletableFuture<>(); - CompletableFuture cas = AtomicsUtil.compareAndExchange(renderSourceLoadFutureRef, null, newFuture); - if (cas == null) + if (!triggerAndWaitForListener) { - this.fileHandler.onRenderSourceLoadedFromCacheAsync(this, cachedRenderDataSource) + // immediately return the render source + return CompletableFuture.completedFuture(cachedRenderDataSource); + } + + // trigger the listener, wait for it to finish, then return the render source + + // Create a new future if one doesn't already exist + CompletableFuture newFuture = new CompletableFuture<>(); + CompletableFuture oldFuture = AtomicsUtil.compareAndExchange(this.renderSourceLoadFutureRef, null, newFuture); + + if (oldFuture != null) + { + return oldFuture; + } + else + { + this.fileHandler.onRenderSourceLoadedFromCacheAsync(this, cachedRenderDataSource) // TODO just calls // metaFile.updateRenderCacheAsync() // wait for the handler to finish before returning the renderSource - .handle((voidObj, ex) -> { - if (ex != null) - { - LOGGER.error("Error while updating render source from cache", ex); - } + .handle((voidObj, ex) -> + { newFuture.complete(cachedRenderDataSource); - renderSourceLoadFutureRef.set(null); + this.renderSourceLoadFutureRef.set(null); + return null; }); return newFuture; } - else - { - return cas; - } } } - public CompletableFuture loadOrGetCachedDataSourceAsync(Executor fileReaderThreads, IDhLevel level) + + + //================// + // helper classes // + //================// + + /** TODO couldn't this just be made into an atomic future or something? */ + private static final class CacheQueryResult { - CacheQueryResult getCachedFuture = this.getOrStartCachedDataSourceAsync(); - if (!getCachedFuture.needsLoad) + public final CompletableFuture future; + public final boolean cacheQueryAlreadyInProcess; + + public CacheQueryResult(CompletableFuture future, boolean cacheQueryAlreadyInProcess) { - return getCachedFuture.future; + this.future = future; + this.cacheQueryAlreadyInProcess = cacheQueryAlreadyInProcess; } - CompletableFuture future = getCachedFuture.future; - // load or create the render source - if (!this.doesFileExist) - { - // create a new Meta file and render source - - - // create the new render source - byte dataDetailLevel = (byte) (this.pos.sectionDetailLevel - ColumnRenderSource.SECTION_SIZE_OFFSET); - int verticalSize = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(dataDetailLevel); - ColumnRenderSource newColumnRenderSource = new ColumnRenderSource(this.pos, verticalSize, level.getMinY()); - - this.baseMetaData = new BaseMetaData( - newColumnRenderSource.getSectionPos(), -1, newColumnRenderSource.getDataDetail(), - newColumnRenderSource.worldGenStep, RenderSourceFileHandler.RENDER_SOURCE_TYPE_ID, - newColumnRenderSource.getRenderDataFormatVersion(), Long.MAX_VALUE); - - this.fileHandler.onRenderFileLoadedAsync(newColumnRenderSource, this) - .whenComplete((renderSource, ex) -> - { - if (ex != null) - { - if (!LodUtil.isInterruptOrReject(ex)) - { - LOGGER.error("Uncaught error on RenderMetaDataFile ColumnRenderSource creation for file: ["+this.file+"]. Error: ", ex); - } - - // set the render source to null to prevent instances where a corrupt or incomplete render source was returned - renderSource = null; - } - - this.renderSourceLoadFutureRef.set(null); - - this.cachedRenderDataSource = new SoftReference<>(renderSource); - future.complete(renderSource); - }); - } - else - { - CompletableFuture.supplyAsync(() -> - { - if (this.baseMetaData == null) - { - throw new IllegalStateException("Meta data not loaded!"); - } - - // Load the file. - ColumnRenderSource renderSource; - try (FileInputStream fileInputStream = this.getFileInputStream(); - DhDataInputStream compressedStream = new DhDataInputStream(fileInputStream)) - { - renderSource = ColumnRenderLoader.INSTANCE.loadRenderSource(this, compressedStream, level); - } - catch (IOException ex) - { - throw new CompletionException(ex); - } - return renderSource; - }, fileReaderThreads) - // TODO: Check for file version and only update if needed. - .thenCompose((renderSource) -> this.fileHandler.onRenderFileLoadedAsync(renderSource, this)) - .whenComplete((renderSource, ex) -> - { - if (ex != null) - { - if (!LodUtil.isInterruptOrReject(ex)) - LOGGER.error("Error loading file {}: ", this.file, ex); - cachedRenderDataSource = new SoftReference<>(null); - renderSourceLoadFutureRef.set(null); - future.complete(null); - } - else - { - cachedRenderDataSource = new SoftReference<>(renderSource); - renderSourceLoadFutureRef.set(null); - future.complete(renderSource); - } - }); - } - return future; - } - - private FileInputStream getFileInputStream() throws IOException - { - FileInputStream fin = new FileInputStream(this.file); - int toSkip = METADATA_SIZE_IN_BYTES; - while (toSkip > 0) - { - long skipped = fin.skip(toSkip); - if (skipped == 0) - { - throw new IOException("Invalid file: Failed to skip metadata."); - } - toSkip -= skipped; - } - - if (toSkip != 0) - { - throw new IOException("File IO Error: Failed to skip metadata."); - } - else - { - return fin; - } - } - - public void save(ColumnRenderSource renderSource) - { - if (renderSource.isEmpty()) - { - if (this.file.exists()) - { - if (!this.file.delete()) - { - LOGGER.warn("Failed to delete render file at {}", this.file); - } - } - this.doesFileExist = false; - } - else - { - //LOGGER.info("Saving updated render file v[{}] at sect {}", this.metaData.dataVersion.get(), this.pos); - try - { - super.writeData((out) -> renderSource.writeData(out)); - this.doesFileExist = true; - } - catch (IOException e) - { - LOGGER.error("Failed to save updated render file at {} for sect {}", this.file, this.pos, e); - } - } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java index bd8a536ad..d44d22eda 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java @@ -21,9 +21,6 @@ package com.seibel.distanthorizons.core.file.renderfile; import com.google.common.collect.HashMultimap; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; -import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; -import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer; -import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.f3.F3Screen; @@ -32,17 +29,13 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.level.IDhClientLevel; -import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.util.FileScanUtil; import com.seibel.distanthorizons.core.util.FileUtil; -import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.ThreadUtil; -import com.seibel.distanthorizons.core.util.objects.Reference; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; -import java.awt.*; import java.io.File; import java.io.IOException; import java.util.*; @@ -67,7 +60,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider /** contains the loaded {@link RenderMetaDataFile}'s */ private final ConcurrentHashMap metaFileBySectionPos = new ConcurrentHashMap<>(); - private final IDhClientLevel level; + private final IDhClientLevel clientLevel; private final File saveDir; /** This is the lowest (highest numeric) detail level that this {@link RenderSourceFileHandler} is keeping track of. */ AtomicInteger topDetailLevel = new AtomicInteger(DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); @@ -81,21 +74,21 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider // constructor // //=============// - public RenderSourceFileHandler(IFullDataSourceProvider sourceProvider, IDhClientLevel level, AbstractSaveStructure saveStructure) + public RenderSourceFileHandler(IFullDataSourceProvider sourceProvider, IDhClientLevel clientLevel, AbstractSaveStructure saveStructure) { this.fullDataSourceProvider = sourceProvider; - this.level = level; - this.saveDir = saveStructure.getRenderCacheFolder(level.getLevelWrapper()); + this.clientLevel = clientLevel; + this.saveDir = saveStructure.getRenderCacheFolder(clientLevel.getLevelWrapper()); if (!this.saveDir.exists() && !this.saveDir.mkdirs()) { LOGGER.warn("Unable to create render data folder, file saving may fail."); } - this.fileHandlerThreadPool = ThreadUtil.makeSingleThreadPool("Render Source File Handler [" + this.level.getLevelWrapper().getDimensionType().getDimensionName() + "]"); + this.fileHandlerThreadPool = ThreadUtil.makeSingleThreadPool("Render Source File Handler [" + this.clientLevel.getLevelWrapper().getDimensionType().getDimensionName() + "]"); this.threadPoolMsg = new F3Screen.NestedMessage(this::f3Log); - FileScanUtil.scanRenderFiles(saveStructure, level.getLevelWrapper(), this); + FileScanUtil.scanRenderFiles(saveStructure, clientLevel.getLevelWrapper(), this); } /** @@ -251,7 +244,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider return CompletableFuture.completedFuture(ColumnRenderSource.createEmptyRenderSource(pos)); } - CompletableFuture getDataSourceFuture = metaFile.loadOrGetCachedDataSourceAsync(this.fileHandlerThreadPool, this.level) + CompletableFuture getDataSourceFuture = metaFile.getOrLoadCachedDataSourceAsync(this.fileHandlerThreadPool, this.clientLevel) .handle((renderSource, exception) -> { if (exception != null) @@ -380,7 +373,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider RenderMetaDataFile metaFile = this.metaFileBySectionPos.get(sectionPos); // bypass the getLoadOrMakeFile() since we only want cached files. if (metaFile != null) { - metaFile.updateChunkIfSourceExists(chunk, this.level); + metaFile.updateChunkIfSourceExistsAsync(chunk, this.clientLevel); } } } @@ -401,7 +394,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider ArrayList> futures = new ArrayList<>(); for (RenderMetaDataFile metaFile : this.metaFileBySectionPos.values()) { - futures.add(metaFile.flushAndSaveAsync(this.fileHandlerThreadPool)); + futures.add(metaFile.flushAndSaveAsync()); } return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) @@ -414,9 +407,10 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider // meta file cache updating // //==========================// - public CompletableFuture onRenderFileLoadedAsync(ColumnRenderSource renderSource, RenderMetaDataFile file) + @Deprecated + public CompletableFuture onRenderFileLoadedAsync(ColumnRenderSource renderSource, RenderMetaDataFile metaFile) { - CompletableFuture future = this.updateMetaFileCacheAsync(renderSource, file).handle((voidObj, ex) -> renderSource); + CompletableFuture future = metaFile.updateRenderCacheAsync(renderSource, this.fullDataSourceProvider, this.clientLevel).handle((voidObj, ex) -> renderSource); synchronized (this.taskTracker) { @@ -425,105 +419,8 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider return future; } - public CompletableFuture onRenderSourceLoadedFromCacheAsync(RenderMetaDataFile file, ColumnRenderSource renderSource) { return this.updateMetaFileCacheAsync(renderSource, file); } - - private CompletableFuture updateMetaFileCacheAsync(ColumnRenderSource renderSource, RenderMetaDataFile renderMetaFile) - { - DebugRenderer.BoxWithLife debugBox = new DebugRenderer.BoxWithLife(new DebugRenderer.Box(renderSource.sectionPos, 74f, 86f, 0.1f, Color.red), 1.0, 32f, Color.green.darker()); - - - // Skip updating the cache if the data file is already up-to-date - FullDataMetaFile dataFile = this.fullDataSourceProvider.getFileIfExist(renderMetaFile.pos); - if (!ALWAYS_INVALIDATE_CACHE && dataFile != null && dataFile.baseMetaData != null && dataFile.baseMetaData.checksum == renderMetaFile.baseMetaData.dataVersion.get()) // TODO can we make it so the version comparisons either both use the checksum or the dataVersion? Comparing checksum and dataVersion is kinda confusing - { - LOGGER.debug("Skipping render cache update for " + renderMetaFile.pos); - renderSource.localVersion.incrementAndGet(); - return CompletableFuture.completedFuture(null); - } - - - - final Reference renderDataVersionRef = new Reference<>(Integer.MAX_VALUE); - - // get the full data source - CompletableFuture fullDataSourceFuture = - this.fullDataSourceProvider.readAsync(renderSource.getSectionPos()) - .thenApply((fullDataSource) -> - { - debugBox.box.color = Color.yellow.darker(); - - // get the metaFile's version - FullDataMetaFile renderSourceMetaFile = this.fullDataSourceProvider.getFileIfExist(renderMetaFile.pos); - if (renderSourceMetaFile != null) - { - renderDataVersionRef.value = renderSourceMetaFile.baseMetaData.checksum; - } - - return fullDataSource; - }).exceptionally((ex) -> - { - LOGGER.error("Exception when getting data for updateCache()", ex); - return null; - }); - - synchronized (this.taskTracker) - { - this.taskTracker.put(fullDataSourceFuture, ETaskType.UPDATE_READ_DATA); - } - - - - // convert the full data source into a render source - CompletableFuture transformFuture = FullDataToRenderDataTransformer.transformFullDataToRenderSourceAsync(fullDataSourceFuture, this.level) - .handle((newRenderSource, ex) -> - { - if (ex == null) - { - try - { - renderMetaFile.baseMetaData.dataVersion.set(renderDataVersionRef.value); - this.mergeRenderSourcesAndWriteToFile(renderSource, renderMetaFile, newRenderSource); - } - catch (Throwable e) - { - LOGGER.error("Exception when writing render data to file: ", e); - } - } - else if (!LodUtil.isInterruptOrReject(ex)) - { - LOGGER.error("Exception when updating render file using data source: ", ex); - } - - debugBox.close(); - return null; - }); - - synchronized (this.taskTracker) - { - this.taskTracker.put(transformFuture, ETaskType.UPDATE); - } - - - return transformFuture; - } - - - - - private void mergeRenderSourcesAndWriteToFile(ColumnRenderSource currentRenderSource, RenderMetaDataFile metaFile, ColumnRenderSource newRenderSource) - { - if (currentRenderSource == null || newRenderSource == null) - { - return; - } - - currentRenderSource.updateFromRenderSource(newRenderSource); - - metaFile.baseMetaData.dataLevel = currentRenderSource.getDataDetail(); - metaFile.baseMetaData.dataTypeId = RENDER_SOURCE_TYPE_ID; - metaFile.baseMetaData.binaryDataFormatVersion = currentRenderSource.getRenderDataFormatVersion(); - metaFile.save(currentRenderSource); - } + @Deprecated + public CompletableFuture onRenderSourceLoadedFromCacheAsync(RenderMetaDataFile metaFile, ColumnRenderSource renderSource) { return metaFile.updateRenderCacheAsync(renderSource, this.fullDataSourceProvider, this.clientLevel); } @@ -535,7 +432,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider private String[] f3Log() { ArrayList lines = new ArrayList<>(); - lines.add("Render Source File Handler [" + this.level.getClientLevelWrapper().getDimensionType().getDimensionName() + "]"); + lines.add("Render Source File Handler [" + this.clientLevel.getClientLevelWrapper().getDimensionType().getDimensionName() + "]"); lines.add(" Loaded files: " + this.metaFileBySectionPos.size() + " / " + (this.unloadedFileBySectionPos.size() + this.metaFileBySectionPos.size())); lines.add(" Thread pool tasks: " + this.fileHandlerThreadPool.getQueue().size() + " (completed: " + this.fileHandlerThreadPool.getCompletedTaskCount() + ")"); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/AtomicsUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/AtomicsUtil.java index f9e725c64..99c3a6b59 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/AtomicsUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/AtomicsUtil.java @@ -55,13 +55,23 @@ public class AtomicsUtil } } + /** + * If the {@link AtomicReference}'s current value matches the expected value, the newValue will be swapped in and the expected value returned.
+ * If the {@link AtomicReference}'s current value DOESN'T match the expected value, the {@link AtomicReference}'s current value will be returned without modification. + */ public static T compareAndExchange(AtomicReference atomic, T expected, T newValue) { while (true) { T oldValue = atomic.get(); - if (oldValue != expected) return oldValue; - if (atomic.weakCompareAndSet(expected, newValue)) return expected; + if (oldValue != expected) + { + return oldValue; + } + else if (atomic.weakCompareAndSet(expected, newValue)) + { + return expected; + } } }