This commit is contained in:
s809
2023-12-24 19:16:03 +05:00
7 changed files with 386 additions and 26 deletions
@@ -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:
@@ -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 <https://www.gnu.org/licenses/>.
*/
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, <br>
* API_CHUNKS <br>
*
* @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;
}
@@ -19,9 +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.DhApiChunk;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
@@ -102,7 +104,8 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
//=================//
/**
* This method is called by Distant Horizons to generate terrain over a given area. <br><br>
* This method is called by Distant Horizons to generate terrain over a given area when
* {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#VANILLA_CHUNKS}. <br><br>
*
* 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 +118,80 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
* - [net.minecraft.world.level.chunk.ChunkAccess] <br>
* - [net.minecraft.world.level.ServerLevel] or [net.minecraft.world.level.ClientLevel] <br>
*
* @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#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
* @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<Void> generateChunks(
int chunkPosMinX, int chunkPosMinZ,
byte granularity, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode,
ExecutorService worldGeneratorThreadPool, Consumer<Object[]> resultConsumer);
default CompletableFuture<Void> generateChunks(
int chunkPosMinX,
int chunkPosMinZ,
byte granularity,
byte targetDataDetail,
EDhApiDistantGeneratorMode generatorMode,
ExecutorService worldGeneratorThreadPool,
Consumer<Object[]> resultConsumer
)
{
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}. <br><br>
*
* 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#API_CHUNKS}.
*
* @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<Void> generateApiChunks(
int chunkPosMinX,
int chunkPosMinZ,
byte granularity,
byte targetDataDetail,
EDhApiDistantGeneratorMode generatorMode,
ExecutorService worldGeneratorThreadPool,
Consumer<DhApiChunk> 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)}
* will be invoked whenever Distant Horizons wants to generate terrain with this world generator.
*
* @since API 1.1.0
*/
default EDhApiWorldGeneratorReturnType getReturnType() { return EDhApiWorldGeneratorReturnType.VANILLA_CHUNKS; }
@@ -0,0 +1,136 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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<List<DhApiTerrainDataPoint>> dataPoints;
//==============//
// constructors //
//==============//
public DhApiChunk(int chunkPosX, int chunkPosZ, int topYBlockPos, int bottomYBlockPos)
{
this.chunkPosX = chunkPosX;
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);
}
}
//=================//
// 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<DhApiTerrainDataPoint> 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<DhApiTerrainDataPoint> 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.");
}
}
}
@@ -19,9 +19,14 @@
package com.seibel.distanthorizons.core.dataObjects.transformers;
import java.util.List;
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;
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;
@@ -39,6 +44,11 @@ public class LodDataBuilder
private static boolean getTopErrorLogged = false;
//============//
// converters //
//============//
public static ChunkSizedFullDataAccessor createChunkData(IChunkWrapper chunkWrapper)
{
if (!canGenerateLodFromChunk(chunkWrapper))
@@ -121,10 +131,54 @@ public class LodDataBuilder
return chunkData;
}
public static boolean canGenerateLodFromChunk(IChunkWrapper chunk)
/** @throws ClassCastException if an API user returns the wrong object type(s) */
public static ChunkSizedFullDataAccessor createApiChunkData(DhApiChunk dataPoints) throws ClassCastException
{
//return true;
return chunk != null && chunk.isLightCorrect(); // TODO client only chunks return chunks with bad lighting, preventing chunk building (or transparent only chunks)
ChunkSizedFullDataAccessor accessor = new ChunkSizedFullDataAccessor(new DhChunkPos(dataPoints.chunkPosX, dataPoints.chunkPosZ));
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
{
List<DhApiTerrainDataPoint> 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++)
{
DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index);
int id = accessor.getMapping().addIfNotPresentAndGetId(
(IBiomeWrapper) (dataPoint.biomeWrapper),
(IBlockStateWrapper) (dataPoint.blockStateWrapper)
);
packedDataPoints[index] = FullDataPointUtil.encode(
id,
dataPoint.topYBlockPos - dataPoint.bottomYBlockPos,
dataPoint.bottomYBlockPos - dataPoints.topYBlockPos,
(byte) (dataPoint.lightLevel)
);
}
accessor.setSingleColumn(packedDataPoints, relX, relZ);
}
}
return accessor;
}
//================//
// helper methods //
//================//
public static boolean canGenerateLodFromChunk(IChunkWrapper chunk) { return chunk != null && chunk.isLightCorrect(); }
}
@@ -21,7 +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.core.config.listeners.ConfigChangeListener;
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.*;
@@ -33,8 +34,8 @@ 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;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPools;
@@ -50,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;
@@ -408,26 +410,72 @@ 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<Void> startGenerationEvent(
DhChunkPos chunkPosMin,
byte granularity, byte targetDataDetail,
Consumer<ChunkSizedFullDataAccessor> chunkDataConsumer)
DhChunkPos chunkPosMin,
byte granularity,
byte targetDataDetail,
Consumer<ChunkSizedFullDataAccessor> chunkDataConsumer
)
{
EDhApiDistantGeneratorMode generatorMode = Config.Client.Advanced.WorldGenerator.distantGeneratorMode.get();
return this.generator.generateChunks(chunkPosMin.x, chunkPosMin.z, granularity, targetDataDetail, generatorMode, ThreadPools.getWorldGenExecutor(), (generatedObjectArray) ->
EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType();
switch (returnType)
{
try
case VANILLA_CHUNKS:
{
IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(generatedObjectArray);
ChunkSizedFullDataAccessor chunkDataAccessor = LodDataBuilder.createChunkData(chunk);
LodUtil.assertTrue(chunkDataAccessor != null);
chunkDataConsumer.accept(chunkDataAccessor);
return this.generator.generateChunks(
chunkPosMin.x,
chunkPosMin.z,
granularity,
targetDataDetail,
generatorMode,
ThreadPools.getWorldGenExecutor(),
(Object[] generatedObjectArray) ->
{
try
{
IChunkWrapper chunk = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
ChunkSizedFullDataAccessor chunkDataAccessor = LodDataBuilder.createChunkData(chunk);
LodUtil.assertTrue(chunkDataAccessor != null);
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);
}
}
);
}
catch (ClassCastException e)
case API_CHUNKS:
{
return this.generator.generateApiChunks(
chunkPosMin.x,
chunkPosMin.z,
granularity,
targetDataDetail,
generatorMode,
ThreadPools.getWorldGenExecutor(),
(DhApiChunk dataPoints) ->
{
try
{
ChunkSizedFullDataAccessor chunkDataAccessor = LodDataBuilder.createApiChunkData(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:
{
DhLoggerBuilder.getLogger().error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e);
Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
throw new AssertFailureException("Unknown return type: " + returnType);
}
});
}
}