From 2f249c39d42ade945a4adb88b1c7359070378b8d Mon Sep 17 00:00:00 2001 From: TomTheFurry Date: Tue, 20 Jun 2023 15:47:52 +0800 Subject: [PATCH] fixed and optimize a lot of stuff. --- .../DependencyInjection/ApiEventInjector.java | 56 +++--- .../render/ColumnRenderSource.java | 26 ++- .../ColumnRenderBufferBuilder.java | 8 +- .../render/columnViews/ColumnArrayView.java | 17 ++ .../FullDataToRenderDataTransformer.java | 20 +- .../file/fullDatafile/FullDataMetaFile.java | 22 +-- .../renderfile/ILodRenderSourceProvider.java | 2 +- .../file/renderfile/RenderMetaDataFile.java | 175 ++++++++++++------ .../renderfile/RenderSourceFileHandler.java | 23 +-- .../core/generation/WorldGenerationQueue.java | 5 +- .../core/level/ClientLevelModule.java | 18 +- .../core/level/DhClientServerLevel.java | 2 +- .../core/render/LodQuadTree.java | 6 +- .../core/render/LodRenderSection.java | 96 +++++++--- .../core/render/RenderBufferHandler.java | 18 +- 15 files changed, 323 insertions(+), 171 deletions(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/DependencyInjection/ApiEventInjector.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/DependencyInjection/ApiEventInjector.java index 0488facb7..13ecea4bc 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/DependencyInjection/ApiEventInjector.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/DependencyInjection/ApiEventInjector.java @@ -118,38 +118,36 @@ public class ApiEventInjector extends DependencyInjector implements @Override public > boolean fireAllEvents(Class abstractEvent, T eventParameterObject) { - boolean cancelEvent = false; - - // if this is a one time event, record that it was called - if (ApiEventDefinitionHandler.INSTANCE.getEventDefinition(abstractEvent).isOneTimeEvent && - !this.firedOneTimeEventParamsByEventInterface.containsKey(abstractEvent)) - { - this.firedOneTimeEventParamsByEventInterface.put(abstractEvent, eventParameterObject); - } - - - - - // fire each bound event - ArrayList eventList = this.getAll(abstractEvent); - for (IDhApiEvent event : eventList) - { - if (event != null) - { - try - { - // fire each event and record if any of them - // request to cancel the event. - cancelEvent |= event.fireEvent(eventParameterObject); - } - catch (Exception e) - { - LOGGER.error("Exception thrown by event handler [" + event.getClass().getSimpleName() + "] for event type [" + abstractEvent.getSimpleName() + "], error:" + e.getMessage(), e); + try { + boolean cancelEvent = false; + + // if this is a one time event, record that it was called + if (ApiEventDefinitionHandler.INSTANCE.getEventDefinition(abstractEvent).isOneTimeEvent && + !this.firedOneTimeEventParamsByEventInterface.containsKey(abstractEvent)) { + this.firedOneTimeEventParamsByEventInterface.put(abstractEvent, eventParameterObject); + } + + + // fire each bound event + ArrayList eventList = this.getAll(abstractEvent); + for (IDhApiEvent event : eventList) { + if (event != null) { + try { + // fire each event and record if any of them + // request to cancel the event. + cancelEvent |= event.fireEvent(eventParameterObject); + } catch (Exception e) { + LOGGER.error("Exception thrown by event handler [" + event.getClass().getSimpleName() + "] for event type [" + abstractEvent.getSimpleName() + "], error:" + e.getMessage(), e); + } } } + return cancelEvent; + } + catch (Throwable e) + { + //LOGGER.error("Exception thrown while firing events for event type [" + abstractEvent.getSimpleName() + "], error:" + e.getMessage(), e); + return false; } - - return cancelEvent; } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java index 7a16f9b2d..632812bde 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java @@ -1,11 +1,13 @@ package com.seibel.distanthorizons.core.dataObjects.render; +import com.kitfox.svg.A; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer; +import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream; import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; @@ -18,7 +20,10 @@ import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.LodUtil; import org.apache.logging.log4j.Logger; +import java.awt.*; import java.io.*; +import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; /** * Stores the render data used to generate OpenGL buffers. @@ -56,6 +61,8 @@ public class ColumnRenderSource private boolean isEmpty = true; public EDhApiWorldGenerationStep worldGenStep; + + public AtomicLong localVersion = new AtomicLong(0); // used to track changes to the data source, so that buffers can be updated when necessary //==============// // constructors // @@ -222,7 +229,6 @@ public class ColumnRenderSource /** Overrides any data that has not been written directly using write(). Skips empty source dataPoints. */ public void updateFromRenderSource(ColumnRenderSource renderSource) { - // validate we are writing for the same location LodUtil.assertTrue(renderSource.sectionPos.equals(this.sectionPos)); @@ -239,8 +245,7 @@ public class ColumnRenderSource } // the source isn't empty, this object won't be empty after the method finishes this.isEmpty = false; - - + for (int i = 0; i < this.renderDataContainer.length; i += this.verticalDataCount) { int thisGenMode = RenderDataPointUtil.getGenerationMode(this.renderDataContainer[i]); @@ -277,11 +282,17 @@ public class ColumnRenderSource } } - public void fastWrite(ChunkSizedFullDataAccessor chunkData, IDhClientLevel level) + public boolean fastWrite(ChunkSizedFullDataAccessor chunkData, IDhClientLevel level) { try { - FullDataToRenderDataTransformer.writeFullDataChunkToColumnData(this, level, chunkData); + if (FullDataToRenderDataTransformer.writeFullDataChunkToColumnData(this, level, chunkData)) { + localVersion.incrementAndGet(); + return true; + } + else { + return false; + } } catch (IllegalArgumentException e) { @@ -293,6 +304,7 @@ public class ColumnRenderSource // expected if the transformer is shut down, the exception can be ignored // LOGGER.warn(ColumnRenderSource.class.getSimpleName()+" fast write interrupted."); } + return false; } @@ -321,10 +333,7 @@ public class ColumnRenderSource /** @return how many data points wide this {@link ColumnRenderSource} is. */ public int getWidthInDataPoints() { return BitShiftUtil.powerOfTwo(this.getDetailOffset()); } public byte getDetailOffset() { return SECTION_SIZE_OFFSET; } - - - public byte getRenderDataFormatVersion() { return DATA_FORMAT_VERSION; } /** @@ -352,6 +361,7 @@ public class ColumnRenderSource this.debugSourceFlags[x * SECTION_SIZE + z] = flag; } } + localVersion.incrementAndGet(); } public DebugSourceFlag debugGetFlag(int ox, int oz) { return this.debugSourceFlags[ox * SECTION_SIZE + oz]; } 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 b776c9d70..95ab9852e 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 @@ -52,15 +52,13 @@ public class ColumnRenderBufferBuilder //==============// // vbo building // //==============// - - /** @return null if busy */ + public static CompletableFuture buildBuffers(IDhClientLevel clientLevel, Reference renderBufferRef, ColumnRenderSource renderSource, ColumnRenderSource[] adjData) { - if (isBusy()) +/* if (isBusy()) { return null; - } - + }*/ //LOGGER.info("RenderRegion startBuild @ {}", renderSource.sectionPos); return CompletableFuture.supplyAsync(() -> { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/columnViews/ColumnArrayView.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/columnViews/ColumnArrayView.java index a4e391446..a01a5158f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/columnViews/ColumnArrayView.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/columnViews/ColumnArrayView.java @@ -159,5 +159,22 @@ public final class ColumnArrayView implements IColumnDataView return sb.toString(); } + + public int getDataHash() { + return arrayHash(data, offset, size); + } + + private static int arrayHash(long[] a, int offset, int length) { + if (a == null) + return 0; + int result = 1; + int end = offset + length; + for (int i = offset; i < end; i++) { + long element = a[i]; + int elementHash = (int)(element ^ (element >>> 32)); + result = 31 * result + elementHash; + } + return result; + } } \ No newline at end of file diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java index 2f142fb40..0ee2c645e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java @@ -22,6 +22,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; +import java.util.Arrays; + /** * Handles converting {@link ChunkSizedFullDataAccessor}, {@link IIncompleteFullDataSource}, * and {@link IFullDataSource}'s to {@link ColumnRenderSource}. @@ -156,7 +158,7 @@ public class FullDataToRenderDataTransformer * @throws InterruptedException Can be caused by interrupting the thread upstream. * Generally thrown if the method is running after the client leaves the current world. */ - public static void writeFullDataChunkToColumnData(ColumnRenderSource renderSource, IDhClientLevel level, ChunkSizedFullDataAccessor chunkDataView) throws InterruptedException, IllegalArgumentException + public static boolean writeFullDataChunkToColumnData(ColumnRenderSource renderSource, IDhClientLevel level, ChunkSizedFullDataAccessor chunkDataView) throws InterruptedException, IllegalArgumentException { final DhSectionPos renderSourcePos = renderSource.getSectionPos(); @@ -169,8 +171,8 @@ public class FullDataToRenderDataTransformer final int sourceDataPointBlockWidth = BitShiftUtil.powerOfTwo(renderSource.getDataDetail()); - - + boolean changed = false; + if (chunkDataView.detailLevel == renderSource.getDataDetail()) { // confirm the render source contains this chunk @@ -181,15 +183,16 @@ public class FullDataToRenderDataTransformer { throw new IllegalArgumentException("Data offset is out of bounds"); } - + + throwIfThreadInterrupted(); for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++) { for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++) { - throwIfThreadInterrupted(); - ColumnArrayView columnArrayView = renderSource.getVerticalDataPointView(blockOffsetX + x, blockOffsetZ + z); + int hash = columnArrayView.getDataHash(); + SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(x, z); convertColumnData(level, @@ -201,12 +204,15 @@ public class FullDataToRenderDataTransformer { LodUtil.assertTrue(renderSource.doesDataPointExist(blockOffsetX + x, blockOffsetZ + z)); } + + changed |= hash != columnArrayView.getDataHash(); } } renderSource.fillDebugFlag(blockOffsetX, blockOffsetZ, LodUtil.CHUNK_WIDTH, LodUtil.CHUNK_WIDTH, ColumnRenderSource.DebugSourceFlag.DIRECT); - + renderSource.markNotEmpty(); } + return changed; } private static void convertColumnData(IDhClientLevel level, int blockX, int blockZ, ColumnArrayView columnArrayView, SingleColumnFullDataAccessor fullArrayView, int genMode) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java index c47122656..60dd7b449 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java @@ -54,6 +54,15 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I private SoftReference cachedFullDataSource = new SoftReference<>(null); private final AtomicReference> dataSourceLoadFutureRef = new AtomicReference<>(null); + 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) { IFullDataSource cached = cachedFullDataSource.get(); @@ -309,15 +318,6 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I data.getDataDetailLevel(), data.getWorldGenStep(), (loader == null ? 0 : loader.datatypeId), data.getBinaryDataFormatVersion()); } - 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; - } - } - /** * @return one of the following: * the cached {@link IFullDataSource}, @@ -328,11 +328,9 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I { // this data source is being written to, use the existing future CompletableFuture dataSourceLoadFuture = this.dataSourceLoadFutureRef.get(); - if (dataSourceLoadFuture != null) - { + if (dataSourceLoadFuture != null) { return new CacheQueryResult(dataSourceLoadFuture, false); } - // attempt to get the cached data source IFullDataSource cachedFullDataSource = this.cachedFullDataSource.get(); if (cachedFullDataSource == null) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/ILodRenderSourceProvider.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/ILodRenderSourceProvider.java index 6fda6d7b2..96e5e829a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/ILodRenderSourceProvider.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/ILodRenderSourceProvider.java @@ -22,7 +22,7 @@ public interface ILodRenderSourceProvider extends AutoCloseable CompletableFuture flushAndSaveAsync(); /** Returns true if the data was refreshed, false otherwise */ - boolean refreshRenderSource(ColumnRenderSource source); + //boolean refreshRenderSource(ColumnRenderSource source); /** Deletes any data stored in the render cache so it can be re-created */ void deleteRenderCache(); 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 4cd17c38e..b2b979217 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 @@ -1,6 +1,8 @@ package com.seibel.distanthorizons.core.file.renderfile; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; import com.seibel.distanthorizons.core.file.metaData.AbstractMetaDataContainerFile; import com.seibel.distanthorizons.core.file.metaData.BaseMetaData; import com.seibel.distanthorizons.core.level.IDhLevel; @@ -11,18 +13,23 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderLoader; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; 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.dataStreams.DhDataInputStream; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import java.awt.*; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.ref.SoftReference; +import java.util.Random; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; -public class RenderMetaDataFile extends AbstractMetaDataContainerFile +public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements IDebugRenderable { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); @@ -32,13 +39,35 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile * When clearing, don't set to null, instead create a SoftReference containing null. * This will make null checks simpler. */ - private SoftReference cachedRenderDataSourceRef = new SoftReference<>(null); + private SoftReference cachedRenderDataSource = new SoftReference<>(null); + private final AtomicReference> renderSourceLoadFutureRef = new AtomicReference<>(null); 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, 0, 256, 0.05f, c)); + } + //=============// // constructor // //=============// @@ -57,6 +86,7 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile this.fileHandler = fileHandler; LodUtil.assertTrue(this.baseMetaData == null); this.doesFileExist = this.file.exists(); + DebugRenderer.register(this); } /** @@ -67,6 +97,7 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile { return new RenderMetaDataFile(fileHandler, path); } + private RenderMetaDataFile(RenderSourceFileHandler fileHandler, File path) throws IOException { super(path); @@ -74,10 +105,10 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile LodUtil.assertTrue(this.baseMetaData != null); this.doesFileExist = this.file.exists(); + + DebugRenderer.register(this); } - - // FIXME: This can cause concurrent modification of LodRenderSource. // Not sure if it will cause issues or not. public void updateChunkIfSourceExists(ChunkSizedFullDataAccessor chunkDataView, IDhClientLevel level) @@ -86,19 +117,23 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile 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 readSourceFuture = this.getCachedDataSourceAsync(); - if (readSourceFuture != null) - { - if (chunkDataView.getLodPos().detailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) + CompletableFuture renderSourceLoadFuture = getCachedDataSourceAsync(); + if (renderSourceLoadFuture == null) return; + +/* renderSourceLoadFuture.thenAccept((renderSource) -> { + boolean worked = renderSource.fastWrite(chunkDataView, level); + + if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) { + float offset = new Random(System.nanoTime() ^ Thread.currentThread().getId()).nextFloat() * 16f; + Color c = worked ? Color.blue : Color.red; DebugRenderer.makeParticle( new DebugRenderer.BoxParticle( - new DebugRenderer.Box(chunkDataView.getLodPos(), 0, 256f, 0.05f, Color.blue), - 0.5, 512f + new DebugRenderer.Box(chunkDataView.getLodPos(), 0, 64f + offset, 0.07f, c), + 2.0, 16f ) ); - readSourceFuture.thenAccept((renderSource) -> renderSource.fastWrite(chunkDataView, level)); - } - + } + });*/ } public CompletableFuture flushAndSave(ExecutorService renderCacheThread) @@ -107,51 +142,78 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile { return CompletableFuture.completedFuture(null); // No need to save if the file doesn't exist. } - - CompletableFuture source = this.getCachedDataSourceAsync(); + CompletableFuture source = getCachedDataSourceAsync(); if (source == null) { return CompletableFuture.completedFuture(null); // If there is no cached data, there is no need to save. } - return source.thenAccept((columnRenderSource) -> { }); // Otherwise, wait for the data to be read (which also flushes changes to the file). } + private CacheQueryResult getOrStartCachedDataSourceAsync() + { + // use the existing future + CompletableFuture renderSourceLoadFuture = getCachedDataSourceAsync(); + if (renderSourceLoadFuture == null) { + // 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) { + return new CacheQueryResult(newFuture, true); + } else { + return new CacheQueryResult(cas, false); + } + } + else { + return new CacheQueryResult(renderSourceLoadFuture, false); + } + } + + @Nullable private CompletableFuture getCachedDataSourceAsync() { - // attempt to get the cached data source - ColumnRenderSource cachedRenderDataSource = this.cachedRenderDataSourceRef.get(); - if (cachedRenderDataSource != null) - { - return this.fileHandler.onReadRenderSourceLoadedFromCacheAsync(this, cachedRenderDataSource) - // wait for the handler to finish before returning the renderSource - .handle((voidObj, ex) -> cachedRenderDataSource); + // use the existing future + CompletableFuture renderSourceLoadFuture = renderSourceLoadFutureRef.get(); + if (renderSourceLoadFuture != null) { + return renderSourceLoadFuture; + } + // attempt to get the cached render source + ColumnRenderSource cachedRenderDataSource = this.cachedRenderDataSource.get(); + if (cachedRenderDataSource == null) { + return null; + } + else { + // 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) { + this.fileHandler.onReadRenderSourceLoadedFromCacheAsync(this, cachedRenderDataSource) + // wait for the handler to finish before returning the renderSource + .handle((voidObj, ex) -> { + newFuture.complete(cachedRenderDataSource); + renderSourceLoadFutureRef.set(null); + return null; + }); + return newFuture; + } + else { + return cas; + } } - - - - // the data source hasn't been loaded - // and isn't in the process of being loaded - return null; } public CompletableFuture loadOrGetCachedDataSourceAsync(Executor fileReaderThreads, IDhLevel level) { - CompletableFuture getCachedFuture = this.getCachedDataSourceAsync(); - if (getCachedFuture != null) + CacheQueryResult getCachedFuture = this.getOrStartCachedDataSourceAsync(); + if (!getCachedFuture.needsLoad) { - return getCachedFuture; + return getCachedFuture.future; } - - - - // Create an empty and non-completed future. - // Note: I do this before actually filling in the future so that I can ensure only - // one task is submitted to the thread pool. - CompletableFuture loadRenderSourceFuture = new CompletableFuture<>(); + + CompletableFuture future = getCachedFuture.future; + // load or create the render source if (!this.doesFileExist) { // create a new Meta file - this.fileHandler.onCreateRenderFileAsync(this) .thenApply((renderSource) -> { @@ -164,13 +226,15 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile if (ex != null) { LOGGER.error("Uncaught error on creation {}: ", this.file, ex); - loadRenderSourceFuture.complete(null); - this.cachedRenderDataSourceRef = new SoftReference<>(null); + cachedRenderDataSource = new SoftReference<>(null); + renderSourceLoadFutureRef.set(null); + future.complete(null); } else { - loadRenderSourceFuture.complete(renderSource); - this.cachedRenderDataSourceRef = new SoftReference<>(renderSource); + cachedRenderDataSource = new SoftReference<>(renderSource); + renderSourceLoadFutureRef.set(null); + future.complete(renderSource); } }); } @@ -198,25 +262,24 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile renderSource = this.fileHandler.onRenderFileLoaded(renderSource, this); return renderSource; }, fileReaderThreads) - .whenComplete((renderSource, ex) -> + .whenComplete((renderSource, ex) -> { if (ex != null) { LOGGER.error("Error loading file {}: ", this.file, ex); - loadRenderSourceFuture.complete(null); - this.cachedRenderDataSourceRef = new SoftReference<>(null); + cachedRenderDataSource = new SoftReference<>(null); + renderSourceLoadFutureRef.set(null); + future.complete(null); } else { - loadRenderSourceFuture.complete(renderSource); - this.cachedRenderDataSourceRef = new SoftReference<>(renderSource); + cachedRenderDataSource = new SoftReference<>(renderSource); + renderSourceLoadFutureRef.set(null); + future.complete(renderSource); } }); } - - - - return loadRenderSourceFuture; + return future; } private BaseMetaData makeMetaData(ColumnRenderSource renderSource) 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 5bb8729cd..a435a7379 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 @@ -311,11 +311,6 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider private CompletableFuture updateCacheAsync(ColumnRenderSource renderSource, RenderMetaDataFile file) { - if (this.cacheUpdateLockBySectionPos.putIfAbsent(file.pos, new Object()) != null) - { - return CompletableFuture.completedFuture(null); - } - // get the full data source loading future CompletableFuture fullDataSourceFuture = this.fullDataSourceProvider.read(renderSource.getSectionPos()); fullDataSourceFuture = fullDataSourceFuture.thenApply((fullDataSource) -> @@ -327,8 +322,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider LOGGER.error("Exception when getting data for updateCache()", ex); return null; }); - - + // future returned CompletableFuture transformationCompleteFuture = new CompletableFuture<>(); @@ -362,12 +356,8 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider LOGGER.error("Exception when updating render file using data source: ", ex); } } - transformationCompleteFuture.complete(null); - }) - .thenRun(() -> this.cacheUpdateLockBySectionPos.remove(file.pos)); - - + }); return transformationCompleteFuture; } @@ -378,7 +368,9 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider return renderSource; } - public CompletableFuture onReadRenderSourceLoadedFromCacheAsync(RenderMetaDataFile file, ColumnRenderSource data) { return this.updateCacheAsync(data, file); } + public CompletableFuture onReadRenderSourceLoadedFromCacheAsync(RenderMetaDataFile file, ColumnRenderSource data) { + return this.updateCacheAsync(data, file); + } private void writeRenderSourceToFile(ColumnRenderSource currentRenderSource, RenderMetaDataFile file, ColumnRenderSource newRenderSource) { @@ -388,6 +380,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider } currentRenderSource.updateFromRenderSource(newRenderSource); + currentRenderSource.localVersion.incrementAndGet(); //file.metaData.dataVersion.set(newDataVersion); file.baseMetaData.dataLevel = currentRenderSource.getDataDetail(); @@ -395,7 +388,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider file.baseMetaData.binaryDataFormatVersion = currentRenderSource.getRenderDataFormatVersion(); file.save(currentRenderSource); } - +/* public boolean refreshRenderSource(ColumnRenderSource renderSource) { RenderMetaDataFile file = this.filesBySectionPos.get(renderSource.getSectionPos()); @@ -418,7 +411,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider // return false; } - + */ //=====================// // clearing / shutdown // diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index 2aa8bb43d..45707d3f9 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -621,12 +621,13 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable @Override public void debugRender(DebugRenderer r) { + if (true) return; CheckingTasks.forEach((t) -> { DhLodPos pos = t.pos; - r.renderBox(new DebugRenderer.Box(pos, -32f, 128f, 0.05f, Color.blue)); + r.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.blue)); }); this.inProgressGenTasksByLodPos.forEach((pos, t) -> { - r.renderBox(new DebugRenderer.Box(pos, -32f, 128f, 0.05f, Color.red)); + r.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.red)); }); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java index 669a82504..626ce1b44 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java @@ -1,5 +1,7 @@ package com.seibel.distanthorizons.core.level; +import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering; +import com.seibel.distanthorizons.api.enums.rendering.ERendererMode; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; @@ -42,6 +44,8 @@ public class ClientLevelModule { // tick methods // //==============// + private EDebugRendering lastDebugRendering = EDebugRendering.OFF; + public void clientTick() { ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); @@ -69,6 +73,18 @@ public class ClientLevelModule { } } clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); + + boolean isBuffersDirty = false; + EDebugRendering newDebugRendering = Config.Client.Advanced.Debugging.debugRendering.get(); + if (newDebugRendering != lastDebugRendering) + { + lastDebugRendering = newDebugRendering; + isBuffersDirty = true; + } + if (isBuffersDirty) { + clientRenderState.renderer.bufferHandler.MarkAllBuffersDirty(); + } + clientRenderState.renderer.bufferHandler.updateQuadTreeRenderSources(); } @@ -236,8 +252,6 @@ public class ClientLevelModule { public final RenderSourceFileHandler renderSourceFileHandler; public final LodRenderer renderer; - - public ClientRenderState(IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider, AbstractSaveStructure saveStructure) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java index 0d9f20703..8587e1feb 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java @@ -176,7 +176,7 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) DebugRenderer.makeParticle( new DebugRenderer.BoxParticle( - new DebugRenderer.Box(pos, 0, 256f, 0.05f, Color.red), + new DebugRenderer.Box(pos, 0, 256f, 0.09f, Color.red), 0.5, 512f ) ); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java index 4983cac60..275dfd660 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java @@ -145,7 +145,7 @@ public class LodQuadTree extends QuadTree implements AutoClose DhSectionPos rootPos = rootPosIterator.next(); if (this.getNode(rootPos) == null) { - this.setValue(rootPos, new LodRenderSection(rootPos)); + this.setValue(rootPos, new LodRenderSection(this, rootPos)); } QuadNode rootNode = this.getNode(rootPos); @@ -162,7 +162,7 @@ public class LodQuadTree extends QuadTree implements AutoClose // make sure the node is created if (quadNode == null && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance { - rootNode.setValue(sectionPos, new LodRenderSection(sectionPos)); + rootNode.setValue(sectionPos, new LodRenderSection(this, sectionPos)); quadNode = rootNode.getNode(sectionPos); } if (quadNode == null) @@ -176,7 +176,7 @@ public class LodQuadTree extends QuadTree implements AutoClose // create a new render section if missing if (renderSection == null) { - LodRenderSection newRenderSection = new LodRenderSection(sectionPos); + LodRenderSection newRenderSection = new LodRenderSection(this, sectionPos); rootNode.setValue(sectionPos, newRenderSection); renderSection = newRenderSection; 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 5d057bd45..6feb3c4d1 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 @@ -48,6 +48,8 @@ public class LodRenderSection implements IDebugRenderable //FIXME: Temp Hack to prevent swapping buffers too quickly private long lastNs = -1; + private long lastSwapLocalVersion = -1; + private boolean neighborUpdated = false; /** 2 sec */ private static final long SWAP_TIMEOUT_IN_NS = 2_000000000L; /** 1 sec */ @@ -58,10 +60,12 @@ public class LodRenderSection implements IDebugRenderable /** a reference is used so the render buffer can be swapped to and from the buffer builder */ public final AtomicReference activeRenderBufferRef = new AtomicReference<>(); + + private final QuadTree parentQuadTree; - - public LodRenderSection(DhSectionPos pos) { + public LodRenderSection(QuadTree parentQuadTree, DhSectionPos pos) { this.pos = pos; + this.parentQuadTree = parentQuadTree; DebugRenderer.register(this); } @@ -108,6 +112,7 @@ public class LodRenderSection implements IDebugRenderable this.renderSourceLoadFuture = null; this.renderSource = renderSource; this.lastNs = -1; + markBufferDirty(); if (this.reloadRenderSourceOnceLoaded) { this.reloadRenderSourceOnceLoaded = false; @@ -138,7 +143,7 @@ public class LodRenderSection implements IDebugRenderable if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) DebugRenderer.makeParticle( new DebugRenderer.BoxParticle( - new DebugRenderer.Box(pos, 0, 256f, 0.05f, Color.cyan), + new DebugRenderer.Box(pos, 0, 256f, 0.03f, Color.cyan), 0.5, 512f ) ); @@ -205,20 +210,44 @@ public class LodRenderSection implements IDebugRenderable } } - private boolean isInBuildBufferTimeout() { - if (this.lastNs == -1) return false; - //return true; - - boolean inTimeout = System.nanoTime() - this.lastNs < SWAP_TIMEOUT_IN_NS; + private boolean isBufferOutdated() { + //if (this.lastNs == -1) return false; +/* 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 true; + }*/ + return neighborUpdated || renderSource.localVersion.get() - lastSwapLocalVersion > 0; + } + + private LodRenderSection[] getNeighbors() + { + LodRenderSection[] adjacents = new LodRenderSection[ELodDirection.ADJ_DIRECTIONS.length]; + for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS) { + try { + DhSectionPos adjPos = pos.getAdjacentPos(direction); + LodRenderSection adjRenderSection = parentQuadTree.getValue(adjPos); + // adjacent render sources might be null + adjacents[direction.ordinal() - 2] = adjRenderSection; + } catch (IndexOutOfBoundsException e) { + // adjacent positions can be out of bounds, in that case a null render source will be used + } + } + return adjacents; + } + + private void tellNeighborsUpdated() + { + LodRenderSection[] adjacents = getNeighbors(); + for (LodRenderSection adj : adjacents) { + if (adj != null) { + adj.neighborUpdated = 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(); } + public boolean canBuildBuffer() { return this.renderSource != null && this.buildRenderBufferFuture == null && !this.renderSource.isEmpty() && isBufferOutdated(); } /** @return true if this section is loaded and set to render */ public boolean canSwapBuffer() { return this.buildRenderBufferFuture != null && this.buildRenderBufferFuture.isDone(); } @@ -230,40 +259,46 @@ public class LodRenderSection implements IDebugRenderable * 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) + public boolean tryBuildAndSwapBuffer() { boolean didSwapped = false; if (canBuildBuffer()) { + //if (false) if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) DebugRenderer.makeParticle( new DebugRenderer.BoxParticle( - new DebugRenderer.Box(pos, 0, 256f, 0.1f, Color.yellow), - 0.8, 512f + new DebugRenderer.Box(pos, 0, 256f, -0.1f, Color.yellow), + 1.0, 512f ) ); - 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 + neighborUpdated = false; + long newVs = renderSource.localVersion.get(); + if (lastSwapLocalVersion != newVs) { + lastSwapLocalVersion = newVs; + tellNeighborsUpdated(); + } + LodRenderSection[] adjacents = getNeighbors(); + ColumnRenderSource[] adjacentSources = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length]; + for (int i = 0; i < ELodDirection.ADJ_DIRECTIONS.length; i++) { + LodRenderSection adj = adjacents[i]; + if (adj != null) { + adjacentSources[i] = adj.getRenderSource(); } } - this.buildRenderBufferFuture = - ColumnRenderBufferBuilder.buildBuffers(level, this.inactiveRenderBufferRef, renderSource, adjacentSources); + 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; + if (newBuffer == null) { + // failed. + markBufferDirty(); + return false; + } + LodUtil.assertTrue(newBuffer.buffersUploaded, "The buffer future for "+pos+" returned an un-built buffer."); ColumnRenderBuffer oldBuffer = this.activeRenderBufferRef.getAndSet(newBuffer); if (oldBuffer != null) { @@ -324,4 +359,9 @@ public class LodRenderSection implements IDebugRenderable buffer.close(); } } + + public void markBufferDirty() { + tellNeighborsUpdated(); + lastSwapLocalVersion = -1; + } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java index c7290243f..595cb53d7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java @@ -165,9 +165,19 @@ public class RenderBufferHandler //TODO: Directional culling this.loadedNearToFarBuffers.forEach(loadedBuffer -> loadedBuffer.buffer.renderTransparent(renderContext)); } - + + private boolean rebuildAllBuffers = false; + + public void MarkAllBuffersDirty() + { + this.rebuildAllBuffers = true; + } + public void updateQuadTreeRenderSources() { + boolean rebuildAllBuffers = this.rebuildAllBuffers; + this.rebuildAllBuffers = false; + Iterator> nodeIterator = this.lodQuadTree.nodeIterator(); while (nodeIterator.hasNext()) { @@ -175,7 +185,11 @@ public class RenderBufferHandler try { if (renderSection != null) { - renderSection.tryBuildAndSwapBuffer(lodQuadTree); + if (rebuildAllBuffers) + { + renderSection.markBufferDirty(); + } + renderSection.tryBuildAndSwapBuffer(); } } catch (Exception e)