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
This commit is contained in:
James Seibel
2023-10-29 15:04:32 -05:00
parent 23ebfcb9bf
commit 446c274094
20 changed files with 427 additions and 632 deletions
@@ -36,20 +36,12 @@ public class DhApiMultiThreadingConfig implements IDhApiMultiThreadingConfig
public IDhApiConfigValue<Integer> worldGeneratorThreads()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads); }
@Override
public IDhApiConfigValue<Integer> bufferBuilderThreads()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.MultiThreading.numberOfBufferBuilderThreads); }
@Override
public IDhApiConfigValue<Integer> fileHandlerThreads()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads); }
@Override
public IDhApiConfigValue<Integer> dataConverterThreads()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads); }
@Override
public IDhApiConfigValue<Integer> chunkLodConverterThreads()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.MultiThreading.numberOfChunkLodConverterThreads); }
public IDhApiConfigValue<Integer> lodBuilderThreads()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads); }
}
@@ -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<Integer> 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. <br>
* 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. <br>
* Does nothing if the executor isn't running.
*/
public static void shutdownExecutorService()
{
if (lightPopulatorThreadPool != null)
{
LOGGER.info("Stopping " + ChunkToLodBuilder.class.getSimpleName());
lightPopulatorThreadPool.shutdownNow();
}
}
}
@@ -865,24 +865,6 @@ public class Config
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
public static ConfigEntry<Integer> numberOfBufferBuilderThreads = new ConfigEntry.Builder<Integer>()
.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<Double> runTimeRatioForBufferBuilderThreads = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getBufferBuilderDefaultRunTimeRatio(), 1.0)
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
public static final ConfigEntry<Integer> numberOfFileHandlerThreads = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(1,
ThreadPresetConfigEventHandler.getFileHandlerDefaultThreadCount(),
@@ -901,60 +883,30 @@ public class Config
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
public static final ConfigEntry<Integer> numberOfDataTransformerThreads = new ConfigEntry.Builder<Integer>()
public static final ConfigEntry<Integer> numberOfLodBuilderThreads = new ConfigEntry.Builder<Integer>()
.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<Double> runTimeRatioForDataTransformerThreads = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getDataTransformerDefaultRunTimeRatio(), 1.0)
public static final ConfigEntry<Double> runTimeRatioForLodBuilderThreads = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getLodBuilderDefaultRunTimeRatio(), 1.0)
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
public static final ConfigEntry<Integer> numberOfChunkLodConverterThreads = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(1,
ThreadPresetConfigEventHandler.getChunkLodConverterDefaultThreadCount(),
Runtime.getRuntime().availableProcessors())
public static final ConfigEntry<Boolean> enableLodBuilderThreadLimiting = new ConfigEntry.Builder<Boolean>()
.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<Double> runTimeRatioForChunkLodConverterThreads = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getChunkLodConverterDefaultRunTimeRatio(), 1.0)
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
public static final ConfigEntry<Integer> numberOfChunkLightBakingThreads = new ConfigEntry.Builder<Integer>()
.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<Double> runTimeRatioForChunkLightBakingThreads = new ConfigEntry.Builder<Double>()
.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();
}
@@ -63,28 +63,6 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
}});
public static int getBufferBuilderDefaultThreadCount() { return getThreadCountByPercent(0.1); }
private final ConfigEntryWithPresetOptions<EThreadPreset, Integer> bufferBuilderThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfBufferBuilderThreads,
new HashMap<EThreadPreset, Integer>()
{{
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<EThreadPreset, Double> bufferBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForBufferBuilderThreads,
new HashMap<EThreadPreset, Double>()
{{
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<EThreadPreset, Integer> fileHandlerThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads,
new HashMap<EThreadPreset, Integer>()
@@ -107,68 +85,24 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
}});
public static int getDataTransformerDefaultThreadCount() { return getThreadCountByPercent(0.1); }
private final ConfigEntryWithPresetOptions<EThreadPreset, Integer> dataTransformerThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads,
public static int getLodBuilderDefaultThreadCount() { return getThreadCountByPercent(0.1); }
private final ConfigEntryWithPresetOptions<EThreadPreset, Integer> lodBuilderThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads,
new HashMap<EThreadPreset, Integer>()
{{
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<EThreadPreset, Double> dataTransformerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForDataTransformerThreads,
new HashMap<EThreadPreset, Double>()
{{
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<EThreadPreset, Integer> chunkLodConverterThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfChunkLodConverterThreads,
new HashMap<EThreadPreset, Integer>()
{{
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<EThreadPreset, Double> chunkLodConverterRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForChunkLodConverterThreads,
new HashMap<EThreadPreset, Double>()
{{
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<EThreadPreset, Integer> lightBakingThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfChunkLightBakingThreads,
new HashMap<EThreadPreset, Integer>()
{{
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<EThreadPreset, Double> lightBakingRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForChunkLightBakingThreads,
public static double getLodBuilderDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.25 : 0.5; }
private final ConfigEntryWithPresetOptions<EThreadPreset, Double> lodBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads,
new HashMap<EThreadPreset, Double>()
{{
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<EThreadPreset, ?> config : this.configList)
@@ -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<Integer> 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. <br>
* 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. <br>
* 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; }
}
@@ -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<Integer> threadConfigListener;
public static final long MAX_TICK_TIME_NS = 1000000000L / 20L;
private final ConcurrentHashMap<DhChunkPos, IChunkWrapper> 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. <br>
* 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. <br>
* 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 //
@@ -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<Integer> configListener;
//==============================//
// public transformer interface //
//==============================//
public static CompletableFuture<ColumnRenderSource> 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. <br>
* 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. <br>
* Does nothing if the executor isn't running.
*/
public static void shutdownExecutorService()
{
if (transformerThreadPool != null)
{
LOGGER.info("Stopping " + FullDataToRenderDataTransformer.class.getSimpleName());
transformerThreadPool.shutdownNow();
}
}
}
@@ -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<Integer> configListener;
protected final ConcurrentHashMap<DhSectionPos, FullDataMetaFile> loadedMetaFileBySectionPos = new ConcurrentHashMap<>();
protected final IDhLevel level;
@@ -493,58 +487,6 @@ public class FullDataFileHandler implements IFullDataSourceProvider
//==========================//
// executor handler methods //
//==========================//
/**
* Creates a new executor. <br>
* 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. <br>
* 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 //
//=========//
@@ -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) ->
{
@@ -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);
@@ -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)
{
@@ -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<DhSectionPos, RenderDataMetaFile> 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<ColumnRenderSource> 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<ColumnRenderSource> getDataSourceFuture = metaFile.getOrLoadCachedDataSourceAsync(this.fileHandlerThreadPool)
CompletableFuture<ColumnRenderSource> 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<String> 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<ETaskType, Integer> 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();
}
@@ -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<DhSectionPos, StackTraceElement[]> alreadyGeneratedPosHashSet = new HashMap<>(MAX_ALREADY_GENERATED_COUNT);
private final Queue<DhSectionPos> alreadyGeneratedPosQueue = new LinkedList<>();
private static RateLimitedThreadPoolExecutor worldGeneratorThreadPool;
private static ConfigChangeListener<Integer> configListener;
//==============//
@@ -431,7 +425,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
Consumer<ChunkSizedFullDataAccessor> 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. <br>
* 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. <br>
* 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.");
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
@@ -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<String, ConfigChangeListener<Double>> 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<Double> runTimeRatioConfigEntry)
{
return makeRateLimitedThreadPool(poolSize, name, DEFAULT_RELATIVE_PRIORITY, runTimeRatioConfigEntry);
}
public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, int relativePriority, ConfigEntry<Double> 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<Double> runTimeRatioConfigEntry)
public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, DhThreadFactory threadFactory, ConfigEntry<Double> 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<Double> 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<Runnable>(),
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); }
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Integer> threadCountConfigListener;
public final ConfigEntry<Integer> threadCountConfig;
public final ConfigEntry<Double> runTimeRatioConfig;
//=============//
// constructor //
//=============//
public ConfigThreadPool(DhThreadFactory threadFactory, ConfigEntry<Integer> threadCountConfig, ConfigEntry<Double> 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. <br>
* 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;
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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()+"]");
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Integer> 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;
}
}
@@ -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":