diff --git a/divinemc-server/minecraft-patches/features/0037-Regionized-Chunk-Ticking.patch b/divinemc-server/minecraft-patches/features/0037-Regionized-Chunk-Ticking.patch new file mode 100644 index 0000000..1af92b9 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0037-Regionized-Chunk-Ticking.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Mon, 24 Feb 2025 19:58:39 +0300 +Subject: [PATCH] Regionized Chunk Ticking + + +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index 2678bf59d557f085c7265e2f3eb038647723d35e..ee4e5462c784ea8588c12eb2b248c708c4d84bee 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -56,6 +56,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + private static final Logger LOGGER = LogUtils.getLogger(); + private final DistanceManager distanceManager; + private final ServerLevel level; ++ public static final Executor REGION_EXECUTOR = java.util.concurrent.Executors.newFixedThreadPool(org.bxteam.divinemc.DivineConfig.regionizedChunkTickingExecutorThreadCount, new org.bxteam.divinemc.util.NamedAgnosticThreadFactory<>("region_ticking", ca.spottedleaf.moonrise.common.util.TickThread::new, org.bxteam.divinemc.DivineConfig.regionizedChunkTickingExecutorThreadPriority)); // DivineMC - Regionized Chunk Ticking + public final Thread mainThread; + final ThreadedLevelLightEngine lightEngine; + public final ServerChunkCache.MainThreadExecutor mainThreadProcessor; +@@ -479,6 +480,41 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + // CraftBukkit end + ++ // DivineMC start - Regionized Chunk Ticking ++ private static final int[] DX = {1, -1, 0, 0, 1, -1, -1, 1}; ++ private static final int[] DZ = {0, 0, 1, -1, 1, 1, -1, -1}; ++ ++ private List> splitChunksIntoRegions(List chunks) { ++ Set chunkSet = new java.util.HashSet<>(chunks); ++ Set visited = new java.util.HashSet<>(chunks.size()); ++ List> groups = new ArrayList<>(); ++ ++ for (LevelChunk chunk : chunks) { ++ if (visited.contains(chunk)) continue; ++ ++ List group = new ArrayList<>(64); ++ java.util.ArrayDeque stack = new java.util.ArrayDeque<>(); ++ stack.push(chunk); ++ visited.add(chunk); ++ ++ while (!stack.isEmpty()) { ++ LevelChunk current = stack.pop(); ++ group.add(current); ++ ++ for (int i = 0; i < 8; i++) { ++ LevelChunk neighbor = getChunk(current.locX + DX[i], current.locZ + DZ[i], false); ++ if (neighbor == null || !chunkSet.contains(neighbor) || !visited.add(neighbor)) continue; ++ stack.push(neighbor); ++ } ++ } ++ ++ groups.add(group); ++ } ++ ++ return groups; ++ } ++ // DivineMC end - Regionized Chunk Ticking ++ + @Override + public void tick(BooleanSupplier hasTimeLeft, boolean tickChunks) { + ProfilerFiller profilerFiller = Profiler.get(); +@@ -519,7 +555,27 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + this.shuffleRandom.setSeed(this.level.random.nextLong()); + if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) Util.shuffle(list, this.shuffleRandom); // Paper - Optional per player mob spawns; do not need this when per-player is enabled + // Paper end - chunk tick iteration optimisation +- this.tickChunks(profilerFiller, l, list); ++ // DivineMC start - Regionized Chunk Ticking ++ if (org.bxteam.divinemc.DivineConfig.enableRegionizedChunkTicking) { ++ List> regions = splitChunksIntoRegions(list); ++ List> futures = new ArrayList<>(); ++ for (List region : regions) { ++ CompletableFuture future = new CompletableFuture<>(); ++ futures.add(future); ++ REGION_EXECUTOR.execute(() -> { ++ try { ++ tickChunks(profilerFiller, l, region); ++ } finally { ++ future.complete(null); ++ } ++ }); ++ } ++ CompletableFuture finalFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); ++ finalFuture.join(); ++ } else { ++ this.tickChunks(profilerFiller, l, list); ++ } ++ // DivineMC end - Regionized Chunk Ticking + profilerFiller.pop(); + } finally { + list.clear(); diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java index 510efba..c6c35bc 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java @@ -246,6 +246,24 @@ public class DivineConfig { "modpacks where many structure mods are using very high weight values in their template pools."); } + public static boolean enableRegionizedChunkTicking = false; + public static int regionizedChunkTickingExecutorThreadCount = 4; + public static int regionizedChunkTickingExecutorThreadPriority = Thread.NORM_PRIORITY; + private static void regionizedChunkTicking() { + enableRegionizedChunkTicking = getBoolean("settings.regionized-chunk-ticking.enable", enableRegionizedChunkTicking, + "Enables regionized chunk ticking. This feature is similar to Folia"); + + regionizedChunkTickingExecutorThreadCount = getInt("settings.regionized-chunk-ticking.executor-thread-count", regionizedChunkTickingExecutorThreadCount, + "The amount of threads to allocate to regionized chunk ticking."); + regionizedChunkTickingExecutorThreadPriority = getInt("settings.regionized-chunk-ticking.executor-thread-priority", regionizedChunkTickingExecutorThreadPriority, + "Configures the thread priority of the executor"); + + if (regionizedChunkTickingExecutorThreadCount < 1 || regionizedChunkTickingExecutorThreadCount > 10) { + LOGGER.warn("Invalid regionized chunk ticking thread count: " + regionizedChunkTickingExecutorThreadCount + ", resetting to default (5)"); + regionizedChunkTickingExecutorThreadCount = 5; + } + } + public static boolean skipUselessSecondaryPoiSensor = true; public static boolean clumpOrbs = true; public static boolean ignoreMovedTooQuicklyWhenLagging = true; diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/NamedAgnosticThreadFactory.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/NamedAgnosticThreadFactory.java new file mode 100644 index 0000000..c0c1293 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/NamedAgnosticThreadFactory.java @@ -0,0 +1,41 @@ +package org.bxteam.divinemc.util; + +import com.mojang.logging.LogUtils; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; + +public class NamedAgnosticThreadFactory implements ThreadFactory { + private static final Logger LOGGER = LogUtils.getLogger(); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + private final ThreadBuilderFunction typeOfThread; + private final int priority; + + public NamedAgnosticThreadFactory(String name, ThreadBuilderFunction typeOfThread, int priority) { + this.typeOfThread = typeOfThread; + this.priority = priority; + this.group = Thread.currentThread().getThreadGroup(); + this.namePrefix = name + "-"; + } + + @Override + public Thread newThread(@NotNull Runnable runnable) { + T thread = typeOfThread.apply(this.group, runnable, this.namePrefix + this.threadNumber.getAndIncrement()); + thread.setUncaughtExceptionHandler((threadx, throwable) -> { + LOGGER.error("Caught exception in thread {} from {}", threadx, runnable); + LOGGER.error("", throwable); + }); + if (thread.getPriority() != priority) { + thread.setPriority(priority); + } + + return thread; + } + + public interface ThreadBuilderFunction { + T apply(ThreadGroup threadGroup, Runnable runnable, String name); + } +}