diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/PriorityTaskPicker.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/PriorityTaskPicker.java
index f61d0ebd8..b4826ddc0 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/PriorityTaskPicker.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/PriorityTaskPicker.java
@@ -15,6 +15,7 @@ import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Stream;
@@ -26,6 +27,21 @@ public class PriorityTaskPicker
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
+ private static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newScheduledThreadPool(1, new DhThreadFactory("Task Picker Re-queue Schedule", Thread.NORM_PRIORITY, true));
+ /**
+ * If a thread is paused we can end up in a situation where
+ * all the other pools are done, causing {@link PriorityTaskPicker#tryStartNextTask()}
+ * to queue nothing, preventing the paused pools from running until
+ * more work is queued.
+ *
+ * This way we can check on those paused threads at a future time
+ * to queue their work.
+ */
+ private static final int MS_TO_CHECK_ON_PAUSED_THREADS = 1_000;
+
+ private final AtomicReference> scheduledFutureRef = new AtomicReference<>();
+ private final Runnable startNextTaskBlockingRunnable = () -> this.startNextTask(true);
+
/** the list of currently registered executors */
private final ArrayList executors = new ArrayList<>();
@@ -56,17 +72,32 @@ public class PriorityTaskPicker
* Tries to start the next queued task
* for one of the available executors.
*/
- private void tryStartNextTask()
+ private void tryStartNextTask() { this.startNextTask(false); }
+
+ private void startNextTask(boolean waitForLock)
{
// only let one thread start the next task to prevent concurrency errors
- if (!this.taskPickerLock.tryLock())
+ if (waitForLock)
{
- return;
+ // generally this will be done if an executor is paused,
+ // and we want to check up on it later
+ this.taskPickerLock.lock();
+ }
+ else
+ {
+ // most threads won't need to try queuing the next
+ // task since that means someone else is already working on it
+ if (!this.taskPickerLock.tryLock())
+ {
+ return;
+ }
}
try
{
+ boolean executorPaused = false;
+
// fill up executors that have run for less time first,
// this prevents long-running tasks from taking up all the CPU time
Iterator iterator = this.getExecutorIteratorSortedByShortestTotalRunTime();
@@ -77,7 +108,7 @@ public class PriorityTaskPicker
// skip executors that are paused
if (!executor.canRun())
{
- // TODO try to re-queue tasks after a timeout
+ executorPaused = true;
continue;
}
@@ -106,6 +137,27 @@ public class PriorityTaskPicker
}
}
}
+
+
+ // if an executor is paused then we'll
+ // need to check on it again sometime in the future
+ // otherwise we may not start the next task for a while
+ ScheduledFuture> newScheduledFuture = null;
+ if (executorPaused)
+ {
+ newScheduledFuture = SCHEDULED_EXECUTOR_SERVICE.schedule(
+ this.startNextTaskBlockingRunnable,
+ MS_TO_CHECK_ON_PAUSED_THREADS, TimeUnit.MILLISECONDS);
+ }
+
+ ScheduledFuture> oldScheduledFuture = this.scheduledFutureRef.getAndSet(newScheduledFuture);
+ if (oldScheduledFuture != null)
+ {
+ // stop the last scheduled check,
+ // we just checked the queue and will want to wait the full
+ // timeout first
+ oldScheduledFuture.cancel(false);
+ }
}
finally
{
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPoolUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPoolUtil.java
index d39a081ad..67c8440ed 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPoolUtil.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPoolUtil.java
@@ -55,7 +55,6 @@ public class ThreadPoolUtil
@Nullable
public static PriorityTaskPicker.Executor getUpdatePropagatorExecutor() { return updatePropagatorThreadPool; }
- public static final DhThreadFactory WORLD_GEN_THREAD_FACTORY = new DhThreadFactory("World Gen", Thread.MIN_PRIORITY, false);
private static PriorityTaskPicker.Executor worldGenThreadPool;
@Nullable
public static PriorityTaskPicker.Executor getWorldGenExecutor() { return worldGenThreadPool; }
@@ -172,10 +171,19 @@ public class ThreadPoolUtil
public static boolean onlyRunThreadIfCameraMovingSlowly()
{
double cameraSpeed = ClientApi.INSTANCE.cameraSpeedRollingAverage.getAverage();
+ // stop these threads if moving a little bit slower than max elytra speed
double maxAllowedSpeed = (LodUtil.ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC - 10.0);
if (cameraSpeed > maxAllowedSpeed)
{
- // pause this thread pool if the user is moving too fast
+ // pause if the user is moving too fast
+ return false;
+ }
+
+ PriorityTaskPicker.Executor executor = getRenderLoadingExecutor();
+ if (executor != null
+ && executor.getQueueSize() > 0)
+ {
+ // pause if LODs are being loaded for rendering
return false;
}