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:
+2
-10
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
+10
-85
@@ -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)
|
||||
|
||||
+3
-76
@@ -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; }
|
||||
|
||||
}
|
||||
|
||||
+5
-56
@@ -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 //
|
||||
|
||||
+2
-55
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-58
@@ -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 //
|
||||
//=========//
|
||||
|
||||
+8
-6
@@ -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) ->
|
||||
{
|
||||
|
||||
-2
@@ -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);
|
||||
|
||||
|
||||
+1
-1
@@ -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)
|
||||
{
|
||||
|
||||
+8
-6
@@ -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();
|
||||
}
|
||||
|
||||
+4
-66
@@ -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); }
|
||||
|
||||
+102
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+8
-8
@@ -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;
|
||||
}
|
||||
|
||||
+64
-6
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user