From c33fb585551d9cddbd82a505d2fed16d5e96698f Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Wed, 29 Oct 2025 01:33:11 +0300 Subject: [PATCH] add back chunk system algorithms --- divinemc-server/build.gradle.kts.patch | 3 +- ...020-Implement-chunk-system-algorithm.patch | 60 +++++++++ .../divinemc/chunk/ChunkSystemAlgorithm.java | 117 ++++++++++++++++++ .../bxteam/divinemc/config/DivineConfig.java | 9 ++ 4 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 divinemc-server/paper-patches/features/0020-Implement-chunk-system-algorithm.patch create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/chunk/ChunkSystemAlgorithm.java diff --git a/divinemc-server/build.gradle.kts.patch b/divinemc-server/build.gradle.kts.patch index c09b187..61e2def 100644 --- a/divinemc-server/build.gradle.kts.patch +++ b/divinemc-server/build.gradle.kts.patch @@ -72,7 +72,7 @@ } } val log4jPlugins = sourceSets.create("log4jPlugins") { -@@ -154,10 +_,22 @@ +@@ -154,10 +_,23 @@ } dependencies { @@ -88,6 +88,7 @@ + implementation("org.lz4:lz4-java:1.8.0") + implementation("net.openhft:zero-allocation-hashing:0.16") + implementation("org.agrona:agrona:2.2.4") ++ implementation("net.objecthunter:exp4j:0.4.8") + // DivineMC end - Dependencies + implementation("ca.spottedleaf:concurrentutil:0.0.5") diff --git a/divinemc-server/paper-patches/features/0020-Implement-chunk-system-algorithm.patch b/divinemc-server/paper-patches/features/0020-Implement-chunk-system-algorithm.patch new file mode 100644 index 0000000..15eda2f --- /dev/null +++ b/divinemc-server/paper-patches/features/0020-Implement-chunk-system-algorithm.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 29 Oct 2025 01:31:51 +0300 +Subject: [PATCH] Implement chunk system algorithm + +This patch adds configurable chunk system algorithms for determining +worker thread allocation for chunk loading and generation. + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +index 9a1fbd88e56f8dd01368a0a5e74cfa8c54965d7f..0a21222c77b84601841e1750daf3eb0865d99b67 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +@@ -34,27 +34,17 @@ public final class MoonriseCommon { + public static final BalancedPrioritisedThreadPool.OrderedStreamGroup CLIENT_GROUP = MoonriseCommon.WORKER_POOL.createOrderedStreamGroup(); + public static final BalancedPrioritisedThreadPool.OrderedStreamGroup SERVER_GROUP = MoonriseCommon.WORKER_POOL.createOrderedStreamGroup(); + +- public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { +- int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; +- if (defaultWorkerThreads <= 4) { +- defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; +- } else { +- defaultWorkerThreads = defaultWorkerThreads / 2; +- } +- defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); +- +- int workerThreads = configWorkerThreads; +- +- if (workerThreads <= 0) { +- workerThreads = defaultWorkerThreads; +- } +- +- final int ioThreads = Math.max(1, configIoThreads); ++ public static void init(final int configWorkerThreads, final int configIoThreads) { ++ // DivineMC start - Implement chunk system algorithm ++ org.bxteam.divinemc.chunk.ChunkSystemAlgorithm algorithm = org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.chunkWorkerAlgorithm; ++ int workerThreads = algorithm.evalWorkers(configWorkerThreads, configIoThreads); ++ int ioThreads = algorithm.evalIO(configWorkerThreads, configIoThreads); ++ // DivineMC end - Implement chunk system algorithm + + WORKER_POOL.adjustThreadCount(workerThreads); + IO_POOL.adjustThreadCount(ioThreads); + +- LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); ++ LOGGER.info("{} is using {} worker threads, {} I/O threads", PlatformHooks.get().getBrand(), workerThreads, ioThreads); // DivineMC - Implement chunk system algorithm - better logging + } + + public static final long IO_QUEUE_HOLD_TIME = (long)(25.0e6); // 25ms +diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +index 6c23b24477dd24ed43932d0c5ae5d97f80d968f6..982ffe9c9ec54242cdf7ee6dca42e9f815666260 100644 +--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +@@ -227,7 +227,7 @@ public class GlobalConfiguration extends ConfigurationPart { + + @PostProcess + private void postProcess() { +- ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads); ++ ca.spottedleaf.moonrise.common.util.MoonriseCommon.init(this.workerThreads, this.ioThreads); // DivineMC - Implement chunk system algorithm + } + } + diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/chunk/ChunkSystemAlgorithm.java b/divinemc-server/src/main/java/org/bxteam/divinemc/chunk/ChunkSystemAlgorithm.java new file mode 100644 index 0000000..4c72914 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/chunk/ChunkSystemAlgorithm.java @@ -0,0 +1,117 @@ +package org.bxteam.divinemc.chunk; + +import ca.spottedleaf.moonrise.common.PlatformHooks; +import io.netty.util.internal.PlatformDependent; +import net.objecthunter.exp4j.ExpressionBuilder; +import net.objecthunter.exp4j.function.Function; +import org.jetbrains.annotations.NotNull; +import oshi.util.tuples.Pair; + +import java.util.function.BiFunction; + +public enum ChunkSystemAlgorithm { + MOONRISE((configWorkerThreads, configIoThreads) -> { + int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; + if (defaultWorkerThreads <= 4) { + defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; + } else { + defaultWorkerThreads = defaultWorkerThreads / 2; + } + defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); + + int workerThreads = configWorkerThreads; + + if (workerThreads <= 0) { + workerThreads = defaultWorkerThreads; + } + final int ioThreads = Math.max(1, configIoThreads); + return new Pair<>(workerThreads, ioThreads); + }), + C2ME_NEW((configWorkerThreads, configIoThreads) -> { + String expression = """ + + max( + 1, + min( + if( is_windows, + (cpus / 1.6), + (cpus / 1.3) + ) - if(is_client, 1, 0), + ( ( mem_gb - (if(is_client, 1.0, 0.5)) ) / 0.6 ) + ) + ) + \040"""; + int eval = configWorkerThreads <= 0 ? tryEvaluateExpression(expression) : configWorkerThreads; + return new Pair<>(eval, Math.max(1, configIoThreads)); + }), + C2ME((configWorkerThreads, configIoThreads) -> { + String expression = """ + + max( + 1, + min( + if( is_windows, + (cpus / 1.6 - 2), + (cpus / 1.2 - 2) + ) - if(is_client, 2, 0), + if( is_j9vm, + ( ( mem_gb - (if(is_client, 0.6, 0.2)) ) / 0.4 ), + ( ( mem_gb - (if(is_client, 1.2, 0.6)) ) / 0.6 ) + ) + ) + ) + \040"""; + int eval = configWorkerThreads <= 0 ? tryEvaluateExpression(expression) : configWorkerThreads; + return new Pair<>(eval, Math.max(1, configIoThreads)); + }); + + private final BiFunction> eval; + + ChunkSystemAlgorithm(BiFunction> eval) { + this.eval = eval; + } + + private static int tryEvaluateExpression(String expression) { + return (int) Math.max(1, + new ExpressionBuilder(expression) + .variables("is_windows", "is_j9vm", "is_client", "cpus", "mem_gb") + .function(new Function("max", 2) { + @Override + public double apply(double... args) { + return Math.max(args[0], args[1]); + } + }) + .function(new Function("min", 2) { + @Override + public double apply(double... args) { + return Math.min(args[0], args[1]); + } + }) + .function(new Function("if", 3) { + @Override + public double apply(double... args) { + return args[0] != 0 ? args[1] : args[2]; + } + }) + .build() + .setVariable("is_windows", PlatformDependent.isWindows() ? 1 : 0) + .setVariable("is_j9vm", PlatformDependent.isJ9Jvm() ? 1 : 0) + .setVariable("is_client", 0) + .setVariable("cpus", Runtime.getRuntime().availableProcessors()) + .setVariable("mem_gb", Runtime.getRuntime().maxMemory() / 1024.0 / 1024.0 / 1024.0) + .evaluate() + ); + } + + public int evalWorkers(final int configWorkerThreads, final int configIoThreads) { + return eval.apply(configWorkerThreads, configIoThreads).getA(); + } + + public int evalIO(final int configWorkerThreads, final int configIoThreads) { + return eval.apply(configWorkerThreads, configIoThreads).getB(); + } + + public @NotNull String asDebugString() { + return this + "(" + evalWorkers(-1, -1) + ")"; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/config/DivineConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/config/DivineConfig.java index ffaaadd..d93a2ef 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/config/DivineConfig.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/config/DivineConfig.java @@ -7,6 +7,7 @@ import net.minecraft.world.entity.EntityType; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bxteam.divinemc.chunk.ChunkSystemAlgorithm; import org.bxteam.divinemc.config.annotations.Experimental; import org.bxteam.divinemc.async.pathfinding.PathfindTaskRejectPolicy; import org.bxteam.divinemc.region.EnumRegionFileExtension; @@ -344,6 +345,7 @@ public class DivineConfig { public static long chunkDataCacheLimit = 32678L; public static int maxViewDistance = 32; public static int playerNearChunkDetectionRange = 128; + public static ChunkSystemAlgorithm chunkWorkerAlgorithm = ChunkSystemAlgorithm.MOONRISE; public static boolean useEuclideanDistanceSquared = true; public static boolean endBiomeCacheEnabled = false; public static int endBiomeCacheCapacity = 1024; @@ -410,6 +412,13 @@ public class DivineConfig { "This value is used in the calculation 'range/16' to get the distance in chunks any player must be to allow the check to pass.", "By default, this range is computed to 8, meaning a player must be within an 8 chunk radius of a chunk position to pass.", "Keep in mind the result is rounded to the nearest whole number."); + chunkWorkerAlgorithm = ChunkSystemAlgorithm.valueOf(getString(ConfigCategory.PERFORMANCE.key("chunks.chunk-worker-algorithm"), chunkWorkerAlgorithm.name(), + "Algorithm used to determine the number of worker threads for chunk loading and generation.", + "", + "Available algorithms:", + " - MOONRISE: Paper's default algorithm. Conservative approach, uses fewer threads (CPU cores / 2).", + " - C2ME: More aggressive thread allocation than MOONRISE. Considers both CPU cores and available memory. May use more threads on high-end systems.", + " - C2ME_NEW: Modern C2ME algorithm. Balanced approach between MOONRISE and C2ME. Optimized for current hardware, slightly less aggressive than old C2ME.")); useEuclideanDistanceSquared = getBoolean(ConfigCategory.PERFORMANCE.key("chunks.use-euclidean-distance-squared"), useEuclideanDistanceSquared, "If enabled, euclidean distance squared for chunk task ordering will be used.");