From 009d7ede1b056549ce709fd08c09e744c8f21fac Mon Sep 17 00:00:00 2001 From: tom lee Date: Sun, 12 Dec 2021 02:01:59 +0800 Subject: [PATCH] WorldGen: Added support for single thread world gen Reworked the world gen so that it no longer creates like 5 new threads every server tick. And combined LodGenWorker into LodWorldGenerator as being a seperate object is not needed. --- .../com/seibel/lod/core/api/EventApi.java | 6 +- .../worldGeneration/LodGenWorker.java | 212 ------------------ .../worldGeneration/LodWorldGenerator.java | 156 +++++++++++-- .../wrapperInterfaces/IWrapperFactory.java | 3 + 4 files changed, 147 insertions(+), 230 deletions(-) delete mode 100644 src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java diff --git a/src/main/java/com/seibel/lod/core/api/EventApi.java b/src/main/java/com/seibel/lod/core/api/EventApi.java index c1aa845de..d5c9ea638 100644 --- a/src/main/java/com/seibel/lod/core/api/EventApi.java +++ b/src/main/java/com/seibel/lod/core/api/EventApi.java @@ -21,7 +21,6 @@ package com.seibel.lod.core.api; import org.lwjgl.glfw.GLFW; -import com.seibel.lod.core.builders.worldGeneration.LodGenWorker; import com.seibel.lod.core.builders.worldGeneration.LodWorldGenerator; import com.seibel.lod.core.enums.config.DistanceGenerationMode; import com.seibel.lod.core.objects.lod.LodDimension; @@ -81,7 +80,8 @@ public class EventApi if (lodDim == null) return; - LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ClientApi.renderer, ApiShared.lodBuilder); + // FIXME: This is in server thread. We shouldn't be accessing the client's renderer! + LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder); } @@ -147,7 +147,7 @@ public class EventApi // if this isn't done unfinished tasks may be left in the queue // preventing new LodChunks form being generated - LodGenWorker.restartExecutorService(); + LodWorldGenerator.INSTANCE.restartExecutorService(); LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0); ApiShared.lodWorld.deselectWorld(); diff --git a/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java deleted file mode 100644 index 55f74dd8d..000000000 --- a/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * This file is part of the Distant Horizon mod (formerly the LOD Mod), - * licensed under the GNU GPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * 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 . - */ - -package com.seibel.lod.core.builders.worldGeneration; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import com.google.common.util.concurrent.ThreadFactoryBuilder; -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.objects.lod.LodDimension; -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.chunk.AbstractChunkPosWrapper; -import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; -import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; -import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper; - -/** - * This is used to generate a LodChunk at a given ChunkPos. - * - * @author James Seibel - * @version 11-20-2021 - */ -public class LodGenWorker -{ - private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); - private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); - - public static ExecutorService genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build()); - - private final LodChunkGenThread thread; - - - - public LodGenWorker(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode, - 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"); - - - - thread = new LodChunkGenThread(newPos, newGenerationMode, - newLodBuilder, - newLodDimension, serverWorld); - } - - public void queueWork() - { - if (CONFIG.client().worldGenerator().getDistanceGenerationMode() == DistanceGenerationMode.FULL) - { - // if we are using FULL generation there is no reason - // to queue up a bunch of generation requests, - // because MC's internal server (as of 1.16.5) only - // responds with a single thread. And we don't - // want to cause more lag than necessary or queue up - // requests that may end up being unneeded. - thread.run(); - } - else - { - // Every other method can - // be done asynchronously - genThreads.execute(thread); - } - - // useful for debugging -// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods()); -// ClientProxy.LOGGER.info(genThreads.toString()); - } - - - - - private static class LodChunkGenThread implements Runnable - { - private final AbstractWorldGeneratorWrapper worldGenWrapper; - - public final LodDimension lodDim; - public final DistanceGenerationMode generationMode; - - private final AbstractChunkPosWrapper pos; - - public LodChunkGenThread(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode, - LodBuilder newLodBuilder, - LodDimension newLodDimension, IWorldWrapper worldWrapper) - { - worldGenWrapper = FACTORY.createWorldGenerator(newLodBuilder, newLodDimension, worldWrapper); - - pos = newPos; - generationMode = newGenerationMode; - lodDim = newLodDimension; - } - - @Override - public void run() - { - //try - { - // 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) - { - case NONE: - // don't generate - break; - case BIOME_ONLY: - case BIOME_ONLY_SIMULATE_HEIGHT: - // fastest - worldGenWrapper.generateBiomesOnly(pos, generationMode); - break; - case SURFACE: - // faster - worldGenWrapper.generateSurface(pos); - break; - case FEATURES: - // fast - worldGenWrapper.generateFeatures(pos); - break; - case FULL: - // very slow - worldGenWrapper.generateFull(pos); - break; - } - - -// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z)); -// if (dataExistence) -// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!"); -// else -// ClientProxy.LOGGER.info(pos.x + " " + pos.z); - - // shows the pool size, active threads, queued tasks and completed tasks -// ClientProxy.LOGGER.info(genThreads.toString()); - -// long endTime = System.currentTimeMillis(); -// System.out.println(endTime - startTime); - - }// if in range - } - //catch (Exception e) - //{ - // ClientApi.LOGGER.error(LodChunkGenThread.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.

- *

- * 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 static void restartExecutorService() - { - if (genThreads != null && !genThreads.isShutdown()) - { - genThreads.shutdownNow(); - } - genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build()); - } - -} diff --git a/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java index e97498c7c..176d8ba1e 100644 --- a/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java +++ b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java @@ -25,11 +25,12 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +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.objects.PosToGenerateContainer; import com.seibel.lod.core.objects.lod.LodDimension; -import com.seibel.lod.core.render.LodRenderer; import com.seibel.lod.core.util.DetailDistanceUtil; import com.seibel.lod.core.util.LevelPosUtil; import com.seibel.lod.core.util.LodThreadFactory; @@ -56,6 +57,9 @@ public class LodWorldGenerator /** 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")); + private ExecutorService genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), + new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build()); + /** we only want to queue up one generator thread at a time */ private boolean generatorThreadRunning = false; @@ -83,19 +87,14 @@ public class LodWorldGenerator */ public static final LodWorldGenerator INSTANCE = new LodWorldGenerator(); - - - private LodWorldGenerator() - { - - } + private LodWorldGenerator() {} /** * Queues up LodNodeGenWorkers for the given lodDimension. * @param renderer needed so the LodNodeGenWorkers can flag that the * buffers need to be rebuilt. */ - public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder) + public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder) { if (CONFIG.client().worldGenerator().getDistanceGenerationMode() != DistanceGenerationMode.NONE && !generatorThreadRunning @@ -107,7 +106,7 @@ public class LodWorldGenerator // just in case the config changed maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * 8; - Thread generatorThread = new Thread(() -> + Runnable generatorFunc = (() -> { try { @@ -159,8 +158,7 @@ public class LodWorldGenerator positionsWaitingToBeGenerated.add(chunkPos); numberOfChunksWaitingToGenerate.addAndGet(1); - LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld); - genWorker.queueWork(); + queueWork(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld); } @@ -185,8 +183,7 @@ public class LodWorldGenerator positionsWaitingToBeGenerated.add(chunkPos); numberOfChunksWaitingToGenerate.addAndGet(1); - LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld); - genWorker.queueWork(); + queueWork(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld); } } @@ -201,9 +198,138 @@ public class LodWorldGenerator generatorThreadRunning = false; } }); - - mainGenThread.execute(generatorThread); + if (WRAPPER_FACTORY.isWorldGeneratorSingleThreaded()) { + generatorFunc.run(); + } else { + mainGenThread.execute(generatorFunc); + } } // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning } // queueGenerationRequests + private void queueWork(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode, + 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 (CONFIG.client().worldGenerator().getDistanceGenerationMode() == DistanceGenerationMode.FULL + || WRAPPER_FACTORY.isWorldGeneratorSingleThreaded()) + { + // if we are using FULL generation there is no reason + // to queue up a bunch of generation requests, + // because MC's internal server (as of 1.16.5) only + // responds with a single thread. And we don't + // want to cause more lag than necessary or queue up + // requests that may end up being unneeded. + // In 1.17+, world generation becomes completely single + // threaded. So to allow that, we check the boolean for + // whether the wrapper requires single thread + method.run(); + } + 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) + { + // try + { + var 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) + { + case NONE: + // don't generate + break; + case BIOME_ONLY: + case BIOME_ONLY_SIMULATE_HEIGHT: + // fastest + worldGenWrapper.generateBiomesOnly(pos, generationMode); + break; + case SURFACE: + // faster + worldGenWrapper.generateSurface(pos); + break; + case FEATURES: + // fast + worldGenWrapper.generateFeatures(pos); + break; + case FULL: + // very slow + worldGenWrapper.generateFull(pos); + break; + } + +// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z)); +// if (dataExistence) +// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!"); +// else +// ClientProxy.LOGGER.info(pos.x + " " + pos.z); + + // shows the pool size, active threads, queued tasks and completed tasks +// ClientProxy.LOGGER.info(genThreads.toString()); + + }// if in range + } + // catch (Exception e) + // { + // 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.

+ *

+ * 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 (genSubThreads != null && !genSubThreads.isShutdown()) + { + genSubThreads.shutdownNow(); + } + genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), + new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build()); + } + } diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java index 123aa33f7..980cdcf31 100644 --- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java @@ -45,4 +45,7 @@ public interface IWrapperFactory AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper); + + // FIXME: A temp getter for chunk gen mutli thread settings. Using default for backward compatibility + default boolean isWorldGeneratorSingleThreaded() {return false;} }