From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Sat, 1 Feb 2025 00:33:03 +0300 Subject: [PATCH] Chunk System optimization diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java index b28083be4384d6c5efbdce898a0e9d7a2f5bd3d3..b3d6b53bd43045a2dd709567e35ae6f60352f1ea 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -301,7 +301,7 @@ public final class RegionizedPlayerChunkLoader { return false; } - public void tick() { + public synchronized void tick() { // DivineMC - Chunk System optimization - synchronized TickThread.ensureTickThread("Cannot tick player chunk loader async"); long currTime = System.nanoTime(); for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) { @@ -362,7 +362,7 @@ public final class RegionizedPlayerChunkLoader { GENERATED_TICKET_LEVEL, TICK_TICKET_LEVEL }; - private final Long2ByteOpenHashMap chunkTicketStage = new Long2ByteOpenHashMap(); + private final it.unimi.dsi.fastutil.longs.Long2ByteMap chunkTicketStage = it.unimi.dsi.fastutil.longs.Long2ByteMaps.synchronize(new Long2ByteOpenHashMap()); // DivineMC - Chunk System optimization { this.chunkTicketStage.defaultReturnValue(CHUNK_TICKET_STAGE_NONE); } @@ -499,7 +499,7 @@ public final class RegionizedPlayerChunkLoader { } @Override - protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { + protected synchronized void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { // DivineMC - Chunk System optimization - synchronized final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); // note: by the time this is called, the tick cleanup should have ran - so, if the chunk is at // the tick stage it was deemed in range for loading. Thus, we need to move it to generated @@ -633,7 +633,7 @@ public final class RegionizedPlayerChunkLoader { return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastTickDistance; } - private boolean areNeighboursGenerated(final int chunkX, final int chunkZ, final int radius) { + private synchronized boolean areNeighboursGenerated(final int chunkX, final int chunkZ, final int radius) { // DivineMC - Chunk System optimization - synchronized for (int dz = -radius; dz <= radius; ++dz) { for (int dx = -radius; dx <= radius; ++dx) { if ((dx | dz) == 0) { @@ -652,19 +652,11 @@ public final class RegionizedPlayerChunkLoader { return true; } - void updateQueues(final long time) { + synchronized void updateQueues(final long time) { // DivineMC - Chunk System optimization - synchronized TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async"); if (this.removed) { throw new IllegalStateException("Ticking removed player chunk loader"); } - // update rate limits - final double loadRate = this.getMaxChunkLoadRate(); - final double genRate = this.getMaxChunkGenRate(); - final double sendRate = this.getMaxChunkSendRate(); - - this.chunkLoadTicketLimiter.tickAllocation(time, loadRate, loadRate); - this.chunkGenerateTicketLimiter.tickAllocation(time, genRate, genRate); - this.chunkSendLimiter.tickAllocation(time, sendRate, sendRate); // try to progress chunk loads while (!this.loadingQueue.isEmpty()) { @@ -691,8 +683,7 @@ public final class RegionizedPlayerChunkLoader { } // try to push more chunk loads - final long maxLoads = Math.max(0L, Math.min(MAX_RATE, Math.min(this.loadQueue.size(), this.getMaxChunkLoads()))); - final int maxLoadsThisTick = (int)this.chunkLoadTicketLimiter.takeAllocation(time, loadRate, maxLoads); + final int maxLoadsThisTick = this.loadQueue.size(); // DivineMC - Chunk System optimization if (maxLoadsThisTick > 0) { final LongArrayList chunks = new LongArrayList(maxLoadsThisTick); for (int i = 0; i < maxLoadsThisTick; ++i) { @@ -767,9 +758,7 @@ public final class RegionizedPlayerChunkLoader { } // try to push more chunk generations - final long maxGens = Math.max(0L, Math.min(MAX_RATE, Math.min(this.genQueue.size(), this.getMaxChunkGenerates()))); - // preview the allocations, as we may not actually utilise all of them - final long maxGensThisTick = this.chunkGenerateTicketLimiter.previewAllocation(time, genRate, maxGens); + final long maxGensThisTick = this.genQueue.size(); // DivineMC - Chunk System optimization long ratedGensThisTick = 0L; while (!this.genQueue.isEmpty()) { final long chunkKey = this.genQueue.firstLong(); @@ -799,8 +788,6 @@ public final class RegionizedPlayerChunkLoader { ); this.generatingQueue.enqueue(chunkKey); } - // take the allocations we actually used - this.chunkGenerateTicketLimiter.takeAllocation(time, genRate, ratedGensThisTick); // try to pull ticking chunks while (!this.tickingQueue.isEmpty()) { @@ -830,10 +817,10 @@ public final class RegionizedPlayerChunkLoader { } // try to pull sending chunks - final long maxSends = Math.max(0L, Math.min(MAX_RATE, Integer.MAX_VALUE)); // note: no logic to track concurrent sends - final int maxSendsThisTick = Math.min((int)this.chunkSendLimiter.takeAllocation(time, sendRate, maxSends), this.sendQueue.size()); + final int maxSendsThisTick = this.sendQueue.size(); // DivineMC - Chunk System optimization // we do not return sends that we took from the allocation back because we want to limit the max send rate, not target it for (int i = 0; i < maxSendsThisTick; ++i) { + if (this.sendQueue.isEmpty()) break; // DivineMC - Chunk System optimization final long pendingSend = this.sendQueue.firstLong(); final int pendingSendX = CoordinateUtils.getChunkX(pendingSend); final int pendingSendZ = CoordinateUtils.getChunkZ(pendingSend); diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java index 0c99bffa769d53562a10d23c4a9b37dc59c7f478..08c7e25c736fc7a1587bcb2a490845f63001ac8d 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java @@ -145,7 +145,7 @@ public final class ChunkHolderManager { public List getOldChunkHolders() { final List ret = new ArrayList<>(this.chunkHolders.size() + 1); - for (final Iterator iterator = this.chunkHolders.valueIterator(); iterator.hasNext();) { + for (final Iterator iterator = this.chunkHolders.values().iterator(); iterator.hasNext();) { // DivineMC - Chunk System optimization ret.add(iterator.next().vanillaChunkHolder); } return ret; @@ -153,7 +153,7 @@ public final class ChunkHolderManager { public List getChunkHolders() { final List ret = new ArrayList<>(this.chunkHolders.size() + 1); - for (final Iterator iterator = this.chunkHolders.valueIterator(); iterator.hasNext();) { + for (final Iterator iterator = this.chunkHolders.values().iterator(); iterator.hasNext();) { // DivineMC - Chunk System optimization ret.add(iterator.next()); } return ret; @@ -168,7 +168,7 @@ public final class ChunkHolderManager { return new Iterable() { @Override public Iterator iterator() { - final Iterator iterator = ChunkHolderManager.this.chunkHolders.valueIterator(); + final Iterator iterator = ChunkHolderManager.this.chunkHolders.values().iterator(); // DivineMC - Chunk System optimization return new Iterator() { @Override public boolean hasNext() { @@ -1208,6 +1208,27 @@ public final class ChunkHolderManager { } } + // DivineMC start - Chunk System optimization + public final org.agrona.collections.Object2ObjectHashMap blockTickingChunkHolders = new org.agrona.collections.Object2ObjectHashMap<>(16384, 0.25f); + public final org.agrona.collections.Object2ObjectHashMap entityTickingChunkHolders = new org.agrona.collections.Object2ObjectHashMap<>(16384, 0.25f); + + public void markBlockTicking(NewChunkHolder newChunkHolder) { + this.blockTickingChunkHolders.put(newChunkHolder.getCachedLongPos(), newChunkHolder); + } + + public void markNonBlockTickingIfPossible(NewChunkHolder newChunkHolder) { + this.blockTickingChunkHolders.remove(newChunkHolder.getCachedLongPos()); + } + + public void markEntityTicking(NewChunkHolder newChunkHolder) { + this.entityTickingChunkHolders.put(newChunkHolder.getCachedLongPos(), newChunkHolder); + } + + public void markNonEntityTickingIfPossible(NewChunkHolder newChunkHolder) { + this.entityTickingChunkHolders.remove(newChunkHolder.getCachedLongPos()); + } + // DivineMC end - Chunk System optimization + public enum TicketOperationType { ADD, REMOVE, ADD_IF_REMOVED, ADD_AND_REMOVE } diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java index e4a5fa25ed368fc4662c30934da2963ef446d782..84354d8e2d391ae9e912c782f9a64b426aeb6c3a 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java @@ -644,11 +644,19 @@ public final class NewChunkHolder { } public final ChunkHolder vanillaChunkHolder; + // DivineMC start - Chunk System optimization + private final long cachedLongPos; + + public long getCachedLongPos() { + return cachedLongPos; + } + // DivineMC end - Chunk System optimization public NewChunkHolder(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkTaskScheduler scheduler) { this.world = world; this.chunkX = chunkX; this.chunkZ = chunkZ; + this.cachedLongPos = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); // DivineMC - Chunk System optimization this.scheduler = scheduler; this.vanillaChunkHolder = new ChunkHolder( new ChunkPos(chunkX, chunkZ), ChunkHolderManager.MAX_TICKET_LEVEL, world, @@ -1214,6 +1222,19 @@ public final class NewChunkHolder { private void updateCurrentState(final FullChunkStatus to) { this.currentFullChunkStatus = to; + // DivineMC start - Chunk System optimization + if (to.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markBlockTicking(this); + } else { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markNonBlockTickingIfPossible(this); + } + + if (to.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markEntityTicking(this); + } else { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markNonEntityTickingIfPossible(this); + } + // DivineMC end - Chunk System optimization } // only to be called on the main thread, no locks need to be held diff --git a/net/minecraft/server/level/DistanceManager.java b/net/minecraft/server/level/DistanceManager.java index 5eab6179ce3913cb4e4d424f910ba423faf21c85..189205fbeed7673398fa6f7706864d3723467811 100644 --- a/net/minecraft/server/level/DistanceManager.java +++ b/net/minecraft/server/level/DistanceManager.java @@ -178,14 +178,14 @@ public abstract class DistanceManager implements ca.spottedleaf.moonrise.patches public boolean inEntityTickingRange(long chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().getChunkHolder(chunkPos); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().entityTickingChunkHolders.get(chunkPos); // DivineMC - Chunk System optimization return chunkHolder != null && chunkHolder.isEntityTickingReady(); // Paper end - rewrite chunk system } public boolean inBlockTickingRange(long chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().getChunkHolder(chunkPos); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().blockTickingChunkHolders.get(chunkPos); // DivineMC - Chunk System optimization return chunkHolder != null && chunkHolder.isTickingReady(); // Paper end - rewrite chunk system } diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java index 2678bf59d557f085c7265e2f3eb038647723d35e..b30d6968fba2ab4d9cde0ac9d4f1cfc629c65359 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -441,7 +441,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon public boolean isPositionTicking(long chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.get(chunkPos); // DivineMC Chunk System optimization return newChunkHolder != null && newChunkHolder.isTickingReady(); // Paper end - rewrite chunk system } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index a3f363d0c86142e03edc7fc6e2ff6ed81de8ed65..57668810e86b1f293c661d01c2486a3da7256c1e 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -857,7 +857,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public boolean shouldTickBlocksAt(long chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.get(chunkPos); // DivineMC - Chunk System optimization return holder != null && holder.isTickingReady(); // Paper end - rewrite chunk system } @@ -2567,7 +2567,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.get(chunkPos); // DivineMC - Chunk System optimization // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded return chunkHolder != null && chunkHolder.isTickingReady(); // Paper end - rewrite chunk system @@ -2582,14 +2582,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe public boolean isNaturalSpawningAllowed(BlockPos pos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.entityTickingChunkHolders.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); // DivineMC - Chunk System optimization return chunkHolder != null && chunkHolder.isEntityTickingReady(); // Paper end - rewrite chunk system } public boolean isNaturalSpawningAllowed(ChunkPos chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkPos)); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.entityTickingChunkHolders.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkPos)); // DivineMC - Chunk System optimization return chunkHolder != null && chunkHolder.isEntityTickingReady(); // Paper end - rewrite chunk system } diff --git a/net/minecraft/world/level/LevelReader.java b/net/minecraft/world/level/LevelReader.java index 26c8c1e5598daf3550aef05b12218c47bda6618b..94c824ab1457939c425e1f99929d3222ee2c18a0 100644 --- a/net/minecraft/world/level/LevelReader.java +++ b/net/minecraft/world/level/LevelReader.java @@ -70,10 +70,27 @@ public interface LevelReader extends ca.spottedleaf.moonrise.patches.chunk_syste @Override default Holder getNoiseBiome(int x, int y, int z) { - ChunkAccess chunk = this.getChunk(QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); + ChunkAccess chunk = this.fasterChunkAccess(this, QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); // DivineMC - Chunk System optimization return chunk != null ? chunk.getNoiseBiome(x, y, z) : this.getUncachedNoiseBiome(x, y, z); } + // DivineMC start - Chunk System optimization + private @Nullable ChunkAccess fasterChunkAccess(LevelReader instance, int x, int z, ChunkStatus chunkStatus, boolean create) { + if (!create && instance instanceof net.minecraft.server.level.ServerLevel world) { + final net.minecraft.server.level.ChunkHolder holder = (world.getChunkSource().chunkMap).getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); + if (holder != null) { + final java.util.concurrent.CompletableFuture> future = holder.getFullChunkFuture(); + final net.minecraft.server.level.ChunkResult either = future.getNow(null); + if (either != null) { + final net.minecraft.world.level.chunk.LevelChunk chunk = either.orElse(null); + if (chunk != null) return chunk; + } + } + } + return instance.getChunk(x, z, chunkStatus, create); + } + // DivineMC end - Chunk System optimization + Holder getUncachedNoiseBiome(int x, int y, int z); boolean isClientSide(); diff --git a/net/minecraft/world/level/chunk/storage/IOWorker.java b/net/minecraft/world/level/chunk/storage/IOWorker.java index 2199a9e2a0141c646d108f2687a27f1d165453c5..c28c2583b257f92207b822a1fdde8f5b7e480992 100644 --- a/net/minecraft/world/level/chunk/storage/IOWorker.java +++ b/net/minecraft/world/level/chunk/storage/IOWorker.java @@ -212,7 +212,38 @@ public class IOWorker implements ChunkScanAccess, AutoCloseable { }); } + // DivineMC start - Chunk System optimization + private void checkHardLimit() { + if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit) { + LOGGER.warn("Chunk data cache size exceeded hard limit ({} >= {}), forcing writes to disk (you can increase chunkDataCacheLimit in c2me.toml)", this.pendingWrites.size(), org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit); + while (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit * 0.75) { + writeResult0(); + } + } + } + + private void writeResult0() { + java.util.Iterator> iterator = this.pendingWrites.entrySet().iterator(); + if (iterator.hasNext()) { + java.util.Map.Entry entry = iterator.next(); + iterator.remove(); + this.runStore(entry.getKey(), entry.getValue()); + } + } + // DivineMC end - Chunk System optimization + private void storePendingChunk() { + // DivineMC start - Chunk System optimization + if (!this.pendingWrites.isEmpty()) { + checkHardLimit(); + if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) { + int writeFrequency = Math.min(1, (this.pendingWrites.size() - (int) org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) / 16); + for (int i = 0; i < writeFrequency; i++) { + writeResult0(); + } + } + } + // DivineMC end - Chunk System optimization Entry entry = this.pendingWrites.pollFirstEntry(); if (entry != null) { this.runStore(entry.getKey(), entry.getValue()); diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java index 6ebd1300c2561116b83cb2472ac7939ead36d576..16cd10ab8de69ca3d29c84cf93715645322fd72a 100644 --- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java @@ -244,7 +244,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise protected RegionFileStorage(RegionStorageInfo info, Path folder, boolean sync) { // Paper - protected this.folder = folder; - this.sync = sync; + this.sync = Boolean.parseBoolean(System.getProperty("com.ishland.c2me.chunkio.syncDiskWrites", String.valueOf(sync))); // DivineMC - C2ME: sync disk writes this.info = info; this.isChunkData = isChunkDataFolder(this.folder); // Paper - recalculate region file headers }