pause world gen when moving quickly

This commit is contained in:
James Seibel
2026-01-17 17:03:43 -06:00
parent eadf19405e
commit 37d08b6dfa
5 changed files with 118 additions and 26 deletions
@@ -36,7 +36,6 @@ import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.text.NumberFormat;
@@ -219,7 +218,11 @@ public class F3Screen
// active threads
int activeThreadCount = pool.getRunningTaskCount();
int threadCount = pool.getPoolSize();
message += ", Active: "+activeThreadCount+"/"+threadCount;
boolean threadPoolActive = pool.canRun();
String poolActiveString = threadPoolActive ? "Active" : "Paused";
message += ", "+poolActiveString+": "+activeThreadCount+"/"+threadCount;
// thread runtime
String runTimeAvgStr;
@@ -85,17 +85,10 @@ public class LodUtil
/** the opacity value returned by {@link IBlockStateWrapper#getOpacity()} if a block is fully opaque */
public static final int BLOCK_FULLY_OPAQUE = 16;
/**
* List of every block that can be used in a beacon's base. <br>
* Should be all lowercase
*/
public static final List<String> BEACON_BASE_BLOCK_NAME_LIST = Arrays.asList(
"iron_block",
"gold_block",
"diamond_block",
"emerald_block",
"netherite_block"
);
public static final double WALKING_SPEED_IN_BLOCKS_PER_SEC = 4.1;
public static final double SPRINTING_SPEED_IN_BLOCKS_PER_SEC = 7.1;
public static final double ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC = 30.0;
public static final double MAX_SPECTATOR_SPEED_IN_BLOCKS_PER_SEC = 100.0;
@@ -37,16 +37,18 @@ public class RenderUtil
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
/** all speeds are measured in blocks per second */
/**
* all speeds are measured in blocks per second
*
* @see LodUtil#WALKING_SPEED_IN_BLOCKS_PER_SEC
* @see LodUtil#SPRINTING_SPEED_IN_BLOCKS_PER_SEC
* @see LodUtil#ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC
* @see LodUtil#MAX_SPECTATOR_SPEED_IN_BLOCKS_PER_SEC
*/
private static class DynamicOverdraw
{
/**
* A walking player moves around 4.1 blocks/sec
* A sprint jumping player around 7.1 blocks/sec
*/
public static final float MIN_SPEED = 10.0f;
/** a max speed spectator player can move just shy of 100 blocks/sec */
public static final float MAX_SPEED = 100.0f;
public static final float MIN_SPEED = 10.0f; // a little faster than sprinting (7)
public static final float MAX_SPEED = (float)LodUtil.MAX_SPECTATOR_SPEED_IN_BLOCKS_PER_SEC;
public static final float MIN_OVERDRAW_RATIO = 0.2f;
}
@@ -6,6 +6,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.*;
@@ -39,10 +40,12 @@ public class PriorityTaskPicker
//==========//
// executor //
//==========//
///region
public Executor createExecutor(String name)
public Executor createExecutor(String name) { return this.createExecutor(name, null); }
public Executor createExecutor(String name, @Nullable PriorityTaskPicker.IExecutorCanRunFunc canRunFunc)
{
Executor executor = new Executor(this, name);
Executor executor = new Executor(this, name, canRunFunc);
this.executors.add(executor);
return executor;
}
@@ -73,6 +76,13 @@ public class PriorityTaskPicker
while (iterator.hasNext())
{
Executor executor = iterator.next();
// skip executors that are paused
if (!executor.canRun())
{
continue;
}
int queuedTaskCount = 0;
TrackedRunnable task;
@@ -142,11 +152,14 @@ public class PriorityTaskPicker
}
}
///endregion
//================//
// helper classes //
//================//
///region
/**
* Each executor handles a specific type of work that DH needs done.
@@ -167,6 +180,9 @@ public class PriorityTaskPicker
/** used for performance logging */
private final RollingAverage runTimeInMsRollingAverage = new RollingAverage(200);
@Nullable
private final PriorityTaskPicker.IExecutorCanRunFunc canRunFunc;
/** holds the threads this {@link Executor} can run */
private RateLimitedThreadPoolExecutor threadPoolExecutor;
@@ -175,11 +191,13 @@ public class PriorityTaskPicker
//=============//
// constructor //
//=============//
///region
public Executor(PriorityTaskPicker parentTaskPicker, String name)
public Executor(PriorityTaskPicker parentTaskPicker, String name, @Nullable PriorityTaskPicker.IExecutorCanRunFunc canRunFunc)
{
this.parentTaskPicker = parentTaskPicker;
this.name = name;
this.canRunFunc = canRunFunc;
this.threadPoolExecutor = this.createThreadPool();
@@ -195,11 +213,14 @@ public class PriorityTaskPicker
);
}
///endregion
//=================//
// config handling //
//=================//
///region
@Override
public void onConfigValueSet()
@@ -215,11 +236,14 @@ public class PriorityTaskPicker
}
}
///endregion
//=====================//
// task queue handling //
//=====================//
///region
@Override
public void execute(@NotNull Runnable command)
@@ -258,11 +282,25 @@ public class PriorityTaskPicker
public void clearQueue() { this.taskQueue.clear(); }
public boolean canRun()
{
if (this.canRunFunc == null)
{
return true;
}
return this.canRunFunc.canRun();
}
//endregion
//==========//
// shutdown //
//==========//
///region
@Override
public void shutdown() { this.threadPoolExecutor.shutdown(); }
@@ -279,6 +317,8 @@ public class PriorityTaskPicker
public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException
{ return this.threadPoolExecutor.awaitTermination(timeout, unit); }
///endregion
}
/** used so we can {@link PriorityTaskPicker.Executor#remove(Runnable)} using the original {@link Runnable} */
@@ -334,6 +374,18 @@ public class PriorityTaskPicker
}
/**
* Provides a way to dynamically enable/disable
* certain {@link PriorityTaskPicker.Executor}'s.
*/
@FunctionalInterface
public interface IExecutorCanRunFunc
{
boolean canRun();
}
///endregion
}
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.core.util.threading;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -94,6 +96,7 @@ public class ThreadPoolUtil
//=================//
// setup / cleanup //
//=================//
///region
public static void setupThreadPools()
{
@@ -112,8 +115,9 @@ public class ThreadPoolUtil
fileHandlerThreadPool = taskPicker.createExecutor("IO");
renderSectionLoadThreadPool = taskPicker.createExecutor("Render Loader");
chunkToLodBuilderThreadPool = taskPicker.createExecutor("LOD Builder");
updatePropagatorThreadPool = taskPicker.createExecutor("Update Propagator");
worldGenThreadPool = taskPicker.createExecutor("World Gen");
updatePropagatorThreadPool = taskPicker.createExecutor("Update Propagator", ThreadPoolUtil::onlyRunThreadIfCameraMovingSlowly);
worldGenThreadPool = taskPicker.createExecutor("World Gen", ThreadPoolUtil::onlyRunThreadIfCameraMovingSlowly);
@@ -144,4 +148,42 @@ public class ThreadPoolUtil
fullDataMigrationThreadPool.shutdown();
}
///endregion
//================//
// helper methods //
//================//
///region
/**
* Some thread pools are very heavy (IE world gen)
* making LOD load times slower. Pausing those
* threads when the player is moving can provide a better experience. <br><br>
*
* all speeds are measured in blocks per second
*
* @see LodUtil#WALKING_SPEED_IN_BLOCKS_PER_SEC
* @see LodUtil#SPRINTING_SPEED_IN_BLOCKS_PER_SEC
* @see LodUtil#ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC
* @see LodUtil#MAX_SPECTATOR_SPEED_IN_BLOCKS_PER_SEC
*/
public static boolean onlyRunThreadIfCameraMovingSlowly()
{
double cameraSpeed = ClientApi.INSTANCE.cameraSpeedRollingAverage.getAverage();
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
return false;
}
return true;
}
///endregion
}