Set up the world generator interface for use with the API

This commit is contained in:
James Seibel
2022-12-08 21:32:19 -06:00
parent 581515efc4
commit 434abcf1ae
10 changed files with 242 additions and 157 deletions
@@ -40,13 +40,13 @@ public enum EDhApiDetailLevel
REGION(9, 512);
public final int detailLevel;
public final int widthInBlocks;
public final byte detailLevel;
public final byte widthInBlocks;
EDhApiDetailLevel(int detailLevel, int widthInBlocks)
{
this.detailLevel = detailLevel;
this.widthInBlocks = widthInBlocks;
this.detailLevel = (byte) detailLevel;
this.widthInBlocks = (byte) widthInBlocks;
}
}
@@ -1,20 +1,125 @@
package com.seibel.lod.api.interfaces.override.worldGenerator;
import com.seibel.lod.api.enums.EDhApiDetailLevel;
import com.seibel.lod.api.enums.worldGeneration.EDhApiWorldGenThreadMode;
import com.seibel.lod.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.lod.api.interfaces.override.IDhApiOverrideable;
import com.seibel.lod.api.interfaces.world.IDhApiChunkWrapper;
import com.seibel.lod.api.interfaces.world.IDhApiLevelWrapper;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* @author James Seibel
* @version 2022-9-8
* @version 2022-12-6
*/
public interface IDhApiWorldGenerator extends IDhApiOverrideable
public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
{
/** Returns which thread chunk generation requests can be created on. */
//============//
// parameters //
//============//
/**
* Returns which thread chunk generation requests will be run on. <br>
* TODO: only {@link EDhApiWorldGenThreadMode#MULTI_THREADED} is supported currently
*/
EDhApiWorldGenThreadMode getThreadingMode();
IDhApiChunkWrapper generateCoreChunk(int chunkPosX, int chunkPosZ, IDhApiLevelWrapper serverLevelWrapper, EDhApiWorldGenerationStep maxStepToGenerate);
/**
* Defines the smallest datapoint size that can be generated at a time. <br>
* Minimum detail level is 0 (1 block) <br>
* Default detail level is 0 <br>
* For more information on what detail levels represent see: {@link EDhApiDetailLevel}. <br><br>
*
* TODO: System currently only supports 1x1 block per data.
*
* @see EDhApiDetailLevel
*/
default byte getMinDataDetailLevel() { return EDhApiDetailLevel.BLOCK.detailLevel; }
/**
* Defines the largest datapoint size that can be generated at a time. <br>
* Minimum detail level is 0 (1 block) <br>
* Default detail level is 0 <br>
* For more information on what detail levels represent see: {@link EDhApiDetailLevel}.
*
* @see EDhApiDetailLevel
*/
default byte getMaxDataDetailLevel() { return EDhApiDetailLevel.BLOCK.detailLevel; }
/**
* When creating generation requests the system will attempt to group nearby tasks together. <br><br>
* What is the minimum size a single generation call can batch together? <br>
*
* Minimum detail level is 4 (the size of a MC chunk) <br>
* Default detail level is 4 <br>
* For more information on what detail levels represent see: {@link EDhApiDetailLevel}.
*
* @see EDhApiDetailLevel
*/
default byte getMinGenerationGranularity() { return EDhApiDetailLevel.CHUNK.detailLevel; }
/**
* When creating generation requests the system will attempt to group nearby tasks together. <br><br>
* What is the maximum size a single generation call can batch together? <br>
*
* Minimum detail level is 4 (the size of a MC chunk) <br>
* Default detail level is 6 (4x4 chunks) <br>
* For more information on what detail levels represent see: {@link EDhApiDetailLevel}.
*
* @see EDhApiDetailLevel
*/
default byte getMaxGenerationGranularity() { return (byte) (EDhApiDetailLevel.CHUNK.detailLevel + 2); }
/** Returns true if the generator is unable to accept new generation requests. */
boolean isBusy();
//=================//
// world generator //
//=================//
/**
* This method is called by Distant Horizons to generate terrain over a given area
* from a thread defined by {@link IDhApiWorldGenerator#getThreadingMode}. <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
* type(s) it will throw a {@link ClassCastException} with a list of what objects it was expecting. <br>
* <strong>Note:</strong> these objects are minecraft version dependent and will change without notice!
* Please run your generator in game at least once to confirm the objects you are returning are correct. <br><br>
*
* Consumer expected inputs for each minecraft version (in order): <br>
* <strong>1.18:</strong> {@link net.minecraft.world.level.chunk.ChunkAccess} and {@link net.minecraft.world.level.LevelReader} <br>
*
* @throws ClassCastException if incompatible objects are passed into the resultConsumer.
*/
CompletableFuture<Void> generateChunks(int chunkPosMinX, int chunkPosMinZ,
byte granularity, byte targetDataDetail,
Consumer<Object[]> resultConsumer) throws ClassCastException;
//===============//
// event methods //
//===============//
/**
* Called before a new generator task is started. <br>
* This can be used to run cleanup on existing tasks before new tasks are started.
*/
void preGeneratorTaskStart();
//===========//
// overrides //
//===========//
// This is overridden to remove the "throws IOException"
// that is present in the default Closeable.close() method
@Override
void close();
}
@@ -19,6 +19,7 @@
package com.seibel.lod.core.generation;
import com.seibel.lod.api.enums.worldGeneration.EDhApiWorldGenThreadMode;
import com.seibel.lod.core.level.IDhLevel;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.api.enums.config.EDistanceGenerationMode;
@@ -65,15 +66,16 @@ public class BatchGenerator implements IChunkGenerator
// generator parameters //
//======================//
@Override
public EDhApiWorldGenThreadMode getThreadingMode() { return EDhApiWorldGenThreadMode.MULTI_THREADED; }
@Override
public byte getMinDataDetailLevel() { return LodUtil.BLOCK_DETAIL_LEVEL; }
@Override
public byte getMaxDataDetailLevel() { return LodUtil.BLOCK_DETAIL_LEVEL; }
@Override
public byte getMinGenerationGranularity() { return LodUtil.CHUNK_DETAIL_LEVEL; }
@Override
public byte getMaxGenerationGranularity() { return LodUtil.CHUNK_DETAIL_LEVEL + 2; }
@@ -99,7 +101,7 @@ public class BatchGenerator implements IChunkGenerator
}
@Override
public CompletableFuture<Void> generateChunks(DhChunkPos chunkPosMin, byte granularity, byte targetDataDetail, Consumer<IChunkWrapper> resultConsumer)
public CompletableFuture<Void> generateChunks(int chunkPosMinX, int chunkPosMinZ, byte granularity, byte targetDataDetail, Consumer<Object[]> resultConsumer)
{
EDistanceGenerationMode mode = Config.Client.WorldGenerator.distanceGenerationMode.get();
Steps targetStep = null;
@@ -123,13 +125,15 @@ public class BatchGenerator implements IChunkGenerator
break;
}
int chunkXMin = chunkPosMin.x;
int chunkZMin = chunkPosMin.z;
int genChunkSize = BitShiftUtil.powerOfTwo(granularity - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale
double runTimeRatio = Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get() > 1 ?
1.0 :
Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get();
return this.generationGroup.generateChunks(chunkXMin, chunkZMin, genChunkSize, targetStep, runTimeRatio, resultConsumer);
// 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<IChunkWrapper> consumer = (chunkWrapper) -> resultConsumer.accept(new Object[]{ chunkWrapper });
return this.generationGroup.generateChunks(chunkPosMinX, chunkPosMinZ, genChunkSize, targetStep, runTimeRatio, consumer);
}
@Override
@@ -1,8 +1,13 @@
package com.seibel.lod.core.generation;
import com.seibel.lod.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.datatype.full.ChunkSizedData;
import com.seibel.lod.core.datatype.transform.LodDataBuilder;
import com.seibel.lod.core.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.pos.DhChunkPos;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import java.io.Closeable;
@@ -10,53 +15,15 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* Most logic is in {@link IDhApiWorldGenerator}, this mainly contains default
* methods that are useful for Core.
*
* @author James Seibel
* @author Leetom
* @version 2022-11-25
* @version 2022-12-5
*/
public interface IChunkGenerator extends Closeable
public interface IChunkGenerator extends IDhApiWorldGenerator
{
//============//
// parameters //
//============//
/**
* What is the detail/resolution of the data? (This will offset the generation granularity)
* (minimum detail is 0, maximum detail is 255) (though that high isn't really... realistic)
* (0 = 1x1 block per data, 1 = 2x2 block per data, 2 = 4x4 block per data, etc. This measured in the same units as LOD Detail Level.)
* TODO: System currently only supports 1x1 block per data.
*/
byte getMinDataDetailLevel();
byte getMaxDataDetailLevel();
/**
* What is the min batch size of a single generation call?
* (minimum return value is 4 since that's the MC chunk size)
* (4 -> 16x16 data per call, 5 -> 32x32 data per call, 6 -> 64x64 data per call, etc. This measured in the same units as LOD Detail Level.)
*/
byte getMinGenerationGranularity();
/**
* What is the max batch size of a single generation call?
* The system will try to group tasks to the max batch size if possible
* (minimum return value is 4 since that's the MC chunk size)
* (4 -> 16x16 data per call, 5 -> 32x32 data per call, 6 -> 64x64 data per call, etc. This measured in the same units as LOD Detail Level.)
*/
byte getMaxGenerationGranularity();
/** Returns whether the generator is unable to accept new generation requests. */
boolean isBusy();
//=================//
// world generator //
//=================//
CompletableFuture<Void> generateChunks(DhChunkPos chunkPosMin,
byte granularity, byte targetDataDetail,
Consumer<IChunkWrapper> resultConsumer);
/**
* Start a generation event
* (Note that the chunkPos is always aligned to the granularity)
@@ -66,32 +33,19 @@ public interface IChunkGenerator extends Closeable
byte granularity, byte targetDataDetail,
Consumer<ChunkSizedData> resultConsumer)
{
return this.generateChunks(chunkPosMin, granularity, targetDataDetail, (chunk) ->
{
resultConsumer.accept(LodDataBuilder.createChunkData(chunk));
return this.generateChunks(chunkPosMin.x, chunkPosMin.z, granularity, targetDataDetail, (objectArray) ->
{
try
{
IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(objectArray);
resultConsumer.accept(LodDataBuilder.createChunkData(chunk));
}
catch (ClassCastException e)
{
DhLoggerBuilder.getLogger().error("World generator return type incorrect. Error: [" + e.getMessage() + "].", e);
Config.Client.WorldGenerator.enableDistantGeneration.set(false);
}
});
}
//===============//
// event methods //
//===============//
/**
* Called before a new generator task is started. <br>
* This can be used to run cleanup on existing tasks before new tasks are started.
*/
void preGeneratorTaskStart();
//===========//
// overrides //
//===========//
// This is overridden to remove the "throws IOException"
// that is present in the default Closeable.close() method
@Override
void close();
}
@@ -22,6 +22,7 @@ package com.seibel.lod.core.wrapperInterfaces;
import com.seibel.lod.core.level.IDhLevel;
import com.seibel.lod.core.interfaces.dependencyInjection.IBindable;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvionmentWrapper;
@@ -31,7 +32,7 @@ import java.io.IOException;
* This handles creating abstract wrapper objects.
*
* @author James Seibel
* @version 3-5-2022
* @version 2022-12-5
*/
public interface IWrapperFactory extends IBindable
{
@@ -39,4 +40,11 @@ public interface IWrapperFactory extends IBindable
IBiomeWrapper deserializeBiomeWrapper(String str) throws IOException;
IBlockStateWrapper deserializeBlockStateWrapper(String str) throws IOException;
IBlockStateWrapper getAirBlockStateWrapper();
/**
* Specifically designed to be used with the API.
* @throws ClassCastException with instructions on expected objects if the object couldn't be cast
*/
IChunkWrapper createChunkWrapper(Object[] objectArray) throws ClassCastException;
}
@@ -43,4 +43,5 @@ public abstract class AbstractBatchGenerationEnvionmentWrapper
public abstract void stop(boolean blocking);
public abstract CompletableFuture<Void> generateChunks(int minX, int minZ, int genSize, Steps targetStep, double runTimeRatio, Consumer<IChunkWrapper> resultConsumer);
}
@@ -0,0 +1,70 @@
package testItems.worldGeneratorInjection.objects;
import com.seibel.lod.api.enums.worldGeneration.EDhApiWorldGenThreadMode;
import com.seibel.lod.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import com.seibel.lod.core.DependencyInjection.OverrideInjector;
import com.seibel.lod.core.util.LodUtil;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* Dummy test implementation object for world generator injection unit tests.
*
* @author James Seibel
* @version 2022-12-5
*/
public class TestWorldGenerator implements IDhApiWorldGenerator
{
public static EDhApiWorldGenThreadMode THREAD_MODE = EDhApiWorldGenThreadMode.SINGLE_THREADED;
// testable methods //
@Override
public int getPriority() { return OverrideInjector.CORE_PRIORITY; }
@Override
public EDhApiWorldGenThreadMode getThreadingMode() { return THREAD_MODE; }
// not used when unit testing //
//======================//
// generator parameters //
//======================//
@Override
public byte getMinDataDetailLevel() { return LodUtil.BLOCK_DETAIL_LEVEL; }
@Override
public byte getMaxDataDetailLevel() { return LodUtil.BLOCK_DETAIL_LEVEL; }
@Override
public byte getMinGenerationGranularity() { return LodUtil.CHUNK_DETAIL_LEVEL; }
@Override
public byte getMaxGenerationGranularity() { return LodUtil.CHUNK_DETAIL_LEVEL + 2; }
//===================//
// generator methods //
//===================//
@Override
public void close() { }
@Override
public boolean isBusy() { return false; }
@Override
public CompletableFuture<Void> generateChunks(int chunkPosMinX, int chunkPosMinZ, byte granularity, byte targetDataDetail, Consumer<Object[]> resultConsumer) { return null; }
@Override
public void preGeneratorTaskStart() { }
}
@@ -1,49 +1,23 @@
package testItems.worldGeneratorInjection.objects;
import com.seibel.lod.api.enums.worldGeneration.EDhApiWorldGenThreadMode;
import com.seibel.lod.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.lod.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import com.seibel.lod.api.interfaces.world.IDhApiChunkWrapper;
import com.seibel.lod.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.lod.core.DependencyInjection.OverrideInjector;
/**
* Dummy test implementation object for world generator injection unit tests.
*
* @author James Seibel
* @version 2022-7-26
* @version 2022-12-5
*/
public class WorldGeneratorTestCore implements IDhApiWorldGenerator
public class WorldGeneratorTestCore extends TestWorldGenerator
{
public static int PRIORITY = OverrideInjector.CORE_PRIORITY;
public static EDhApiWorldGenThreadMode THREAD_MODE = EDhApiWorldGenThreadMode.SINGLE_THREADED;
//==============//
// IOverridable //
//==============//
@Override
public int getPriority() { return OverrideInjector.CORE_PRIORITY; }
//======================//
// IDhApiWorldGenerator //
//======================//
/** Returns which thread chunk generation requests can be created on. */
@Override
public EDhApiWorldGenThreadMode getThreadingMode()
{
return THREAD_MODE;
}
public int getPriority() { return PRIORITY; }
@Override
public IDhApiChunkWrapper generateCoreChunk(int chunkPosX, int chunkPosZ, IDhApiLevelWrapper serverLevelWrapper, EDhApiWorldGenerationStep maxStepToGenerate)
{
return null;
}
public EDhApiWorldGenThreadMode getThreadingMode() { return THREAD_MODE; }
}
@@ -11,34 +11,19 @@ import com.seibel.lod.core.DependencyInjection.OverrideInjector;
* Dummy test implementation object for world generator injection unit tests.
*
* @author James Seibel
* @version 2022-8-9
* @version 2022-12-5
*/
public class WorldGeneratorTestPrimary implements IDhApiWorldGenerator
public class WorldGeneratorTestPrimary extends TestWorldGenerator
{
public static int PRIORITY = OverrideInjector.DEFAULT_NON_CORE_OVERRIDE_PRIORITY + 5;
public static EDhApiWorldGenThreadMode THREAD_MODE = EDhApiWorldGenThreadMode.MULTI_THREADED;
//==============//
// IOverridable //
//==============//
@Override
public int getPriority() { return PRIORITY; }
//======================//
// IDhApiWorldGenerator //
//======================//
@Override
public EDhApiWorldGenThreadMode getThreadingMode() { return THREAD_MODE; }
@Override
public IDhApiChunkWrapper generateCoreChunk(int chunkPosX, int chunkPosZ, IDhApiLevelWrapper serverLevelWrapper, EDhApiWorldGenerationStep maxStepToGenerate)
{
return null;
}
}
@@ -11,34 +11,18 @@ import com.seibel.lod.core.DependencyInjection.OverrideInjector;
* Dummy test implementation object for world generator injection unit tests.
*
* @author James Seibel
* @version 2022-8-9
* @version 2022-12-5
*/
public class WorldGeneratorTestSecondary implements IDhApiWorldGenerator
public class WorldGeneratorTestSecondary extends TestWorldGenerator
{
public static int PRIORITY = OverrideInjector.DEFAULT_NON_CORE_OVERRIDE_PRIORITY;
public static EDhApiWorldGenThreadMode THREAD_MODE = EDhApiWorldGenThreadMode.SERVER_THREAD;
//==============//
// IOverridable //
//==============//
@Override
public int getPriority() { return PRIORITY; }
//======================//
// IDhApiWorldGenerator //
//======================//
@Override
public EDhApiWorldGenThreadMode getThreadingMode() { return THREAD_MODE; }
@Override
public IDhApiChunkWrapper generateCoreChunk(int chunkPosX, int chunkPosZ, IDhApiLevelWrapper serverLevelWrapper, EDhApiWorldGenerationStep maxStepToGenerate)
{
return null;
}
}