From 446c2740942eb4994df8864fbc2fc8defabadd7a Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 29 Oct 2023 15:04:32 -0500 Subject: [PATCH] Reduce CPU load when moving around the world Should've made this multiple commits, but too late now - Create ConfigThreadPool to remove duplicate thread setup/config code - Move configurable thread pools into their own ThreadPools class - Add a semaphore to limit how many LOD builder/lighting/bufferBuilder threads can be active at once --- .../client/IDhApiMultiThreadingConfig.java | 21 ++- .../client/DhApiMultiThreadingConfig.java | 12 +- .../core/api/internal/SharedApi.java | 84 +--------- .../distanthorizons/core/config/Config.java | 74 ++------- .../ThreadPresetConfigEventHandler.java | 95 ++--------- .../ColumnRenderBufferBuilder.java | 79 +--------- .../transformers/ChunkToLodBuilder.java | 61 +------ .../FullDataToRenderDataTransformer.java | 57 +------ .../fullDatafile/FullDataFileHandler.java | 58 ------- .../file/fullDatafile/FullDataMetaFile.java | 14 +- .../fullDatafile/IFullDataSourceProvider.java | 2 - .../file/renderfile/RenderDataMetaFile.java | 2 +- .../renderfile/RenderSourceFileHandler.java | 14 +- .../core/generation/WorldGenerationQueue.java | 70 +------- .../distanthorizons/core/util/ThreadUtil.java | 48 +++--- .../core/util/threading/ConfigThreadPool.java | 102 ++++++++++++ .../DhThreadFactory.java | 16 +- .../RateLimitedThreadPoolExecutor.java | 70 +++++++- .../core/util/threading/ThreadPools.java | 149 ++++++++++++++++++ .../assets/distanthorizons/lang/en_us.json | 31 ++-- 20 files changed, 427 insertions(+), 632 deletions(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/util/threading/ConfigThreadPool.java rename core/src/main/java/com/seibel/distanthorizons/core/util/{objects => threading}/DhThreadFactory.java (91%) rename core/src/main/java/com/seibel/distanthorizons/core/util/{objects => threading}/RateLimitedThreadPoolExecutor.java (55%) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPools.java diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiMultiThreadingConfig.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiMultiThreadingConfig.java index c43428908..469569e2b 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiMultiThreadingConfig.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiMultiThreadingConfig.java @@ -26,7 +26,7 @@ import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup; * Distant Horizons' threading configuration. * * @author James Seibel - * @version 2023-6-14 + * @version 2023-10-29 * @since API 1.0.0 */ public interface IDhApiMultiThreadingConfig extends IDhApiConfigGroup @@ -41,21 +41,18 @@ public interface IDhApiMultiThreadingConfig extends IDhApiConfigGroup */ IDhApiConfigValue worldGeneratorThreads(); - /** Defines how many buffer (GPU Terrain data) builder threads are used. */ - IDhApiConfigValue bufferBuilderThreads(); - /** Defines how many file handler threads are used. */ IDhApiConfigValue fileHandlerThreads(); /** - * Defines how many Full to Render data converter threads are used.

- * - * Full data - Distant Horizons data based on BlockState and Biome IDs
- * Render data - color data used when Distant Horizons is rendering + * Defines how many threads are used + * to build LODs.

+ * + * This includes:
+ * - lighting
+ * - Chunk -> LOD conversion
+ * - Buffer generation
*/ - IDhApiConfigValue dataConverterThreads(); - - /** Defines how many threads should be used to convert Minecraft chunks to LOD data. */ - IDhApiConfigValue chunkLodConverterThreads(); + IDhApiConfigValue lodBuilderThreads(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiMultiThreadingConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiMultiThreadingConfig.java index da80be1ba..ebd8f1eba 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiMultiThreadingConfig.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiMultiThreadingConfig.java @@ -36,20 +36,12 @@ public class DhApiMultiThreadingConfig implements IDhApiMultiThreadingConfig public IDhApiConfigValue worldGeneratorThreads() { return new DhApiConfigValue(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads); } - @Override - public IDhApiConfigValue bufferBuilderThreads() - { return new DhApiConfigValue(Config.Client.Advanced.MultiThreading.numberOfBufferBuilderThreads); } - @Override public IDhApiConfigValue fileHandlerThreads() { return new DhApiConfigValue(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads); } @Override - public IDhApiConfigValue dataConverterThreads() - { return new DhApiConfigValue(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads); } - - @Override - public IDhApiConfigValue chunkLodConverterThreads() - { return new DhApiConfigValue(Config.Client.Advanced.MultiThreading.numberOfChunkLodConverterThreads); } + public IDhApiConfigValue lodBuilderThreads() + { return new DhApiConfigValue(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index ce4a231ca..e697ed20c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -21,19 +21,13 @@ package com.seibel.distanthorizons.core.api.internal; import com.seibel.distanthorizons.core.Initializer; import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; -import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder; -import com.seibel.distanthorizons.core.dataObjects.transformers.ChunkToLodBuilder; -import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer; -import com.seibel.distanthorizons.core.file.fullDatafile.FullDataFileHandler; import com.seibel.distanthorizons.core.generation.DhLightingEngine; -import com.seibel.distanthorizons.core.generation.WorldGenerationQueue; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; -import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.Pair; +import com.seibel.distanthorizons.core.util.threading.ThreadPools; import com.seibel.distanthorizons.core.world.*; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; @@ -60,10 +54,6 @@ public class SharedApi private static AbstractDhWorld currentWorld; private static int lastWorldGenTickDelta = 0; - // TODO make an interface or object for handling thread pools like this, this same code is in ~8 places - private static ThreadPoolExecutor lightPopulatorThreadPool; - private static ConfigChangeListener threadConfigListener; - private static final Timer CHUNK_UPDATE_TIMER = new Timer(); @@ -92,27 +82,14 @@ public class SharedApi // access the MC level at inappropriate times, which can cause exceptions if (currentWorld != null) { - // static thread pool setup - FullDataToRenderDataTransformer.setupExecutorService(); - FullDataFileHandler.setupExecutorService(); - ColumnRenderBufferBuilder.setupExecutorService(); - WorldGenerationQueue.setupWorldGenThreadPool(); - ChunkToLodBuilder.setupExecutorService(); - SharedApi.setupExecutorService(); + ThreadPools.setupThreadPools(); } else { - // static thread pool shutdown - FullDataToRenderDataTransformer.shutdownExecutorService(); - FullDataFileHandler.shutdownExecutorService(); - ColumnRenderBufferBuilder.shutdownExecutorService(); - WorldGenerationQueue.shutdownWorldGenThreadPool(); - ChunkToLodBuilder.shutdownExecutorService(); - SharedApi.shutdownExecutorService(); - + ThreadPools.shutdownThreadPools(); DebugRenderer.clearRenderables(); - // recommend that the garbage collector cleans up any objects from the old world + // recommend that the garbage collector cleans up any objects from the old world and thread pools System.gc(); } } @@ -250,9 +227,10 @@ public class SharedApi // lighting the chunk needs to be done on a separate thread to prevent lagging any of the event threads - lightPopulatorThreadPool.execute(() -> + ThreadPoolExecutor executor = ThreadPools.getLightPopulatorExecutor(); + executor.execute(() -> { - LOGGER.trace(chunkWrapper.getChunkPos() + " " + lightPopulatorThreadPool.getActiveCount() + " / " + lightPopulatorThreadPool.getQueue().size() + " - " + lightPopulatorThreadPool.getCompletedTaskCount()); + LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount()); try { @@ -317,52 +295,4 @@ public class SharedApi } - - //==========================// - // executor handler methods // - //==========================// - - /** - * Creates a new executor.
- * Does nothing if an executor already exists. - */ - public static void setupExecutorService() - { - // static setup - if (threadConfigListener == null) - { - threadConfigListener = new ConfigChangeListener<>(Config.Client.Advanced.MultiThreading.numberOfChunkLightBakingThreads, (threadCount) -> { setThreadPoolSize(threadCount); }); - } - - - if (lightPopulatorThreadPool == null || lightPopulatorThreadPool.isTerminated()) - { - LOGGER.info("Starting " + ChunkToLodBuilder.class.getSimpleName()); - setThreadPoolSize(Config.Client.Advanced.MultiThreading.numberOfChunkLightBakingThreads.get()); - } - } - public static void setThreadPoolSize(int threadPoolSize) - { - if (lightPopulatorThreadPool != null && !lightPopulatorThreadPool.isTerminated()) - { - lightPopulatorThreadPool.shutdownNow(); - } - - lightPopulatorThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, SharedApi.class.getSimpleName()+" - Light Populator", Config.Client.Advanced.MultiThreading.runTimeRatioForChunkLightBakingThreads); - } - - /** - * Stops any executing tasks and destroys the executor.
- * Does nothing if the executor isn't running. - */ - public static void shutdownExecutorService() - { - if (lightPopulatorThreadPool != null) - { - LOGGER.info("Stopping " + ChunkToLodBuilder.class.getSimpleName()); - lightPopulatorThreadPool.shutdownNow(); - } - } - - } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index ef96111a9..ef344012d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -865,24 +865,6 @@ public class Config .comment(THREAD_RUN_TIME_RATIO_NOTE) .build(); - public static ConfigEntry numberOfBufferBuilderThreads = new ConfigEntry.Builder() - .setMinDefaultMax(1, - ThreadPresetConfigEventHandler.getBufferBuilderDefaultThreadCount(), - Runtime.getRuntime().availableProcessors()) - .comment("" - + "How many threads are used when building geometry data for the GPU? \n" - + "\n" - + "If you experience high CPU usage when NOT generating distant \n" - + "LODs, lower this number. A higher number will make \n" - + "LODs' transition faster when moving around the world. \n" - + "\n" - + THREAD_NOTE) - .build(); - public static final ConfigEntry runTimeRatioForBufferBuilderThreads = new ConfigEntry.Builder() - .setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getBufferBuilderDefaultRunTimeRatio(), 1.0) - .comment(THREAD_RUN_TIME_RATIO_NOTE) - .build(); - public static final ConfigEntry numberOfFileHandlerThreads = new ConfigEntry.Builder() .setMinDefaultMax(1, ThreadPresetConfigEventHandler.getFileHandlerDefaultThreadCount(), @@ -901,60 +883,30 @@ public class Config .comment(THREAD_RUN_TIME_RATIO_NOTE) .build(); - public static final ConfigEntry numberOfDataTransformerThreads = new ConfigEntry.Builder() + public static final ConfigEntry numberOfLodBuilderThreads = new ConfigEntry.Builder() .setMinDefaultMax(1, - ThreadPresetConfigEventHandler.getDataTransformerDefaultThreadCount(), + ThreadPresetConfigEventHandler.getLodBuilderDefaultThreadCount(), Runtime.getRuntime().availableProcessors()) .comment("" - + "How many threads should be used when converting full ID data to render data? \n" + + "How many threads should be used when building LODs? \n" + "\n" - + "These threads run both when terrain is generated and when\n" - + "certain graphics settings are changed. \n" - + "\n" - + "Generally this number should be equal to the number of world\n" - + "generator threads, although these threads shouldn't run as\n" - + "often (or as long) as the world generator threads.\n" + + "These threads run when terrain is generated, when\n" + + "certain graphics settings are changed, and when moving around the world. \n" + "\n" + THREAD_NOTE) .build(); - public static final ConfigEntry runTimeRatioForDataTransformerThreads = new ConfigEntry.Builder() - .setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getDataTransformerDefaultRunTimeRatio(), 1.0) + public static final ConfigEntry runTimeRatioForLodBuilderThreads = new ConfigEntry.Builder() + .setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getLodBuilderDefaultRunTimeRatio(), 1.0) .comment(THREAD_RUN_TIME_RATIO_NOTE) .build(); - - public static final ConfigEntry numberOfChunkLodConverterThreads = new ConfigEntry.Builder() - .setMinDefaultMax(1, - ThreadPresetConfigEventHandler.getChunkLodConverterDefaultThreadCount(), - Runtime.getRuntime().availableProcessors()) + public static final ConfigEntry enableLodBuilderThreadLimiting = new ConfigEntry.Builder() + .set(true) .comment("" - + "How many threads should be used to convert Minecraft chunks into LOD data? \n" + + "Should only be disabled if deadlock occurs and LODs refuse to update. \n" + + "This will cause CPU usage to drastically increase for the Lod Builder threads. \n" + "\n" - + "These threads run both when terrain is generated and when\n" - + "chunks are loaded, unloaded, and modified. \n" - + "\n" - + THREAD_NOTE) - .build(); - public static final ConfigEntry runTimeRatioForChunkLodConverterThreads = new ConfigEntry.Builder() - .setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getChunkLodConverterDefaultRunTimeRatio(), 1.0) - .comment(THREAD_RUN_TIME_RATIO_NOTE) - .build(); - - public static final ConfigEntry numberOfChunkLightBakingThreads = new ConfigEntry.Builder() - .setMinDefaultMax(1, - ThreadPresetConfigEventHandler.getLightBakingDefaultThreadCount(), - Runtime.getRuntime().availableProcessors()) - .comment("" - + "How many threads should be used to either pull existing or generating new lighting for chunks \n" - + "that were recently loading/unloaded? \n" - + "\n" - + "These threads run when traveling around the world\n" - + "and when moving between dimensions. \n" - + "\n" - + THREAD_NOTE) - .build(); - public static final ConfigEntry runTimeRatioForChunkLightBakingThreads = new ConfigEntry.Builder() - .setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getLightBakingDefaultRunTimeRatio(), 1.0) - .comment(THREAD_RUN_TIME_RATIO_NOTE) + + "Note that if deadlock did occur restarting MC may be necessary to stop the locked threads. \n" + + "") .build(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java index 0445f4167..a7d47c523 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java @@ -63,28 +63,6 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan }}); - public static int getBufferBuilderDefaultThreadCount() { return getThreadCountByPercent(0.1); } - private final ConfigEntryWithPresetOptions bufferBuilderThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfBufferBuilderThreads, - new HashMap() - {{ - this.put(EThreadPreset.MINIMAL_IMPACT, 1); - this.put(EThreadPreset.LOW_IMPACT, getBufferBuilderDefaultThreadCount()); - this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2)); - this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.4)); - //this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0)); - }}); - public static double getBufferBuilderDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.5 : 0.75; } - private final ConfigEntryWithPresetOptions bufferBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForBufferBuilderThreads, - new HashMap() - {{ - this.put(EThreadPreset.MINIMAL_IMPACT, 0.25); - this.put(EThreadPreset.LOW_IMPACT, getBufferBuilderDefaultRunTimeRatio()); - this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.75 : 1.0); - this.put(EThreadPreset.AGGRESSIVE, 1.0); - //this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0); - }}); - - public static int getFileHandlerDefaultThreadCount() { return getThreadCountByPercent(0.1); } private final ConfigEntryWithPresetOptions fileHandlerThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads, new HashMap() @@ -107,68 +85,24 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan }}); - public static int getDataTransformerDefaultThreadCount() { return getThreadCountByPercent(0.1); } - private final ConfigEntryWithPresetOptions dataTransformerThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads, + public static int getLodBuilderDefaultThreadCount() { return getThreadCountByPercent(0.1); } + private final ConfigEntryWithPresetOptions lodBuilderThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads, new HashMap() {{ this.put(EThreadPreset.MINIMAL_IMPACT, 1); - this.put(EThreadPreset.LOW_IMPACT, getDataTransformerDefaultThreadCount()); - this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2)); - this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.2)); - //this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0)); - }}); - public static double getDataTransformerDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.5 : 1; } - private final ConfigEntryWithPresetOptions dataTransformerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForDataTransformerThreads, - new HashMap() - {{ - this.put(EThreadPreset.MINIMAL_IMPACT, 0.25); - this.put(EThreadPreset.LOW_IMPACT, getDataTransformerDefaultRunTimeRatio()); - this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.75 : 1); - this.put(EThreadPreset.AGGRESSIVE, 1.0); - //this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0); - }}); - - - public static int getChunkLodConverterDefaultThreadCount() { return getThreadCountByPercent(0.1); } - private final ConfigEntryWithPresetOptions chunkLodConverterThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfChunkLodConverterThreads, - new HashMap() - {{ - this.put(EThreadPreset.MINIMAL_IMPACT, 1); - this.put(EThreadPreset.LOW_IMPACT, getChunkLodConverterDefaultThreadCount()); + this.put(EThreadPreset.LOW_IMPACT, getLodBuilderDefaultThreadCount()); this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2)); this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.4)); //this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0)); }}); - public static double getChunkLodConverterDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.5 : 0.75; } - private final ConfigEntryWithPresetOptions chunkLodConverterRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForChunkLodConverterThreads, - new HashMap() - {{ - this.put(EThreadPreset.MINIMAL_IMPACT, LOW_THREAD_COUNT_CPU ? 0.1 : 0.25); - this.put(EThreadPreset.LOW_IMPACT, getChunkLodConverterDefaultRunTimeRatio()); - this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.75 : 1); - this.put(EThreadPreset.AGGRESSIVE, 1.0); - //this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0); - }}); - - - public static int getLightBakingDefaultThreadCount() { return getThreadCountByPercent(0.1); } - private final ConfigEntryWithPresetOptions lightBakingThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfChunkLightBakingThreads, - new HashMap() - {{ - this.put(EThreadPreset.MINIMAL_IMPACT, 1); - this.put(EThreadPreset.LOW_IMPACT, getChunkLodConverterDefaultThreadCount()); - this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2)); - this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.4)); - //this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0)); - }}); - public static double getLightBakingDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.25 : 0.4; } - private final ConfigEntryWithPresetOptions lightBakingRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForChunkLightBakingThreads, + public static double getLodBuilderDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.25 : 0.5; } + private final ConfigEntryWithPresetOptions lodBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads, new HashMap() {{ this.put(EThreadPreset.MINIMAL_IMPACT, 0.1); - this.put(EThreadPreset.LOW_IMPACT, getChunkLodConverterDefaultRunTimeRatio()); - this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.5 : 0.65); - this.put(EThreadPreset.AGGRESSIVE, LOW_THREAD_COUNT_CPU ? 0.75 : 1.0); + this.put(EThreadPreset.LOW_IMPACT, getLodBuilderDefaultRunTimeRatio()); + this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.5 : 0.75); + this.put(EThreadPreset.AGGRESSIVE, 1.0); //this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0); }}); @@ -185,20 +119,11 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan this.configList.add(this.worldGenThreadCount); this.configList.add(this.worldGenRunTime); - this.configList.add(this.bufferBuilderThreadCount); - this.configList.add(this.bufferBuilderRunTime); - this.configList.add(this.fileHandlerThreadCount); this.configList.add(this.fileHandlerRunTime); - this.configList.add(this.dataTransformerThreadCount); - this.configList.add(this.dataTransformerRunTime); - - this.configList.add(this.chunkLodConverterThreadCount); - this.configList.add(this.chunkLodConverterRunTime); - - this.configList.add(this.lightBakingThreadCount); - this.configList.add(this.lightBakingRunTime); + this.configList.add(this.lodBuilderThreadCount); + this.configList.add(this.lodBuilderRunTime); for (ConfigEntryWithPresetOptions config : this.configList) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java index 369eefd00..dcdd65be7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java @@ -22,7 +22,6 @@ package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering; import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; @@ -32,17 +31,14 @@ import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil; -import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.Reference; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; +import com.seibel.distanthorizons.core.util.threading.ThreadPools; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; /** * Used to populate the buffers in a {@link ColumnRenderSource} object. @@ -56,10 +52,6 @@ public class ColumnRenderBufferBuilder private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - public static ExecutorService bufferUploaderThreadPool = ThreadUtil.makeSingleThreadPool("Column Buffer Uploader"); - public static ExecutorService bufferBuilderThreadPool; - private static ConfigChangeListener configListener; - public static final int MAX_NUMBER_OF_CONCURRENT_CALLS_PER_THREAD = 3; public static int maxNumberOfConcurrentCalls = MAX_NUMBER_OF_CONCURRENT_CALLS_PER_THREAD; @@ -109,7 +101,7 @@ public class ColumnRenderBufferBuilder LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3); throw e3; } - }, bufferBuilderThreadPool) + }, ThreadPools.getBufferBuilderExecutor()) .thenApplyAsync((quadBuilder) -> { try @@ -144,7 +136,7 @@ public class ColumnRenderBufferBuilder LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3); throw e3; } - }, bufferUploaderThreadPool) + }, ThreadPools.getBufferUploaderExecutor()) .handle((columnRenderBuffer, ex) -> { //LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos); @@ -367,69 +359,4 @@ public class ColumnRenderBufferBuilder return newVbos; } - - - //==========================// - // executor handler methods // - //==========================// - - /** - * Creates a new executor.
- * Does nothing if an executor already exists. - */ - public static void setupExecutorService() - { - // static setup - if (configListener == null) - { - configListener = new ConfigChangeListener<>(Config.Client.Advanced.MultiThreading.numberOfBufferBuilderThreads, (threadCount) -> { setThreadPoolSize(threadCount); }); - } - - - if (bufferBuilderThreadPool == null || bufferBuilderThreadPool.isTerminated()) - { - LOGGER.info("Starting " + ColumnRenderBufferBuilder.class.getSimpleName()); - setThreadPoolSize(Config.Client.Advanced.MultiThreading.numberOfBufferBuilderThreads.get()); - } - } - public static void setThreadPoolSize(int threadPoolSize) - { - if (bufferBuilderThreadPool != null) - { - // close the previous thread pool if one exists - bufferBuilderThreadPool.shutdown(); - } - - bufferBuilderThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, "Buffer Builder", Config.Client.Advanced.MultiThreading.runTimeRatioForBufferBuilderThreads); - maxNumberOfConcurrentCalls = threadPoolSize * MAX_NUMBER_OF_CONCURRENT_CALLS_PER_THREAD; - } - - /** - * Stops any executing tasks and destroys the executor.
- * Does nothing if the executor isn't running. - */ - public static void shutdownExecutorService() - { - if (bufferBuilderThreadPool != null) - { - LOGGER.info("Stopping " + ColumnRenderBufferBuilder.class.getSimpleName()); - bufferBuilderThreadPool.shutdownNow(); - } - } - - - - //=========// - // getters // - //=========// - - // TODO move static methods to their own class to avoid confusion - private static long getCurrentJobsCount() - { - long jobs = ((ThreadPoolExecutor) bufferBuilderThreadPool).getQueue().stream().filter(runnable -> !((Future) runnable).isDone()).count(); - jobs += ((ThreadPoolExecutor) bufferUploaderThreadPool).getQueue().stream().filter(runnable -> !((Future) runnable).isDone()).count(); - return jobs; - } - public static boolean isBusy() { return getCurrentJobsCount() > maxNumberOfConcurrentCalls; } - } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java index ee87ec95a..19d78f4d0 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java @@ -23,12 +23,11 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.pos.DhChunkPos; -import com.seibel.distanthorizons.core.util.ThreadUtil; +import com.seibel.distanthorizons.core.util.threading.ThreadPools; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import org.apache.logging.log4j.LogManager; @@ -38,10 +37,6 @@ public class ChunkToLodBuilder implements AutoCloseable public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), () -> Config.Client.Advanced.Logging.logLodBuilderEvent.get()); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - private static int threadCount = -1; - private static ExecutorService executorThreadPool = null; - private static ConfigChangeListener threadConfigListener; - public static final long MAX_TICK_TIME_NS = 1000000000L / 20L; private final ConcurrentHashMap concurrentChunkToBuildByChunkPos = new ConcurrentHashMap<>(); @@ -84,9 +79,11 @@ public class ChunkToLodBuilder implements AutoCloseable return future; } + // TODO why on tick? public void tick() { - if (this.runningCount.get() >= this.threadCount) + int threadCount = ThreadPools.getWorkerThreadCount(); + if (this.runningCount.get() >= threadCount) { return; } @@ -119,7 +116,7 @@ public class ChunkToLodBuilder implements AutoCloseable { this.runningCount.decrementAndGet(); } - }, executorThreadPool); + }, ThreadPools.getChunkToLodBuilderExecutor()); } } private void tickThreadTask() @@ -213,54 +210,6 @@ public class ChunkToLodBuilder implements AutoCloseable - //==========================// - // executor handler methods // - //==========================// - - /** - * Creates a new executor.
- * Does nothing if an executor already exists. - */ - public static void setupExecutorService() - { - // static setup - if (threadConfigListener == null) - { - threadConfigListener = new ConfigChangeListener<>(Config.Client.Advanced.MultiThreading.numberOfChunkLodConverterThreads, (threadCount) -> { setThreadPoolSize(threadCount); }); - } - - - if (executorThreadPool == null || executorThreadPool.isTerminated()) - { - LOGGER.info("Starting " + ChunkToLodBuilder.class.getSimpleName()); - setThreadPoolSize(Config.Client.Advanced.MultiThreading.numberOfChunkLodConverterThreads.get()); - } - } - public static void setThreadPoolSize(int threadPoolSize) - { - if (executorThreadPool != null && !executorThreadPool.isTerminated()) - { - executorThreadPool.shutdownNow(); - } - - threadCount = threadPoolSize; - executorThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, ChunkToLodBuilder.class.getSimpleName(), Config.Client.Advanced.MultiThreading.runTimeRatioForChunkLodConverterThreads); - } - - /** - * Stops any executing tasks and destroys the executor.
- * Does nothing if the executor isn't running. - */ - public static void shutdownExecutorService() - { - if (executorThreadPool != null) - { - LOGGER.info("Stopping " + ChunkToLodBuilder.class.getSimpleName()); - executorThreadPool.shutdownNow(); - } - } - - //==============// // base methods // diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java index 459829e8e..7d5f28923 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java @@ -29,6 +29,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFull import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; +import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.level.IDhClientLevel; @@ -60,17 +61,13 @@ public class FullDataToRenderDataTransformer private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - private static ExecutorService transformerThreadPool = null; - private static ConfigChangeListener configListener; - //==============================// // public transformer interface // //==============================// - public static CompletableFuture transformFullDataToRenderSourceUsingExecutorAsync(IFullDataSource fullDataSource, IDhClientLevel level) { return CompletableFuture.supplyAsync(() -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool); } - private static ColumnRenderSource transformFullDataToRenderSource(IFullDataSource fullDataSource, IDhClientLevel level) + public static ColumnRenderSource transformFullDataToRenderSource(IFullDataSource fullDataSource, IDhClientLevel level) { if (fullDataSource == null) { @@ -332,54 +329,4 @@ public class FullDataToRenderDataTransformer } } - - - //==========================// - // executor handler methods // - //==========================// - - /** - * Creates a new executor.
- * Does nothing if an executor already exists. - */ - public static void setupExecutorService() - { - // static setup - if (configListener == null) - { - configListener = new ConfigChangeListener<>(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads, (threadCount) -> { setThreadPoolSize(threadCount); }); - } - - - // TODO this didn't seem to be re-sizing when changed via the config - if (transformerThreadPool == null || transformerThreadPool.isTerminated()) - { - LOGGER.info("Starting " + FullDataToRenderDataTransformer.class.getSimpleName()); - setThreadPoolSize(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads.get()); - } - } - public static void setThreadPoolSize(int threadPoolSize) - { - if (transformerThreadPool != null) - { - // close the previous thread pool if one exists - transformerThreadPool.shutdown(); - } - - transformerThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, "Full/Render Data Transformer", Config.Client.Advanced.MultiThreading.runTimeRatioForDataTransformerThreads); - } - - /** - * Stops any executing tasks and destroys the executor.
- * Does nothing if the executor isn't running. - */ - public static void shutdownExecutorService() - { - if (transformerThreadPool != null) - { - LOGGER.info("Stopping " + FullDataToRenderDataTransformer.class.getSimpleName()); - transformerThreadPool.shutdownNow(); - } - } - } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java index e664a2984..c1aab9afc 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java @@ -20,7 +20,6 @@ package com.seibel.distanthorizons.core.file.fullDatafile; import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dataObjects.fullData.loader.AbstractFullDataSourceLoader; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.HighDetailIncompleteFullDataSource; @@ -36,9 +35,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFull import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.sql.FullDataRepo; import com.seibel.distanthorizons.core.sql.MetaDataDto; -import com.seibel.distanthorizons.core.util.FileUtil; import com.seibel.distanthorizons.core.util.LodUtil; -import com.seibel.distanthorizons.core.util.ThreadUtil; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; @@ -55,9 +52,6 @@ public class FullDataFileHandler implements IFullDataSourceProvider { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - protected static ExecutorService fileHandlerThreadPool; - protected static ConfigChangeListener configListener; - protected final ConcurrentHashMap loadedMetaFileBySectionPos = new ConcurrentHashMap<>(); protected final IDhLevel level; @@ -493,58 +487,6 @@ public class FullDataFileHandler implements IFullDataSourceProvider - //==========================// - // executor handler methods // - //==========================// - - /** - * Creates a new executor.
- * Does nothing if an executor already exists. - */ - public static void setupExecutorService() - { - // static setup - if (configListener == null) - { - configListener = new ConfigChangeListener<>(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads, (threadCount) -> { setThreadPoolSize(threadCount); }); - } - - - if (fileHandlerThreadPool == null || fileHandlerThreadPool.isTerminated()) - { - LOGGER.info("Starting " + FullDataFileHandler.class.getSimpleName()); - setThreadPoolSize(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads.get()); - } - } - public static void setThreadPoolSize(int threadPoolSize) - { - if (fileHandlerThreadPool != null) - { - // close the previous thread pool if one exists - fileHandlerThreadPool.shutdown(); - } - - fileHandlerThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, FullDataFileHandler.class.getSimpleName() + "Thread", Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads); - } - - /** - * Stops any executing tasks and destroys the executor.
- * Does nothing if the executor isn't running. - */ - public static void shutdownExecutorService() - { - if (fileHandlerThreadPool != null) - { - LOGGER.info("Stopping " + FullDataFileHandler.class.getSimpleName()); - fileHandlerThreadPool.shutdownNow(); - } - } - - @Override - public ExecutorService getIOExecutor() { return fileHandlerThreadPool; } - - - //=========// // cleanup // //=========// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java index 2e565437a..c13f21890 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java @@ -45,8 +45,10 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.sql.MetaDataDto; import com.seibel.distanthorizons.core.util.AtomicsUtil; import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; +import com.seibel.distanthorizons.core.util.threading.ThreadPools; import org.apache.logging.log4j.Logger; /** Represents a File that contains a {@link IFullDataSource}. */ @@ -244,8 +246,8 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I } - ExecutorService executorService = this.fullDataSourceProvider.getIOExecutor(); - if (!executorService.isTerminated()) + ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor(); + if (!executor.isTerminated()) { // load the data source @@ -276,7 +278,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I throw new CompletionException(ex); } return fullDataSource; - }, executorService) + }, executor) .thenCompose((fullDataSource) -> this.applyWriteQueueAndSaveAsync(fullDataSource)) .thenAccept((fullDataSource) -> { @@ -341,12 +343,12 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I } else { - ExecutorService executorService = this.fullDataSourceProvider.getIOExecutor(); - if (!executorService.isTerminated()) + ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor(); + if (!executor.isTerminated()) { // wait for the update to finish before returning the data source - CompletableFuture.supplyAsync(() -> cachedFullDataSource, executorService) + CompletableFuture.supplyAsync(() -> cachedFullDataSource, executor) .thenCompose((fullDataSource) -> this.applyWriteQueueAndSaveAsync(fullDataSource)) .thenAccept((fullDataSource) -> { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/IFullDataSourceProvider.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/IFullDataSourceProvider.java index 13f8e8fbe..df6403cb4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/IFullDataSourceProvider.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/IFullDataSourceProvider.java @@ -43,8 +43,6 @@ public interface IFullDataSourceProvider extends AutoCloseable /** Can be used to update world gen queues or run any other data checking necessary when initially loading a file */ default void onRenderDataFileLoaded(DhSectionPos pos) { } - ExecutorService getIOExecutor(); - @Nullable FullDataMetaFile getFileIfExist(DhSectionPos pos); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderDataMetaFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderDataMetaFile.java index abb2d9f36..7ec584743 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderDataMetaFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderDataMetaFile.java @@ -318,7 +318,7 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements ColumnRenderSource newRenderSource = null; try { - newRenderSource = FullDataToRenderDataTransformer.transformFullDataToRenderSourceUsingExecutorAsync(fullDataSource, this.clientLevel).join(); + newRenderSource = FullDataToRenderDataTransformer.transformFullDataToRenderSource(fullDataSource, this.clientLevel); } catch (Exception e) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java index f6ef01654..6396ba783 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java @@ -20,6 +20,7 @@ package com.seibel.distanthorizons.core.file.renderfile; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; +import com.seibel.distanthorizons.core.file.fullDatafile.FullDataFileHandler; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.f3.F3Screen; @@ -30,6 +31,7 @@ import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.sql.MetaDataDto; import com.seibel.distanthorizons.core.sql.RenderDataRepo; import com.seibel.distanthorizons.core.util.ThreadUtil; +import com.seibel.distanthorizons.core.util.threading.ThreadPools; import org.apache.logging.log4j.Logger; import java.io.File; @@ -43,7 +45,6 @@ public class RenderSourceFileHandler implements IRenderSourceProvider { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - private final ThreadPoolExecutor fileHandlerThreadPool; private final F3Screen.NestedMessage threadPoolMsg; protected final ConcurrentHashMap loadedMetaFileBySectionPos = new ConcurrentHashMap<>(); @@ -75,7 +76,6 @@ public class RenderSourceFileHandler implements IRenderSourceProvider { LOGGER.warn("Unable to create render data folder, file saving may fail."); } - this.fileHandlerThreadPool = ThreadUtil.makeSingleThreadPool("Render Source File Handler [" + this.clientLevel.getLevelWrapper().getDimensionType().getDimensionName() + "]"); this.threadPoolMsg = new F3Screen.NestedMessage(this::f3Log); @@ -103,7 +103,8 @@ public class RenderSourceFileHandler implements IRenderSourceProvider public CompletableFuture readAsync(DhSectionPos pos) { // don't continue if the handler has been shut down - if (this.fileHandlerThreadPool.isTerminated()) + ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor(); + if (executor.isTerminated()) { return CompletableFuture.completedFuture(null); } @@ -117,7 +118,7 @@ public class RenderSourceFileHandler implements IRenderSourceProvider return CompletableFuture.completedFuture(ColumnRenderSource.createEmptyRenderSource(pos)); } - CompletableFuture getDataSourceFuture = metaFile.getOrLoadCachedDataSourceAsync(this.fileHandlerThreadPool) + CompletableFuture getDataSourceFuture = metaFile.getOrLoadCachedDataSourceAsync(executor) .handle((renderSource, exception) -> { if (exception != null) @@ -264,10 +265,12 @@ public class RenderSourceFileHandler implements IRenderSourceProvider /** Returns what should be displayed in Minecraft's F3 debug menu */ private String[] f3Log() { + ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor(); + ArrayList lines = new ArrayList<>(); lines.add("Render Source File Handler [" + this.clientLevel.getClientLevelWrapper().getDimensionType().getDimensionName() + "]"); lines.add(" Loaded files: " + this.loadedMetaFileBySectionPos.size()); - lines.add(" Thread pool tasks: " + this.fileHandlerThreadPool.getQueue().size() + " (completed: " + this.fileHandlerThreadPool.getCompletedTaskCount() + ")"); + lines.add(" Thread pool tasks: " + executor.getQueue().size() + " (completed: " + executor.getCompletedTaskCount() + ")"); int totalFutures = this.taskTracker.size(); EnumMap tasksOutstanding = new EnumMap<>(ETaskType.class); @@ -311,7 +314,6 @@ public class RenderSourceFileHandler implements IRenderSourceProvider public void close() { LOGGER.info("Closing " + this.getClass().getSimpleName() + " with [" + this.loadedMetaFileBySectionPos.size() + "] files..."); - this.fileHandlerThreadPool.shutdown(); this.threadPoolMsg.close(); this.renderDataRepo.close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index 35011af14..0fd8cafe4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -31,14 +31,13 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder; -import com.seibel.distanthorizons.core.file.fullDatafile.FullDataFileHandler; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.util.ThreadUtil; -import com.seibel.distanthorizons.core.util.objects.DhThreadFactory; -import com.seibel.distanthorizons.core.util.objects.RateLimitedThreadPoolExecutor; +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; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import org.apache.logging.log4j.Logger; @@ -52,8 +51,6 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - public static final DhThreadFactory THREAD_FACTORY = new DhThreadFactory(ThreadUtil.THREAD_NAME_PREFIX + "World-Gen-Worker-Thread", Thread.MIN_PRIORITY); - private final IDhApiWorldGenerator generator; /** contains the positions that need to be generated */ @@ -96,9 +93,6 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender private final HashMap alreadyGeneratedPosHashSet = new HashMap<>(MAX_ALREADY_GENERATED_COUNT); private final Queue alreadyGeneratedPosQueue = new LinkedList<>(); - private static RateLimitedThreadPoolExecutor worldGeneratorThreadPool; - private static ConfigChangeListener configListener; - //==============// @@ -431,7 +425,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender Consumer chunkDataConsumer) { EDhApiDistantGeneratorMode generatorMode = Config.Client.Advanced.WorldGenerator.distantGeneratorMode.get(); - return this.generator.generateChunks(chunkPosMin.x, chunkPosMin.z, granularity, targetDataDetail, generatorMode, worldGeneratorThreadPool, (generatedObjectArray) -> + return this.generator.generateChunks(chunkPosMin.x, chunkPosMin.z, granularity, targetDataDetail, generatorMode, ThreadPools.getWorldGenExecutor(), (generatedObjectArray) -> { try { @@ -450,62 +444,6 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender - //==========================// - // executor handler methods // - //==========================// - - /** - * Creates a new executor.
- * Does nothing if an executor already exists. - */ - public static void setupWorldGenThreadPool() - { - // static setup - if (configListener == null) - { - configListener = new ConfigChangeListener<>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads, (threadCount) -> { setThreadPoolSize(threadCount); }); - } - - - if (worldGeneratorThreadPool == null || worldGeneratorThreadPool.isTerminated()) - { - LOGGER.info("Starting " + FullDataFileHandler.class.getSimpleName()); - setThreadPoolSize(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads.get()); - } - } - public static void setThreadPoolSize(int threadPoolSize) - { - if (worldGeneratorThreadPool != null) - { - // close the previous thread pool if one exists - worldGeneratorThreadPool.shutdown(); - } - - worldGeneratorThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, THREAD_FACTORY, Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads); - worldGeneratorThreadPool.setOnTerminatedEventHandler(WorldGenerationQueue::onWorldGenThreadPoolTerminated); - } - - /** - * Stops any executing tasks and destroys the executor.
- * Does nothing if the executor isn't running. - */ - public static void shutdownWorldGenThreadPool() - { - if (worldGeneratorThreadPool != null) - { - LOGGER.info("Stopping " + FullDataFileHandler.class.getSimpleName()); - worldGeneratorThreadPool.shutdownNow(); - } - } - - private static void onWorldGenThreadPoolTerminated() - { - LOGGER.debug("World generator thread pool terminated. Suggesting the JVM runs a garbage collection to clean up any loose world generation objects..."); - System.gc(); - } - - - //=========// // getters // //=========// @@ -580,7 +518,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender try { int waitTimeInSeconds = 3; - if (!worldGeneratorThreadPool.awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS)) + if (!ThreadPools.getWorldGenExecutor().awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS)) { LOGGER.warn("World generator thread pool shutdown didn't complete after [" + waitTimeInSeconds + "] seconds. Some world generator requests may still be running."); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/ThreadUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/ThreadUtil.java index f9fbdefad..63a102c64 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/ThreadUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/ThreadUtil.java @@ -1,18 +1,18 @@ /* * 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 . */ @@ -21,13 +21,19 @@ package com.seibel.distanthorizons.core.util; import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.config.types.ConfigEntry; -import com.seibel.distanthorizons.core.util.objects.DhThreadFactory; -import com.seibel.distanthorizons.core.util.objects.RateLimitedThreadPoolExecutor; +import com.seibel.distanthorizons.core.util.threading.DhThreadFactory; +import com.seibel.distanthorizons.core.util.threading.RateLimitedThreadPoolExecutor; +import com.seibel.distanthorizons.core.util.threading.ThreadPools; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.concurrent.*; +/** + * Handles thread pool creation. + * + * @see ThreadPools + */ public class ThreadUtil { private static final Logger LOGGER = LogManager.getLogger(); @@ -38,7 +44,6 @@ public class ThreadUtil public static int MINIMUM_RELATIVE_PRIORITY = -4; public static int DEFAULT_RELATIVE_PRIORITY = 0; - // TODO currently only used for RateLimitedThreadPools could this be used for all thread pools? /** used to track and remove old listeners for certain pools if the thread pool is recreated. */ private static final ConcurrentHashMap> THREAD_CHANGE_LISTENERS_BY_THREAD_NAME = new ConcurrentHashMap<>(); @@ -46,18 +51,9 @@ public class ThreadUtil - // create rate limited thread pool // + // rate limited thread pool // - public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, ConfigEntry runTimeRatioConfigEntry) - { - return makeRateLimitedThreadPool(poolSize, name, DEFAULT_RELATIVE_PRIORITY, runTimeRatioConfigEntry); - } - public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, int relativePriority, ConfigEntry runTimeRatioConfigEntry) - { - DhThreadFactory threadFactory = new DhThreadFactory(THREAD_NAME_PREFIX + name, Thread.NORM_PRIORITY + relativePriority); - return makeRateLimitedThreadPool(poolSize, threadFactory, runTimeRatioConfigEntry); - } - public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, DhThreadFactory threadFactory, ConfigEntry runTimeRatioConfigEntry) + public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, DhThreadFactory threadFactory, ConfigEntry runTimeRatioConfigEntry, Semaphore activeThreadCountSemaphore) { // remove the old listener if one exists if (THREAD_CHANGE_LISTENERS_BY_THREAD_NAME.containsKey(threadFactory.threadName)) @@ -74,7 +70,7 @@ public class ThreadUtil } - RateLimitedThreadPoolExecutor executor = makeRateLimitedThreadPool(poolSize, runTimeRatioConfigEntry.get(), threadFactory); + RateLimitedThreadPoolExecutor executor = makeRateLimitedThreadPool(poolSize, runTimeRatioConfigEntry.get(), threadFactory, activeThreadCountSemaphore); ConfigChangeListener changeListener = new ConfigChangeListener<>(runTimeRatioConfigEntry, (newRunTimeRatio) -> { executor.runTimeRatio = newRunTimeRatio; }); THREAD_CHANGE_LISTENERS_BY_THREAD_NAME.put(threadFactory.threadName, changeListener); @@ -84,17 +80,17 @@ public class ThreadUtil /** should only be used if there isn't a config controlling the run time ratio of this thread pool */ - public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, Double runTimeRatio, int relativePriority) + public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, Double runTimeRatio, int relativePriority, Semaphore activeThreadCountSemaphore) { - return new RateLimitedThreadPoolExecutor(poolSize, runTimeRatio, new DhThreadFactory(THREAD_NAME_PREFIX + name, Thread.NORM_PRIORITY + relativePriority)); + return new RateLimitedThreadPoolExecutor(poolSize, runTimeRatio, new DhThreadFactory(name, Thread.NORM_PRIORITY + relativePriority), activeThreadCountSemaphore); } - public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, Double runTimeRatio, DhThreadFactory threadFactory) + public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, Double runTimeRatio, DhThreadFactory threadFactory, Semaphore activeThreadCountSemaphore) { - return new RateLimitedThreadPoolExecutor(poolSize, runTimeRatio, threadFactory); + return new RateLimitedThreadPoolExecutor(poolSize, runTimeRatio, threadFactory, activeThreadCountSemaphore); } - // create thread pool // + // thread pool executor // public static ThreadPoolExecutor makeThreadPool(int poolSize, String name, int relativePriority) { @@ -104,7 +100,7 @@ public class ThreadUtil return new ThreadPoolExecutor(/*corePoolSize*/ poolSize, /*maxPoolSize*/ poolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), - new DhThreadFactory(THREAD_NAME_PREFIX + name, Thread.NORM_PRIORITY + relativePriority)); + new DhThreadFactory(name, Thread.NORM_PRIORITY + relativePriority)); } public static ThreadPoolExecutor makeThreadPool(int poolSize, Class clazz, int relativePriority) { return makeThreadPool(poolSize, clazz.getSimpleName(), relativePriority); } @@ -112,7 +108,7 @@ public class ThreadUtil public static ThreadPoolExecutor makeThreadPool(int poolSize, Class clazz) { return makeThreadPool(poolSize, clazz.getSimpleName(), DEFAULT_RELATIVE_PRIORITY); } - // create single thread pool // + // single thread pool executor // public static ThreadPoolExecutor makeSingleThreadPool(String name, int relativePriority) { return makeThreadPool(1, name, relativePriority); } public static ThreadPoolExecutor makeSingleThreadPool(Class clazz, int relativePriority) { return makeThreadPool(1, clazz.getSimpleName(), relativePriority); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ConfigThreadPool.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ConfigThreadPool.java new file mode 100644 index 000000000..47a509b91 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ConfigThreadPool.java @@ -0,0 +1,102 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.core.util.threading; + +import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; +import com.seibel.distanthorizons.core.config.types.ConfigEntry; +import com.seibel.distanthorizons.core.util.ThreadUtil; + +import java.util.concurrent.Semaphore; + +/** + * Handles thread pools with config values for their + * thread count and run time ratio. + */ +public class ConfigThreadPool +{ + /** Caution must be used to prevent deadlock */ + private final Semaphore activeThreadCountSemaphore; + + public RateLimitedThreadPoolExecutor executor = null; + private int threadCount = 0; + public int getThreadCount() { return this.threadCount; } + + public final DhThreadFactory threadFactory; + + public final ConfigChangeListener threadCountConfigListener; + public final ConfigEntry threadCountConfig; + public final ConfigEntry runTimeRatioConfig; + + + + //=============// + // constructor // + //=============// + + public ConfigThreadPool(DhThreadFactory threadFactory, ConfigEntry threadCountConfig, ConfigEntry runTimeRatioConfig, Semaphore activeThreadCountSemaphore) + { + this.threadFactory = threadFactory; + this.activeThreadCountSemaphore = activeThreadCountSemaphore; + + this.threadCountConfig = threadCountConfig; + this.threadCountConfigListener = new ConfigChangeListener<>(threadCountConfig, + (threadCount) -> { this.setThreadPoolSize(threadCount); }); + this.runTimeRatioConfig = runTimeRatioConfig; + + this.setThreadPoolSize(threadCountConfig.get()); + } + + + + + //==============// + // thread setup // + //==============// + + public void setThreadPoolSize(int threadPoolSize) + { + if (this.executor != null) + { + // close the previous thread pool if one exists + this.executor.shutdown(); + } + + this.threadCount = threadPoolSize; + this.executor = ThreadUtil.makeRateLimitedThreadPool(this.threadCount, this.threadFactory, this.runTimeRatioConfig, this.activeThreadCountSemaphore); + } + + /** + * Stops any executing tasks and destroys the executor.
+ * Does nothing if the executor isn't running. + */ + public void shutdownExecutorService() + { + if (this.executor != null) + { + //LOGGER.info("Stopping File Handler"); + this.executor.shutdownNow(); + this.executor = null; + } + + this.threadCount = 0; + } + + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/DhThreadFactory.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/DhThreadFactory.java similarity index 91% rename from core/src/main/java/com/seibel/distanthorizons/core/util/objects/DhThreadFactory.java rename to core/src/main/java/com/seibel/distanthorizons/core/util/threading/DhThreadFactory.java index 02e483b49..d2a32e903 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/DhThreadFactory.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/DhThreadFactory.java @@ -1,23 +1,23 @@ /* * This file is part of the Distant Horizons mod * licensed under the GNU LGPL v3 License. - * + * * Copyright (C) 2020-2023 James Seibel - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -package com.seibel.distanthorizons.core.util.objects; +package com.seibel.distanthorizons.core.util.threading; import java.lang.invoke.MethodHandles; import java.lang.ref.WeakReference; @@ -25,15 +25,15 @@ import java.util.LinkedList; import java.util.concurrent.ThreadFactory; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.util.ThreadUtil; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; /** * Just a simple ThreadFactory to name ExecutorService - * threads, which can be helpful when debugging. + * threads, which is helpful when debugging. * * @author James Seibel - * @version 2023-6-5 */ public class DhThreadFactory implements ThreadFactory { @@ -52,7 +52,7 @@ public class DhThreadFactory implements ThreadFactory throw new IllegalArgumentException("Thread priority [" + priority + "] out of bounds. Priority should be between [" + Thread.MIN_PRIORITY + "-" + Thread.MAX_PRIORITY + "]!"); } - this.threadName = newThreadName + " Thread"; + this.threadName = ThreadUtil.THREAD_NAME_PREFIX + newThreadName + " Thread"; this.priority = priority; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/RateLimitedThreadPoolExecutor.java similarity index 55% rename from core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java rename to core/src/main/java/com/seibel/distanthorizons/core/util/threading/RateLimitedThreadPoolExecutor.java index fbfb20c5a..5b27bb6fb 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/RateLimitedThreadPoolExecutor.java @@ -1,25 +1,30 @@ /* * This file is part of the Distant Horizons mod * licensed under the GNU LGPL v3 License. - * + * * Copyright (C) 2020-2023 James Seibel - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -package com.seibel.distanthorizons.core.util.objects; +package com.seibel.distanthorizons.core.util.threading; + +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; /** * Can be used to more finely control CPU usage and @@ -27,6 +32,10 @@ import java.util.concurrent.*; */ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor { + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + /** logs include the thread name by default which can help diagnose deadlocks */ + private static final boolean LOG_SEMAPHORE_ACTIONS = false; + public volatile double runTimeRatio; /** When this thread started running its last task */ @@ -36,13 +45,20 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor private Runnable onTerminatedEventHandler = null; + /** if null the thread pool will run independently of other pools */ + @Nullable + private final Semaphore activeThreadCountSemaphore; + /** will always be zero if no semaphore is present */ + private final AtomicInteger semaphoresAcquired = new AtomicInteger(0); + //==============// // constructors // //==============// - public RateLimitedThreadPoolExecutor(int corePoolSize, double runTimeRatio, ThreadFactory threadFactory) + public RateLimitedThreadPoolExecutor(int corePoolSize, double runTimeRatio, ThreadFactory threadFactory) { this(corePoolSize, runTimeRatio, threadFactory, null); } + public RateLimitedThreadPoolExecutor(int corePoolSize, double runTimeRatio, ThreadFactory threadFactory, @Nullable Semaphore activeThreadCountSemaphore) { super(corePoolSize, corePoolSize, 0L, TimeUnit.MILLISECONDS, @@ -50,6 +66,7 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor threadFactory); this.runTimeRatio = runTimeRatio; + this.activeThreadCountSemaphore = activeThreadCountSemaphore; } @@ -74,6 +91,23 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor } } + if (this.activeThreadCountSemaphore != null) + { + try + { + // Warning, this can cause deadlock if one thread calls another. + this.activeThreadCountSemaphore.acquire(); + this.semaphoresAcquired.getAndAdd(1); + + if (LOG_SEMAPHORE_ACTIONS) + { + LOGGER.debug("acquired, available count: ["+this.activeThreadCountSemaphore.availablePermits()+"]"); + } + } + catch (InterruptedException ignore) { } + } + + this.runStartNanoTimeRef.set(System.nanoTime()); } @@ -82,6 +116,18 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor { super.afterExecute(runnable, throwable); this.lastRunDurationNanoTimeRef.set(System.nanoTime() - this.runStartNanoTimeRef.get()); + + + if (this.activeThreadCountSemaphore != null) + { + this.activeThreadCountSemaphore.release(); + this.semaphoresAcquired.getAndAdd(-1); + + if (LOG_SEMAPHORE_ACTIONS) + { + LOGGER.debug("released, available count: ["+this.activeThreadCountSemaphore.availablePermits()+"]"); + } + } } @Override @@ -92,6 +138,18 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor { this.onTerminatedEventHandler.run(); } + + // release all held semaphores (shouldn't normally be necessary, but just in case) + if (this.activeThreadCountSemaphore != null) + { + int semaphoresAcquired = this.semaphoresAcquired.getAndSet(0); + this.activeThreadCountSemaphore.release(semaphoresAcquired); + + if (LOG_SEMAPHORE_ACTIONS) + { + LOGGER.info("terminated, released ["+semaphoresAcquired+"], available count: ["+this.activeThreadCountSemaphore.availablePermits()+"]"); + } + } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPools.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPools.java new file mode 100644 index 000000000..f5899ac4e --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPools.java @@ -0,0 +1,149 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.core.util.threading; + +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; +import com.seibel.distanthorizons.core.util.ThreadUtil; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * Holds each thread pool the system uses. + * + * @see ThreadUtil + */ +public class ThreadPools +{ + //=========================// + // standalone thread pools // + //=========================// + + // standalone thread pools all handle independent systems + // and don't interfere with any other pool + + public static final DhThreadFactory FILE_HANDLER_THREAD_FACTORY = new DhThreadFactory("File Handler", Thread.MIN_PRIORITY); + private static ConfigThreadPool fileHandlerThreadPool; + public static ThreadPoolExecutor getFileHandlerExecutor() { return fileHandlerThreadPool.executor; } + + public static final DhThreadFactory WORLD_GEN_THREAD_FACTORY = new DhThreadFactory("World Gen", Thread.MIN_PRIORITY); + private static ConfigThreadPool worldGenThreadPool; + public static ThreadPoolExecutor getWorldGenExecutor() { return worldGenThreadPool.executor; } + + private static ThreadPoolExecutor bufferUploaderThreadPool; + public static ThreadPoolExecutor getBufferUploaderExecutor() { return bufferUploaderThreadPool; } + + + + //======================// + // worker threads pools // + //======================// + + // worker thread pools are generally related with LOD building + // and all share an underlying number of threads. + // WARNING: great care should be used when setting up these threads since deadlock can occur if they are handled poorly. + + public static final DhThreadFactory LIGHT_POPULATOR_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Light Populator", Thread.MIN_PRIORITY); + private static ConfigThreadPool lightPopulatorThreadPool; + public static ThreadPoolExecutor getLightPopulatorExecutor() { return lightPopulatorThreadPool.executor; } + + public static final DhThreadFactory CHUNK_TO_LOD_BUILDER_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Chunk to Lod Builder", Thread.MIN_PRIORITY); + private static ConfigThreadPool chunkToLodBuilderThreadPool; + public static ThreadPoolExecutor getChunkToLodBuilderExecutor() { return chunkToLodBuilderThreadPool.executor; } + + public static final DhThreadFactory BUFFER_BUILDER_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Buffer Builder", Thread.MIN_PRIORITY); + private static ConfigThreadPool bufferBuilderThreadPool; + public static ThreadPoolExecutor getBufferBuilderExecutor() { return bufferBuilderThreadPool.executor; } + + + /** how many total worker threads can be used */ + private static int workerThreadSemaphoreCount = 0; + public static int getWorkerThreadCount() { return workerThreadSemaphoreCount; } + + private static Semaphore workerThreadSemaphore = null; + private static ConfigChangeListener workerThreadSemaphoreConfigListener = null; + + + + //=================// + // setup / cleanup // + //=================// + + public static void setupThreadPools() + { + // standalone threads // + + fileHandlerThreadPool = new ConfigThreadPool(FILE_HANDLER_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads, null); + worldGenThreadPool = new ConfigThreadPool(WORLD_GEN_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads, null); + bufferUploaderThreadPool = ThreadUtil.makeSingleThreadPool("Buffer Uploader"); + + + + // worker threads // + + // create thread semaphore + if (Config.Client.Advanced.MultiThreading.enableLodBuilderThreadLimiting.get()) + { + workerThreadSemaphoreCount = Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get(); + workerThreadSemaphore = new Semaphore(workerThreadSemaphoreCount); + + workerThreadSemaphoreConfigListener = new ConfigChangeListener<>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads, (val) -> + { + int changePermit = val - workerThreadSemaphoreCount; + if (changePermit > 0) + { + workerThreadSemaphore.release(changePermit); + } + else + { + workerThreadSemaphore.acquireUninterruptibly(changePermit * -1); + } + workerThreadSemaphoreCount = workerThreadSemaphoreCount + changePermit; + }); + } + + // create thread pools + lightPopulatorThreadPool = new ConfigThreadPool(LIGHT_POPULATOR_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads, workerThreadSemaphore); + chunkToLodBuilderThreadPool = new ConfigThreadPool(CHUNK_TO_LOD_BUILDER_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads, workerThreadSemaphore); + bufferBuilderThreadPool = new ConfigThreadPool(BUFFER_BUILDER_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads, workerThreadSemaphore); + + } + + public static void shutdownThreadPools() + { + // standalone threads + fileHandlerThreadPool.shutdownExecutorService(); + worldGenThreadPool.shutdownExecutorService(); + bufferUploaderThreadPool.shutdown(); + + + // worker threads + ThreadPools.lightPopulatorThreadPool.shutdownExecutorService(); + ThreadPools.chunkToLodBuilderThreadPool.shutdownExecutorService(); + ThreadPools.bufferBuilderThreadPool.shutdownExecutorService(); + + workerThreadSemaphore = null; + workerThreadSemaphoreConfigListener.close(); + workerThreadSemaphoreConfigListener = null; + + } + +} diff --git a/core/src/main/resources/assets/distanthorizons/lang/en_us.json b/core/src/main/resources/assets/distanthorizons/lang/en_us.json index f5ad40972..49722f353 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -383,27 +383,16 @@ "distanthorizons.config.client.advanced.multiThreading.runTimeRatioForFileHandlerThreads": "Runtime % for file handler threads", - "distanthorizons.config.client.advanced.multiThreading.numberOfDataTransformerThreads": - "NO. of data transformer threads", - "distanthorizons.config.client.advanced.multiThreading.numberOfDataTransformerThreads.@tooltip": - "The number of threads used when converting ID data to render-able data. \n(This generally happens when generating new terrain or changing graphics settings). \nCan only be between 1 and your CPU's processor count.", - "distanthorizons.config.client.advanced.multiThreading.runTimeRatioForDataTransformerThreads": - "Runtime % for data transformer threads", - - "distanthorizons.config.client.advanced.multiThreading.numberOfChunkLodConverterThreads": - "NO. of chunk LOD converter threads", - "distanthorizons.config.client.advanced.multiThreading.numberOfChunkLodConverterThreads.@tooltip": - "How many threads should be used to convert Minecraft chunks into LOD data? \nThese threads run both when terrain is generated and when \nchunks are loaded, unloaded, and modified.", - "distanthorizons.config.client.advanced.multiThreading.runTimeRatioForChunkLodConverterThreads": - "Runtime % for chunk LOD converter threads", - - "distanthorizons.config.client.advanced.multiThreading.numberOfChunkLightBakingThreads": - "NO. of chunk light baking threads", - "distanthorizons.config.client.advanced.multiThreading.numberOfChunkLightBakingThreads.@tooltip": - "How many threads should be used to either pull existing or generating new lighting for chunks\n that were recently loading/unloaded?\n\n These threads run when traveling around the world\n and when moving between dimensions.", - "distanthorizons.config.client.advanced.multiThreading.runTimeRatioForChunkLightBakingThreads": - "Runtime % for chunk light baking threads", - + "distanthorizons.config.client.advanced.multiThreading.numberOfLodBuilderThreads": + "NO. of LOD builder threads", + "distanthorizons.config.client.advanced.multiThreading.numberOfLodBuilderThreads.@tooltip": + "The number of threads used when building LODs. \nThese threads run when terrain is generated, when \ncertain graphics settings are changed, and when moving around the world.", + "distanthorizons.config.client.advanced.multiThreading.runTimeRatioForLodBuilderThreads": + "Runtime % for LOD builder threads", + "distanthorizons.config.client.advanced.multiThreading.enableLodBuilderThreadLimiting": + "Enable LOD builder thread limiting", + "distanthorizons.config.client.advanced.multiThreading.enableLodBuilderThreadLimiting.@tooltip": + "Should only be disabled if deadlock occurs and LODs refuse to update. \nThis will cause CPU usage to drastically increase for the Lod Builder threads. \nNote that if a deadlock did occur restarting MC may be necessary to stop the locked threads.", "distanthorizons.config.client.advanced.debugging":