diff --git a/sources/src/main/java/io/akarin/server/mixin/cps/MixinChunkProviderServer.java b/sources/src/main/java/io/akarin/server/mixin/cps/MixinChunkProviderServer.java new file mode 100644 index 000000000..6f912119b --- /dev/null +++ b/sources/src/main/java/io/akarin/server/mixin/cps/MixinChunkProviderServer.java @@ -0,0 +1,79 @@ +package io.akarin.server.mixin.cps; + +import java.util.Iterator; + +import org.spigotmc.SlackActivityAccountant; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.server.Chunk; +import net.minecraft.server.ChunkProviderServer; +import net.minecraft.server.IChunkLoader; +import net.minecraft.server.WorldServer; + +@Mixin(value = ChunkProviderServer.class, remap = false) +public class MixinChunkProviderServer { + @Shadow @Final public WorldServer world; + @Shadow public Long2ObjectOpenHashMap chunks; + + public int pendingUnloadChunks; // For keeping unload target-size features + + public void unload(Chunk chunk) { + if (this.world.worldProvider.c(chunk.locX, chunk.locZ)) { + // Akarin - avoid using the queue and simply check the unloaded flag during unloads + // this.unloadQueue.add(Long.valueOf(ChunkCoordIntPair.a(chunk.locX, chunk.locZ))); + pendingUnloadChunks++; + chunk.setShouldUnload(true); // PAIL: shouldUnload + } + } + + @Shadow public boolean unloadChunk(Chunk chunk, boolean save) { return true; } + @Shadow @Final private IChunkLoader chunkLoader; + @Shadow @Final private static double UNLOAD_QUEUE_RESIZE_FACTOR; + + @Overwrite + public boolean unloadChunks() { + if (!this.world.savingDisabled) { + SlackActivityAccountant activityAccountant = world.getMinecraftServer().slackActivityAccountant; + activityAccountant.startActivity(0.5); + + Iterator it = chunks.values().iterator(); + long now = System.currentTimeMillis(); + long unloadAfter = world.paperConfig.delayChunkUnloadsBy; + int targetSize = Math.min(pendingUnloadChunks - 100, (int) (pendingUnloadChunks * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive + + for (int i = 0; i < chunks.size() && pendingUnloadChunks > targetSize; i++) { // CraftBukkit removes unload logic to its method, we must check index + Chunk chunk = it.next(); + + if (chunk != null && chunk.isUnloading()) { + if (unloadAfter > 0) { + // We changed Paper's delay unload logic, the original behavior is just mark as unloading + if (chunk.scheduledForUnload == null || now - chunk.scheduledForUnload < unloadAfter) { + continue; + } + chunk.scheduledForUnload = null; + } + + // If a plugin cancelled it, we shouldn't trying unload it for a while + chunk.setShouldUnload(false); // Paper + + if (!unloadChunk(chunk, true)) continue; // Event cancelled + pendingUnloadChunks--; + + if (activityAccountant.activityTimeIsExhausted()) break; + } + } + activityAccountant.endActivity(); + this.chunkLoader.b(); // PAIL: chunkTick + } + return false; + } + + @Overwrite + public String getName() { + return "ServerChunkCache: " + chunks.size(); // Akarin - remove unload queue + } +} diff --git a/sources/src/main/java/io/akarin/server/mixin/cps/MixinCraftWorld.java b/sources/src/main/java/io/akarin/server/mixin/cps/MixinCraftWorld.java new file mode 100644 index 000000000..1c9251778 --- /dev/null +++ b/sources/src/main/java/io/akarin/server/mixin/cps/MixinCraftWorld.java @@ -0,0 +1,52 @@ +package io.akarin.server.mixin.cps; + +import java.util.Set; + +import org.bukkit.craftbukkit.CraftWorld; +import org.spongepowered.asm.lib.Opcodes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.Inject; + +import net.minecraft.server.Chunk; +import net.minecraft.server.PlayerChunk; + +@Mixin(value = CraftWorld.class, remap = false) +public class MixinCraftWorld { + @Inject(method = "processChunkGC()V", at = @At( + value = "INVOKE", + target = "net/minecraft/server/ChunkProviderServer.unload(Lnet/minecraft/server/Chunk;)V" + )) + public void cancelUnloading(Chunk chunk, CallbackInfo ci) { + if (chunk.isUnloading()) ci.cancel(); + } + + @Redirect(method = "processChunkGC()V", at = @At( + value = "INVOKE", + target = "java/util/Set.contains(Ljava/lang/Object;)Z", + opcode = Opcodes.INVOKEINTERFACE + )) + public boolean checkUnloading(Set set, Object chunkHash) { + return false; + } + + @Redirect(method = "regenerateChunk", at = @At( + value = "INVOKE", + target = "java/util/Set.remove(Ljava/lang/Object;)Z", + opcode = Opcodes.INVOKEINTERFACE + )) + public boolean regenChunk(Set set, Object chunkHash) { + return false; + } + + @Inject(method = "processChunkGC()V", at = @At( + value = "FIELD", + target = "net/minecraft/server/PlayerChunk.chunk:Lnet/minecraft/server/Chunk;", + opcode = Opcodes.PUTFIELD + )) + public void noUnload(PlayerChunk playerChunk, Chunk chunk, CallbackInfo ci) { + chunk.setShouldUnload(false); + } +} diff --git a/sources/src/main/resources/mixins.akarin.core.json b/sources/src/main/resources/mixins.akarin.core.json index c62008ef4..06ac77b6b 100644 --- a/sources/src/main/resources/mixins.akarin.core.json +++ b/sources/src/main/resources/mixins.akarin.core.json @@ -21,6 +21,9 @@ "core.MixinMinecraftServer", "core.MixinChunkIOExecutor", + "cps.MixinCraftWorld", + "cps.MixinChunkProviderServer", + "nsc.MixinPlayerConnection", "nsc.OptimisticNetworkManager", "nsc.NonblockingServerConnection",