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 35b6f8365f4568da7bc0f4e47c39cb3690292aaf..e28d859b457ca0e24bc6dc9d6cd4a97f12ae0671 100644 --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java @@ -327,7 +327,7 @@ public class Connection extends SimpleChannelInboundHandler> { 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/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java index 04dd1bec1aff470e67a21fb0b25932685992ec82..72a0a80f1fffa43e143c80c689db5302f462114e 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -737,7 +737,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper start - optimise chunk tick iteration final ca.spottedleaf.moonrise.common.list.ReferenceList tickingChunks = ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel)this.level).moonrise$getPlayerTickingChunks(); - final LevelChunk[] raw = tickingChunks.getRawDataUnchecked(); + final LevelChunk[] raw = tickingChunks.toArray(new LevelChunk[0]); // DivineMC - Regionized Chunk Ticking - sync fix final int size = tickingChunks.size(); Objects.checkFromToIndex(0, size, raw.length); diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java index e23f2004705fc299934a8b30e736ddf0a8eb2147..e8efa7b12746423de11b6970efe1651db2509511 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -57,7 +57,7 @@ import org.slf4j.Logger; public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache { // Paper - rewrite chunk system private static final Logger LOGGER = LogUtils.getLogger(); private final DistanceManager distanceManager; - private final ServerLevel level; + protected final ServerLevel level; // DivineMC - Regionized Chunk Ticking - private -> protected public final Thread mainThread; final ThreadedLevelLightEngine lightEngine; public final ServerChunkCache.MainThreadExecutor mainThreadProcessor; @@ -71,8 +71,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 spawningChunks = new ObjectArrayList<>(); - private final Set chunkHoldersToBroadcast = new ReferenceOpenHashSet<>(); + // DivineMC start - Regionized Chunk Ticking + private final ObjectArrayList spawningChunks = new ObjectArrayList<>(); + private final Set chunkHoldersToBroadcast = java.util.Collections.synchronizedSet(new ReferenceOpenHashSet<>()); + // DivineMC end - Regionized Chunk Ticking @Nullable @VisibleForDebug private NaturalSpawner.SpawnState lastSpawnState; @@ -156,34 +158,46 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon // Paper end - rewrite chunk system // 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 CompletableFuture 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 entityTickingChunks = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getEntityTickingChunks(); + final ca.spottedleaf.moonrise.common.list.ReferenceList 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); - - // call mid-tick tasks for chunk system - if ((i & 7) == 0) { - // DivineMC start - Parallel world ticking - if (!org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) { - ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer) this.level.getServer()).moonrise$executeMidTickTasks(); - continue; + // DivineMC start - Regionized Chunk Ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableRegionizedChunkTicking) { + if (this instanceof org.bxteam.divinemc.async.rct.RegionizedChunkTicking rct) { + rct.execute(spawns, raw); + } + } 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) { + // DivineMC start - Parallel world ticking + if (!org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) { + ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer) this.level.getServer()).moonrise$executeMidTickTasks(); + continue; + } + // DivineMC end - Parallel world ticking } - // DivineMC end - Parallel world ticking + } + + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.asyncNaturalSpawn) { + spawns.join(); } } + // DivineMC end - Regionized Chunk Ticking } // Paper end - chunk tick iteration optimisations @@ -502,14 +516,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 - Pufferfish: Optimize mob spawning if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableAsyncSpawning) { for (ServerPlayer player : this.level.players) { @@ -553,14 +574,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) { @@ -610,6 +635,24 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon filteredSpawningCategories = List.of(); } + // DivineMC start - Regionized Chunk Ticking + final CompletableFuture spawns; + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.asyncNaturalSpawn) { + spawns = CompletableFuture.runAsync(() -> naturalSpawn(filteredSpawningCategories, timeInhabited), org.bxteam.divinemc.async.rct.RegionizedChunkTicking.REGION_EXECUTOR); + } else { + naturalSpawn(filteredSpawningCategories, timeInhabited); + spawns = new CompletableFuture<>(); + } + // DivineMC end - Regionized Chunk Ticking + + this.iterateTickingChunksFaster(spawns); // Paper - chunk tick iteration optimisations // DivineMC - Regionized Chunk Ticking + if (_boolean) { + this.level.tickCustomSpawners(this.spawnEnemies); + } + } + + // DivineMC start - Regionized Chunk Ticking + private void naturalSpawn(List filteredSpawningCategories, long timeInhabited) { List list = this.spawningChunks; try { @@ -625,12 +668,12 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon } finally { list.clear(); } + } - this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations - if (_boolean) { - this.level.tickCustomSpawners(this.spawnEnemies); - } + protected net.minecraft.world.level.entity.EntityTickList getEntityTickList() { + return level.entityTickList; } + // DivineMC end - Regionized Chunk Ticking private void tickSpawningChunk(LevelChunk chunk, long timeInhabited, List spawnCategories, NaturalSpawner.SpawnState spawnState) { ChunkPos pos = chunk.getPos(); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index 8bd2e419791f0dffbcbc43edd93178dead0c9620..86469253ecc2a142b70c2601cbc50fe9ffbfedea 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -197,7 +197,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<>(); @@ -666,19 +666,37 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe boolean flag = server.forceSynchronousWrites(); DataFixer fixerUpper = server.getFixerUpper(); // Paper - rewrite chunk system - this.chunkSource = new ServerChunkCache( - this, - levelStorageAccess, - fixerUpper, - server.getStructureManager(), - dispatcher, - chunkGenerator, - this.spigotConfig.viewDistance, // Spigot - this.spigotConfig.simulationDistance, // Spigot - flag, - null, // Paper - rewrite chunk system - () -> server.overworld().getDataStorage() - ); + // DivineMC start - Regionized Chunk Ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableRegionizedChunkTicking) { + this.chunkSource = new org.bxteam.divinemc.async.rct.RegionizedChunkTicking( + this, + levelStorageAccess, + fixerUpper, + server.getStructureManager(), + dispatcher, + chunkGenerator, + this.spigotConfig.viewDistance, // Spigot + this.spigotConfig.simulationDistance, // Spigot + flag, + null, // Paper - rewrite chunk system + () -> server.overworld().getDataStorage() + ); + } else { + this.chunkSource = new ServerChunkCache( + this, + levelStorageAccess, + fixerUpper, + server.getStructureManager(), + dispatcher, + chunkGenerator, + this.spigotConfig.viewDistance, // Spigot + this.spigotConfig.simulationDistance, // Spigot + flag, + null, // Paper - rewrite chunk system + () -> server.overworld().getDataStorage() + ); + } + // DivineMC end - Regionized Chunk Ticking this.chunkSource.getGeneratorState().ensureStructuresGenerated(); this.portalForcer = new PortalForcer(this); this.updateSkyBrightness(); @@ -834,6 +852,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( @@ -1862,22 +1887,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 a8c2e02450caa52714bc3e064237d12fa56b4207..ebe34866e79397ac4c6a6bba97c99f5354adbe64 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -112,7 +112,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl .build(); public final org.bxteam.divinemc.util.BlockEntityTickersList blockEntityTickers = new org.bxteam.divinemc.util.BlockEntityTickersList(); // DivineMC - optimize block entity removals - Fix MC-117075 protected final CollectingNeighborUpdater 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; @@ -144,7 +144,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public boolean captureBlockStates = false; public boolean captureTreeGeneration = false; - 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 @Nullable public List captureDrops; @@ -1457,10 +1457,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public void tickBlockEntities() { this.tickingBlockEntities = true; - if (!this.pendingBlockEntityTickers.isEmpty()) { - this.blockEntityTickers.addAll(this.pendingBlockEntityTickers); - this.pendingBlockEntityTickers.clear(); + // DivineMC start - Regionized Chunk Ticking - synchronization fix + synchronized (pendingBlockEntityTickers) { + if (!this.pendingBlockEntityTickers.isEmpty()) { + this.blockEntityTickers.addAll(this.pendingBlockEntityTickers); + this.pendingBlockEntityTickers.clear(); + } } + // DivineMC end - Regionized Chunk Ticking - synchronization fix // Spigot start boolean runsNormally = this.tickRateManager().runsNormally(); diff --git a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java index 5d17213a692016d2f005c7820bf2cf1f42ce411f..ccb2e0c28aeaebbeef15fbb650fa3c2e5c241ceb 100644 --- a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +++ b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java @@ -53,7 +53,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++; @@ -72,7 +72,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--) {