From a691b27b4f51595efa317ed267688be0800161fd Mon Sep 17 00:00:00 2001 From: Builderb0y Date: Fri, 22 Dec 2023 01:15:25 +0000 Subject: [PATCH 1/6] expand API to allow world generator to generate chunks of data points in addition to actual chunks. --- .../worldGenerator/IDhApiWorldGenerator.java | 106 ++++++++++++++++-- .../objects/data/DhApiChunkOfDataPoints.java | 26 +++++ .../transformers/LodDataBuilder.java | 34 ++++++ .../core/generation/WorldGenerationQueue.java | 65 ++++++++--- 4 files changed, 205 insertions(+), 26 deletions(-) create mode 100644 api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunkOfDataPoints.java 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 5ec1293f0..3812c40f7 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 @@ -22,6 +22,7 @@ package com.seibel.distanthorizons.api.interfaces.override.worldGenerator; 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.DhApiChunkOfDataPoints; import java.io.Closeable; import java.util.concurrent.CompletableFuture; @@ -102,7 +103,8 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable //=================// /** - * This method is called by Distant Horizons to generate terrain over a given area.

+ * This method is called by Distant Horizons to generate terrain over a given area when + * {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#CHUNKS}.

* * After a chunk has been generated it (and any necessary supporting objects as listed below) should be passed into the * resultConsumer's {@link Consumer#accept} method. If the Consumer is given the wrong data @@ -115,22 +117,81 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable * - [net.minecraft.world.level.chunk.ChunkAccess]
* - [net.minecraft.world.level.ServerLevel] or [net.minecraft.world.level.ClientLevel]
* - * @param chunkPosMinX the chunk X position closest to negative infinity - * @param chunkPosMinZ the chunk Z position closest to negative infinity + * @implNote the default implementation of this method throws an {@link UnsupportedOperationException}, + * and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#CHUNKS}. + * since {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#CHUNKS} by default, + * this method must also be overridden when {@link #getReturnType()} is NOT overridden. + * + * @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 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 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 1.0.0 */ - CompletableFuture generateChunks( - int chunkPosMinX, int chunkPosMinZ, - byte granularity, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, - ExecutorService worldGeneratorThreadPool, Consumer resultConsumer); + default CompletableFuture generateChunks( + int chunkPosMinX, + int chunkPosMinZ, + byte granularity, + byte targetDataDetail, + EDhApiDistantGeneratorMode generatorMode, + ExecutorService worldGeneratorThreadPool, + Consumer resultConsumer + ) { + throw new UnsupportedOperationException(); + } + + /** + * This method is called by Distant Horizons to generate terrain over a given area when + * {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#CHUNKS_OF_DATA_POINTS}.

+ * + * after the chunk of data points has been generated, it should be passed into the + * resultConsumer's {@link Consumer#accept(Object)} method. + * + * @implNote the default implementation of this method throws an {@link UnsupportedOperationException}, + * and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#CHUNKS_OF_DATA_POINTS}. + * + * @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 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 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 1.1.0 + */ + default CompletableFuture generateChunksOfDataPoints( + int chunkPosMinX, + int chunkPosMinZ, + byte granularity, + byte targetDataDetail, + EDhApiDistantGeneratorMode generatorMode, + ExecutorService worldGeneratorThreadPool, + Consumer resultConsumer + ) { + throw new UnsupportedOperationException(); + } + + /** + * This method controls how Distant Horizons should request that + * this world generator generate terrain over a given area. + * by default, the return value is {@link EDhApiWorldGeneratorReturnType#CHUNKS}, + * which means that {@link #generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} + * will be invoked whenever Distant Horizons wants this world generator to generate terrain over a given area. + * + * @since API 1.1.0 + */ + default EDhApiWorldGeneratorReturnType getReturnType() { + return EDhApiWorldGeneratorReturnType.CHUNKS; + } @@ -157,5 +218,30 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable @Override void close(); - + /** + * an enum to control how Distant Horizons should request that + * a world generator generate terrain over a given area. + * + * @since API 1.1.0 + */ + enum EDhApiWorldGeneratorReturnType { + + /** + * when this constant is returned by {@link #getReturnType()}, + * {@link #generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} + * will be called to generate terrain. + * + * @since API 1.1.0 + */ + CHUNKS, + + /** + * when this constant is returned by {@link #getReturnType()}, + * {@link #generateChunksOfDataPoints(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} + * will be called to generate terrain. + * + * @since API 1.1.0 + */ + CHUNKS_OF_DATA_POINTS; + } } diff --git a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunkOfDataPoints.java b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunkOfDataPoints.java new file mode 100644 index 000000000..ce91ffff1 --- /dev/null +++ b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunkOfDataPoints.java @@ -0,0 +1,26 @@ +package com.seibel.distanthorizons.api.objects.data; + +import java.util.List; + +public class DhApiChunkOfDataPoints { + + public final int chunkPosX, chunkPosZ; + public final int chunkBottomY, chunkTopY; + @SuppressWarnings("unchecked") //generic array. + private final List[] dataPoints = new List[256]; + + public DhApiChunkOfDataPoints(int chunkPosX, int chunkPosZ, int chunkBottomY, int chunkTopY) { + this.chunkPosX = chunkPosX; + this.chunkPosZ = chunkPosZ; + this.chunkBottomY = chunkBottomY; + this.chunkTopY = chunkTopY; + } + + public List getDataPoints(int x, int z) { + return this.dataPoints[(z << 4) | x]; + } + + public void setDataPoints(int x, int z, List dataPoints) { + this.dataPoints[(z << 4) | x] = dataPoints; + } +} \ No newline at end of file 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 1a1030dde..3e00d1968 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 @@ -19,9 +19,14 @@ package com.seibel.distanthorizons.core.dataObjects.transformers; +import java.util.List; + +import com.seibel.distanthorizons.api.objects.data.DhApiChunkOfDataPoints; +import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; @@ -121,6 +126,35 @@ public class LodDataBuilder return chunkData; } + public static ChunkSizedFullDataAccessor convertDataPoints(DhApiChunkOfDataPoints dataPoints) { + ChunkSizedFullDataAccessor accessor = new ChunkSizedFullDataAccessor(new DhChunkPos(dataPoints.chunkPosX, dataPoints.chunkPosZ)); + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + List columnDataPoints = dataPoints.getDataPoints(x, z); + //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; + long[] packedDataPoints = new long[size]; + for (int index = 0; index < size; index++) { + DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index); + packedDataPoints[index] = FullDataPointUtil.encode( + accessor.getMapping().addIfNotPresentAndGetId( + (IBiomeWrapper)(dataPoint.biomeWrapper), + (IBlockStateWrapper)(dataPoint.blockStateWrapper) + ), + dataPoint.topYBlockPos - dataPoint.bottomYBlockPos, + dataPoint.bottomYBlockPos - dataPoints.chunkBottomY, + (byte)(dataPoint.lightLevel) + ); + } + accessor.setSingleColumn(packedDataPoints, x, z); + } + } + return accessor; + } + public static boolean canGenerateLodFromChunk(IChunkWrapper chunk) { //return true; 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 e0d99e001..58f7e0079 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 @@ -21,6 +21,8 @@ package com.seibel.distanthorizons.core.generation; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode; import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; +import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator.EDhApiWorldGeneratorReturnType; +import com.seibel.distanthorizons.api.objects.data.DhApiChunkOfDataPoints; import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; @@ -33,6 +35,7 @@ import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; +import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException; import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.threading.RateLimitedThreadPoolExecutor; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; @@ -408,26 +411,56 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender * (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, byte targetDataDetail, - Consumer chunkDataConsumer) - { + DhChunkPos chunkPosMin, + byte granularity, + byte targetDataDetail, + Consumer chunkDataConsumer + ) { EDhApiDistantGeneratorMode generatorMode = Config.Client.Advanced.WorldGenerator.distantGeneratorMode.get(); - return this.generator.generateChunks(chunkPosMin.x, chunkPosMin.z, granularity, targetDataDetail, generatorMode, ThreadPools.getWorldGenExecutor(), (generatedObjectArray) -> - { - try - { - IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(generatedObjectArray); - ChunkSizedFullDataAccessor chunkDataAccessor = LodDataBuilder.createChunkData(chunk); - LodUtil.assertTrue(chunkDataAccessor != null); - chunkDataConsumer.accept(chunkDataAccessor); + EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType(); + switch (returnType) { + case CHUNKS: { + return this.generator.generateChunks( + chunkPosMin.x, + chunkPosMin.z, + granularity, + targetDataDetail, + generatorMode, + ThreadPools.getWorldGenExecutor(), + (Object[] generatedObjectArray) -> { + try + { + IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(generatedObjectArray); + ChunkSizedFullDataAccessor chunkDataAccessor = LodDataBuilder.createChunkData(chunk); + LodUtil.assertTrue(chunkDataAccessor != null); + chunkDataConsumer.accept(chunkDataAccessor); + } + catch (ClassCastException e) + { + DhLoggerBuilder.getLogger().error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e); + Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); + } + } + ); } - catch (ClassCastException e) - { - DhLoggerBuilder.getLogger().error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e); + case CHUNKS_OF_DATA_POINTS: { + return this.generator.generateChunksOfDataPoints( + chunkPosMin.x, + chunkPosMin.z, + granularity, + targetDataDetail, + generatorMode, + ThreadPools.getWorldGenExecutor(), + (DhApiChunkOfDataPoints dataPoints) -> { + chunkDataConsumer.accept(LodDataBuilder.convertDataPoints(dataPoints)); + } + ); + } + default: { Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); + throw new AssertFailureException("Unknown return type: " + returnType); } - }); + } } From 8299955a55c4fd142fb39d01809018428868c798 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Thu, 21 Dec 2023 21:12:57 -0600 Subject: [PATCH 2/6] Add concurrent instructions for Gitlab runner --- .../setup documentation/Gitlab Docker Container Setup.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/_Misc Files/setup documentation/Gitlab Docker Container Setup.txt b/_Misc Files/setup documentation/Gitlab Docker Container Setup.txt index 2ec91c357..6c7af33e7 100644 --- a/_Misc Files/setup documentation/Gitlab Docker Container Setup.txt +++ b/_Misc Files/setup documentation/Gitlab Docker Container Setup.txt @@ -32,6 +32,11 @@ gitlab project -> settings (on the left menu bar) -> CI/CD -> Runners and you should see the newly registered runner (the description should show up under the runner's auto generated ID) +5. optional config +Open Docker Desktop -> Containers -> Files, go to the file "etc/gitlab-runner/config.toml" +concurrent can be changed to allow for concurrent builds. + + Additional commands: From 9e8ca25a713c8c59112e73a3d0e8c38d2d47edf8 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Thu, 21 Dec 2023 21:24:29 -0600 Subject: [PATCH 3/6] Improve error handling in WorldGenQueue --- .../core/generation/WorldGenerationQueue.java | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) 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 58f7e0079..36ea3819d 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,7 +23,6 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGenerat import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator.EDhApiWorldGeneratorReturnType; import com.seibel.distanthorizons.api.objects.data.DhApiChunkOfDataPoints; -import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.generation.tasks.*; @@ -37,7 +36,6 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException; import com.seibel.distanthorizons.core.util.ThreadUtil; -import com.seibel.distanthorizons.core.util.threading.RateLimitedThreadPoolExecutor; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPools; @@ -53,6 +51,7 @@ import java.util.function.Consumer; public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRenderable { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private final IDhApiWorldGenerator generator; @@ -415,11 +414,14 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender byte granularity, byte targetDataDetail, Consumer chunkDataConsumer - ) { + ) + { EDhApiDistantGeneratorMode generatorMode = Config.Client.Advanced.WorldGenerator.distantGeneratorMode.get(); EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType(); - switch (returnType) { - case CHUNKS: { + switch (returnType) + { + case CHUNKS: + { return this.generator.generateChunks( chunkPosMin.x, chunkPosMin.z, @@ -427,23 +429,25 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender targetDataDetail, generatorMode, ThreadPools.getWorldGenExecutor(), - (Object[] generatedObjectArray) -> { + (Object[] generatedObjectArray) -> + { try { - IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(generatedObjectArray); + IChunkWrapper chunk = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray); ChunkSizedFullDataAccessor chunkDataAccessor = LodDataBuilder.createChunkData(chunk); LodUtil.assertTrue(chunkDataAccessor != null); chunkDataConsumer.accept(chunkDataAccessor); } catch (ClassCastException e) { - DhLoggerBuilder.getLogger().error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e); + LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e); Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); } } ); } - case CHUNKS_OF_DATA_POINTS: { + case CHUNKS_OF_DATA_POINTS: + { return this.generator.generateChunksOfDataPoints( chunkPosMin.x, chunkPosMin.z, @@ -451,12 +455,23 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender targetDataDetail, generatorMode, ThreadPools.getWorldGenExecutor(), - (DhApiChunkOfDataPoints dataPoints) -> { - chunkDataConsumer.accept(LodDataBuilder.convertDataPoints(dataPoints)); + (DhApiChunkOfDataPoints dataPoints) -> + { + try + { + ChunkSizedFullDataAccessor chunkDataAccessor = LodDataBuilder.createChunkDataFromApiDataPoints(dataPoints); + chunkDataConsumer.accept(chunkDataAccessor); + } + 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: { + default: + { Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); throw new AssertFailureException("Unknown return type: " + returnType); } From 51c76fe5fbf57d8c400ade288a096044b4d702f4 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Thu, 21 Dec 2023 22:14:13 -0600 Subject: [PATCH 4/6] Refactor and rename DhApiChunkOfDataPoints -> DhApiChunk --- .../EDhApiWorldGeneratorReturnType.java | 56 ++++++++ .../worldGenerator/IDhApiWorldGenerator.java | 63 +++------ .../api/objects/data/DhApiChunk.java | 129 ++++++++++++++++++ .../objects/data/DhApiChunkOfDataPoints.java | 26 ---- .../transformers/LodDataBuilder.java | 70 ++++++---- .../core/generation/WorldGenerationQueue.java | 14 +- 6 files changed, 256 insertions(+), 102 deletions(-) create mode 100644 api/src/main/java/com/seibel/distanthorizons/api/enums/worldGeneration/EDhApiWorldGeneratorReturnType.java create mode 100644 api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java delete mode 100644 api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunkOfDataPoints.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 new file mode 100644 index 000000000..30e411b69 --- /dev/null +++ b/api/src/main/java/com/seibel/distanthorizons/api/enums/worldGeneration/EDhApiWorldGeneratorReturnType.java @@ -0,0 +1,56 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +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 java.util.concurrent.ExecutorService; +import java.util.function.Consumer; + +/** + * VANILLA_CHUNKS,
+ * API_CHUNKS
+ * + * @author Builderb0y, James Seibel + * @version 2023-12-21 + * @since API 1.1.0 + */ +public enum EDhApiWorldGeneratorReturnType +{ + /** + * when this constant is returned by {@link IDhApiWorldGenerator#getReturnType()}, + * {@link IDhApiWorldGenerator#generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} + * will be used when generating terrain. + * + * @since API 1.1.0 + */ + VANILLA_CHUNKS, + + /** + * when this constant is returned by {@link IDhApiWorldGenerator#getReturnType()}, + * {@link IDhApiWorldGenerator#generateApiChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} + * will be used when generating terrain. + * + * @since API 1.1.0 + */ + API_CHUNKS; + +} 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 3812c40f7..16f3c0a68 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 @@ -19,10 +19,11 @@ package com.seibel.distanthorizons.api.interfaces.override.worldGenerator; +import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGeneratorReturnType; 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.DhApiChunkOfDataPoints; +import com.seibel.distanthorizons.api.objects.data.DhApiChunk; import java.io.Closeable; import java.util.concurrent.CompletableFuture; @@ -104,7 +105,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable /** * This method is called by Distant Horizons to generate terrain over a given area when - * {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#CHUNKS}.

+ * {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#VANILLA_CHUNKS}.

* * After a chunk has been generated it (and any necessary supporting objects as listed below) should be passed into the * resultConsumer's {@link Consumer#accept} method. If the Consumer is given the wrong data @@ -118,8 +119,8 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable * - [net.minecraft.world.level.ServerLevel] or [net.minecraft.world.level.ClientLevel]
* * @implNote the default implementation of this method throws an {@link UnsupportedOperationException}, - * and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#CHUNKS}. - * since {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#CHUNKS} by default, + * and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#VANILLA_CHUNKS}. + * since {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#VANILLA_CHUNKS} by default, * this method must also be overridden when {@link #getReturnType()} is NOT overridden. * * @param chunkPosMinX the chunk X position closest to negative infinity @@ -142,19 +143,20 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable EDhApiDistantGeneratorMode generatorMode, ExecutorService worldGeneratorThreadPool, Consumer resultConsumer - ) { + ) + { throw new UnsupportedOperationException(); } /** * This method is called by Distant Horizons to generate terrain over a given area when - * {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#CHUNKS_OF_DATA_POINTS}.

+ * {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.

* - * after the chunk of data points has been generated, it should be passed into the + * After the {@link DhApiChunk} has been generated, it should be passed into the * resultConsumer's {@link Consumer#accept(Object)} method. * * @implNote the default implementation of this method throws an {@link UnsupportedOperationException}, - * and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#CHUNKS_OF_DATA_POINTS}. + * 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 @@ -168,30 +170,28 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable * * @since API 1.1.0 */ - default CompletableFuture generateChunksOfDataPoints( + default CompletableFuture generateApiChunks( int chunkPosMinX, int chunkPosMinZ, byte granularity, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, ExecutorService worldGeneratorThreadPool, - Consumer resultConsumer - ) { + Consumer resultConsumer + ) + { throw new UnsupportedOperationException(); } /** - * This method controls how Distant Horizons should request that - * this world generator generate terrain over a given area. - * by default, the return value is {@link EDhApiWorldGeneratorReturnType#CHUNKS}, + * 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)} - * will be invoked whenever Distant Horizons wants this world generator to generate terrain over a given area. + * will be invoked whenever Distant Horizons wants to generate terrain with this world generator. * * @since API 1.1.0 */ - default EDhApiWorldGeneratorReturnType getReturnType() { - return EDhApiWorldGeneratorReturnType.CHUNKS; - } + default EDhApiWorldGeneratorReturnType getReturnType() { return EDhApiWorldGeneratorReturnType.VANILLA_CHUNKS; } @@ -218,30 +218,5 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable @Override void close(); - /** - * an enum to control how Distant Horizons should request that - * a world generator generate terrain over a given area. - * - * @since API 1.1.0 - */ - enum EDhApiWorldGeneratorReturnType { - - /** - * when this constant is returned by {@link #getReturnType()}, - * {@link #generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} - * will be called to generate terrain. - * - * @since API 1.1.0 - */ - CHUNKS, - - /** - * when this constant is returned by {@link #getReturnType()}, - * {@link #generateChunksOfDataPoints(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)} - * will be called to generate terrain. - * - * @since API 1.1.0 - */ - CHUNKS_OF_DATA_POINTS; - } + } diff --git a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java new file mode 100644 index 000000000..f5c480adc --- /dev/null +++ b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java @@ -0,0 +1,129 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.api.objects.data; + +import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory; +import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; + +import java.util.ArrayList; +import java.util.List; + +/** + * Contains a list of {@link DhApiTerrainDataPoint} representing the blocks in a Minecraft chunk. + * + * @author Builderb0y, James Seibel + * @version 2023-12-21 + * @since API 1.1.0 + * + * @see IDhApiWrapperFactory + * @see DhApiTerrainDataPoint + * @see IDhApiWorldGenerator + */ +public class DhApiChunk +{ + public final int chunkPosX; + public final int chunkPosZ; + + public final int topYBlockPos; + public final int bottomYBlockPos; + + private final List> dataPoints = new ArrayList<>(16 * 16); // 256 + + + + //==============// + // constructors // + //==============// + + public DhApiChunk(int chunkPosX, int chunkPosZ, int topYBlockPos, int bottomYBlockPos) + { + this.chunkPosX = chunkPosX; + this.chunkPosZ = chunkPosZ; + this.topYBlockPos = topYBlockPos; + this.bottomYBlockPos = bottomYBlockPos; + } + + + + //=================// + // getters/setters // + //=================// + + /** + * @param relX a block position between 0 and 15 (inclusive) representing the X axis in the chunk + * @param relZ a block position between 0 and 15 (inclusive) representing the Z axis in the chunk + * @return the {@link DhApiTerrainDataPoint}'s representing the blocks at the relative X and Z position in the chunk. + * @throws IndexOutOfBoundsException if relX or relZ are outside the chunk + */ + public List getDataPoints(int relX, int relZ) throws IndexOutOfBoundsException + { + throwIfRelativePosOutOfBounds(relX, relZ); + return this.dataPoints.get((relZ << 4) | relX); + } + + /** + * @param relX a block position between 0 and 15 (inclusive) representing the X axis in the chunk + * @param relZ a block position between 0 and 15 (inclusive) representing the Z axis in the chunk + * @param dataPoints Represents the blocks at the relative X and Z position in the chunk. + * Cannot contain null objects or data points with any detail level but 0 (block-sized). + * @throws IndexOutOfBoundsException if relX or relZ are outside the chunk + */ + public void setDataPoints(int relX, int relZ, List dataPoints) throws IndexOutOfBoundsException, IllegalArgumentException + { + throwIfRelativePosOutOfBounds(relX, relZ); + + // validate the incoming datapoints + if (dataPoints != null) + { + 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 + { + DhApiTerrainDataPoint dataPoint = dataPoints.get(i); + if (dataPoint == null) + { + throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR."); + } + + if (dataPoint.detailLevel != 0) + { + throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0]."); + } + } + } + + this.dataPoints.set((relZ << 4) | relX, dataPoints); + } + + + + //================// + // helper methods // + //================// + + /** Included to prevent users accidentally setting columns outside the chunk */ + private static void throwIfRelativePosOutOfBounds(int relX, int relZ) + { + if (relX < 0 || relX > 15 || + relZ < 0 || relZ > 15) + { + throw new IndexOutOfBoundsException("Relative block positions must be between 0 and 15 (inclusive) the block pos: ("+relX+","+relZ+") is outside of those boundaries."); + } + } + +} \ No newline at end of file diff --git a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunkOfDataPoints.java b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunkOfDataPoints.java deleted file mode 100644 index ce91ffff1..000000000 --- a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunkOfDataPoints.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.seibel.distanthorizons.api.objects.data; - -import java.util.List; - -public class DhApiChunkOfDataPoints { - - public final int chunkPosX, chunkPosZ; - public final int chunkBottomY, chunkTopY; - @SuppressWarnings("unchecked") //generic array. - private final List[] dataPoints = new List[256]; - - public DhApiChunkOfDataPoints(int chunkPosX, int chunkPosZ, int chunkBottomY, int chunkTopY) { - this.chunkPosX = chunkPosX; - this.chunkPosZ = chunkPosZ; - this.chunkBottomY = chunkBottomY; - this.chunkTopY = chunkTopY; - } - - public List getDataPoints(int x, int z) { - return this.dataPoints[(z << 4) | x]; - } - - public void setDataPoints(int x, int z, List dataPoints) { - this.dataPoints[(z << 4) | x] = dataPoints; - } -} \ No newline at end of file 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 3e00d1968..8ac100f70 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 @@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.dataObjects.transformers; import java.util.List; -import com.seibel.distanthorizons.api.objects.data.DhApiChunkOfDataPoints; +import com.seibel.distanthorizons.api.objects.data.DhApiChunk; import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; @@ -44,6 +44,11 @@ public class LodDataBuilder private static boolean getTopErrorLogged = false; + + //============// + // converters // + //============// + public static ChunkSizedFullDataAccessor createChunkData(IChunkWrapper chunkWrapper) { if (!canGenerateLodFromChunk(chunkWrapper)) @@ -126,39 +131,54 @@ public class LodDataBuilder return chunkData; } - public static ChunkSizedFullDataAccessor convertDataPoints(DhApiChunkOfDataPoints dataPoints) { + /** @throws ClassCastException if an API user returns the wrong object type(s) */ + public static ChunkSizedFullDataAccessor createApiChunkData(DhApiChunk dataPoints) throws ClassCastException + { ChunkSizedFullDataAccessor accessor = new ChunkSizedFullDataAccessor(new DhChunkPos(dataPoints.chunkPosX, dataPoints.chunkPosZ)); - for (int z = 0; z < 16; z++) { - for (int x = 0; x < 16; x++) { - List columnDataPoints = dataPoints.getDataPoints(x, z); - //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; + for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) + { + for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) + { + List columnDataPoints = dataPoints.getDataPoints(relX, relZ); + + + // 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; + long[] packedDataPoints = new long[size]; - for (int index = 0; index < size; index++) { + for (int index = 0; index < size; index++) + { DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index); + + int id = accessor.getMapping().addIfNotPresentAndGetId( + (IBiomeWrapper) (dataPoint.biomeWrapper), + (IBlockStateWrapper) (dataPoint.blockStateWrapper) + ); + packedDataPoints[index] = FullDataPointUtil.encode( - accessor.getMapping().addIfNotPresentAndGetId( - (IBiomeWrapper)(dataPoint.biomeWrapper), - (IBlockStateWrapper)(dataPoint.blockStateWrapper) - ), - dataPoint.topYBlockPos - dataPoint.bottomYBlockPos, - dataPoint.bottomYBlockPos - dataPoints.chunkBottomY, - (byte)(dataPoint.lightLevel) - ); + id, + dataPoint.topYBlockPos - dataPoint.bottomYBlockPos, + dataPoint.bottomYBlockPos - dataPoints.topYBlockPos, + (byte) (dataPoint.lightLevel) + ); } - accessor.setSingleColumn(packedDataPoints, x, z); + + accessor.setSingleColumn(packedDataPoints, relX, relZ); } } + return accessor; } - public static boolean canGenerateLodFromChunk(IChunkWrapper chunk) - { - //return true; - return chunk != null && chunk.isLightCorrect(); // TODO client only chunks return chunks with bad lighting, preventing chunk building (or transparent only chunks) - } + + + //================// + // helper methods // + //================// + + public static boolean canGenerateLodFromChunk(IChunkWrapper chunk) { return chunk != null && chunk.isLightCorrect(); } } 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 36ea3819d..00fd702f0 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 @@ -21,8 +21,8 @@ package com.seibel.distanthorizons.core.generation; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode; import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; -import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator.EDhApiWorldGeneratorReturnType; -import com.seibel.distanthorizons.api.objects.data.DhApiChunkOfDataPoints; +import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGeneratorReturnType; +import com.seibel.distanthorizons.api.objects.data.DhApiChunk; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.generation.tasks.*; @@ -420,7 +420,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType(); switch (returnType) { - case CHUNKS: + case VANILLA_CHUNKS: { return this.generator.generateChunks( chunkPosMin.x, @@ -446,20 +446,20 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender } ); } - case CHUNKS_OF_DATA_POINTS: + case API_CHUNKS: { - return this.generator.generateChunksOfDataPoints( + return this.generator.generateApiChunks( chunkPosMin.x, chunkPosMin.z, granularity, targetDataDetail, generatorMode, ThreadPools.getWorldGenExecutor(), - (DhApiChunkOfDataPoints dataPoints) -> + (DhApiChunk dataPoints) -> { try { - ChunkSizedFullDataAccessor chunkDataAccessor = LodDataBuilder.createChunkDataFromApiDataPoints(dataPoints); + ChunkSizedFullDataAccessor chunkDataAccessor = LodDataBuilder.createApiChunkData(dataPoints); chunkDataConsumer.accept(chunkDataAccessor); } catch (ClassCastException e) From fa12443cb11a096ab0cb11ba25cbd297ed5deaa1 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 22 Dec 2023 07:19:20 -0600 Subject: [PATCH 5/6] Fix null pointers in DhApiChunk --- .../distanthorizons/api/objects/data/DhApiChunk.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java index f5c480adc..b8f479ff7 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java @@ -44,7 +44,7 @@ public class DhApiChunk public final int topYBlockPos; public final int bottomYBlockPos; - private final List> dataPoints = new ArrayList<>(16 * 16); // 256 + private final List> dataPoints; @@ -58,6 +58,13 @@ public class DhApiChunk this.chunkPosZ = chunkPosZ; this.topYBlockPos = topYBlockPos; this.bottomYBlockPos = bottomYBlockPos; + + // populate the array to prevent null pointers + this.dataPoints = new ArrayList<>(16 * 16); // 256 + for (int i = 0; i < (16*16); i++) + { + this.dataPoints.add(i, null); + } } From 1b8ee5cd48f8abc18bbdfe23fe160d17dc5d6cef Mon Sep 17 00:00:00 2001 From: coolGi Date: Sat, 23 Dec 2023 05:28:31 +1030 Subject: [PATCH 6/6] Renamed api's fabric.mod.json to not.fabric.mod.json --- api/src/main/resources/{fabric.mod.json => not.fabric.mod.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename api/src/main/resources/{fabric.mod.json => not.fabric.mod.json} (100%) diff --git a/api/src/main/resources/fabric.mod.json b/api/src/main/resources/not.fabric.mod.json similarity index 100% rename from api/src/main/resources/fabric.mod.json rename to api/src/main/resources/not.fabric.mod.json