diff --git a/divinemc-server/minecraft-patches/features/0034-Async-Chunk-Sending.patch b/divinemc-server/minecraft-patches/features/0033-Async-Chunk-Sending.patch similarity index 98% rename from divinemc-server/minecraft-patches/features/0034-Async-Chunk-Sending.patch rename to divinemc-server/minecraft-patches/features/0033-Async-Chunk-Sending.patch index 426243e..6689f09 100644 --- a/divinemc-server/minecraft-patches/features/0034-Async-Chunk-Sending.patch +++ b/divinemc-server/minecraft-patches/features/0033-Async-Chunk-Sending.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Async Chunk Sending diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -index 76b8d42ae530b59cdaba0583365a557da6b90ede..235772cc9a7c878235b97c8d84cacda3016f91ca 100644 +index 886825a10bd06b4b656d19a05624c74f2686feb3..1f3f15e3c3eb2c8e71caba069b457cec53f635f2 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -55,6 +55,8 @@ public final class RegionizedPlayerChunkLoader { diff --git a/divinemc-server/minecraft-patches/features/0033-Regionized-Chunk-Ticking.patch b/divinemc-server/minecraft-patches/features/0033-Regionized-Chunk-Ticking.patch deleted file mode 100644 index 5569b2b..0000000 --- a/divinemc-server/minecraft-patches/features/0033-Regionized-Chunk-Ticking.patch +++ /dev/null @@ -1,114 +0,0 @@ -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 ae0e36d198ad8243920c8e8a55c0be4945542763..7f982949304535376dabf42aab1848cabc8987cf 100644 ---- a/net/minecraft/server/level/ServerChunkCache.java -+++ b/net/minecraft/server/level/ServerChunkCache.java -@@ -54,6 +54,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - private static final Logger LOGGER = LogUtils.getLogger(); - private final DistanceManager distanceManager; - private final ServerLevel level; -+ // DivineMC - Regionized Chunk Ticking -+ 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)); -+ public volatile int tickingRegionsCount = 0; -+ // DivineMC end - Regionized Chunk Ticking - public final Thread mainThread; - final ThreadedLevelLightEngine lightEngine; - public final ServerChunkCache.MainThreadExecutor mainThreadProcessor; -@@ -461,6 +465,46 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.close(save, true); // Paper - rewrite chunk system - } - -+ // 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) { -+ int size = chunks.size(); -+ java.util.IdentityHashMap chunkSet = new java.util.IdentityHashMap<>(size); -+ -+ for (LevelChunk chunk : chunks) { -+ chunkSet.put(chunk, Boolean.TRUE); -+ } -+ -+ List> groups = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(size >> 3); -+ LevelChunk[] stack = new LevelChunk[size]; -+ int stackPointer = 0; -+ -+ for (LevelChunk chunk : chunks) { -+ if (chunkSet.remove(chunk) == null) continue; -+ -+ List group = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(64); -+ stack[stackPointer++] = chunk; -+ -+ while (stackPointer > 0) { -+ LevelChunk current = stack[--stackPointer]; -+ 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.remove(neighbor) == null) continue; -+ stack[stackPointer++] = neighbor; -+ } -+ } -+ -+ groups.add(group); -+ } -+ -+ return groups.toArray(new List[0]); -+ } -+ // DivineMC end - Regionized Chunk Ticking -+ - @Override - public void tick(BooleanSupplier hasTimeLeft, boolean tickChunks) { - if (this.level.tickRateManager().runsNormally() || !tickChunks || this.level.spigotConfig.unloadFrozenChunks) { // Spigot -@@ -492,7 +536,44 @@ 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(l, list); -+ // DivineMC start - Regionized Chunk Ticking -+ if (org.bxteam.divinemc.DivineConfig.enableRegionizedChunkTicking) { -+ List[] regions = splitChunksIntoRegions(list); -+ int regionCount = regions.length; -+ this.tickingRegionsCount = regionCount; -+ java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(regionCount); -+ -+ try { -+ java.util.concurrent.ForkJoinPool.managedBlock(new java.util.concurrent.ForkJoinPool.ManagedBlocker() { -+ @Override -+ public boolean block() throws InterruptedException { -+ for (List region : regions) { -+ if (region == null) continue; -+ REGION_EXECUTOR.execute(() -> { -+ try { -+ tickChunks(l, region); -+ } finally { -+ latch.countDown(); -+ } -+ }); -+ } -+ -+ latch.await(); -+ return true; -+ } -+ -+ @Override -+ public boolean isReleasable() { -+ return latch.getCount() == 0; -+ } -+ }); -+ } catch (InterruptedException ex) { -+ throw new RuntimeException("Interrupted managed block during region ticking", ex); -+ } -+ } else { -+ this.tickChunks(l, list); -+ } -+ // DivineMC end - Regionized Chunk Ticking - } finally { - list.clear(); - } diff --git a/divinemc-server/minecraft-patches/features/0035-Option-to-disable-disconnect.spam.patch b/divinemc-server/minecraft-patches/features/0034-Option-to-disable-disconnect.spam.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0035-Option-to-disable-disconnect.spam.patch rename to divinemc-server/minecraft-patches/features/0034-Option-to-disable-disconnect.spam.patch diff --git a/divinemc-server/minecraft-patches/features/0036-Configurable-MC-59471.patch b/divinemc-server/minecraft-patches/features/0035-Configurable-MC-59471.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0036-Configurable-MC-59471.patch rename to divinemc-server/minecraft-patches/features/0035-Configurable-MC-59471.patch diff --git a/divinemc-server/minecraft-patches/features/0037-ModernFix-compact_bit_storage.patch b/divinemc-server/minecraft-patches/features/0036-ModernFix-compact_bit_storage.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0037-ModernFix-compact_bit_storage.patch rename to divinemc-server/minecraft-patches/features/0036-ModernFix-compact_bit_storage.patch diff --git a/divinemc-server/minecraft-patches/features/0038-Command-block-parse-results-caching.patch b/divinemc-server/minecraft-patches/features/0037-Command-block-parse-results-caching.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0038-Command-block-parse-results-caching.patch rename to divinemc-server/minecraft-patches/features/0037-Command-block-parse-results-caching.patch diff --git a/divinemc-server/minecraft-patches/features/0039-Linear-region-file-format.patch b/divinemc-server/minecraft-patches/features/0038-Linear-region-file-format.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0039-Linear-region-file-format.patch rename to divinemc-server/minecraft-patches/features/0038-Linear-region-file-format.patch diff --git a/divinemc-server/minecraft-patches/features/0042-Configurable-movement-speed-for-entities.patch b/divinemc-server/minecraft-patches/features/0039-Configurable-movement-speed-for-entities.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0042-Configurable-movement-speed-for-entities.patch rename to divinemc-server/minecraft-patches/features/0039-Configurable-movement-speed-for-entities.patch diff --git a/divinemc-server/minecraft-patches/features/0043-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch b/divinemc-server/minecraft-patches/features/0040-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch similarity index 99% rename from divinemc-server/minecraft-patches/features/0043-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch rename to divinemc-server/minecraft-patches/features/0040-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch index ab22096..7f8f5c0 100644 --- a/divinemc-server/minecraft-patches/features/0043-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch +++ b/divinemc-server/minecraft-patches/features/0040-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch @@ -23,7 +23,7 @@ index b402f6a6ecb8047bbb791b212fba375f4c9e6af5..f381f3ecd27315e06ca6883006a8a4c3 player.awardStat(Stats.ITEM_USED.get(item)); // level.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState()); // CraftBukkit diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java -index c8c8351f5645cf4041d26b0e02c072546ad329c6..75a695aaf0dd2f58e1fa5e1c532ae298c2a2abdb 100644 +index 075987ec1eabb7385918049c54d7210ff2b38847..d7488164313cbf038ff7669da275bfdb41e8343c 100644 --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java @@ -727,13 +727,25 @@ public interface DispenseItemBehavior { diff --git a/divinemc-server/minecraft-patches/features/0044-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch b/divinemc-server/minecraft-patches/features/0041-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch similarity index 95% rename from divinemc-server/minecraft-patches/features/0044-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch rename to divinemc-server/minecraft-patches/features/0041-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch index 5f6f427..6a672e6 100644 --- a/divinemc-server/minecraft-patches/features/0044-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch +++ b/divinemc-server/minecraft-patches/features/0041-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Leaf: Improve BlockEntity ticking isRemoved check diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index 0337f4b9ca3c9c9a1e2a7cf19fcbad5e78b949dc..1820069a7c5833b0a13e034c232f06af234788e3 100644 +index 534384727e852dc8ea822f49f182af49eb3a40c1..d779ff76432e498123ed1d70946a9833c939b754 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -989,13 +989,26 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p diff --git a/divinemc-server/minecraft-patches/features/0045-Paper-PR-Throttle-failed-spawn-attempts.patch b/divinemc-server/minecraft-patches/features/0042-Paper-PR-Throttle-failed-spawn-attempts.patch similarity index 97% rename from divinemc-server/minecraft-patches/features/0045-Paper-PR-Throttle-failed-spawn-attempts.patch rename to divinemc-server/minecraft-patches/features/0042-Paper-PR-Throttle-failed-spawn-attempts.patch index 7adef18..d914b08 100644 --- a/divinemc-server/minecraft-patches/features/0045-Paper-PR-Throttle-failed-spawn-attempts.patch +++ b/divinemc-server/minecraft-patches/features/0042-Paper-PR-Throttle-failed-spawn-attempts.patch @@ -22,10 +22,10 @@ Example config in paper-world-defaults.yml: ``` diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java -index 650dfce05bfc68d4c664471b430bd5c0f9629283..3e9ab446632ffe56de45f7622db44070e1cbaf1f 100644 +index d3f5242fc66529bf3137da4d505a6cf55e749e43..1056a17c53e7d16d5fba7f9a354dfbc235d4d974 100644 --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java -@@ -175,29 +175,52 @@ public final class NaturalSpawner { +@@ -164,29 +164,52 @@ public final class NaturalSpawner { // Copied from getFilteredSpawningCategories int limit = mobCategory.getMaxInstancesPerChunk(); SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(mobCategory); @@ -91,7 +91,7 @@ index 650dfce05bfc68d4c664471b430bd5c0f9629283..3e9ab446632ffe56de45f7622db44070 // Paper end - Optional per player mob spawns } } -@@ -221,12 +244,21 @@ public final class NaturalSpawner { +@@ -210,12 +233,21 @@ public final class NaturalSpawner { } public static void spawnCategoryForChunk( MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final Consumer trackEntity @@ -114,7 +114,7 @@ index 650dfce05bfc68d4c664471b430bd5c0f9629283..3e9ab446632ffe56de45f7622db44070 } @VisibleForDebug -@@ -246,16 +278,22 @@ public final class NaturalSpawner { +@@ -235,16 +267,22 @@ public final class NaturalSpawner { } public static void spawnCategoryForPosition( MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer trackEntity @@ -139,7 +139,7 @@ index 650dfce05bfc68d4c664471b430bd5c0f9629283..3e9ab446632ffe56de45f7622db44070 for (int i1 = 0; i1 < 3; i1++) { int x = pos.getX(); int z = pos.getZ(); -@@ -295,13 +333,13 @@ public final class NaturalSpawner { +@@ -284,13 +322,13 @@ public final class NaturalSpawner { } // Paper end - per player mob count backoff if (doSpawning == PreSpawnStatus.ABORT) { @@ -155,7 +155,7 @@ index 650dfce05bfc68d4c664471b430bd5c0f9629283..3e9ab446632ffe56de45f7622db44070 } mobForSpawn.moveTo(d, y, d1, level.random.nextFloat() * 360.0F, 0.0F); -@@ -324,7 +362,7 @@ public final class NaturalSpawner { +@@ -313,7 +351,7 @@ public final class NaturalSpawner { } // CraftBukkit end if (i >= mobForSpawn.getMaxSpawnClusterSize() || i >= maxSpawns) { // Paper - Optional per player mob spawns @@ -164,7 +164,7 @@ index 650dfce05bfc68d4c664471b430bd5c0f9629283..3e9ab446632ffe56de45f7622db44070 } if (mobForSpawn.isMaxGroupSizeReached(i3)) { -@@ -337,6 +375,8 @@ public final class NaturalSpawner { +@@ -326,6 +364,8 @@ public final class NaturalSpawner { } } } diff --git a/divinemc-server/minecraft-patches/features/0046-Optimize-Raids.patch b/divinemc-server/minecraft-patches/features/0043-Optimize-Raids.patch similarity index 98% rename from divinemc-server/minecraft-patches/features/0046-Optimize-Raids.patch rename to divinemc-server/minecraft-patches/features/0043-Optimize-Raids.patch index c380052..4b4b488 100644 --- a/divinemc-server/minecraft-patches/features/0046-Optimize-Raids.patch +++ b/divinemc-server/minecraft-patches/features/0043-Optimize-Raids.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Optimize Raids diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 92e35158b68dcb8d1f34fb1b748c12d1d39468c7..c87d1f81bd1b4b9756bd2f4c1dbc58a2dc85c63b 100644 +index 18719b17cfe19b7f864adcec0842da9ab6ee6abb..9ed108791b6e6eb81dfd90b36f054566ce62022f 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -219,6 +219,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe diff --git a/divinemc-server/minecraft-patches/features/0047-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch b/divinemc-server/minecraft-patches/features/0044-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0047-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch rename to divinemc-server/minecraft-patches/features/0044-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch diff --git a/divinemc-server/minecraft-patches/features/0048-Player-ProfileResult-caching.patch b/divinemc-server/minecraft-patches/features/0045-Player-ProfileResult-caching.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0048-Player-ProfileResult-caching.patch rename to divinemc-server/minecraft-patches/features/0045-Player-ProfileResult-caching.patch diff --git a/divinemc-server/minecraft-patches/features/0046-Regionized-Chunk-Ticking.patch b/divinemc-server/minecraft-patches/features/0046-Regionized-Chunk-Ticking.patch new file mode 100644 index 0000000..2536b0d --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0046-Regionized-Chunk-Ticking.patch @@ -0,0 +1,357 @@ +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 + +This patch adds regionized chunk ticking feature, by grouping adjacent chunks into regions and processing each region on its own thread. + +Original idea by Dueris, modified by NONPLAYT and dan28000 + +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index ae0e36d198ad8243920c8e8a55c0be4945542763..aa515bc07b899351f2b0ac8d61df8e5586616084 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -54,6 +54,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; +@@ -66,8 +67,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + private final long[] lastChunkPos = new long[4]; + private final ChunkStatus[] lastChunkStatus = new ChunkStatus[4]; + private final ChunkAccess[] lastChunk = new ChunkAccess[4]; +- private final List tickingChunks = new ArrayList<>(); +- private final Set chunkHoldersToBroadcast = new ReferenceOpenHashSet<>(); ++ // DivineMC start - Regionized Chunk Ticking ++ private final it.unimi.dsi.fastutil.objects.ObjectArrayList tickingChunks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ private final Set chunkHoldersToBroadcast = java.util.Collections.synchronizedSet(new ReferenceOpenHashSet<>()); ++ // DivineMC end - Regionized Chunk Ticking + @Nullable + @VisibleForDebug + private NaturalSpawner.SpawnState lastSpawnState; +@@ -80,6 +83,92 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + // Paper end + // Paper start - rewrite chunk system + ++ // DivineMC start - Regionized Chunk Ticking ++ private record RegionData(List chunks, List entities) { ++ public boolean isEmpty() { ++ return chunks.isEmpty(); ++ } ++ } ++ ++ 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 RegionData[] splitChunksIntoRegions(List chunks) { ++ if (chunks.isEmpty()) return new RegionData[0]; ++ ++ int size = chunks.size(); ++ it.unimi.dsi.fastutil.objects.ObjectOpenHashSet chunkSet = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(chunks); ++ ++ it.unimi.dsi.fastutil.objects.ObjectArrayList groups = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(Math.max(1, level.players.size())); ++ LevelChunk[] stack = new LevelChunk[size]; ++ it.unimi.dsi.fastutil.longs.Long2IntMap chunkToRegionMap = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(size); ++ ++ for (LevelChunk chunk : chunks) { ++ if (!chunkSet.contains(chunk)) continue; ++ ++ it.unimi.dsi.fastutil.objects.ObjectArrayList group = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(64); ++ stack[0] = chunk; ++ int stackPointer = 1; ++ chunkSet.remove(chunk); ++ ++ while (stackPointer > 0) { ++ LevelChunk current = stack[--stackPointer]; ++ 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.remove(neighbor)) { ++ stack[stackPointer++] = neighbor; ++ } ++ } ++ } ++ ++ RegionData regionData = new RegionData(group, new it.unimi.dsi.fastutil.objects.ObjectArrayList<>()); ++ groups.add(regionData); ++ int index = groups.indexOf(regionData); ++ ++ for (LevelChunk regionChunk : group) { ++ chunkToRegionMap.put(regionChunk.coordinateKey, index); ++ } ++ ++ if (chunkSet.isEmpty()) break; ++ } ++ ++ level.entityTickList.entities.forEach(entity -> { ++ long chunkKey = entity.chunkPosition().longKey; ++ ++ int index = chunkToRegionMap.get(chunkKey); ++ RegionData regionData = groups.get(index); ++ if (regionData != null) { ++ regionData.entities().add(entity); ++ } else { ++ tickEntity(entity); ++ } ++ }); ++ ++ return groups.toArray(new RegionData[0]); ++ } ++ ++ private void tickEntity(Entity entity) { ++ if (!entity.isRemoved()) { ++ if (!level.tickRateManager().isEntityFrozen(entity)) { ++ entity.checkDespawn(); ++ // Paper - rewrite chunk system ++ Entity vehicle = entity.getVehicle(); ++ if (vehicle != null) { ++ if (!vehicle.isRemoved() && vehicle.hasPassenger(entity)) { ++ return; ++ } ++ ++ entity.stopRiding(); ++ } ++ ++ level.guardEntityTick(level::tickNonPassenger, entity); ++ } ++ } ++ } ++ // DivineMC end - Regionized Chunk Ticking ++ + @Override + public final void moonrise$setFullChunk(final int chunkX, final int chunkZ, final LevelChunk chunk) { + final long key = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ); +@@ -478,39 +567,106 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + this.clearCache(); + } + ++ // DivineMC start - Regionized Chunk Ticking + private void tickChunks() { +- long gameTime = this.level.getGameTime(); +- long l = gameTime - this.lastInhabitedUpdate; ++ final long gameTime = this.level.getGameTime(); ++ final long l = gameTime - this.lastInhabitedUpdate; + this.lastInhabitedUpdate = gameTime; +- if (!this.level.isDebug()) { +- if (this.level.tickRateManager().runsNormally()) { +- List list = this.tickingChunks; +- +- try { +- this.collectTickingChunks(list); +- // Paper start - chunk tick iteration optimisation +- 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(l, list); +- } finally { +- list.clear(); +- } +- } + ++ if (this.level.isDebug()) return; ++ ++ if (!this.level.tickRateManager().runsNormally()) { + this.broadcastChangedChunks(); ++ return; ++ } ++ ++ final it.unimi.dsi.fastutil.objects.ObjectArrayList list = this.tickingChunks; ++ ++ try { ++ this.collectTickingChunks(list); ++ ++ if (list.isEmpty()) return; ++ ++ // Paper start - chunk tick iteration optimisation ++ 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 ++ ++ if (org.bxteam.divinemc.DivineConfig.enableRegionizedChunkTicking) { ++ this.processChunksRegionized(l, list); ++ } else { ++ this.tickChunks(l, list); ++ this.broadcastChangedChunks(); ++ } ++ } finally { ++ list.clear(); + } + } + ++ private void processChunksRegionized(final long timeDelta, final List chunks) { ++ final RegionData[] regions = splitChunksIntoRegions(chunks); ++ final int regionCount = regions.length; ++ ++ if (regionCount == 0) return; ++ ++ java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(regionCount); ++ ++ try { ++ io.papermc.paper.entity.activation.ActivationRange.activateEntities(level); ++ java.util.concurrent.ForkJoinPool.managedBlock(new java.util.concurrent.ForkJoinPool.ManagedBlocker() { ++ @Override ++ public boolean block() throws InterruptedException { ++ for (final RegionData region : regions) { ++ if (region == null || region.isEmpty()) { ++ latch.countDown(); ++ continue; ++ } ++ ++ REGION_EXECUTOR.execute(() -> { ++ try { ++ tickChunks(timeDelta, region.chunks()); ++ ++ for (Entity entity : region.entities()) { ++ tickEntity(entity); ++ } ++ } finally { ++ latch.countDown(); ++ } ++ }); ++ } ++ broadcastChangedChunks(); ++ ++ latch.await(); ++ return true; ++ } ++ ++ @Override ++ public boolean isReleasable() { ++ return latch.getCount() == 0; ++ } ++ }); ++ } catch (InterruptedException ex) { ++ throw new RuntimeException("Interrupted managed block during region ticking", ex); ++ } ++ } ++ // DivineMC end - Regionized Chunk Ticking ++ + private void broadcastChangedChunks() { +- for (ChunkHolder chunkHolder : this.chunkHoldersToBroadcast) { +- LevelChunk tickingChunk = chunkHolder.getChunkToSend(); // Paper - rewrite chunk system +- if (tickingChunk != null) { +- chunkHolder.broadcastChanges(tickingChunk); ++ // DivineMC start - Regionized Chunk Ticking ++ synchronized (chunkHoldersToBroadcast) { ++ for (ChunkHolder chunkHolder : this.chunkHoldersToBroadcast) { ++ LevelChunk tickingChunk = chunkHolder.getChunkToSend(); // Paper - rewrite chunk system ++ if (tickingChunk != null) { ++ chunkHolder.broadcastChanges(tickingChunk); ++ } + } +- } + +- this.chunkHoldersToBroadcast.clear(); ++ this.chunkHoldersToBroadcast.clear(); ++ } ++ // DivineMC end - Regionized Chunk Ticking + } + + private void collectTickingChunks(List output) { +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 9ed108791b6e6eb81dfd90b36f054566ce62022f..a21924072632d8195d803b372058c2557d6428b2 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -193,7 +193,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final LevelTicks blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); + private final LevelTicks fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); + private final PathTypeCache pathTypesByPosCache = new PathTypeCache(); +- final Set navigatingMobs = new ObjectOpenHashSet<>(); ++ final Set navigatingMobs = java.util.Collections.synchronizedSet(new ObjectOpenHashSet<>()); // DivineMC - Regionized Chunk Ticking + volatile boolean isUpdatingNavigations; + protected final Raids raids; + private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); +@@ -816,6 +816,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.dragonFight.tick(); + } + ++ if (org.bxteam.divinemc.DivineConfig.enableRegionizedChunkTicking) { ++ this.tickBlockEntities(); ++ return; ++ } ++ + io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR + this.entityTickList + .forEach( +@@ -1793,22 +1798,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + if (Shapes.joinIsNotEmpty(collisionShape, collisionShape1, BooleanOp.NOT_SAME)) { + List list = new ObjectArrayList<>(); + +- try { // Paper - catch CME see below why +- for (Mob mob : this.navigatingMobs) { +- PathNavigation navigation = mob.getNavigation(); +- if (navigation.shouldRecomputePath(pos)) { +- list.add(navigation); ++ // DivineMC start - Regionized Chunk Ticking ++ synchronized (this.navigatingMobs) { ++ for (Mob mob : this.navigatingMobs) { ++ PathNavigation navigation = mob.getNavigation(); ++ if (navigation.shouldRecomputePath(pos)) { ++ list.add(navigation); ++ } + } + } +- // Paper start - catch CME see below why +- } catch (final java.util.ConcurrentModificationException concurrentModificationException) { +- // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register +- // In this case we just run the update again across all the iterators as the chunk will then be loaded +- // As this is a relative edge case it is much faster than copying navigators (on either read or write) +- this.sendBlockUpdated(pos, oldState, newState, flags); +- return; +- } +- // Paper end - catch CME see below why ++ // DivineMC end - Regionized Chunk Ticking + + try { + this.isUpdatingNavigations = true; +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 502473920a8dec1fcd3321d863f07c977b3643f2..f420cf116ca010f00a41df2f28fc8d9c658fc1f3 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -115,7 +115,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public static final int MIN_ENTITY_SPAWN_Y = -20000000; + public final org.bxteam.divinemc.util.BlockEntityTickersList blockEntityTickers = new org.bxteam.divinemc.util.BlockEntityTickersList(); // Paper - public // DivineMC - optimize block entity removals - Fix MC-117075 + protected final NeighborUpdater neighborUpdater; +- private final List pendingBlockEntityTickers = Lists.newArrayList(); ++ private final List pendingBlockEntityTickers = java.util.Collections.synchronizedList(Lists.newArrayList()); // DivineMC - Regionized Chunk Ticking + private boolean tickingBlockEntities; + public final Thread thread; + private final boolean isDebug; +@@ -148,7 +148,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public boolean captureBlockStates = false; + public boolean captureTreeGeneration = false; + public boolean isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent +- public Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper ++ public Map capturedBlockStates = java.util.Collections.synchronizedMap(new java.util.LinkedHashMap<>()); // Paper // DivineMC - Regionized Chunk Ticking + public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates + public List captureDrops; + public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); +diff --git a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +index 028eae2f9a459b60e92f3344091083aa93b54485..51e5a54aff069cac14deef6c04899d3a469842ce 100644 +--- a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java ++++ b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +@@ -46,7 +46,7 @@ public class CollectingNeighborUpdater implements NeighborUpdater { + this.addAndRun(pos, new CollectingNeighborUpdater.MultiNeighborUpdate(pos.immutable(), block, orientation, facing)); + } + +- private void addAndRun(BlockPos pos, CollectingNeighborUpdater.NeighborUpdates updates) { ++ private synchronized void addAndRun(BlockPos pos, CollectingNeighborUpdater.NeighborUpdates updates) { // DivineMC - Regionized Chunk Ticking - synchronized + boolean flag = this.count > 0; + boolean flag1 = this.maxChainedNeighborUpdates >= 0 && this.count >= this.maxChainedNeighborUpdates; + this.count++; +@@ -65,7 +65,7 @@ public class CollectingNeighborUpdater implements NeighborUpdater { + } + } + +- private void runUpdates() { ++ private synchronized void runUpdates() { // DivineMC - Regionized Chunk Ticking - synchronized + try { + while (!this.stack.isEmpty() || !this.addedThisLayer.isEmpty()) { + for (int i = this.addedThisLayer.size() - 1; i >= 0; i--) { diff --git a/divinemc-server/minecraft-patches/features/0040-Async-mob-spawning.patch b/divinemc-server/minecraft-patches/features/0047-Async-mob-spawning.patch similarity index 94% rename from divinemc-server/minecraft-patches/features/0040-Async-mob-spawning.patch rename to divinemc-server/minecraft-patches/features/0047-Async-mob-spawning.patch index ccd9bae..f5d5d5d 100644 --- a/divinemc-server/minecraft-patches/features/0040-Async-mob-spawning.patch +++ b/divinemc-server/minecraft-patches/features/0047-Async-mob-spawning.patch @@ -17,10 +17,10 @@ index 51b79f614417f231951e9ba05b29ff0044e081e7..dee93ae262a2a06e68dfe8ae1b719317 public static S spin(Function threadFunction) { ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 7f982949304535376dabf42aab1848cabc8987cf..a2bb32b964d08079456d93d49f12b23f7c17a7db 100644 +index aa515bc07b899351f2b0ac8d61df8e5586616084..83157183ab2363eebd8a19d1e018b6bd5c736507 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java -@@ -183,6 +183,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -268,6 +268,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon } // Paper end - chunk tick iteration optimisations @@ -31,9 +31,9 @@ index 7f982949304535376dabf42aab1848cabc8987cf..a2bb32b964d08079456d93d49f12b23f public ServerChunkCache( ServerLevel level, -@@ -581,6 +585,35 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - - this.broadcastChangedChunks(); +@@ -651,6 +655,35 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } catch (InterruptedException ex) { + throw new RuntimeException("Interrupted managed block during region ticking", ex); } + + // DivineMC start - Async mob spawning @@ -65,9 +65,9 @@ index 7f982949304535376dabf42aab1848cabc8987cf..a2bb32b964d08079456d93d49f12b23f + } + // DivineMC end - Async mob spawning } + // DivineMC end - Regionized Chunk Ticking - private void broadcastChangedChunks() { -@@ -621,27 +654,31 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -696,27 +729,31 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount(); // Paper start - Optional per player mob spawns NaturalSpawner.SpawnState spawnState; @@ -113,7 +113,7 @@ index 7f982949304535376dabf42aab1848cabc8987cf..a2bb32b964d08079456d93d49f12b23f boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); List filteredSpawningCategories; -@@ -655,7 +692,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -730,7 +767,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon } // Paper end - PlayerNaturallySpawnCreaturesEvent boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit @@ -122,7 +122,7 @@ index 7f982949304535376dabf42aab1848cabc8987cf..a2bb32b964d08079456d93d49f12b23f } else { filteredSpawningCategories = List.of(); } -@@ -663,8 +700,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -738,8 +775,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon for (LevelChunk levelChunk : chunks) { ChunkPos pos = levelChunk.getPos(); levelChunk.incrementInhabitedTime(timeInhabited); @@ -136,7 +136,7 @@ index 7f982949304535376dabf42aab1848cabc8987cf..a2bb32b964d08079456d93d49f12b23f if (true) { // Paper - rewrite chunk system diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java -index d3f5242fc66529bf3137da4d505a6cf55e749e43..650dfce05bfc68d4c664471b430bd5c0f9629283 100644 +index 1056a17c53e7d16d5fba7f9a354dfbc235d4d974..3e9ab446632ffe56de45f7622db44070e1cbaf1f 100644 --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java @@ -155,7 +155,18 @@ public final class NaturalSpawner { @@ -158,7 +158,7 @@ index d3f5242fc66529bf3137da4d505a6cf55e749e43..650dfce05bfc68d4c664471b430bd5c0 for (MobCategory mobCategory : categories) { // Paper start - Optional per player mob spawns final boolean canSpawn; -@@ -642,6 +653,13 @@ public final class NaturalSpawner { +@@ -682,6 +693,13 @@ public final class NaturalSpawner { } boolean canSpawnForCategoryLocal(MobCategory category, ChunkPos chunkPos) { diff --git a/divinemc-server/minecraft-patches/features/0041-Dynamic-Activation-of-Brain.patch b/divinemc-server/minecraft-patches/features/0048-Dynamic-Activation-of-Brain.patch similarity index 95% rename from divinemc-server/minecraft-patches/features/0041-Dynamic-Activation-of-Brain.patch rename to divinemc-server/minecraft-patches/features/0048-Dynamic-Activation-of-Brain.patch index 48e1f64..9e57b43 100644 --- a/divinemc-server/minecraft-patches/features/0041-Dynamic-Activation-of-Brain.patch +++ b/divinemc-server/minecraft-patches/features/0048-Dynamic-Activation-of-Brain.patch @@ -30,11 +30,23 @@ index ae0a3c3d9d6300293a6d0dff5cae49ebe7c11dab..3b08dad7a9fac7ac9acec0bfb85d4826 } } } +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index 83157183ab2363eebd8a19d1e018b6bd5c736507..4377b6712c8990f9bd444d662414b68ab9d92963 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -150,6 +150,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + private void tickEntity(Entity entity) { ++ entity.activatedPriorityReset = false; // DivineMC - Dynamic Activation of Brain + if (!entity.isRemoved()) { + if (!level.tickRateManager().isEntityFrozen(entity)) { + entity.checkDespawn(); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 561066a2cf769e13ef3cea0881f7a2010ecbf2ec..5fe908ce51f95e1eab024dcd41ed108373f17fea 100644 +index a21924072632d8195d803b372058c2557d6428b2..b50f8ff69157c07b509e88c65fb217d3dde73f1e 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -818,6 +818,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -825,6 +825,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.entityTickList .forEach( entity -> { @@ -43,7 +55,7 @@ index 561066a2cf769e13ef3cea0881f7a2010ecbf2ec..5fe908ce51f95e1eab024dcd41ed1083 if (!tickRateManager.isEntityFrozen(entity)) { entity.checkDespawn(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 04ae7636d14a40a427b5d9b746632b0c489efa21..f1cd66d7d96771bc4967e214f70c756fec30efe5 100644 +index 07e8bda8eb200d5a7554e0319e1a00dc85454e1a..ed8c715f4a24188e43769fa04af8e71e1573c041 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -336,6 +336,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -414,7 +426,7 @@ index 59a12809e5c7f5ee85ca1f587f6b77383a1ff062..9777f5e99909790b49b05ea64fe12ded @Override diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java -index fec90e482c8935dfca609bbf90e67f86a1586221..2ac8a11e9afc6f776eba3dbe852d7b680ed21705 100644 +index 4ee1791f293ba3abdbfe69824b85566fd9a36586..2a4b5821d352f828e3c955a6ffa0bfdf6ff6911a 100644 --- a/net/minecraft/world/entity/npc/Villager.java +++ b/net/minecraft/world/entity/npc/Villager.java @@ -179,6 +179,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler diff --git a/divinemc-server/minecraft-patches/features/0049-Sync-Paper-upstream.patch b/divinemc-server/minecraft-patches/features/0049-Sync-Paper-upstream.patch index 7ca7854..17aaf6f 100644 --- a/divinemc-server/minecraft-patches/features/0049-Sync-Paper-upstream.patch +++ b/divinemc-server/minecraft-patches/features/0049-Sync-Paper-upstream.patch @@ -18,10 +18,10 @@ index 34682217252cb98a70511a8cb25f077ec9f872b8..eccd330a332a927354f47acd16295c23 this.scheduleReadIO(); return; diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index bea62fca118efdd6257188cd53c813dc5fd0c19c..adca106b1100b5c5236f40eb3795ac89c1fa0288 100644 +index b50f8ff69157c07b509e88c65fb217d3dde73f1e..9e0d482065bd6434128cd653c3580b633ed7202d 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1403,7 +1403,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1408,7 +1408,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } if (doFull) { diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java index 328f27d..955d9ce 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java @@ -95,4 +95,11 @@ public final class BlockEntityTickersList extends ObjectArrayList c) { + synchronized (c) { + return super.addAll(index, c); + } + } }