Add a thread config for ChunkToLodBuilder
This commit is contained in:
@@ -805,6 +805,19 @@ public class Config
|
||||
+ THREAD_NOTE)
|
||||
.build();
|
||||
|
||||
public static final ConfigEntry<Integer> numberOfChunkLodConverterThreads = new ConfigEntry.Builder<Integer>()
|
||||
.setMinDefaultMax(1,
|
||||
Runtime.getRuntime().availableProcessors()/16,
|
||||
Runtime.getRuntime().availableProcessors())
|
||||
.comment(""
|
||||
+ "How many threads should be used to convert Minecraft chunks into LOD data? \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 class GpuBuffers
|
||||
|
||||
+10
@@ -55,6 +55,15 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.2));
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
}});
|
||||
private final ConfigEntryWithPresetOptions<EThreadPreset, Integer> chunkLodConverters = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfChunkLodConverterThreads,
|
||||
new HashMap<EThreadPreset, Integer>()
|
||||
{{
|
||||
this.put(EThreadPreset.MINIMAL_IMPACT, 1);
|
||||
this.put(EThreadPreset.LOW_IMPACT, getThreadCountByPercent(0.1));
|
||||
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));
|
||||
}});
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +79,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
this.configList.add(this.bufferBuilders);
|
||||
this.configList.add(this.fileHandlers);
|
||||
this.configList.add(this.dataConverters);
|
||||
this.configList.add(this.chunkLodConverters);
|
||||
|
||||
|
||||
for (ConfigEntryWithPresetOptions<EThreadPreset, ?> config : this.configList)
|
||||
|
||||
+171
-94
@@ -1,8 +1,11 @@
|
||||
package com.seibel.lod.core.dataObjects.transformers;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.seibel.lod.core.config.listeners.ConfigChangeListener;
|
||||
import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
|
||||
import com.seibel.lod.core.config.Config;
|
||||
import com.seibel.lod.core.logging.ConfigBasedLogger;
|
||||
@@ -13,66 +16,65 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.lod.core.dependencyInjection.SingletonInjector;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
//FIXME: To-Be-Used class
|
||||
public class ChunkToLodBuilder
|
||||
public class ChunkToLodBuilder implements AutoCloseable
|
||||
{
|
||||
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), () -> Config.Client.Advanced.Logging.logLodBuilderEvent.get());
|
||||
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);
|
||||
|
||||
public static final long MAX_TICK_TIME_NS = 1000000000L / 20L;
|
||||
public static final int THREAD_COUNT = 1;
|
||||
public static final long MAX_TICK_TIME_NS = 1000000000L / 20L;
|
||||
|
||||
private static class Task
|
||||
|
||||
private final ConcurrentHashMap<DhChunkPos, IChunkWrapper> latestChunkToBuild = new ConcurrentHashMap<>();
|
||||
private final ConcurrentLinkedDeque<Task> taskToBuild = new ConcurrentLinkedDeque<>();
|
||||
private final AtomicInteger runningCount = new AtomicInteger(0);
|
||||
|
||||
private int threadCount = -1;
|
||||
private ExecutorService executorThreadPool = null;
|
||||
private ConfigChangeListener<Integer> configListener;
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
|
||||
public ChunkToLodBuilder() { this.setupExecutorService(); }
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// data generation //
|
||||
//=================//
|
||||
|
||||
public CompletableFuture<ChunkSizedFullDataAccessor> tryGenerateData(IChunkWrapper chunkWrapper)
|
||||
{
|
||||
final DhChunkPos chunkPos;
|
||||
final CompletableFuture<ChunkSizedFullDataAccessor> future;
|
||||
|
||||
Task(DhChunkPos chunkPos, CompletableFuture<ChunkSizedFullDataAccessor> future)
|
||||
{
|
||||
this.chunkPos = chunkPos;
|
||||
this.future = future;
|
||||
}
|
||||
}
|
||||
private final ConcurrentHashMap<DhChunkPos, IChunkWrapper> latestChunkToBuild = new ConcurrentHashMap<>();
|
||||
private final ConcurrentLinkedDeque<Task> taskToBuild = new ConcurrentLinkedDeque<>();
|
||||
private final ExecutorService executor = ThreadUtil.makeThreadPool(THREAD_COUNT, ChunkToLodBuilder.class);
|
||||
private final AtomicInteger runningCount = new AtomicInteger(0);
|
||||
|
||||
|
||||
|
||||
public ChunkToLodBuilder() { }
|
||||
|
||||
|
||||
|
||||
public CompletableFuture<ChunkSizedFullDataAccessor> tryGenerateData(IChunkWrapper chunkWrapper)
|
||||
{
|
||||
if (chunkWrapper == null)
|
||||
if (chunkWrapper == null)
|
||||
{
|
||||
throw new NullPointerException("ChunkWrapper cannot be null!");
|
||||
}
|
||||
|
||||
IChunkWrapper oldChunk = this.latestChunkToBuild.put(chunkWrapper.getChunkPos(), chunkWrapper); // an Exchange operation
|
||||
// If there's old chunk, that means we just replaced an unprocessed old request on generating data on this pos.
|
||||
// if so, we can just return null to signal this, as the old request's future will instead be the proper one
|
||||
// that will return the latest generated data.
|
||||
if (oldChunk != null)
|
||||
IChunkWrapper oldChunk = this.latestChunkToBuild.put(chunkWrapper.getChunkPos(), chunkWrapper); // an Exchange operation
|
||||
// If there's old chunk, that means we just replaced an unprocessed old request on generating data on this pos.
|
||||
// if so, we can just return null to signal this, as the old request's future will instead be the proper one
|
||||
// that will return the latest generated data.
|
||||
if (oldChunk != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, it means we're the first to do so. Let's submit our task to this entry.
|
||||
CompletableFuture<ChunkSizedFullDataAccessor> future = new CompletableFuture<>();
|
||||
// Otherwise, it means we're the first to do so. Let's submit our task to this entry.
|
||||
CompletableFuture<ChunkSizedFullDataAccessor> future = new CompletableFuture<>();
|
||||
this.taskToBuild.addLast(new Task(chunkWrapper.getChunkPos(), future));
|
||||
return future;
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
public void tick()
|
||||
public void tick()
|
||||
{
|
||||
if (this.runningCount.get() >= THREAD_COUNT)
|
||||
if (this.runningCount.get() >= this.threadCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (this.taskToBuild.isEmpty())
|
||||
else if (this.taskToBuild.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -86,95 +88,94 @@ public class ChunkToLodBuilder
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i<THREAD_COUNT; i++)
|
||||
for (int i = 0; i< this.threadCount; i++)
|
||||
{
|
||||
this.runningCount.incrementAndGet();
|
||||
CompletableFuture.runAsync(() ->
|
||||
CompletableFuture.runAsync(() ->
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
_tick();
|
||||
}
|
||||
this._tick();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.runningCount.decrementAndGet();
|
||||
}
|
||||
}, this.executor);
|
||||
}
|
||||
}
|
||||
private void _tick()
|
||||
}
|
||||
}, this.executorThreadPool);
|
||||
}
|
||||
}
|
||||
private void _tick()
|
||||
{
|
||||
long time = System.nanoTime();
|
||||
int count = 0;
|
||||
boolean allDone = false;
|
||||
while (true)
|
||||
long time = System.nanoTime();
|
||||
int count = 0;
|
||||
boolean allDone = false;
|
||||
while (true)
|
||||
{
|
||||
// run until we either run out of time, or all tasks are complete
|
||||
if (System.nanoTime() - time > MAX_TICK_TIME_NS && !this.taskToBuild.isEmpty())
|
||||
if (System.nanoTime() - time > MAX_TICK_TIME_NS && !this.taskToBuild.isEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Task task = this.taskToBuild.pollFirst();
|
||||
if (task == null)
|
||||
Task task = this.taskToBuild.pollFirst();
|
||||
if (task == null)
|
||||
{
|
||||
allDone = true;
|
||||
break;
|
||||
}
|
||||
allDone = true;
|
||||
break;
|
||||
}
|
||||
|
||||
count++;
|
||||
IChunkWrapper latestChunk = this.latestChunkToBuild.remove(task.chunkPos); // Basically an Exchange operation
|
||||
if (latestChunk == null)
|
||||
count++;
|
||||
IChunkWrapper latestChunk = this.latestChunkToBuild.remove(task.chunkPos); // Basically an Exchange operation
|
||||
if (latestChunk == null)
|
||||
{
|
||||
LOGGER.error("Somehow Task at "+task.chunkPos+" has latestChunk as null. Skipping task.");
|
||||
task.future.complete(null);
|
||||
continue;
|
||||
}
|
||||
LOGGER.error("Somehow Task at "+task.chunkPos+" has latestChunk as null. Skipping task.");
|
||||
task.future.complete(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
try
|
||||
{
|
||||
if (LodDataBuilder.canGenerateLodFromChunk(latestChunk))
|
||||
if (LodDataBuilder.canGenerateLodFromChunk(latestChunk))
|
||||
{
|
||||
ChunkSizedFullDataAccessor data = LodDataBuilder.createChunkData(latestChunk);
|
||||
if (data != null)
|
||||
ChunkSizedFullDataAccessor data = LodDataBuilder.createChunkData(latestChunk);
|
||||
if (data != null)
|
||||
{
|
||||
task.future.complete(data);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
task.future.complete(data);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOGGER.error("Error while processing Task at "+task.chunkPos, ex);
|
||||
}
|
||||
LOGGER.error("Error while processing Task at "+task.chunkPos, ex);
|
||||
}
|
||||
|
||||
// Failed to build due to chunk not meeting requirement.
|
||||
IChunkWrapper casChunk = this.latestChunkToBuild.putIfAbsent(task.chunkPos, latestChunk); // CAS operation with expected=null
|
||||
if (casChunk == null || latestChunk.isStillValid()) // That means CAS have been successful
|
||||
// Failed to build due to chunk not meeting requirement.
|
||||
IChunkWrapper casChunk = this.latestChunkToBuild.putIfAbsent(task.chunkPos, latestChunk); // CAS operation with expected=null
|
||||
if (casChunk == null || latestChunk.isStillValid()) // That means CAS have been successful
|
||||
{
|
||||
this.taskToBuild.addLast(task); // Then add back the same old task.
|
||||
}
|
||||
else // Else, it means someone managed to sneak in a new gen request in this pos. Then lets drop this old task.
|
||||
else // Else, it means someone managed to sneak in a new gen request in this pos. Then lets drop this old task.
|
||||
{
|
||||
task.future.complete(null);
|
||||
}
|
||||
|
||||
count--;
|
||||
}
|
||||
count--;
|
||||
}
|
||||
|
||||
long time2 = System.nanoTime();
|
||||
if (!allDone)
|
||||
long time2 = System.nanoTime();
|
||||
if (!allDone)
|
||||
{
|
||||
//LOGGER.info("Completed {} tasks in {} in this tick", count, Duration.ofNanos(time2 - time));
|
||||
}
|
||||
//LOGGER.info("Completed {} tasks in {} in this tick", count, Duration.ofNanos(time2 - time));
|
||||
}
|
||||
else if (count > 0)
|
||||
{
|
||||
//LOGGER.info("Completed all {} tasks in {}", count, Duration.ofNanos(time2 - time));
|
||||
}
|
||||
}
|
||||
//LOGGER.info("Completed all {} tasks in {}", count, Duration.ofNanos(time2 - time));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* should be called whenever changing levels/worlds
|
||||
* to prevent trying to generate LODs for chunk(s) that are no longer loaded
|
||||
* (which can cause exceptions)
|
||||
@@ -185,4 +186,80 @@ public class ChunkToLodBuilder
|
||||
this.latestChunkToBuild.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==========================//
|
||||
// executor handler methods //
|
||||
//==========================//
|
||||
|
||||
/**
|
||||
* Creates a new executor. <br>
|
||||
* Does nothing if an executor already exists.
|
||||
*/
|
||||
public void setupExecutorService()
|
||||
{
|
||||
// static setup
|
||||
if (this.configListener == null)
|
||||
{
|
||||
this.configListener = new ConfigChangeListener<>(Config.Client.Advanced.MultiThreading.numberOfChunkLodConverterThreads, (threadCount) -> { this.setThreadPoolSize(threadCount); });
|
||||
}
|
||||
|
||||
|
||||
if (this.executorThreadPool == null || this.executorThreadPool.isTerminated())
|
||||
{
|
||||
LOGGER.info("Starting "+ChunkToLodBuilder.class.getSimpleName());
|
||||
this.setThreadPoolSize(Config.Client.Advanced.MultiThreading.numberOfChunkLodConverterThreads.get());
|
||||
}
|
||||
}
|
||||
public void setThreadPoolSize(int threadPoolSize)
|
||||
{
|
||||
this.threadCount = threadPoolSize;
|
||||
this.executorThreadPool = ThreadUtil.makeThreadPool(threadPoolSize, ChunkToLodBuilder.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any executing tasks and destroys the executor. <br>
|
||||
* Does nothing if the executor isn't running.
|
||||
*/
|
||||
public void shutdownExecutorService()
|
||||
{
|
||||
if (this.executorThreadPool != null)
|
||||
{
|
||||
LOGGER.info("Stopping "+ChunkToLodBuilder.class.getSimpleName());
|
||||
this.executorThreadPool.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// base methods //
|
||||
//==============//
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
this.shutdownExecutorService();
|
||||
this.clearCurrentTasks();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
private static class Task
|
||||
{
|
||||
final DhChunkPos chunkPos;
|
||||
final CompletableFuture<ChunkSizedFullDataAccessor> future;
|
||||
|
||||
Task(DhChunkPos chunkPos, CompletableFuture<ChunkSizedFullDataAccessor> future)
|
||||
{
|
||||
this.chunkPos = chunkPos;
|
||||
this.future = future;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ public abstract class AbstractDhClientLevel implements IDhClientLevel
|
||||
this.fullDataFileHandler.close();
|
||||
|
||||
// clear the chunk builder to prevent generating LODs for chunks that are unloaded
|
||||
this.chunkToLodBuilder.clearCurrentTasks();
|
||||
this.chunkToLodBuilder.close();
|
||||
|
||||
|
||||
// shutdown the renderer
|
||||
|
||||
@@ -308,15 +308,19 @@
|
||||
"lod.config.client.advanced.multiThreading.numberOfBufferBuilderThreads":
|
||||
"NO. of buffer builder threads",
|
||||
"lod.config.client.advanced.multiThreading.numberOfBufferBuilderThreads.@tooltip":
|
||||
"The number of threads used when building geometry data.\nCan only be between 1 and your CPU's processor count.",
|
||||
"The number of threads used when building geometry data. \nCan only be between 1 and your CPU's processor count.",
|
||||
"lod.config.client.advanced.multiThreading.numberOfFileHandlerThreads":
|
||||
"NO. of file handler threads",
|
||||
"NO. of file handler threads",
|
||||
"lod.config.client.advanced.multiThreading.numberOfFileHandlerThreads.@tooltip":
|
||||
"The number of threads used when building vertex buffers\n(The things sent to your GPU to draw the LODs).\nCan only be between 1 and your CPU's processor count.",
|
||||
"The number of threads used when building vertex buffers \n(The things sent to your GPU to draw the LODs). \nCan only be between 1 and your CPU's processor count.",
|
||||
"lod.config.client.advanced.multiThreading.numberOfDataConverterThreads":
|
||||
"NO. of data converter threads",
|
||||
"NO. of data converter threads",
|
||||
"lod.config.client.advanced.multiThreading.numberOfDataConverterThreads.@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.",
|
||||
"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.",
|
||||
"lod.config.client.advanced.multiThreading.numberOfChunkLodConverterThreads":
|
||||
"NO. of chunk LOD converter threads",
|
||||
"lod.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.",
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user