BatchGen: Refactor the experGen into proper generator

This commit is contained in:
tom lee
2022-02-03 16:45:36 +08:00
parent a0dd0d5aca
commit 068622895f
8 changed files with 578 additions and 295 deletions
@@ -23,6 +23,7 @@ import org.lwjgl.glfw.GLFW;
import com.seibel.lod.core.api.ClientApi.LagSpikeCatcher;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.worldGeneration.BatchGenerator;
import com.seibel.lod.core.builders.worldGeneration.LodWorldGenerator;
import com.seibel.lod.core.enums.WorldType;
import com.seibel.lod.core.objects.lod.LodDimension;
@@ -33,6 +34,7 @@ import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
@@ -40,197 +42,206 @@ import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
/**
* This holds the methods that should be called
* by the host mod loader (Fabric, Forge, etc.).
* Specifically server and client events.
* This holds the methods that should be called by the host mod loader (Fabric,
* Forge, etc.). Specifically server and client events.
*
* @author James Seibel
* @version 11-12-2021
*/
public class EventApi
{
public class EventApi {
public static final boolean ENABLE_STACK_DUMP_LOGGING = false;
public static final EventApi INSTANCE = new EventApi();
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
/**
* can be set if we want to recalculate variables related
* to the LOD view distance
* can be set if we want to recalculate variables related to the LOD view
* distance
*/
private boolean recalculateWidths = false;
private EventApi()
{
private EventApi() {
}
//=============//
// =============//
// tick events //
//=============//
public void serverTickEvent()
{
// =============//
public BatchGenerator batchGenerator = null;
public void serverTickEvent() {
if (!MC.playerExists() || ApiShared.lodWorld.getIsWorldNotLoaded())
return;
LodDimension lodDim = ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension());
if (lodDim == null)
return;
if (ApiShared.isShuttingDown) return;
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
if (ApiShared.isShuttingDown)
return;
try {
if (VERSION_CONSTANTS.hasBatchGenerationImplementation()) {
if (batchGenerator == null)
batchGenerator = new BatchGenerator(ApiShared.lodBuilder, lodDim);
batchGenerator.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
} else {
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
}
} catch (Exception e) {
// Exception may happen if world got unloaded unorderly
e.printStackTrace();
}
}
//==============//
// ==============//
// world events //
//==============//
public void worldSaveEvent()
{
// ==============//
public void worldSaveEvent() {
ApiShared.lodWorld.saveAllDimensions(false); // Do an async save.
}
private boolean isCurrentlyOnSinglePlayerServer = false;
/** This is also called when a new dimension loads */
public void worldLoadEvent(IWorldWrapper world)
{
public void worldLoadEvent(IWorldWrapper world) {
if (ENABLE_STACK_DUMP_LOGGING)
ClientApi.LOGGER.info("WorldLoadEvent called here for "+ (world.getWorldType() == WorldType.ClientWorld ?
"clientLevel" : "serverLevel"), new RuntimeException());
ClientApi.LOGGER.info(
"WorldLoadEvent called here for "
+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"),
new RuntimeException());
// Always ignore ServerWorld event
if (world.getWorldType() == WorldType.ServerWorld) return;
if (world.getWorldType() == WorldType.ServerWorld)
return;
isCurrentlyOnSinglePlayerServer = MC.hasSinglePlayerServer();
ApiShared.isShuttingDown = false;
DataPointUtil.WORLD_HEIGHT = world.getHeight();
LodBuilder.MIN_WORLD_HEIGHT = world.getMinHeight(); // This updates the World height
//LodNodeGenWorker.restartExecutorService();
//ThreadMapUtil.clearMaps();
// LodNodeGenWorker.restartExecutorService();
// ThreadMapUtil.clearMaps();
// the player just loaded a new world/dimension
ApiShared.lodWorld.selectWorld(LodUtil.getWorldID(world));
// make sure the correct LODs are being rendered
// (if this isn't done the previous world's LODs may be drawn)
ClientApi.renderer.regenerateLODsNextFrame();
}
/** This is also called when the user disconnects from a server+ */
public void worldUnloadEvent(IWorldWrapper world)
{
public void worldUnloadEvent(IWorldWrapper world) {
if (ENABLE_STACK_DUMP_LOGGING)
ClientApi.LOGGER.info("WorldUnloadEvent called here for "+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"), new RuntimeException());
ClientApi.LOGGER.info(
"WorldUnloadEvent called here for "
+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"),
new RuntimeException());
// If it's single player, ignore the client side world unload event
// Note: using isCurrentlyOnSinglePlayerServer as often API call unload event AFTER setting MC to not be in a singlePlayerServer
if (isCurrentlyOnSinglePlayerServer && world.getWorldType() == WorldType.ClientWorld) return;
// Note: using isCurrentlyOnSinglePlayerServer as often API call unload event
// AFTER setting MC to not be in a singlePlayerServer
if (isCurrentlyOnSinglePlayerServer && world.getWorldType() == WorldType.ClientWorld)
return;
// TODO should "resetMod()" be called here? -James
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
ApiShared.isShuttingDown = true;
// TODO Better report on when world gen is stuck and timeout
LodWorldGenerator.INSTANCE.restartExecutorService();
if (VERSION_CONSTANTS.hasBatchGenerationImplementation()) {
if (batchGenerator != null)
batchGenerator.stop();
batchGenerator = null;
} else {
LodWorldGenerator.INSTANCE.restartExecutorService();
}
ApiShared.lodWorld.deselectWorld(); // This force a save and shutdown lodDim properly
// prevent issues related to the buffer builder
// breaking or retaining previous data when changing worlds.
ClientApi.renderer.destroyBuffers();
ClientApi.renderer.requestCleanup();
GLProxy.ensureAllGLJobCompleted();
recalculateWidths = true;
// TODO: Check if after the refactoring, is this still needed
ClientApi.renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory);
ClientApi.INSTANCE.rendererDisabledBecauseOfExceptions = false;
}
public void blockChangeEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType)
{
if (dimType != MC.getCurrentDimension()) return;
public void blockChangeEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType) {
if (dimType != MC.getCurrentDimension())
return;
// recreate the LOD where the blocks were changed
LagSpikeCatcher blockChangeUpdate = new LagSpikeCatcher();
ClientApi.INSTANCE.toBeLoaded.add(chunk.getLongChunkPos());
blockChangeUpdate.end("clientChunkLoad");
}
//=============//
// =============//
// Misc Events //
//=============//
public void onKeyInput(int key, int keyAction)
{
if (CONFIG.client().advanced().debugging().getDebugKeybindingsEnabled())
{
if (key == GLFW.GLFW_KEY_F8 && keyAction == GLFW.GLFW_PRESS)
{
CONFIG.client().advanced().debugging().setDebugMode(CONFIG.client().advanced().debugging().getDebugMode().getNext());
// =============//
public void onKeyInput(int key, int keyAction) {
if (CONFIG.client().advanced().debugging().getDebugKeybindingsEnabled()) {
if (key == GLFW.GLFW_KEY_F8 && keyAction == GLFW.GLFW_PRESS) {
CONFIG.client().advanced().debugging()
.setDebugMode(CONFIG.client().advanced().debugging().getDebugMode().getNext());
MC.sendChatMessage("F8: Set debug mode " + CONFIG.client().advanced().debugging().getDebugMode());
}
if (key == GLFW.GLFW_KEY_F6 && keyAction == GLFW.GLFW_PRESS)
{
CONFIG.client().advanced().debugging().setDrawLods(!CONFIG.client().advanced().debugging().getDrawLods());
if (key == GLFW.GLFW_KEY_F6 && keyAction == GLFW.GLFW_PRESS) {
CONFIG.client().advanced().debugging()
.setDrawLods(!CONFIG.client().advanced().debugging().getDrawLods());
MC.sendChatMessage("F6: Set rendering " + CONFIG.client().advanced().debugging().getDrawLods());
}
}
}
// NOTE: This is being called from Render Thread.
/** Re-centers the given LodDimension if it needs to be. */
public void playerMoveEvent(LodDimension lodDim)
{
public void playerMoveEvent(LodDimension lodDim) {
// make sure the dimension is centered
RegionPos playerRegionPos = new RegionPos(MC.getPlayerBlockPos());
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterRegionPosX(), playerRegionPos.z - lodDim.getCenterRegionPosZ());
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
{
RegionPos center = lodDim.getCenterRegionPos();
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - center.x, playerRegionPos.z - center.z);
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0) {
lodDim.move(worldRegionOffset);
//LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
// LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z +
// "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
}
}
/** Re-sizes all LodDimensions if they need to be. */
public void viewDistanceChangedEvent()
{
public void viewDistanceChangedEvent() {
// calculate how wide the dimension(s) should be in regions
int chunksWide;
if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
chunksWide = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
chunksWide = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(),
LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
else
chunksWide = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 2 + 1;
int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
// make sure we have an odd number of regions
newWidth += (newWidth & 1) == 0 ? 1 : 0;
// do the dimensions need to change in size?
if (ApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
{
if (ApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths) {
// update the dimensions to fit the new width
ApiShared.lodWorld.resizeDimensionRegionWidth(newWidth);
ApiShared.lodBuilder.defaultDimensionWidthInRegions = newWidth;
ClientApi.renderer.setupBuffers();
recalculateWidths = false;
//LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth );
// LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: "
// + newWidth );
}
DetailDistanceUtil.updateSettings();
}
}
@@ -0,0 +1,262 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 Tom Lee (TomTheFurry) & James Seibel (Original code)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.worldGeneration;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.objects.PosToGenerateContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvionmentWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvionmentWrapper.Steps;
public class BatchGenerator {
public static final boolean ENABLE_GENERATOR_STATS_LOGGING = false;
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
public AbstractBatchGenerationEnvionmentWrapper generationGroup;
public LodDimension targetLodDim;
public static final int generationGroupSize = 4;
public static int previousThreadCount = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads();
private int estimatedSampleNeeded = 128;
private int estimatedPointsToQueue = 1;
public BatchGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension) {
IWorldWrapper world = LodUtil.getServerWorldFromDimension(newLodDimension.dimension);
generationGroup = FACTORY.createBatchGenerator(newLodBuilder, newLodDimension, world);
MC.sendChatMessage("NOTE: You are currently using Distant Horizon's Batch Chunk Pre-Generator.");
ClientApi.LOGGER.info("1.18 Experimental Chunk Generator initialized");
}
@SuppressWarnings("unused")
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder) {
if (lodDim != targetLodDim) {
stop();
IWorldWrapper dim = LodUtil.getServerWorldFromDimension(lodDim.dimension);
generationGroup = FACTORY.createBatchGenerator(lodBuilder, lodDim, dim);
targetLodDim = lodDim;
ClientApi.LOGGER.info("1.18 Experimental Chunk Generator reinitialized");
}
DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
int newThreadCount = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads();
if (newThreadCount != previousThreadCount) {
generationGroup.resizeThreadPool(newThreadCount);
previousThreadCount = newThreadCount;
}
if (estimatedPointsToQueue < newThreadCount)
estimatedPointsToQueue = newThreadCount;
GenerationPriority priority = CONFIG.client().worldGenerator().getGenerationPriority();
if (priority == GenerationPriority.AUTO)
priority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
generationGroup.updateAllFutures();
if (!MC.hasSinglePlayerServer())
return;
int eventsCount = generationGroup.getEventCount();
// If we still all jobs running, return.
if (eventsCount >= estimatedPointsToQueue) {
estimatedPointsToQueue--;
if (estimatedPointsToQueue < newThreadCount)
estimatedPointsToQueue = newThreadCount;
return;
}
final int targetToGenerate = estimatedPointsToQueue - eventsCount;
int toGenerate = targetToGenerate;
int positionGoneThough = 0;
// round the player's block position down to the nearest chunk BlockPos
int playerPosX = MC.getPlayerBlockPos().getX();
int playerPosZ = MC.getPlayerBlockPos().getZ();
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(estimatedSampleNeeded, playerPosX, playerPosZ,
priority, mode);
if (eventsCount == 0 && posToGenerate.getNumberOfPos() >= estimatedSampleNeeded) {
estimatedPointsToQueue++;
if (estimatedPointsToQueue > newThreadCount * 10)
estimatedPointsToQueue = newThreadCount * 10;
}
// ClientApi.LOGGER.info("PosToGenerate: {}", posToGenerate);
// Find the max number of iterations we need to go though.
// We are checking one FarPos, and one NearPos per iterations. This ensure we
// aren't just
// always picking one or the other.
Steps targetStep;
switch (mode) {
case NONE:
targetStep = Steps.Empty; // NOTE: Only load in existing chunks. No new chunk generation
break;
case BIOME_ONLY:
targetStep = Steps.Biomes; // NOTE: No block. Require fake height in LodBuilder
break;
case BIOME_ONLY_SIMULATE_HEIGHT:
targetStep = Steps.Noise; // NOTE: Stone only. Require fake surface
break;
case SURFACE:
targetStep = Steps.Surface; // Carvers or Surface???
break;
case FEATURES:
case FULL:
targetStep = Steps.Features;
break;
default:
assert false;
return;
}
if (ENABLE_GENERATOR_STATS_LOGGING)
ClientApi.LOGGER.info("WorldGen. Near:" + posToGenerate.getNumberOfNearPos() + " Far:"
+ posToGenerate.getNumberOfFarPos());
if (priority == GenerationPriority.FAR_FIRST || priority == GenerationPriority.BALANCED) {
int nearCount = posToGenerate.getNumberOfNearPos();
int farCount = posToGenerate.getNumberOfFarPos();
if (ENABLE_GENERATOR_STATS_LOGGING)
ClientApi.LOGGER.info("WorldGen. Near:" + nearCount + " Far:" + farCount);
int maxIteration = Math.max(nearCount, farCount);
for (int i = 0; i < maxIteration; i++) {
// We have farPos to go though
if (i < farCount && posToGenerate.getNthDetail(i, false) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, false) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, false));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, false));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep)) {
toGenerate--;
}
}
if (toGenerate <= 0)
break;
// We have nearPos to go though
if (i < nearCount && posToGenerate.getNthDetail(i, true) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, true) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, true));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, true));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep)) {
toGenerate--;
}
}
if (toGenerate <= 0)
break;
}
} else {
int nearCount = posToGenerate.getNumberOfNearPos();
for (int i = 0; i < nearCount; i++) {
// We have nearPos to go though
if (posToGenerate.getNthDetail(i, true) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, true) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, true));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, true));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep)) {
toGenerate--;
}
if (toGenerate <= 0)
break;
}
}
// Only do far gen if toGenerate is non 0 and that we have requested all samples
// we can get.
if (toGenerate > 0 && estimatedSampleNeeded > posToGenerate.getNumberOfPos()) {
int farCount = posToGenerate.getNumberOfFarPos();
for (int i = 0; i < farCount; i++) {
// We have farPos to go though
if (posToGenerate.getNthDetail(i, false) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, false) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, false));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, false));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep)) {
toGenerate--;
}
}
if (toGenerate <= 0)
break;
}
}
}
if (targetToGenerate != toGenerate && ENABLE_GENERATOR_STATS_LOGGING) {
if (toGenerate <= 0) {
ClientApi.LOGGER.info(
"WorldGenerator: Sampled " + posToGenerate.getNumberOfPos() + " out of " + estimatedSampleNeeded
+ " points, started all targeted " + targetToGenerate + " generations.");
} else {
ClientApi.LOGGER.info("WorldGenerator: Sampled " + posToGenerate.getNumberOfPos() + " out of "
+ estimatedSampleNeeded + " points, started " + (targetToGenerate - toGenerate)
+ " out of targeted " + targetToGenerate + " generations.");
}
}
if (toGenerate > 0 && estimatedSampleNeeded <= posToGenerate.getNumberOfPos()) {
// We failed to generate enough points from the samples.
// Let's increase the estimatedSampleNeeded.
estimatedSampleNeeded *= 1.3;
// Ensure wee don't go to basically infinity
if (estimatedSampleNeeded > 32768)
estimatedSampleNeeded = 32768;
if (ENABLE_GENERATOR_STATS_LOGGING)
ClientApi.LOGGER.info("WorldGenerator: Increasing estimatedSampleNeeeded to " + estimatedSampleNeeded);
} else if (toGenerate <= 0 && positionGoneThough * 1.5 < posToGenerate.getNumberOfPos()) {
// We haven't gone though half of them and it's already enough.
// Let's shink the estimatedSampleNeeded.
estimatedSampleNeeded /= 1.2;
// Ensure we don't go to near zero.
if (estimatedSampleNeeded < 4)
estimatedSampleNeeded = 4;
if (ENABLE_GENERATOR_STATS_LOGGING)
ClientApi.LOGGER.info("WorldGenerator: Decreasing estimatedSampleNeeeded to " + estimatedSampleNeeded);
}
}
public void stop() {
ClientApi.LOGGER.info("1.18 Experimental Chunk Generator shutting down...");
generationGroup.stop();
}
}
@@ -43,230 +43,206 @@ import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractExperimentalWorldGeneratorWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
/**
* A singleton that handles all long distance LOD world generation.
*
* @author Leonardo Amato
* @author James Seibel
* @version 12-11-2021
*/
public class LodWorldGenerator
{
public class LodWorldGenerator {
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
/** This holds the thread used to create LOD generation requests off the main thread. */
private final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(
new LodThreadFactory(this.getClass().getSimpleName() + " world generator", 1));
private ExecutorService genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
/**
* This holds the thread used to create LOD generation requests off the main
* thread.
*/
private final ExecutorService mainGenThread = Executors
.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator", 1));
private ExecutorService genSubThreads = Executors.newFixedThreadPool(
CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new LodThreadFactory("Gen-Worker-Thread", 1));
/** we only want to queue up one generator thread at a time */
private boolean generatorThreadRunning = false;
/**
* This keeps track of how many chunk generation requests are on going. This is
* to limit how many chunks are queued at once. To prevent chunks from being
* generated for a long time in an area the player is no longer in.
*/
public AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
public final Set<AbstractChunkPosWrapper> positionsWaitingToBeGenerated = new HashSet<>();
/**
* Singleton copy of this object
*/
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
public AbstractExperimentalWorldGeneratorWrapper experimentalWorldGenerator;
private LodWorldGenerator() {}
private LodWorldGenerator() {
}
/**
* Queues up LodNodeGenWorkers for the given lodDimension.
* renderer needed so the LodNodeGenWorkers can flag that the
* buffers need to be rebuilt.
* Queues up LodNodeGenWorkers for the given lodDimension. renderer needed so
* the LodNodeGenWorkers can flag that the buffers need to be rebuilt.
*/
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder)
{
if (!CONFIG.client().worldGenerator().getEnableDistantGeneration()) return;
IWorldWrapper world = LodUtil.getServerWorldFromDimension(lodDim.dimension);
// TODO: Rename the config option
if (experimentalWorldGenerator == null) {
try {
experimentalWorldGenerator = WRAPPER_FACTORY.createExperimentalWorldGenerator(lodBuilder, lodDim, world);
} catch (RuntimeException e) {
// Exception may happen if world got unloaded unorderly
e.printStackTrace();
}
}
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.queueGenerationRequests(lodDim, lodBuilder);
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder) {
if (!CONFIG.client().worldGenerator().getEnableDistantGeneration())
return;
}
// TODO: This currently doesn't use the DetailDistanceUtil.getDistanceGenerationMode(int detail) to get the mode.
// This is fine currently since DistanceGenerationMode doesn't care about the detail level for now.
// TODO: This currently doesn't use the
// DetailDistanceUtil.getDistanceGenerationMode(int detail) to get the mode.
// This is fine currently since DistanceGenerationMode doesn't care about the
// detail level for now.
// However, If that was to be changed, This will need to be fixed.
DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
final GenerationPriority priority;
if (CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.AUTO)
priority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
else priority = CONFIG.client().worldGenerator().getGenerationPriority();
if (mode != DistanceGenerationMode.NONE
&& !generatorThreadRunning
&& MC.hasSinglePlayerServer())
{
else
priority = CONFIG.client().worldGenerator().getGenerationPriority();
if (mode != DistanceGenerationMode.NONE && !generatorThreadRunning && MC.hasSinglePlayerServer()) {
// the thread is now running, don't queue up another thread
generatorThreadRunning = true;
/**
* How many chunks to generate outside the player's view distance at one
* time. (or more specifically how many requests to make at one time). I
* multiply by 8 to make sure there is always a buffer of chunk requests, to
* make sure the CPU is always busy, and we can generate LODs as quickly as
* possible.
* How many chunks to generate outside the player's view distance at one time.
* (or more specifically how many requests to make at one time). I multiply by 8
* to make sure there is always a buffer of chunk requests, to make sure the CPU
* is always busy, and we can generate LODs as quickly as possible.
*/
int genRequestPerThread = VERSION_CONSTANTS.getWorldGenerationCountPerThread();
int maxChunkGenRequests;
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * genRequestPerThread;
else maxChunkGenRequests = genRequestPerThread;
Runnable generatorFunc = (() ->
{
try
{
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads()
* genRequestPerThread;
else
maxChunkGenRequests = genRequestPerThread;
Runnable generatorFunc = (() -> {
try {
// round the player's block position down to the nearest chunk BlockPos
int playerPosX = MC.getPlayerBlockPos().getX();
int playerPosZ = MC.getPlayerBlockPos().getZ();
//=======================================//
// =======================================//
// fill in positionsWaitingToBeGenerated //
//=======================================//
// =======================================//
IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(
maxChunkGenRequests, playerPosX, playerPosZ, priority, mode);
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(maxChunkGenRequests, playerPosX,
playerPosZ, priority, mode);
byte detailLevel;
int posX;
int posZ;
int nearIndex = 0;
int farIndex = 0;
for (int i = 0; i < posToGenerate.getNumberOfPos(); i++)
{
// I wish there was a way to compress this code, but I'm not aware of
for (int i = 0; i < posToGenerate.getNumberOfPos(); i++) {
// I wish there was a way to compress this code, but I'm not aware of
// an easy way to do so.
// add the near positions
if (nearIndex < posToGenerate.getNumberOfNearPos() && posToGenerate.getNthDetail(nearIndex, true) != 0)
{
if (nearIndex < posToGenerate.getNumberOfNearPos()
&& posToGenerate.getNthDetail(nearIndex, true) != 0) {
detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
posX = posToGenerate.getNthPosX(nearIndex, true);
posZ = posToGenerate.getNthPosZ(nearIndex, true);
nearIndex++;
AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(
LevelPosUtil.getChunkPos(detailLevel, posX),
LevelPosUtil.getChunkPos(detailLevel, posZ));
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
break;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
}
// add the far positions
// But if priority is NEAR_FIRST, we only do that if near pos has ran out.
if ((nearIndex >= posToGenerate.getNumberOfNearPos() || priority != GenerationPriority.NEAR_FIRST) &&
farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0)
{
if ((nearIndex >= posToGenerate.getNumberOfNearPos()
|| priority != GenerationPriority.NEAR_FIRST)
&& farIndex < posToGenerate.getNumberOfFarPos()
&& posToGenerate.getNthDetail(farIndex, false) != 0) {
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
posX = posToGenerate.getNthPosX(farIndex, false);
posZ = posToGenerate.getNthPosZ(farIndex, false);
farIndex++;
AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(
LevelPosUtil.getChunkPos(detailLevel, posX),
LevelPosUtil.getChunkPos(detailLevel, posZ));
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
continue;
//break;
// break;
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
}
}
}
catch (RuntimeException e)
{
} catch (RuntimeException e) {
// this shouldn't ever happen, but just in case
e.printStackTrace();
}
finally
{
} finally {
generatorThreadRunning = false;
}
});
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
{
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode)) {
generatorFunc.run();
}
else
{
} else {
mainGenThread.execute(generatorFunc);
}
} // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning
} // if distanceGenerationMode != DistanceGenerationMode.NONE &&
// !generatorThreadRunning
} // queueGenerationRequests
private void queueWork(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, IWorldWrapper serverWorld)
{
LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper serverWorld) {
// just a few sanity checks
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (serverWorld == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
Runnable method = (() -> {generateChunk(newPos, newGenerationMode,
newLodBuilder, newLodDimension, serverWorld);});
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(newGenerationMode))
{
Runnable method = (() -> {
generateChunk(newPos, newGenerationMode, newLodBuilder, newLodDimension, serverWorld);
});
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(newGenerationMode)) {
// --Note: This is now using version constants--
// if we are using FULL generation there is no reason
// to queue up a bunch of generation requests,
@@ -278,32 +254,29 @@ public class LodWorldGenerator
// threaded. So to allow that, we check the boolean for
// whether the wrapper requires single thread
method.run();
}
else
{
} else {
// Every other method can
// be done asynchronously
genSubThreads.execute(method);
}
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
// ClientProxy.LOGGER.info(genThreads.toString());
}
private void generateChunk(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode,
LodBuilder newLodBuilder, LodDimension lodDim, IWorldWrapper worldWrapper)
{
private void generateChunk(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode,
LodBuilder newLodBuilder, LodDimension lodDim, IWorldWrapper worldWrapper) {
// try
{
AbstractWorldGeneratorWrapper worldGenWrapper = WRAPPER_FACTORY.createWorldGenerator(newLodBuilder, lodDim, worldWrapper);
AbstractWorldGeneratorWrapper worldGenWrapper = WRAPPER_FACTORY.createWorldGenerator(newLodBuilder, lodDim,
worldWrapper);
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS))
{
switch (generationMode)
{
if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS,
pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS)) {
switch (generationMode) {
case NONE:
// don't generate
break;
@@ -334,59 +307,58 @@ public class LodWorldGenerator
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
}// if in range
} // if in range
}
// catch (Exception e)
// {
// ClientApi.LOGGER.error(LodWorldGenerator.class.getSimpleName() + ": ran into an error: " + e.getMessage());
// e.printStackTrace();
// ClientApi.LOGGER.error(LodWorldGenerator.class.getSimpleName() + ": ran into
// an error: " + e.getMessage());
// e.printStackTrace();
// }
// finally
{
// decrement how many threads are running
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
// this position is no longer being generated
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
}
}// run
/**
* Stops the current genThreads if they are running
* and then recreates the Executor service. <br><br>
* Stops the current genThreads if they are running and then recreates the
* Executor service. <br>
* <br>
* <p>
* This is done to clear any outstanding tasks
* that may exist after the player leaves their current world.
* If this isn't done unfinished tasks may be left in the queue
* preventing new LodChunks form being generated.
* This is done to clear any outstanding tasks that may exist after the player
* leaves their current world. If this isn't done unfinished tasks may be left
* in the queue preventing new LodChunks form being generated.
*/
public void restartExecutorService()
{
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.stop();
experimentalWorldGenerator = null;
}
if (genSubThreads != null && !genSubThreads.isShutdown())
{
public void restartExecutorService() {
if (genSubThreads != null && !genSubThreads.isShutdown()) {
ClientApi.LOGGER.info("Blocking until generator sub threads terminated!!");
try {
mainGenThread.shutdownNow();
genSubThreads.shutdownNow();
boolean worked = genSubThreads.awaitTermination(30, TimeUnit.SECONDS);
if (!worked)
ClientApi.LOGGER.error("Generator sub threads timed out! May cause crash on game exit due to cleanup failure.");
ClientApi.LOGGER.error(
"Generator sub threads timed out! May cause crash on game exit due to cleanup failure.");
} catch (InterruptedException e) {
ClientApi.LOGGER.error("Generator sub threads shutdown is interrupted! May cause crash on game exit due to cleanup failure.");
ClientApi.LOGGER.error(
"Generator sub threads shutdown is interrupted! May cause crash on game exit due to cleanup failure.");
e.printStackTrace();
} finally {
genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
genSubThreads = Executors.newFixedThreadPool(
CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
}
// Doing this instead of setting it to 0 because even if shutdown fail, it won't cause the int to underflow below 0 afterwards
// Doing this instead of setting it to 0 because even if shutdown fail, it won't
// cause the int to underflow below 0 afterwards
numberOfChunksWaitingToGenerate = new AtomicInteger(0);
}
}
@@ -4,29 +4,34 @@ import com.seibel.lod.core.enums.config.DistanceGenerationMode;
/**
* A singleton that contains variables specific to each version of Minecraft
* which can be used to change how DH-Core runs.
* For example: After MC 1.17 blocks can be negative, which changes how we generate LODs.
* which can be used to change how DH-Core runs. For example: After MC 1.17
* blocks can be negative, which changes how we generate LODs.
*
* @author James Seibel
* @version 12-11-2021
*/
public interface IVersionConstants
{
public interface IVersionConstants {
/** @returns the minimum height blocks can be generated */
int getMinimumWorldHeight();
/**
* @Returns True if the given DistanceGenerationMode can be run on our own thread. <br>
* False if the generation must be run on Minecraft's server thread.
/**
* @Returns True if the given DistanceGenerationMode can be run on our own
* thread. <br>
* False if the generation must be run on Minecraft's server thread.
*/
boolean isWorldGeneratorSingleThreaded(DistanceGenerationMode distanceGenerationMode);
/**
/**
* @Returns True if BatchGeneration is implemented <br>
* False if it is not supported
*/
boolean hasBatchGenerationImplementation();
/**
* @Returns the number of generations call per thread.
*/
default int getWorldGenerationCountPerThread() {
return 8;
}
}
@@ -24,6 +24,7 @@ import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvionmentWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractExperimentalWorldGeneratorWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
@@ -33,22 +34,26 @@ import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGenera
* @author James Seibel
* @version 12-14-2021
*/
public interface IWrapperFactory
{
public interface IWrapperFactory {
AbstractBlockPosWrapper createBlockPos();
AbstractBlockPosWrapper createBlockPos(int x, int y, int z);
AbstractChunkPosWrapper createChunkPos();
AbstractChunkPosWrapper createChunkPos(long xAndZPositionCombined);
AbstractChunkPosWrapper createChunkPos(int x, int z);
AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos);
AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos);
AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper);
// Return null to signal that there is no AbstractWorldGenerator
default AbstractExperimentalWorldGeneratorWrapper createExperimentalWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) {
AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension,
IWorldWrapper worldWrapper);
default AbstractBatchGenerationEnvionmentWrapper createBatchGenerator(LodBuilder newLodBuilder,
LodDimension newLodDimension, IWorldWrapper worldWrapper) {
return null;
}
}
@@ -0,0 +1,25 @@
package com.seibel.lod.core.wrapperInterfaces.worldGeneration;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
public abstract class AbstractBatchGenerationEnvionmentWrapper {
public static enum Steps {
Empty, StructureStart, StructureReference, Biomes, Noise, Surface, Carvers, LiquidCarvers, Features, Light,
}
public AbstractBatchGenerationEnvionmentWrapper(IWorldWrapper serverLevel, LodBuilder lodBuilder,
LodDimension lodDim) {
}
public abstract void resizeThreadPool(int newThreadCount);
public abstract void updateAllFutures();
public abstract int getEventCount();
public abstract boolean tryAddPoint(int chunkX, int chunkZ, int genSize, Steps targetStep);
public abstract void stop();
}
@@ -5,7 +5,11 @@ import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
public abstract class AbstractExperimentalWorldGeneratorWrapper {
public AbstractExperimentalWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) { }
public AbstractExperimentalWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension,
IWorldWrapper worldWrapper) {
}
public abstract void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder);
public abstract void stop();
}
@@ -26,25 +26,24 @@ import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
/**
* This is used for generating chunks
* in a variety of detail and threading levels.
* This is used for generating chunks in a variety of detail and threading
* levels.
* <p>
* Abstract instead of an interface, so
* we can define its constructors.
* Abstract instead of an interface, so we can define its constructors.
*
* @author James Seibel
* @version 11-20-2021
*/
public abstract class AbstractWorldGeneratorWrapper
{
public AbstractWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) { }
public abstract class AbstractWorldGeneratorWrapper {
public AbstractWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension,
IWorldWrapper worldWrapper) {
}
public abstract void generateBiomesOnly(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode);
public abstract void generateSurface(AbstractChunkPosWrapper pos);
public abstract void generateFeatures(AbstractChunkPosWrapper pos);
public abstract void generateFull(AbstractChunkPosWrapper pos);
}