9
0
mirror of https://github.com/BX-Team/DivineMC.git synced 2025-12-22 16:29:23 +00:00

Regionized Chunk Ticking (#26)

* remove this

* rct dev

* experiment this

* it finally works!

* update description

* fix mob issues

* sync to new config

* Fix realocation of configs for spark

* RCT split chunks rewrite and other features

* add debug

* even more optimizations and comment out debug

* oops

* merge RCT patches

* some final changes

* add experimental warn

* [ci-skip] update readme info

---------

Co-authored-by: dan28000 <pirkldan28@gmail.com>
This commit is contained in:
Artem Ostrasev
2025-06-17 16:57:24 +03:00
committed by GitHub
parent d7183c4d36
commit 9e5dd65338
8 changed files with 608 additions and 133 deletions

View File

@@ -16,6 +16,7 @@ DivineMC is a high-performance [Purpur](https://github.com/PurpurMC/Purpur) fork
## ⚙️ Features ## ⚙️ Features
- **Based on [Purpur](https://github.com/PurpurMC/Purpur)** that adds a high customization level to the server. - **Based on [Purpur](https://github.com/PurpurMC/Purpur)** that adds a high customization level to the server.
- **Regionized Chunk Ticking** feature that allows to tick chunks in parallel, similar to how Folia does it.
- Implemented **Parallel world ticking** feature, that allows to server take advantage of multiple CPU cores to tick worlds. - Implemented **Parallel world ticking** feature, that allows to server take advantage of multiple CPU cores to tick worlds.
- Implemented **Secure Seed** mod that changes default 64-bit seed to a 1024-bit seed, making it almost impossible to crack the seed. - Implemented **Secure Seed** mod that changes default 64-bit seed to a 1024-bit seed, making it almost impossible to crack the seed.
- **Optimized chunk generation** that can generate chunks up to 70% faster than vanilla. - **Optimized chunk generation** that can generate chunks up to 70% faster than vanilla.
@@ -90,10 +91,10 @@ DivineMC includes patches from other forks, and without these forks, DivineMC wo
<a href="https://github.com/fxmorin/carpet-fixes">Carpet Fixes</a><br> <a href="https://github.com/fxmorin/carpet-fixes">Carpet Fixes</a><br>
<a href="https://github.com/ProjectEdenGG/Parchment">Parchment</a><br> <a href="https://github.com/ProjectEdenGG/Parchment">Parchment</a><br>
<a href="https://github.com/LeavesMC/Leaves">Leaves</a><br> <a href="https://github.com/LeavesMC/Leaves">Leaves</a><br>
<a href="https://github.com/KaiijuMC/Kaiiju">Kaiiju</a><br>
<a href="https://github.com/SparklyPower/SparklyPaper">SparklyPaper</a><br> <a href="https://github.com/SparklyPower/SparklyPaper">SparklyPaper</a><br>
<a href="https://github.com/plasmoapp/matter">Matter</a><br> <a href="https://github.com/plasmoapp/matter">Matter</a><br>
<a href="https://github.com/CraftCanvasMC/Canvas">Canvas</a><br> <a href="https://github.com/CraftCanvasMC/Canvas">Canvas</a><br>
<a href="https://github.com/Winds-Studio/Leaf">Leaf</a><br>
</p> </p>
</details> </details>

View File

@@ -31,7 +31,7 @@ index 9a65321ce62f21b150d29be30dbae7dba0ff40be..4ff1f78eb963e2baf0c2871b4fea624c
public ServerChunkCache( public ServerChunkCache(
ServerLevel level, ServerLevel level,
@@ -505,6 +509,35 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -505,6 +509,32 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
this.broadcastChangedChunks(); this.broadcastChangedChunks();
} }
@@ -42,10 +42,7 @@ index 9a65321ce62f21b150d29be30dbae7dba0ff40be..4ff1f78eb963e2baf0c2871b4fea624c
+ for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) { + for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) {
+ player.mobCounts[ii] = 0; + player.mobCounts[ii] = 0;
+ +
+ int newBackoff = player.mobBackoffCounts[ii] - 1; + int newBackoff = Math.max(0, player.mobBackoffCounts[ii] - 1); // DivineMC - Async mob spawning
+ if (newBackoff < 0) {
+ newBackoff = 0;
+ }
+ player.mobBackoffCounts[ii] = newBackoff; + player.mobBackoffCounts[ii] = newBackoff;
+ } + }
+ } + }
@@ -67,7 +64,7 @@ index 9a65321ce62f21b150d29be30dbae7dba0ff40be..4ff1f78eb963e2baf0c2871b4fea624c
} }
private void broadcastChangedChunks() { private void broadcastChangedChunks() {
@@ -522,27 +555,31 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -522,27 +552,31 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount(); int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount();
// Paper start - Optional per player mob spawns // Paper start - Optional per player mob spawns
NaturalSpawner.SpawnState spawnState; NaturalSpawner.SpawnState spawnState;
@@ -113,7 +110,7 @@ index 9a65321ce62f21b150d29be30dbae7dba0ff40be..4ff1f78eb963e2baf0c2871b4fea624c
boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
List<MobCategory> filteredSpawningCategories; List<MobCategory> filteredSpawningCategories;
@@ -556,7 +593,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -556,7 +590,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
} }
// Paper end - PlayerNaturallySpawnCreaturesEvent // 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 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 +119,7 @@ index 9a65321ce62f21b150d29be30dbae7dba0ff40be..4ff1f78eb963e2baf0c2871b4fea624c
} else { } else {
filteredSpawningCategories = List.of(); filteredSpawningCategories = List.of();
} }
@@ -571,7 +608,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -571,7 +605,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
// Paper end - chunk tick iteration optimisation // Paper end - chunk tick iteration optimisation
for (LevelChunk levelChunk : list) { for (LevelChunk levelChunk : list) {
@@ -131,7 +128,7 @@ index 9a65321ce62f21b150d29be30dbae7dba0ff40be..4ff1f78eb963e2baf0c2871b4fea624c
} }
} finally { } finally {
list.clear(); list.clear();
@@ -590,11 +627,11 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -590,11 +624,11 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
this.level.tickThunder(chunk); this.level.tickThunder(chunk);
} }

View File

@@ -25,7 +25,7 @@ index ef2cf6d9ca57266bb0466ca1aa5d2066349f9954..c4ae883af4337e04d0944c603f298ee1
CrashReport crashReport = CrashReport.forThrowable(levelTickingException, "Exception ticking world"); CrashReport crashReport = CrashReport.forThrowable(levelTickingException, "Exception ticking world");
serverLevel.fillReportDetails(crashReport); serverLevel.fillReportDetails(crashReport);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 857439f7b59549cff75e318fdf9c1effe83c7e17..d93014a396c13fed0e86dfadcb5436773efaa2a0 100644 index b609361d4ff1d42d3ac40411013de767ad8665d7..1d4e6ffc1c17f2e5ab363b71a2e8cbcc63bdfb7c 100644
--- a/net/minecraft/server/level/ServerLevel.java --- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java
@@ -568,6 +568,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -568,6 +568,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe

View File

@@ -0,0 +1,514 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com>
Date: Mon, 9 Jun 2025 13:51:43 +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 heavily optimized by dan28000
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index b28829f9f4c084a8dba35219dae9f4a9f293c416..583f0d2a54508e0a6e99771d5d1fb787f7a913f3 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -325,7 +325,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
private static void syncAfterConfigurationChange(ChannelFuture future) {
try {
- future.syncUninterruptibly();
+ future.awaitUninterruptibly(5000L); // DivineMC - In rare cases this can get stuck, so we time out instead in worst case 5s of lag
} catch (Exception var2) {
if (var2 instanceof ClosedChannelException) {
LOGGER.info("Connection closed during protocol change");
diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
index a2dbb52fab3f82dd3ddcac9b7d1e0ed44ebf1574..ae6ea190a67bb8a90049ac3ee21fb7fe2c525d40 100644
--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -57,6 +57,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.config.DivineConfig.AsyncCategory.regionizedChunkTickingExecutorThreadCount, new org.bxteam.divinemc.util.NamedAgnosticThreadFactory<>("Region Ticking", ca.spottedleaf.moonrise.common.util.TickThread::new, org.bxteam.divinemc.config.DivineConfig.AsyncCategory.regionizedChunkTickingExecutorThreadPriority)); // DivineMC - Regionized Chunk Ticking
public final Thread mainThread;
final ThreadedLevelLightEngine lightEngine;
public final ServerChunkCache.MainThreadExecutor mainThreadProcessor;
@@ -70,13 +71,15 @@ 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<LevelChunk> spawningChunks = new ObjectArrayList<>();
- private final Set<ChunkHolder> chunkHoldersToBroadcast = new ReferenceOpenHashSet<>();
+ // DivineMC start - Regionized Chunk Ticking
+ private final ObjectArrayList<LevelChunk> spawningChunks = new ObjectArrayList<>();
+ private final Set<ChunkHolder> chunkHoldersToBroadcast = java.util.Collections.synchronizedSet(new ReferenceOpenHashSet<>());
+ // DivineMC end - Regionized Chunk Ticking
@Nullable
@VisibleForDebug
private NaturalSpawner.SpawnState lastSpawnState;
// Paper start
- private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
+ private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); // DivineMC - Regionized Chunk Ticking
public int getFullChunksCount() {
return this.fullChunks.size();
}
@@ -153,32 +156,241 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
return load ? this.syncLoad(chunkX, chunkZ, toStatus) : null;
}
// Paper end - rewrite chunk system
+
+ // DivineMC start - Regionized Chunk Ticking
+ private record RegionData(it.unimi.dsi.fastutil.longs.LongOpenHashSet chunks, Set<Entity> entities) {
+ public boolean isEmpty() {
+ return chunks.isEmpty();
+ }
+ }
+
+ private record Output(RegionData[] regions, Set<Entity> entities) {}
+
+ private Output computePlayerRegionsParallel() {
+ int tickViewDistance = level.moonrise$getViewDistanceHolder().getViewDistances().tickViewDistance();
+ List<ServerPlayer> players = new java.util.ArrayList<>(level.players);
+ int max = maxChunksForViewDistance();
+
+ List<it.unimi.dsi.fastutil.longs.LongOpenHashSet> playerChunkSets = players.parallelStream()
+ .map(player -> {
+ ChunkPos playerChunk = player.chunkPosition();
+ int px = playerChunk.x;
+ int pz = playerChunk.z;
+ it.unimi.dsi.fastutil.longs.LongOpenHashSet chunkKeys = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(max);
+ for (int dx = -tickViewDistance; dx <= tickViewDistance; dx++) {
+ for (int dz = -tickViewDistance; dz <= tickViewDistance; dz++) {
+ long key = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(px + dx, pz + dz);
+ chunkKeys.add(key);
+ }
+ }
+ return chunkKeys;
+ }).toList();
+
+ List<it.unimi.dsi.fastutil.longs.LongOpenHashSet> mergedRegions = new java.util.ArrayList<>();
+ boolean[] merged = new boolean[playerChunkSets.size()];
+
+ for (int i = 0; i < playerChunkSets.size(); i++) {
+ if (merged[i]) continue;
+
+ it.unimi.dsi.fastutil.longs.LongOpenHashSet region = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(playerChunkSets.get(i));
+ merged[i] = true;
+
+ boolean madeChanges;
+ do {
+ madeChanges = false;
+ for (int j = i + 1; j < playerChunkSets.size(); j++) {
+ if (merged[j]) continue;
+
+ it.unimi.dsi.fastutil.longs.LongOpenHashSet set = playerChunkSets.get(j);
+
+ boolean hasIntersection = false;
+ it.unimi.dsi.fastutil.longs.LongIterator iter = set.iterator();
+ while (iter.hasNext()) {
+ if (region.contains(iter.nextLong())) {
+ hasIntersection = true;
+ break;
+ }
+ }
+
+ if (hasIntersection) {
+ region.addAll(set);
+ merged[j] = true;
+ madeChanges = true;
+ }
+ }
+ } while (madeChanges);
+
+ mergedRegions.add(region);
+ }
+
+ ObjectArrayList<RegionData> regions = new ObjectArrayList<>();
+ it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap chunkToRegion = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(max * mergedRegions.size());
+ chunkToRegion.defaultReturnValue(-1);
+ for (int i = 0; i < mergedRegions.size(); i++) {
+ regions.add(new RegionData(mergedRegions.get(i), java.util.Collections.newSetFromMap(new java.util.concurrent.ConcurrentHashMap<>())));
+ for (long key : mergedRegions.get(i)) {
+ chunkToRegion.put(key, i);
+ }
+ }
+
+ final Set<Entity> firstTick = java.util.Collections.newSetFromMap(new java.util.concurrent.ConcurrentHashMap<>());
+
+ level.entityTickList.entities.parallelStream().forEach(entity -> {
+ long chunkKey = entity.chunkPosition().longKey;
+ int regionIndex = chunkToRegion.get(chunkKey);
+ if (regionIndex != -1) {
+ regions.get(regionIndex).entities().add(entity);
+ } else {
+ firstTick.add(entity);
+ }
+ });
+
+ return new Output(regions.toArray(new RegionData[0]), firstTick);
+ }
+
+ // Should be max safe estimate of ticking chunks in a region
+ private int maxChunksForViewDistance() {
+ ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.ViewDistances distances = level.moonrise$getViewDistanceHolder().getViewDistances();
+ int diameter = 2 * distances.tickViewDistance() + 1;
+ return diameter * diameter;
+ }
+
+ 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
+
// Paper start - chunk tick iteration optimisations
private final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom shuffleRandom = new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(0L);
- private void iterateTickingChunksFaster() {
+ private void iterateTickingChunksFaster(final java.util.concurrent.CompletableFuture<Void> spawns) { // DivineMC - Regionized Chunk Ticking
final ServerLevel world = this.level;
final int randomTickSpeed = world.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
// TODO check on update: impl of forEachBlockTickingChunk will only iterate ENTITY ticking chunks!
// TODO check on update: consumer just runs tickChunk
- final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.world.level.chunk.LevelChunk> entityTickingChunks = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getEntityTickingChunks();
+ final ca.spottedleaf.moonrise.common.list.ReferenceList<LevelChunk> entityTickingChunks = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getEntityTickingChunks(); // DivineMC - Regionized Chunk Ticking
// note: we can use the backing array here because:
// 1. we do not care about new additions
// 2. _removes_ are impossible at this stage in the tick
- final LevelChunk[] raw = entityTickingChunks.getRawDataUnchecked();
+ final LevelChunk[] raw = entityTickingChunks.toArray(new LevelChunk[0]); // DivineMC - use toArray instead of getRawDataUnchecked this way is safe and doesn't have performance impact
final int size = entityTickingChunks.size();
- java.util.Objects.checkFromToIndex(0, size, raw.length);
- for (int i = 0; i < size; ++i) {
- world.tickChunk(raw[i], randomTickSpeed);
+ // DivineMC start - Regionized Chunk Ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableRegionizedChunkTicking) {
+ final Output output = computePlayerRegionsParallel();
+ final RegionData[] regions = output.regions();
+ int regionCount = regions.length;
+
+ java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(regionCount);
+ io.papermc.paper.entity.activation.ActivationRange.activateEntities(level); // Paper - EAR
+
+ try {
+ java.util.concurrent.ForkJoinPool.managedBlock(new java.util.concurrent.ForkJoinPool.ManagedBlocker() {
+ @Override
+ public boolean block() throws InterruptedException {
+ ObjectArrayList<java.util.concurrent.CompletableFuture<it.unimi.dsi.fastutil.longs.LongOpenHashSet>> ticked = new ObjectArrayList<>(regionCount);
+ for (final RegionData region : regions) {
+ if (region == null || region.isEmpty()) {
+ latch.countDown();
+ continue;
+ }
+
+ ticked.add(java.util.concurrent.CompletableFuture.supplyAsync(() -> {
+ ObjectArrayList<LevelChunk> regionChunks = new ObjectArrayList<>(region.chunks().size());
+ it.unimi.dsi.fastutil.longs.LongOpenHashSet regionChunksIDs = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(region.chunks().size());
+ for (long key : region.chunks()) {
+ LevelChunk chunk = fullChunks.get(key);
+ if (chunk != null) {
+ regionChunks.add(chunk);
+ regionChunksIDs.add(key);
+ }
+ }
+
+ try {
+ for (LevelChunk chunk : regionChunks) {
+ world.tickChunk(chunk, randomTickSpeed);
+ }
+ for (net.minecraft.world.entity.Entity entity : region.entities()) {
+ tickEntity(entity);
+ }
+ } finally {
+ latch.countDown();
+ }
+ return regionChunksIDs;
+ }, REGION_EXECUTOR));
+ }
- // call mid-tick tasks for chunk system
- if ((i & 7) == 0) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.level.getServer()).moonrise$executeMidTickTasks();
- continue;
+ CompletableFuture.runAsync(() -> {
+ try {
+ CompletableFuture.allOf(ticked.toArray(new CompletableFuture[0])).join();
+ } catch (java.util.concurrent.CompletionException ex) {
+ LOGGER.error("Error during region chunk ticking", ex.getCause());
+ }
+
+ it.unimi.dsi.fastutil.longs.LongOpenHashSet tickedChunkKeys = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(raw.length);
+
+ for (CompletableFuture<it.unimi.dsi.fastutil.longs.LongOpenHashSet> future : ticked) {
+ if (!future.isCompletedExceptionally()) {
+ try {
+ it.unimi.dsi.fastutil.longs.LongOpenHashSet regionChunks = future.join();
+ tickedChunkKeys.addAll(regionChunks);
+ } catch (Exception e) {
+ LOGGER.error("Exception in region ticking future", e);
+ }
+ }
+ }
+
+ for (LevelChunk chunk : raw) {
+ if (!tickedChunkKeys.contains(chunk.coordinateKey)) {
+ world.tickChunk(chunk, randomTickSpeed);
+ }
+ }
+
+ output.entities.forEach(ServerChunkCache.this::tickEntity);
+ spawns.join();
+ }, REGION_EXECUTOR).join();
+
+ 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 {
+ java.util.Objects.checkFromToIndex(0, size, raw.length);
+ for (int i = 0; i < size; ++i) {
+ world.tickChunk(raw[i], randomTickSpeed);
+
+ // call mid-tick tasks for chunk system
+ if ((i & 7) == 0) {
+ ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer) this.level.getServer()).moonrise$executeMidTickTasks();
+ continue;
+ }
}
}
+ // DivineMC end - Regionized Chunk Ticking
}
// Paper end - chunk tick iteration optimisations
@@ -502,14 +714,21 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
long gameTime = this.level.getGameTime();
long l = gameTime - this.lastInhabitedUpdate;
this.lastInhabitedUpdate = gameTime;
- if (!this.level.isDebug()) {
- if (this.level.tickRateManager().runsNormally()) {
- this.tickChunks(l);
- }
+ // DivineMC start - Regionized Chunk Ticking
+ if (this.level.isDebug()) {
+ return;
+ }
+
+ if (!this.level.tickRateManager().runsNormally()) { // DivineMC - when frozen only broadcast changed chunks and don't run async mob spawning
this.broadcastChangedChunks();
+ return;
}
+ this.tickChunks(l);
+ this.broadcastChangedChunks();
+ // DivineMC end - Regionized Chunk Ticking
+
// DivineMC start - Async mob spawning
if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableAsyncSpawning) {
for (ServerPlayer player : this.level.players) {
@@ -538,14 +757,18 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
}
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 tickChunks(long timeInhabited) {
@@ -595,23 +818,25 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
filteredSpawningCategories = List.of();
}
- List<LevelChunk> list = this.spawningChunks;
-
- try {
- this.chunkMap.collectSpawningChunks(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
-
- for (LevelChunk levelChunk : list) {
- this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, lastSpawnState); // DivineMC - Async mob spawning
+ final java.util.concurrent.CompletableFuture<Void> spawns = java.util.concurrent.CompletableFuture.runAsync(() -> {
+ java.util.List<LevelChunk> list = this.spawningChunks;
+ try {
+ this.chunkMap.collectSpawningChunks(list);
+ // Paper start - chunk tick iteration optimisation
+ this.shuffleRandom.setSeed(this.level.random.nextLong());
+ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns)
+ net.minecraft.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
+
+ for (LevelChunk levelChunk : list) {
+ this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, lastSpawnState); // DivineMC - Async mob spawning
+ }
+ } finally {
+ list.clear();
}
- } finally {
- list.clear();
- }
+ }, REGION_EXECUTOR);
- this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations
+ this.iterateTickingChunksFaster(spawns); // Paper - chunk tick iteration optimisations
if (_boolean) {
this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index caa1fd0e35b69c8bd55170af6140442bef5d6b4d..4b874acfdfd2aaa3477e65da20a73b1e693c67bd 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -189,7 +189,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
private final LevelTicks<Block> blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded);
private final LevelTicks<Fluid> fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded);
private final PathTypeCache pathTypesByPosCache = new PathTypeCache();
- final Set<Mob> navigatingMobs = new ObjectOpenHashSet<>();
+ final Set<Mob> navigatingMobs = java.util.Collections.synchronizedSet(new ObjectOpenHashSet<>()); // DivineMC - Regionized Chunk Ticking
volatile boolean isUpdatingNavigations;
protected final Raids raids;
private final ObjectLinkedOpenHashSet<BlockEventData> blockEvents = new ObjectLinkedOpenHashSet<>();
@@ -806,6 +806,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
this.dragonFight.tick();
}
+ // DivineMC start - Regionized Chunk Ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableRegionizedChunkTicking) {
+ this.tickBlockEntities();
+ return;
+ }
+ // DivineMC end - Regionized Chunk Ticking
+
io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
this.entityTickList
.forEach(
@@ -1794,22 +1801,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
if (Shapes.joinIsNotEmpty(collisionShape, collisionShape1, BooleanOp.NOT_SAME)) {
List<PathNavigation> 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 698f9c3a66dbb4f9ef1fbea873a1769d314d2bb4..7ef3ea374f36554ff66ace50ffaef9ad8920d961 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -106,7 +106,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
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<TickingBlockEntity> pendingBlockEntityTickers = Lists.newArrayList();
+ private final List<TickingBlockEntity> pendingBlockEntityTickers = java.util.Collections.synchronizedList(Lists.newArrayList()); // DivineMC - Regionized Chunk Ticking
private boolean tickingBlockEntities;
public final Thread thread;
private final boolean isDebug;
@@ -138,7 +138,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
public boolean captureBlockStates = false;
public boolean captureTreeGeneration = false;
- public Map<BlockPos, org.bukkit.craftbukkit.block.CraftBlockState> capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper
+ public Map<BlockPos, org.bukkit.craftbukkit.block.CraftBlockState> capturedBlockStates = java.util.Collections.synchronizedMap(new java.util.LinkedHashMap<>()); // Paper // DivineMC - Regionized Chunk Ticking
public Map<BlockPos, BlockEntity> capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates
@Nullable
public List<net.minecraft.world.entity.item.ItemEntity> captureDrops;
@@ -1503,9 +1503,11 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
protected void tickBlockEntities() {
this.tickingBlockEntities = true;
- if (!this.pendingBlockEntityTickers.isEmpty()) {
- this.blockEntityTickers.addAll(this.pendingBlockEntityTickers);
- this.pendingBlockEntityTickers.clear();
+ synchronized (pendingBlockEntityTickers) { // DivineMC - RCT synchronization fix
+ if (!this.pendingBlockEntityTickers.isEmpty()) {
+ this.blockEntityTickers.addAll(this.pendingBlockEntityTickers);
+ this.pendingBlockEntityTickers.clear();
+ }
}
// Spigot start
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--) {

View File

@@ -0,0 +1,50 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: dan28000 <pirkldan28@gmail.com>
Date: Thu, 12 Jun 2025 10:08:25 +0200
Subject: [PATCH] Implement loading plugins from external folder
diff --git a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
index 70413fddd23ca1165cb5090cce4fddcb1bbca93f..ae70b84e6473fa2ed94416bf4bef88492de3e5f8 100644
--- a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
+++ b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
@@ -112,6 +112,20 @@ public class PluginInitializerManager {
// Register the default plugin directory
io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.DirectoryProviderSource.INSTANCE, pluginSystem.pluginDirectoryPath());
+ // DivineMC start - Register the plugin directory from flags
+ @SuppressWarnings("unchecked")
+ java.util.List<Path> pluginList = ((java.util.List<File>) optionSet.valuesOf("add-plugin-dir")).stream()
+ .filter(java.util.Objects::nonNull)
+ .map(f -> f.listFiles(file -> file.getName().endsWith(".jar")))
+ .filter(java.util.Objects::nonNull)
+ .flatMap(java.util.Arrays::stream)
+ .filter(File::isFile)
+ .map(File::toPath)
+ .toList();
+
+ io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.PluginFlagProviderSource.INSTANCE, pluginList);
+ // DivineMC end - Register the plugin directory from flags
+
// Register plugins from the flag
@SuppressWarnings("unchecked")
java.util.List<Path> files = ((java.util.List<File>) optionSet.valuesOf("add-plugin")).stream().map(File::toPath).toList();
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index d7f9da2e624d3e27aff36d8818adaf735d78a2d9..3f3bbc71bc4870cf1271d6c28a77ff78a5e102f8 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -181,6 +181,14 @@ public class Main {
.describedAs("Yml file");
// DivineMC end - Configuration
+ // DivineMC start - Implement loading plugins from external folder
+ acceptsAll(asList("add-plugin-dir", "add-extra-plugin-dir"), "Specify paths to directories containing extra plugin jars to be loaded in addition to those in the plugins folder. This argument can be specified multiple times, once for each extra plugin directory path.")
+ .withRequiredArg()
+ .ofType(File.class)
+ .defaultsTo(new File("extra"))
+ .describedAs("Directory");
+ // DivineMC end - Implement loading plugins from external folder
+
acceptsAll(asList("server-name"), "Name of the server")
.withRequiredArg()
.ofType(String.class)

View File

@@ -198,6 +198,11 @@ public class DivineConfig {
public static boolean disableHardThrow = false; public static boolean disableHardThrow = false;
public static boolean pwtCompatabilityMode = false; public static boolean pwtCompatabilityMode = false;
// Regionized chunk ticking
public static boolean enableRegionizedChunkTicking = false;
public static int regionizedChunkTickingExecutorThreadCount = 4;
public static int regionizedChunkTickingExecutorThreadPriority = Thread.NORM_PRIORITY + 2;
// Async pathfinding settings // Async pathfinding settings
public static boolean asyncPathfinding = true; public static boolean asyncPathfinding = true;
public static int asyncPathfindingMaxThreads = 2; public static int asyncPathfindingMaxThreads = 2;
@@ -220,6 +225,7 @@ public class DivineConfig {
public static void load() { public static void load() {
parallelWorldTicking(); parallelWorldTicking();
regionizedChunkTicking();
asyncPathfinding(); asyncPathfinding();
multithreadedTracker(); multithreadedTracker();
asyncChunkSending(); asyncChunkSending();
@@ -239,6 +245,25 @@ public class DivineConfig {
"Enables compatibility mode for plugins that are not compatible with Parallel World Ticking. This makes all async tasks run synchronously."); "Enables compatibility mode for plugins that are not compatible with Parallel World Ticking. This makes all async tasks run synchronously.");
} }
private static void regionizedChunkTicking() {
enableRegionizedChunkTicking = getBoolean(ConfigCategory.ASYNC.key("regionized-chunk-ticking.enable"), enableRegionizedChunkTicking,
"Enables regionized chunk ticking, similar to like Folia works.",
"",
"Read more info about this feature at https://bxteam.org/docs/divinemc/features/regionized-chunk-ticking");
regionizedChunkTickingExecutorThreadCount = getInt(ConfigCategory.ASYNC.key("regionized-chunk-ticking.executor-thread-count"), regionizedChunkTickingExecutorThreadCount,
"The amount of threads to allocate to regionized chunk ticking.");
regionizedChunkTickingExecutorThreadPriority = getInt(ConfigCategory.ASYNC.key("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: {}, resetting to default (5)", regionizedChunkTickingExecutorThreadCount);
regionizedChunkTickingExecutorThreadCount = 5;
}
LOGGER.warn("You have enabled Regionized Chunk Ticking. This feature is an experimental, and may not work as expected. Please report any issues you encounter to the BX Team Discord server");
}
private static void asyncPathfinding() { private static void asyncPathfinding() {
asyncPathfinding = getBoolean(ConfigCategory.ASYNC.key("pathfinding.enable"), asyncPathfinding); asyncPathfinding = getBoolean(ConfigCategory.ASYNC.key("pathfinding.enable"), asyncPathfinding);
asyncPathfindingMaxThreads = getInt(ConfigCategory.ASYNC.key("pathfinding.max-threads"), asyncPathfindingMaxThreads); asyncPathfindingMaxThreads = getInt(ConfigCategory.ASYNC.key("pathfinding.max-threads"), asyncPathfindingMaxThreads);

View File

@@ -65,7 +65,7 @@ public class DivineServerConfigProvider extends ServerConfigProvider {
public JsonElement load(@NotNull String group, ExcludedConfigFilter filter) throws IOException { public JsonElement load(@NotNull String group, ExcludedConfigFilter filter) throws IOException {
String prefix = group.replace("/", ""); String prefix = group.replace("/", "");
Path configDir = Paths.get("config"); Path configDir = Paths.get(getPath("paper-dir"));
if (!Files.exists(configDir)) { if (!Files.exists(configDir)) {
return null; return null;
} }
@@ -99,16 +99,18 @@ public class DivineServerConfigProvider extends ServerConfigProvider {
} }
} }
private static String getPath(String optionsName) {
return ((java.io.File) net.minecraft.server.MinecraftServer.getServer().options.valueOf(optionsName)).getPath();
}
static { static {
ImmutableMap.Builder<String, ConfigParser> files = ImmutableMap.<String, ConfigParser>builder() ImmutableMap.Builder<String, ConfigParser> files = ImmutableMap.<String, ConfigParser>builder()
.put("server.properties", PropertiesConfigParser.INSTANCE) .put(getPath("config"), PropertiesConfigParser.INSTANCE)
.put("bukkit.yml", YamlConfigParser.INSTANCE) .put(getPath("bukkit-settings"), YamlConfigParser.INSTANCE)
.put("spigot.yml", YamlConfigParser.INSTANCE) .put(getPath("spigot-settings"), YamlConfigParser.INSTANCE)
.put("paper.yml", YamlConfigParser.INSTANCE)
.put("paper/", SplitYamlConfigParser.INSTANCE) .put("paper/", SplitYamlConfigParser.INSTANCE)
.put("pufferfish.yml", YamlConfigParser.INSTANCE) .put(getPath("purpur-settings"), YamlConfigParser.INSTANCE)
.put("purpur.yml", YamlConfigParser.INSTANCE) .put(getPath("divinemc-settings"), YamlConfigParser.INSTANCE);
.put("divinemc.yml", YamlConfigParser.INSTANCE);
for (String config : getSystemPropertyList("spark.serverconfigs.extra")) { for (String config : getSystemPropertyList("spark.serverconfigs.extra")) {
files.put(config, YamlConfigParser.INSTANCE); files.put(config, YamlConfigParser.INSTANCE);

View File

@@ -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.config.DivineConfig.regionizedChunkTickingExecutorThreadCount, new org.bxteam.divinemc.util.NamedAgnosticThreadFactory<>("region_ticking", ca.spottedleaf.moonrise.common.util.TickThread::new, org.bxteam.divinemc.config.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<LevelChunk>[] splitChunksIntoRegions(List<LevelChunk> chunks) {
+ int size = chunks.size();
+ java.util.IdentityHashMap<LevelChunk, Boolean> chunkSet = new java.util.IdentityHashMap<>(size);
+
+ for (LevelChunk chunk : chunks) {
+ chunkSet.put(chunk, Boolean.TRUE);
+ }
+
+ List<List<LevelChunk>> 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<LevelChunk> 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.config.DivineConfig.enableRegionizedChunkTicking) {
+ List<LevelChunk>[] 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<LevelChunk> 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();
}