From 1b59a269e6aaaeb1a6fbbf1ed5c33f542f2f06a3 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 7 Oct 2024 19:45:28 -0500 Subject: [PATCH] Update the API to allow for N-sized world generation requests This breaks old world generators --- .../EDhApiWorldGeneratorReturnType.java | 20 ++- .../AbstractDhApiChunkWorldGenerator.java | 28 ++-- .../worldGenerator/IDhApiWorldGenerator.java | 116 +++++++-------- .../objects/data/IDhApiFullDataSource.java | 45 ++++++ .../fullData/sources/FullDataSourceV2.java | 71 +++++++++- .../transformers/LodDataBuilder.java | 89 +++++++----- .../DelayedFullDataSourceSaveCache.java | 10 +- .../GeneratedFullDataSourceProvider.java | 18 ++- .../core/generation/BatchGenerator.java | 11 +- .../core/generation/WorldGenerationQueue.java | 134 ++++++------------ .../objects/TestWorldGenerator.java | 10 +- 11 files changed, 325 insertions(+), 227 deletions(-) create mode 100644 api/src/main/java/com/seibel/distanthorizons/api/objects/data/IDhApiFullDataSource.java diff --git a/api/src/main/java/com/seibel/distanthorizons/api/enums/worldGeneration/EDhApiWorldGeneratorReturnType.java b/api/src/main/java/com/seibel/distanthorizons/api/enums/worldGeneration/EDhApiWorldGeneratorReturnType.java index b77dd2ea3..c7d68c795 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/enums/worldGeneration/EDhApiWorldGeneratorReturnType.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/enums/worldGeneration/EDhApiWorldGeneratorReturnType.java @@ -19,8 +19,8 @@ package com.seibel.distanthorizons.api.enums.worldGeneration; -import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode; import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; +import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource; import java.util.concurrent.ExecutorService; import java.util.function.Consumer; @@ -28,16 +28,17 @@ import java.util.function.Consumer; /** * VANILLA_CHUNKS,
* API_CHUNKS
+ * API_DATA_SOURCES
* * @author Builderb0y, James Seibel - * @version 2023-12-21 + * @version 2024-10-5 * @since API 2.0.0 */ public enum EDhApiWorldGeneratorReturnType { /** * when this constant is returned by {@link IDhApiWorldGenerator#getReturnType()}, - * {@link IDhApiWorldGenerator#generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} + * {@link IDhApiWorldGenerator#generateChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} * will be used when generating terrain. * * @since API 2.0.0 @@ -46,11 +47,20 @@ public enum EDhApiWorldGeneratorReturnType /** * when this constant is returned by {@link IDhApiWorldGenerator#getReturnType()}, - * {@link IDhApiWorldGenerator#generateApiChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} + * {@link IDhApiWorldGenerator#generateApiChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} * will be used when generating terrain. * * @since API 2.0.0 */ - API_CHUNKS; + API_CHUNKS, + + /** + * when this constant is returned by {@link IDhApiWorldGenerator#getReturnType()}, + * {@link IDhApiWorldGenerator#generateLod(int, int, byte, IDhApiFullDataSource, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} + * will be used when generating terrain. + * + * @since API 4.0.0 + */ + API_DATA_SOURCES; } diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/AbstractDhApiChunkWorldGenerator.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/AbstractDhApiChunkWorldGenerator.java index 09e71d706..2070dcc79 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/AbstractDhApiChunkWorldGenerator.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/AbstractDhApiChunkWorldGenerator.java @@ -46,10 +46,6 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh public final byte getSmallestDataDetailLevel() { return EDhApiDetailLevel.BLOCK.detailLevel; } @Override public final byte getLargestDataDetailLevel() { return EDhApiDetailLevel.BLOCK.detailLevel; } - @Override - public final byte getMinGenerationGranularity() { return EDhApiDetailLevel.CHUNK.detailLevel; } - @Override - public final byte getMaxGenerationGranularity() { return (byte) (EDhApiDetailLevel.CHUNK.detailLevel + 2); } @@ -60,17 +56,14 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh @Override public final CompletableFuture generateChunks( int chunkPosMinX, int chunkPosMinZ, - byte granularity, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, + int generationRequestChunkWidthCount, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, ExecutorService worldGeneratorThreadPool, Consumer resultConsumer) throws ClassCastException { return CompletableFuture.runAsync(() -> { - // TODO what does this mean? - int genChunkWidth = BitShiftUtil.powerOfTwo(granularity - 4); - - for (int chunkX = chunkPosMinX; chunkX < chunkPosMinX + genChunkWidth; chunkX++) + for (int chunkX = chunkPosMinX; chunkX < chunkPosMinX + generationRequestChunkWidthCount; chunkX++) { - for (int chunkZ = chunkPosMinZ; chunkZ < chunkPosMinZ + genChunkWidth; chunkZ++) + for (int chunkZ = chunkPosMinZ; chunkZ < chunkPosMinZ + generationRequestChunkWidthCount; chunkZ++) { Object[] rawMcObjectArray = this.generateChunk(chunkX, chunkZ, generatorMode); resultConsumer.accept(rawMcObjectArray); @@ -83,7 +76,7 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh public final CompletableFuture generateApiChunks( int chunkPosMinX, int chunkPosMinZ, - byte granularity, + int generationRequestChunkWidthCount, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, ExecutorService worldGeneratorThreadPool, @@ -92,12 +85,9 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh { return CompletableFuture.runAsync(() -> { - // TODO what does this mean? - int genChunkWidth = BitShiftUtil.powerOfTwo(granularity - 4); - - for (int chunkX = chunkPosMinX; chunkX < chunkPosMinX + genChunkWidth; chunkX++) + for (int chunkX = chunkPosMinX; chunkX < chunkPosMinX + generationRequestChunkWidthCount; chunkX++) { - for (int chunkZ = chunkPosMinZ; chunkZ < chunkPosMinZ + genChunkWidth; chunkZ++) + for (int chunkZ = chunkPosMinZ; chunkZ < chunkPosMinZ + generationRequestChunkWidthCount; chunkZ++) { DhApiChunk apiChunk = this.generateApiChunk(chunkX, chunkZ, generatorMode); resultConsumer.accept(apiChunk); @@ -115,10 +105,10 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh * @param chunkPosZ the chunk Z position in the level (not to be confused with the chunk's BlockPos in the level) * @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation. * - * @return See {@link IDhApiWorldGenerator#generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator.generateChunks} + * @return See {@link IDhApiWorldGenerator#generateChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator.generateChunks} * for the list of Object's this method should return along with additional documentation. * - * @see IDhApiWorldGenerator#generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator#generateChunks + * @see IDhApiWorldGenerator#generateChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator#generateChunks */ public abstract Object[] generateChunk(int chunkPosX, int chunkPosZ, EDhApiDistantGeneratorMode generatorMode); @@ -133,7 +123,7 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh * @return A {@link DhApiChunk} with the generated {@link DhApiTerrainDataPoint} including air blocks. * Note: if air blocks aren't included with the proper lighting, lower detail levels will appear as black/unlit. * - * @see IDhApiWorldGenerator#generateApiChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) + * @see IDhApiWorldGenerator#generateApiChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) * * @since API 3.0.0 */ diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/IDhApiWorldGenerator.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/IDhApiWorldGenerator.java index 4419b8686..438ed2bba 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/IDhApiWorldGenerator.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/IDhApiWorldGenerator.java @@ -24,6 +24,7 @@ import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable; import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode; import com.seibel.distanthorizons.api.objects.data.DhApiChunk; +import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource; import java.io.Closeable; import java.util.concurrent.CompletableFuture; @@ -32,7 +33,7 @@ import java.util.function.Consumer; /** * @author James Seibel - * @version 2023-6-22 + * @version 2024-10-07 * @since API 1.0.0 */ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable @@ -43,19 +44,17 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable /** * Defines the smallest datapoint size that can be generated at a time.
- * Minimum detail level is 0 (1 block)
+ * Maximum detail level (smallest numerical value) is 0 (1 block)
* Default detail level is 0
* For more information on what detail levels represent see: {@link EDhApiDetailLevel}.

- * - * TODO: System currently only supports 1x1 block per data. - * + * * @see EDhApiDetailLevel * @since API 1.0.0 */ default byte getSmallestDataDetailLevel() { return EDhApiDetailLevel.BLOCK.detailLevel; } /** * Defines the largest datapoint size that can be generated at a time.
- * Minimum detail level is 0 (1 block)
+ * Maximum detail level (smallest numerical value) is 0 (1 block)
* Default detail level is 0
* For more information on what detail levels represent see: {@link EDhApiDetailLevel}. * @@ -64,56 +63,18 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable */ default byte getLargestDataDetailLevel() { return EDhApiDetailLevel.BLOCK.detailLevel; } - /** - * When creating generation requests the system will attempt to group nearby tasks together.

- * What is the minimum size a single generation call can batch together?
- * - * Minimum detail level is 4 (the size of a MC chunk)
- * Default detail level is 4
- * For more information on what detail levels represent see: {@link EDhApiDetailLevel}. - * - * @see EDhApiDetailLevel - * @since API 1.0.0 - */ - default byte getMinGenerationGranularity() { return EDhApiDetailLevel.CHUNK.detailLevel; } - - /** - * When creating generation requests the system will attempt to group nearby tasks together.

- * What is the maximum size a single generation call can batch together?
- * - * Minimum detail level is 4 (the size of a MC chunk)
- * Default detail level is 6 (4x4 chunks)
- * For more information on what detail levels represent see: {@link EDhApiDetailLevel}. - * - * @see EDhApiDetailLevel - * @since API 1.0.0 - */ - default byte getMaxGenerationGranularity() { return (byte) (EDhApiDetailLevel.CHUNK.detailLevel + 2); } - - /** - * Starting in API 3.0.0 DH now handles future queuing/management internally.

- * - * Previous description:
- * true if the generator is unable to accept new generation requests.
- * - * @since API 1.0.0 - * @deprecated API 3.0.0 - */ - @Deprecated - default boolean isBusy() { return false; } - /** - * Only used if {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.
- * If true DH will run additional validation on the {@link DhApiChunk}'s returned.
+ * Used if {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS} or {@link EDhApiWorldGeneratorReturnType#API_DATA_SOURCES}.
+ * If true DH will run additional validation on the {@link DhApiChunk} or {@link IDhApiFullDataSource}'s returned.
* This should be disabled during release but should be enabled during development to help spot issues with your data format. * * @see #getReturnType() * @see DhApiChunk + * @see IDhApiFullDataSource * @see EDhApiWorldGeneratorReturnType#API_CHUNKS - * @since API 3.0.0 + * @since API 4.0.0 */ - default boolean runApiChunkValidation() { return true; } - + default boolean runApiValidation() { return true; } @@ -143,9 +104,9 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable * * @param chunkPosMinX the chunk X position closest to negative infinity * @param chunkPosMinZ the chunk Z position closest to negative infinity - * @param granularity TODO find a central location to store the definition of granularity. For now it is stored in the Core method: WorldGenerationQueue#startGenerationEvent + * @param generationRequestChunkWidthCount how many chunks wide you should generate * @param targetDataDetail the LOD Detail level requested to generate. See {@link EDhApiDetailLevel} for additional information. - * @param generatorMode how far into the world gen pipeline this method run. See {@link EDhApiDistantGeneratorMode} for additional documentation. + * @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation. * @param worldGeneratorThreadPool the thread pool that should be used when generating the returned {@link CompletableFuture}. * @param resultConsumer the consumer that should be fired whenever a chunk finishes generating. * @@ -156,7 +117,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable default CompletableFuture generateChunks( int chunkPosMinX, int chunkPosMinZ, - byte granularity, + int generationRequestChunkWidthCount, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, ExecutorService worldGeneratorThreadPool, @@ -165,7 +126,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable { throw new UnsupportedOperationException(); } - + /** * This method is called by Distant Horizons to generate terrain over a given area when * {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.

@@ -179,9 +140,9 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable * * @param chunkPosMinX the chunk X position closest to negative infinity * @param chunkPosMinZ the chunk Z position closest to negative infinity - * @param granularity TODO find a central location to store the definition of granularity. For now it is stored in the Core method: WorldGenerationQueue#startGenerationEvent + * @param generationRequestChunkWidthCount how many chunks wide you should generate * @param targetDataDetail the LOD Detail level requested to generate. See {@link EDhApiDetailLevel} for additional information. - * @param generatorMode how far into the world gen pipeline this method run. See {@link EDhApiDistantGeneratorMode} for additional documentation. + * @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation. * @param worldGeneratorThreadPool the thread pool that should be used when generating the returned {@link CompletableFuture}. * @param resultConsumer the consumer that should be fired whenever a chunk finishes generating. * @@ -192,7 +153,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable default CompletableFuture generateApiChunks( int chunkPosMinX, int chunkPosMinZ, - byte granularity, + int generationRequestChunkWidthCount, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, ExecutorService worldGeneratorThreadPool, @@ -201,11 +162,51 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable { throw new UnsupportedOperationException(); } + + /** + * This method is called by Distant Horizons to generate terrain over a given area when + * {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_DATA_SOURCES}.

+ * + * After the {@link IDhApiWorldGenerator} has been generated, it should be passed into the + * resultConsumer's {@link Consumer#accept(Object)} method. + * Note: if air blocks aren't included in the with the {@link DhApiChunk} with proper lighting, lower detail levels will appear as black/unlit. + * + * @implNote the default implementation of this method throws an {@link UnsupportedOperationException}, + * and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}. + * + * @param chunkPosMinX the chunk X position closest to negative infinity + * @param chunkPosMinZ the chunk Z position closest to negative infinity + * @param lodPosX the LOD's X position, relative to the given {@link EDhApiDetailLevel} + * @param lodPosZ the LOD's Z position, relative to the given {@link EDhApiDetailLevel} + * @param detailLevel the LOD Detail level requested to generate. See {@link EDhApiDetailLevel} for additional information. + * @param pooledFullDataSource The data source you should populate during your world generation. + * This data source is pooled by DH and may be reused multiple times by different internal DH systems.
+ * This data source should not be referenced or stored outside of this method nor the executor provided by worldGeneratorThreadPool. + * Attempting to do so will corrupt DH's data. + * @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation. + * @param worldGeneratorThreadPool the thread pool that should be used when generating the returned {@link CompletableFuture}. + * @param resultConsumer the consumer that should be fired whenever a chunk finishes generating. + * + * @return a future that should run on the worldGeneratorThreadPool and complete once the given generation task has completed. + * + * @since API 4.0.0 + */ + default CompletableFuture generateLod( + int chunkPosMinX, int chunkPosMinZ, + int lodPosX, int lodPosZ, byte detailLevel, + IDhApiFullDataSource pooledFullDataSource, + EDhApiDistantGeneratorMode generatorMode, + ExecutorService worldGeneratorThreadPool, + Consumer resultConsumer + ) + { + throw new UnsupportedOperationException(); + } /** * This method controls how Distant Horizons requests generated chunks. * By default, the return value is {@link EDhApiWorldGeneratorReturnType#VANILLA_CHUNKS}, - * which means that {@link #generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} + * which means that {@link #generateChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} * will be invoked whenever Distant Horizons wants to generate terrain with this world generator. * * @since API 2.0.0 @@ -238,4 +239,5 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable void close(); + } diff --git a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/IDhApiFullDataSource.java b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/IDhApiFullDataSource.java new file mode 100644 index 000000000..91d4fa05c --- /dev/null +++ b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/IDhApiFullDataSource.java @@ -0,0 +1,45 @@ +package com.seibel.distanthorizons.api.objects.data; + +import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; + +import java.util.List; + +/** + * Represents a single full LOD backed by Distant Horizons' ID system. + * + * @see IDhApiWorldGenerator + * @since API 4.0.0 + */ +public interface IDhApiFullDataSource +{ + /** @return how many data columns wide this data source is */ + int getWidthInDataColumns(); + + /** + * Sets the data column at the relative X and Z position to the list given. + * The given list may be resorted based on the internal format DH requires. + * + * @param relX can be in the range 0 to {@link IDhApiFullDataSource#getWidthInDataColumns()}-1 (both inclusive) + * @param relZ can be in the range 0 to {@link IDhApiFullDataSource#getWidthInDataColumns()}-1 (both inclusive) + * + * @return the same columnDataPoints list after it has been imported into the data source. + * The returned list and contained objects can then be re-used. + * + * @throws IndexOutOfBoundsException if the relative positions are negative or outside the bounds of this data source. + */ + List setApiDataPointColumn(int relX, int relZ, List columnDataPoints) + throws IndexOutOfBoundsException, IllegalArgumentException; + + /** + * @param relX can be in the range 0 to {@link IDhApiFullDataSource#getWidthInDataColumns()}-1 (both inclusive) + * @param relZ can be in the range 0 to {@link IDhApiFullDataSource#getWidthInDataColumns()}-1 (both inclusive) + * + * @return a {@link List} of {@link DhApiTerrainDataPoint} representing the data for the given relative position. + * + * @throws IndexOutOfBoundsException if the relative positions are negative or outside the bounds of this data source. + */ + List getApiDataPointColumn(int relX, int relZ) throws IndexOutOfBoundsException; + + + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java index 3b6bb11f4..88ec4119c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java @@ -21,6 +21,8 @@ package com.seibel.distanthorizons.core.dataObjects.fullData.sources; import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; +import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint; +import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource; import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder; import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler; @@ -29,6 +31,7 @@ import com.seibel.distanthorizons.core.file.IDataSource; 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.util.DhApiTerrainDataPointUtil; import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil; @@ -40,7 +43,9 @@ import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * This data source contains every datapoint over its given {@link DhSectionPos}.

@@ -48,7 +53,7 @@ import java.util.Arrays; * @see FullDataPointUtil * @see FullDataSourceV1 */ -public class FullDataSourceV2 implements IDataSource +public class FullDataSourceV2 implements IDataSource, IDhApiFullDataSource { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); /** useful for debugging, but can slow down update operations quite a bit due to being called so often. */ @@ -112,6 +117,9 @@ public class FullDataSourceV2 implements IDataSource public boolean isEmpty; public boolean applyToParent = false; + /** should only be used by methods exposed by the DH API */ + private boolean runApiChunkValidation = false; + //==============// @@ -870,6 +878,10 @@ public class FullDataSourceV2 implements IDataSource Arrays.fill(dataSource.columnGenerationSteps, (byte) 0); Arrays.fill(dataSource.columnWorldCompressionMode, (byte) 0); } + + // default to API validation disabled so it's opt-in + // to reduce the chance of performance loss with unnecessary validation + dataSource.setRunApiChunkValidation(false); } @@ -916,6 +928,59 @@ public class FullDataSourceV2 implements IDataSource + //=============// + // API methods // + //=============// + + public void setRunApiChunkValidation(boolean runValidation) { this.runApiChunkValidation = runValidation; } + + @Override + public int getWidthInDataColumns() { return WIDTH; } + + @Override + public List setApiDataPointColumn(int relX, int relZ, List columnDataPoints) + throws IndexOutOfBoundsException, IllegalArgumentException + { + try + { + LodDataBuilder.correctDataColumnOrder(columnDataPoints); + if (this.runApiChunkValidation) + { + LodDataBuilder.validateOrThrowApiDataColumn(columnDataPoints); + } + + LongArrayList packedDataPoints = LodDataBuilder.convertApiDataPointListToPackedLongArray(columnDataPoints, this, 0); + + // TODO there should be an "unknown" compression and generation step, or be defined via the datapoints + this.setSingleColumn(packedDataPoints, relX, relZ, EDhApiWorldGenerationStep.LIGHT, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS); + + return columnDataPoints; + } + catch (DataCorruptedException e) + { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + @Override + public List getApiDataPointColumn(int relX, int relZ) throws IndexOutOfBoundsException + { + LongArrayList dataColumn = this.get(relX, relZ); + + ArrayList apiList = new ArrayList<>(); + for (int i = 0; i < dataColumn.size(); i++) + { + long datapoint = dataColumn.getLong(i); + + DhApiTerrainDataPoint apiDataPoint = DhApiTerrainDataPointUtil.createApiDatapoint(this.levelMinY, this.mapping, DhSectionPos.getDetailLevel(this.pos), datapoint); + apiList.add(apiDataPoint); + } + + return apiList; + } + + + //================// // base overrides // //================// @@ -967,8 +1032,6 @@ public class FullDataSourceV2 implements IDataSource @Override public void close() throws Exception - { - DATA_SOURCE_POOL.returnPooledDataSource(this); - } + { DATA_SOURCE_POOL.returnPooledDataSource(this); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java index 203f92837..c8b8d65e1 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java @@ -45,6 +45,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import it.unimi.dsi.fastutil.longs.LongArrayList; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; public class LodDataBuilder { @@ -70,7 +71,7 @@ public class LodDataBuilder int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ()); long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ); - FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos); + FullDataSourceV2 dataSource = FullDataSourceV2.DATA_SOURCE_POOL.getPooledSource(pos); dataSource.isEmpty = false; @@ -311,44 +312,19 @@ public class LodDataBuilder int relSourceBlockX = Math.floorMod(apiChunk.chunkPosX, 4) * LodUtil.CHUNK_WIDTH; int relSourceBlockZ = Math.floorMod(apiChunk.chunkPosZ, 4) * LodUtil.CHUNK_WIDTH; - FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos); + FullDataSourceV2 dataSource = FullDataSourceV2.DATA_SOURCE_POOL.getPooledSource(pos); for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++) { for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++) { List columnDataPoints = apiChunk.getDataPoints(relBlockX, relBlockZ); + LodDataBuilder.correctDataColumnOrder(columnDataPoints); if (runAdditionalValidation) { - validateOrThrowDataColumn(columnDataPoints); + validateOrThrowApiDataColumn(columnDataPoints); } - - // this null check does 2 nice things at the same time: - // if columnDataPoints is null, - // then packedDataPoints will be of length 0 - // AND the below loop won't run. - int size = (columnDataPoints != null) ? columnDataPoints.size() : 0; - - // TODO make missing air LODs - // TODO merge duplicate datapoints - LongArrayList packedDataPoints = new LongArrayList(new long[size]); - for (int index = 0; index < size; index++) - { - DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index); - - int id = dataSource.mapping.addIfNotPresentAndGetId( - (IBiomeWrapper) (dataPoint.biomeWrapper), - (IBlockStateWrapper) (dataPoint.blockStateWrapper) - ); - - packedDataPoints.set(index, FullDataPointUtil.encode( - id, - dataPoint.topYBlockPos - dataPoint.bottomYBlockPos, - dataPoint.bottomYBlockPos - apiChunk.bottomYBlockPos, - (byte) (dataPoint.blockLightLevel), - (byte) (dataPoint.skyLightLevel) - )); - } + LongArrayList packedDataPoints = convertApiDataPointListToPackedLongArray(columnDataPoints, dataSource, apiChunk.bottomYBlockPos); // TODO add the ability for API users to define a different compression mode // or add a "unkown" compression mode @@ -361,7 +337,50 @@ public class LodDataBuilder } return dataSource; } - private static void validateOrThrowDataColumn(List dataPoints) throws IllegalArgumentException + + + + //================// + // public helpers // + //================// + + /** @see FullDataPointUtil */ + public static LongArrayList convertApiDataPointListToPackedLongArray( + @Nullable List columnDataPoints, FullDataSourceV2 dataSource, + int bottomYBlockPos) throws DataCorruptedException + { + // this null check does 2 nice things at the same time: + // if columnDataPoints is null, + // then packedDataPoints will be of length 0 + // AND the below loop won't run. + int size = (columnDataPoints != null) ? columnDataPoints.size() : 0; + + // TODO make missing air LODs + // TODO merge duplicate datapoints + LongArrayList packedDataPoints = new LongArrayList(new long[size]); + for (int index = 0; index < size; index++) + { + DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index); + + int id = dataSource.mapping.addIfNotPresentAndGetId( + (IBiomeWrapper) (dataPoint.biomeWrapper), + (IBlockStateWrapper) (dataPoint.blockStateWrapper) + ); + + packedDataPoints.set(index, FullDataPointUtil.encode( + id, + dataPoint.topYBlockPos - dataPoint.bottomYBlockPos, + dataPoint.bottomYBlockPos - bottomYBlockPos, + (byte) (dataPoint.blockLightLevel), + (byte) (dataPoint.skyLightLevel) + )); + } + + return packedDataPoints; + } + + /** also corrects the order if it's backwards */ + public static void correctDataColumnOrder(List dataPoints) { // order doesn't need to be checked if there is 0 or 1 items if (dataPoints.size() > 1) @@ -376,9 +395,10 @@ public class LodDataBuilder } } - - - + } + + public static void validateOrThrowApiDataColumn(List dataPoints) throws IllegalArgumentException + { // check that each datapoint is valid int lastBottomYPos = Integer.MIN_VALUE; for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation @@ -432,7 +452,6 @@ public class LodDataBuilder - //================// // helper methods // //================// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/DelayedFullDataSourceSaveCache.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/DelayedFullDataSourceSaveCache.java index e2efce4c7..456b92052 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/DelayedFullDataSourceSaveCache.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/DelayedFullDataSourceSaveCache.java @@ -52,7 +52,7 @@ public class DelayedFullDataSourceSaveCache { if (temporaryDataSource == null) { - temporaryDataSource = FullDataSourceV2.createEmpty(inputPos); + temporaryDataSource = FullDataSourceV2.DATA_SOURCE_POOL.getPooledSource(inputPos); } temporaryDataSource.update(inputDataSource); @@ -104,6 +104,14 @@ public class DelayedFullDataSourceSaveCache public int getUnsavedCount() { return this.dataSourceByPosition.size(); } + public void flush() + { + this.saveTimerTasksBySectionPos.forEach((pos, timerTask)-> + { + timerTask.run(); + }); + } + //================// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java index 60784e755..ffec5aa6f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java @@ -199,6 +199,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im if (this.delayedFullDataSourceSaveCache.getUnsavedCount() >= maxQueueCount) { + // flushing since we're waiting for this timer to expire anyway + this.delayedFullDataSourceSaveCache.flush(); + // don't queue additional world gen requests if there are // a lot of data sources in memory // (this is done to prevent infinite memory growth) @@ -391,6 +394,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im // TODO may not be needed private class WorldGenTaskTracker implements IWorldGenTaskTracker { + /** just used when debugging/troubleshooting */ private final long pos; public WorldGenTaskTracker(long pos) { this.pos = pos; } @@ -413,7 +417,19 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im // allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, LodUtil.MAX_MC_LIGHT); - GeneratedFullDataSourceProvider.this.updateDataSourceAsync(fullDataSource); + GeneratedFullDataSourceProvider.this.updateDataSourceAsync(fullDataSource) + .thenRun(() -> + { + try + { + // send this datasource back to the pool to hopefully reduce GC overhead + fullDataSource.close(); + } + catch (Exception e) + { + LOGGER.error("Unexpected issue closing full data source", e); + } + }); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/BatchGenerator.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/BatchGenerator.java index 655797a2a..c82376609 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/BatchGenerator.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/BatchGenerator.java @@ -83,11 +83,6 @@ public class BatchGenerator implements IDhApiWorldGenerator @Override public byte getLargestDataDetailLevel() { return LodUtil.BLOCK_DETAIL_LEVEL; } - @Override - public byte getMinGenerationGranularity() { return LodUtil.CHUNK_DETAIL_LEVEL; } - @Override - public byte getMaxGenerationGranularity() { return LodUtil.CHUNK_DETAIL_LEVEL + 2; } - @@ -97,7 +92,7 @@ public class BatchGenerator implements IDhApiWorldGenerator @Override public CompletableFuture generateChunks( - int chunkPosMinX, int chunkPosMinZ, byte granularity, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, + int chunkPosMinX, int chunkPosMinZ, int generationRequestChunkWidthCount, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, ExecutorService worldGeneratorThreadPool, Consumer resultConsumer) { EDhApiWorldGenerationStep targetStep = null; @@ -118,13 +113,11 @@ public class BatchGenerator implements IDhApiWorldGenerator break; } - int genChunkSize = BitShiftUtil.powerOfTwo(granularity - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale - // the consumer needs to be wrapped like this because the API can't use DH core objects (and IChunkWrapper can't be easily put into the API project) Consumer consumerWrapper = (chunkWrapper) -> resultConsumer.accept(new Object[]{chunkWrapper}); try { - return this.generationEnvironment.generateChunks(chunkPosMinX, chunkPosMinZ, genChunkSize, targetStep, worldGeneratorThreadPool, consumerWrapper); + return this.generationEnvironment.generateChunks(chunkPosMinX, chunkPosMinZ, generationRequestChunkWidthCount, targetStep, worldGeneratorThreadPool, consumerWrapper); } catch (Exception e) { 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 09a6d8163..aa6a20f37 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 @@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGenerat import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGeneratorReturnType; import com.seibel.distanthorizons.api.objects.data.DhApiChunk; +import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.generation.tasks.*; @@ -43,7 +44,7 @@ import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; -import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import org.apache.logging.log4j.Logger; import java.awt.*; @@ -74,10 +75,6 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb private final ConcurrentHashMap inProgressGenTasksByLodPos = new ConcurrentHashMap<>(); - // granularity is the detail level for batching world generator requests together - public final byte maxGranularity; - public final byte minGranularity; - /** largest numerical detail level allowed */ public final byte lowestDataDetail; /** smallest numerical detail level allowed */ @@ -95,15 +92,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb private final ExecutorService queueingThread = ThreadUtil.makeSingleThreadPool("World Gen Queue"); private boolean generationQueueRunning = false; private DhBlockPos2D generationTargetPos = DhBlockPos2D.ZERO; - /** can be used for debugging how many tasks are currently in the queue */ - private int numberOfTasksQueued = 0; - - // debug variables to test for duplicate world generator requests // - /** limits how many of the previous world gen requests we should track */ - private static final int MAX_ALREADY_GENERATED_COUNT = 100; - private final HashMap alreadyGeneratedPosHashSet = new HashMap<>(MAX_ALREADY_GENERATED_COUNT); - private final LongArrayFIFOQueue alreadyGeneratedPosQueue = new LongArrayFIFOQueue(); - + /** just used for rendering to the F3 menu */ private int estimatedTotalTaskCount = 0; @@ -117,20 +106,9 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb { LOGGER.info("Creating world gen queue"); this.generator = generator; - this.maxGranularity = generator.getMaxGenerationGranularity(); - this.minGranularity = generator.getMinGenerationGranularity(); this.lowestDataDetail = generator.getLargestDataDetailLevel(); this.highestDataDetail = generator.getSmallestDataDetailLevel(); - - if (this.minGranularity < LodUtil.CHUNK_DETAIL_LEVEL) - { - throw new IllegalArgumentException(IDhApiWorldGenerator.class.getSimpleName() + ": min granularity must be at least 4 (Chunk sized)!"); - } - if (this.maxGranularity < this.minGranularity) - { - throw new IllegalArgumentException(IDhApiWorldGenerator.class.getSimpleName() + ": max granularity smaller than min granularity!"); - } DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue); LOGGER.info("Created world gen queue"); } @@ -349,44 +327,15 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb { byte taskDetailLevel = newTaskGroup.group.dataDetail; long taskPos = newTaskGroup.group.pos; - byte granularity = (byte) (DhSectionPos.getDetailLevel(taskPos) - taskDetailLevel); - LodUtil.assertTrue(granularity >= this.minGranularity && granularity <= this.maxGranularity); LodUtil.assertTrue(taskDetailLevel >= this.highestDataDetail && taskDetailLevel <= this.lowestDataDetail); - DhChunkPos chunkPosMin = new DhChunkPos(DhSectionPos.getSectionBBoxPos(taskPos).getCornerBlockPos()); - - // check if this is a duplicate generation task - if (this.alreadyGeneratedPosHashSet.containsKey(newTaskGroup.group.pos)) - { - // temporary solution to prevent generating the same section multiple times - //LOGGER.trace("Duplicate generation section " + taskPos + " with granularity [" + granularity + "] at " + chunkPosMin + ". Skipping..."); - - // sending a success result is necessary to make sure the render sections are reloaded correctly - newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(DhSectionPos.encode(granularity, DhSectionPos.getX(taskPos), DhSectionPos.getZ(taskPos))))); - return false; - } - this.alreadyGeneratedPosHashSet.put(newTaskGroup.group.pos, Thread.currentThread().getStackTrace()); - this.alreadyGeneratedPosQueue.enqueue(newTaskGroup.group.pos); - - // remove extra tracked duplicate positions - while (this.alreadyGeneratedPosQueue.size() > MAX_ALREADY_GENERATED_COUNT) - { - long posToRemove = this.alreadyGeneratedPosQueue.dequeueLong(); - this.alreadyGeneratedPosHashSet.remove(posToRemove); - } - - - //LOGGER.info("Generating section "+taskPos+" with granularity "+granularity+" at "+chunkPosMin); - - this.numberOfTasksQueued++; - newTaskGroup.genFuture = this.startGenerationEvent(chunkPosMin, taskPos, granularity, taskDetailLevel, newTaskGroup.group::consumeDataSource); + newTaskGroup.genFuture = this.startGenerationEvent(taskPos, taskDetailLevel, newTaskGroup.group::consumeDataSource); LodUtil.assertTrue(newTaskGroup.genFuture != null); newTaskGroup.genFuture.whenComplete((voidObj, exception) -> { try { - this.numberOfTasksQueued--; if (exception != null) { // don't log the shutdown exceptions @@ -399,7 +348,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb } else { - newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(DhSectionPos.encode(granularity, DhSectionPos.getX(taskPos), DhSectionPos.getZ(taskPos))))); + newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(taskPos))); } boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, newTaskGroup); LodUtil.assertTrue(worked, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]"); @@ -413,35 +362,15 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb this.inProgressGenTasksByLodPos.put(taskPos, newTaskGroup); return true; } - /** - * The chunkPos is always aligned to the granularity. - * For example: if the granularity is 4 (chunk sized) with a data detail level of 0 (block sized), - * the chunkPos will be aligned to 16x16 blocks.

- * - * - * Full Granularity definition (as of 2023-6-21):

- * - * world gen actually supports (in theory) generating stuff with a data detail that's higher than the per-block.

- * - * Granularity basically means, on a single generation task, how big such group should be, in terms of the data points it will make.

- * - * For example, a granularity of 4 means the task will generate a 16 by 16 data points. - * Now, those data points might be per block, or per 4 by 4 blocks. Granularity doesn't say what detail those would be.

- * - * Note: currently the core system sends data via the chunk sized container, - * which has the locked granularity of 4 (16 by 16 data columns), and thus generators should at least have min granularity of 4. - * (Gen chunk width in that context means how many 'chunk sized containers' it will fill up. - * Again, note that a 'chunk sized container' isn't necessary 16 by 16 Minecraft blocks wide. - * It only has to contain 16 by 16 columns of data points, in whatever data detail it might be in.) - * (So, with a generator whose only gen data detail is 0, it is the same as a MC chunk.) - */ private CompletableFuture startGenerationEvent( - DhChunkPos chunkPosMin, - byte granularity, + long requestPos, byte targetDataDetail, Consumer dataSourceConsumer ) { + DhChunkPos chunkPosMin = new DhChunkPos(DhSectionPos.getSectionBBoxPos(requestPos).getCornerBlockPos()); + int generationRequestChunkWidthCount = BitShiftUtil.powerOfTwo(DhSectionPos.getDetailLevel(requestPos) - targetDataDetail - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale + EDhApiDistantGeneratorMode generatorMode = Config.Client.Advanced.WorldGenerator.distantGeneratorMode.get(); EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType(); switch (returnType) @@ -449,9 +378,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb case VANILLA_CHUNKS: { return this.generator.generateChunks( - chunkPosMin.getX(), - chunkPosMin.getZ(), - granularity, + chunkPosMin.getX(), chunkPosMin.getZ(), + generationRequestChunkWidthCount, targetDataDetail, generatorMode, ThreadPoolUtil.getWorldGenExecutor(), @@ -475,9 +403,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb case API_CHUNKS: { return this.generator.generateApiChunks( - chunkPosMin.getX(), - chunkPosMin.getZ(), - granularity, + chunkPosMin.getX(), chunkPosMin.getZ(), + generationRequestChunkWidthCount, targetDataDetail, generatorMode, ThreadPoolUtil.getWorldGenExecutor(), @@ -485,7 +412,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb { try { - FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints, this.generator.runApiChunkValidation()); + FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints, this.generator.runApiValidation()); dataSourceConsumer.accept(dataSource); } catch (DataCorruptedException | IllegalArgumentException e) @@ -501,6 +428,39 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb } ); } + case API_DATA_SOURCES: + { + // done to reduce GC overhead + FullDataSourceV2 pooledDataSource = FullDataSourceV2.DATA_SOURCE_POOL.getPooledSource(requestPos); + // set here so the API user doesn't have to pass in this value anywhere themselves + pooledDataSource.setRunApiChunkValidation(this.generator.runApiValidation()); + + return this.generator.generateLod( + chunkPosMin.getX(), chunkPosMin.getZ(), + DhSectionPos.getX(requestPos), DhSectionPos.getZ(requestPos), + (byte) (DhSectionPos.getDetailLevel(requestPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), + pooledDataSource, + generatorMode, + ThreadPoolUtil.getWorldGenExecutor(), + (IDhApiFullDataSource dataSource) -> + { + try + { + dataSourceConsumer.accept((FullDataSourceV2)dataSource); + } + catch (IllegalArgumentException e) + { + LOGGER.error("World generator returned a corrupt data source. Error: [" + e.getMessage() + "]. World generator disabled.", e); + Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); + } + catch (ClassCastException e) + { + LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e); + Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); + } + } + ); + } default: { Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); diff --git a/core/src/test/java/testItems/worldGeneratorInjection/objects/TestWorldGenerator.java b/core/src/test/java/testItems/worldGeneratorInjection/objects/TestWorldGenerator.java index 6d73d394b..9afa6868d 100644 --- a/core/src/test/java/testItems/worldGeneratorInjection/objects/TestWorldGenerator.java +++ b/core/src/test/java/testItems/worldGeneratorInjection/objects/TestWorldGenerator.java @@ -59,11 +59,6 @@ public class TestWorldGenerator implements IDhApiWorldGenerator @Override public byte getLargestDataDetailLevel() { return LodUtil.BLOCK_DETAIL_LEVEL; } - @Override - public byte getMinGenerationGranularity() { return LodUtil.CHUNK_DETAIL_LEVEL; } - - @Override - public byte getMaxGenerationGranularity() { return LodUtil.CHUNK_DETAIL_LEVEL + 2; } //===================// @@ -74,10 +69,7 @@ public class TestWorldGenerator implements IDhApiWorldGenerator public void close() { } @Override - public boolean isBusy() { return false; } - - @Override - public CompletableFuture generateChunks(int chunkPosMinX, int chunkPosMinZ, byte granularity, byte targetDataDetail, EDhApiDistantGeneratorMode maxGenerationStep, ExecutorService executorService, Consumer resultConsumer) { return null; } + public CompletableFuture generateChunks(int chunkPosMinX, int chunkPosMinZ, int generationRequestChunkWidthCount, byte targetDataDetail, EDhApiDistantGeneratorMode maxGenerationStep, ExecutorService executorService, Consumer resultConsumer) { return null; } @Override public void preGeneratorTaskStart() { }