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 c35cfc7d5..bf148f9be 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 @@ -20,6 +20,8 @@ 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; @@ -47,21 +49,20 @@ import java.util.concurrent.ThreadPoolExecutor; /** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */ public class SharedApi { + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); public static final SharedApi INSTANCE = new SharedApi(); + private static final Set UPDATING_CHUNK_SET = ConcurrentHashMap.newKeySet(); + + private static AbstractDhWorld currentWorld; private static int lastWorldGenTickDelta = 0; - private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + // 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 ThreadPoolExecutor LIGHT_POPULATOR_THREAD_POOL = ThreadUtil.makeRateLimitedThreadPool( - // thread count doesn't need to be very high since the player can only move so fast, 1 should be plenty - (Runtime.getRuntime().availableProcessors() <= 12) ? 1 : 2, - "Server Light Populator", - (Runtime.getRuntime().availableProcessors() <= 12) ? 0.5 : 0.9, - ThreadUtil.MINIMUM_RELATIVE_PRIORITY); - private static final Set UPDATING_CHUNK_SET = ConcurrentHashMap.newKeySet(); @@ -95,6 +96,7 @@ public class SharedApi ColumnRenderBufferBuilder.setupExecutorService(); WorldGenerationQueue.setupWorldGenThreadPool(); ChunkToLodBuilder.setupExecutorService(); + SharedApi.setupExecutorService(); } else { @@ -104,6 +106,7 @@ public class SharedApi ColumnRenderBufferBuilder.shutdownExecutorService(); WorldGenerationQueue.shutdownWorldGenThreadPool(); ChunkToLodBuilder.shutdownExecutorService(); + SharedApi.shutdownExecutorService(); DebugRenderer.clearRenderables(); @@ -235,7 +238,7 @@ public class SharedApi private static void bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList neighbourChunkList, IDhLevel dhLevel) { // lighting the chunk needs to be done on a separate thread to prevent lagging any of the event threads - LIGHT_POPULATOR_THREAD_POOL.execute(() -> + lightPopulatorThreadPool.execute(() -> { try { @@ -285,4 +288,52 @@ 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 28693a882..72b2c6e3c 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 @@ -29,7 +29,6 @@ import com.seibel.distanthorizons.core.config.eventHandlers.presets.*; import com.seibel.distanthorizons.core.config.types.*; import com.seibel.distanthorizons.core.config.types.enums.*; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; -import com.seibel.distanthorizons.core.jar.EPlatform; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.coreapi.ModInfo; @@ -924,6 +923,24 @@ public class Config .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) + .build(); + } public static class GpuBuffers 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 2f0e21cf0..0445f4167 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 @@ -151,6 +151,28 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan }}); + 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, + 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.I_PAID_FOR_THE_WHOLE_CPU, 1.0); + }}); + + //==============// // constructors // @@ -175,6 +197,9 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan this.configList.add(this.chunkLodConverterThreadCount); this.configList.add(this.chunkLodConverterRunTime); + this.configList.add(this.lightBakingThreadCount); + this.configList.add(this.lightBakingRunTime); + for (ConfigEntryWithPresetOptions config : this.configList) { 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 8a67548ce..2187b8e09 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -388,6 +388,13 @@ "distanthorizons.config.client.advanced.multiThreading.runTimeRatioForChunkLodConverterThreads": "Runtime % for chunk LOD converter threads", + "distanthorizons.config.client.advanced.multiThreading.numberOfLightBakingThreads": + "NO. of chunk light baking threads", + "distanthorizons.config.client.advanced.multiThreading.numberOfLightBakingThreads.@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.runTimeRatioForLightBakingThreads": + "Runtime % for chunk light baking threads", + "distanthorizons.config.client.advanced.debugging":