From fa04d53cbf041f6b6a56abcbd2bd78cec5d096ad Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Wed, 24 Nov 2021 17:06:46 +0100 Subject: [PATCH] More more more more more more more more more more more more more more work --- .../api/Add-BellRingEvent.patch | 0 .../api/Add-moon-phase-API.patch | 0 ...yPickupItemAnimation-to-LivingEntity.patch | 0 .../api/Add-setMaxPlayers-API.patch | 0 .../{unapplied => }/api/Brand-support.patch | 0 .../Support-hex-colors-in-getLastColors.patch | 0 .../server/Add-BellRingEvent.patch | 0 ...keLighting-call-to-World-spigot-stri.patch | 0 .../server/Add-moon-phase-API.patch | 0 ...yPickupItemAnimation-to-LivingEntity.patch | 0 .../server/Add-setMaxPlayers-API.patch | 2 +- ...Add-zombie-targets-turtle-egg-config.patch | 0 .../server/Brand-support.patch | 4 +- .../server/Buffer-joins-to-world.patch | 8 +- ...erver-load-chunks-from-newer-version.patch | 15 +- .../server/Don-t-require-FACING-data.patch | 0 ...ataPlayer-leak-due-from-quitting-ear.patch | 2 +- ...4-Bukkit-world-container-is-not-used.patch | 0 ...-5885-Unable-to-disable-advancements.patch | 0 .../server/Fix-SPIGOT-5989.patch | 2 +- ...geEvent-not-firing-for-all-use-cases.patch | 0 ...ix-arrows-never-despawning-MC-125757.patch | 0 ...istake-in-CB-NBT-int-deserialization.patch | 0 ...Fix-some-rails-connecting-improperly.patch | 0 ...ove-range-check-for-block-placing-up.patch | 2 +- ...ze-NetworkManager-Exception-Handling.patch | 2 +- .../server/Optimize-redstone-algorithm.patch | 0 ...ancement-data-player-iteration-to-be.patch | 0 ...-headless-pistons-from-being-created.patch | 15 +- ...-Vanilla-Command-permission-checking.patch | 13 +- ...k-Priority-Urgency-System-for-Chunks.patch | 1547 ----------------- ...mprove-Chunk-Status-Transition-Speed.patch | 81 - 32 files changed, 21 insertions(+), 1672 deletions(-) rename patches/{unapplied => }/api/Add-BellRingEvent.patch (100%) rename patches/{unapplied => }/api/Add-moon-phase-API.patch (100%) rename patches/{unapplied => }/api/Add-playPickupItemAnimation-to-LivingEntity.patch (100%) rename patches/{unapplied => }/api/Add-setMaxPlayers-API.patch (100%) rename patches/{unapplied => }/api/Brand-support.patch (100%) rename patches/{unapplied => }/api/Support-hex-colors-in-getLastColors.patch (100%) rename patches/{unapplied => }/server/Add-BellRingEvent.patch (100%) rename patches/{unapplied => }/server/Add-missing-strikeLighting-call-to-World-spigot-stri.patch (100%) rename patches/{unapplied => }/server/Add-moon-phase-API.patch (100%) rename patches/{unapplied => }/server/Add-playPickupItemAnimation-to-LivingEntity.patch (100%) rename patches/{unapplied => }/server/Add-setMaxPlayers-API.patch (96%) rename patches/{unapplied => }/server/Add-zombie-targets-turtle-egg-config.patch (100%) rename patches/{unapplied => }/server/Brand-support.patch (98%) rename patches/{unapplied => }/server/Buffer-joins-to-world.patch (92%) rename patches/{unapplied => }/server/Do-not-let-the-server-load-chunks-from-newer-version.patch (73%) rename patches/{unapplied => }/server/Don-t-require-FACING-data.patch (100%) rename patches/{unapplied => }/server/Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch (98%) rename patches/{unapplied => }/server/Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch (100%) rename patches/{unapplied => }/server/Fix-SPIGOT-5885-Unable-to-disable-advancements.patch (100%) rename patches/{unapplied => }/server/Fix-SPIGOT-5989.patch (98%) rename patches/{unapplied => }/server/Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch (100%) rename patches/{unapplied => }/server/Fix-arrows-never-despawning-MC-125757.patch (100%) rename patches/{unapplied => }/server/Fix-regex-mistake-in-CB-NBT-int-deserialization.patch (100%) rename patches/{unapplied => }/server/Fix-some-rails-connecting-improperly.patch (100%) rename patches/{unapplied => }/server/Move-range-check-for-block-placing-up.patch (96%) rename patches/{unapplied => }/server/Optimize-NetworkManager-Exception-Handling.patch (99%) rename patches/{unapplied => }/server/Optimize-redstone-algorithm.patch (100%) rename patches/{unapplied => }/server/Optimize-the-advancement-data-player-iteration-to-be.patch (100%) rename patches/{unapplied => }/server/Prevent-headless-pistons-from-being-created.patch (84%) rename patches/{unapplied => }/server/Thread-Safe-Vanilla-Command-permission-checking.patch (84%) delete mode 100644 patches/unapplied/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch delete mode 100644 patches/unapplied/server/Improve-Chunk-Status-Transition-Speed.patch diff --git a/patches/unapplied/api/Add-BellRingEvent.patch b/patches/api/Add-BellRingEvent.patch similarity index 100% rename from patches/unapplied/api/Add-BellRingEvent.patch rename to patches/api/Add-BellRingEvent.patch diff --git a/patches/unapplied/api/Add-moon-phase-API.patch b/patches/api/Add-moon-phase-API.patch similarity index 100% rename from patches/unapplied/api/Add-moon-phase-API.patch rename to patches/api/Add-moon-phase-API.patch diff --git a/patches/unapplied/api/Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/api/Add-playPickupItemAnimation-to-LivingEntity.patch similarity index 100% rename from patches/unapplied/api/Add-playPickupItemAnimation-to-LivingEntity.patch rename to patches/api/Add-playPickupItemAnimation-to-LivingEntity.patch diff --git a/patches/unapplied/api/Add-setMaxPlayers-API.patch b/patches/api/Add-setMaxPlayers-API.patch similarity index 100% rename from patches/unapplied/api/Add-setMaxPlayers-API.patch rename to patches/api/Add-setMaxPlayers-API.patch diff --git a/patches/unapplied/api/Brand-support.patch b/patches/api/Brand-support.patch similarity index 100% rename from patches/unapplied/api/Brand-support.patch rename to patches/api/Brand-support.patch diff --git a/patches/unapplied/api/Support-hex-colors-in-getLastColors.patch b/patches/api/Support-hex-colors-in-getLastColors.patch similarity index 100% rename from patches/unapplied/api/Support-hex-colors-in-getLastColors.patch rename to patches/api/Support-hex-colors-in-getLastColors.patch diff --git a/patches/unapplied/server/Add-BellRingEvent.patch b/patches/server/Add-BellRingEvent.patch similarity index 100% rename from patches/unapplied/server/Add-BellRingEvent.patch rename to patches/server/Add-BellRingEvent.patch diff --git a/patches/unapplied/server/Add-missing-strikeLighting-call-to-World-spigot-stri.patch b/patches/server/Add-missing-strikeLighting-call-to-World-spigot-stri.patch similarity index 100% rename from patches/unapplied/server/Add-missing-strikeLighting-call-to-World-spigot-stri.patch rename to patches/server/Add-missing-strikeLighting-call-to-World-spigot-stri.patch diff --git a/patches/unapplied/server/Add-moon-phase-API.patch b/patches/server/Add-moon-phase-API.patch similarity index 100% rename from patches/unapplied/server/Add-moon-phase-API.patch rename to patches/server/Add-moon-phase-API.patch diff --git a/patches/unapplied/server/Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/server/Add-playPickupItemAnimation-to-LivingEntity.patch similarity index 100% rename from patches/unapplied/server/Add-playPickupItemAnimation-to-LivingEntity.patch rename to patches/server/Add-playPickupItemAnimation-to-LivingEntity.patch diff --git a/patches/unapplied/server/Add-setMaxPlayers-API.patch b/patches/server/Add-setMaxPlayers-API.patch similarity index 96% rename from patches/unapplied/server/Add-setMaxPlayers-API.patch rename to patches/server/Add-setMaxPlayers-API.patch index 9ab4824b66..196cca7904 100644 --- a/patches/unapplied/server/Add-setMaxPlayers-API.patch +++ b/patches/server/Add-setMaxPlayers-API.patch @@ -15,8 +15,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - protected final int maxPlayers; + protected int maxPlayers; public final void setMaxPlayers(int maxPlayers) { this.maxPlayers = maxPlayers; } // Paper - remove final and add setter private int viewDistance; + private int simulationDistance; private boolean allowCheatsForAllPlayers; - private static final boolean ALLOW_LOGOUTIVATOR = false; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java diff --git a/patches/unapplied/server/Add-zombie-targets-turtle-egg-config.patch b/patches/server/Add-zombie-targets-turtle-egg-config.patch similarity index 100% rename from patches/unapplied/server/Add-zombie-targets-turtle-egg-config.patch rename to patches/server/Add-zombie-targets-turtle-egg-config.patch diff --git a/patches/unapplied/server/Brand-support.patch b/patches/server/Brand-support.patch similarity index 98% rename from patches/unapplied/server/Brand-support.patch rename to patches/server/Brand-support.patch index 9f3da7b777..690571b08e 100644 --- a/patches/unapplied/server/Brand-support.patch +++ b/patches/server/Brand-support.patch @@ -16,9 +16,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; -@@ -0,0 +0,0 @@ import net.minecraft.nbt.ListTag; +@@ -0,0 +0,0 @@ import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; import net.minecraft.nbt.StringTag; - import net.minecraft.nbt.Tag; import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.ChatType; diff --git a/patches/unapplied/server/Buffer-joins-to-world.patch b/patches/server/Buffer-joins-to-world.patch similarity index 92% rename from patches/unapplied/server/Buffer-joins-to-world.patch rename to patches/server/Buffer-joins-to-world.patch index 332ac170b0..2634c25596 100644 --- a/patches/unapplied/server/Buffer-joins-to-world.patch +++ b/patches/server/Buffer-joins-to-world.patch @@ -12,15 +12,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java @@ -0,0 +0,0 @@ public class PaperConfig { - maxPlayerAutoSavePerTick = (playerAutoSaveRate == -1 || playerAutoSaveRate > 100) ? 10 : 20; } } -+ + + public static int maxJoinsPerTick; + private static void maxJoinsPerTick() { + maxJoinsPerTick = getInt("settings.max-joins-per-tick", 3); + } - } ++ + public static void registerCommands() { + for (Map.Entry entry : commands.entrySet()) { + MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Paper", entry.getValue()); diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/Connection.java diff --git a/patches/unapplied/server/Do-not-let-the-server-load-chunks-from-newer-version.patch b/patches/server/Do-not-let-the-server-load-chunks-from-newer-version.patch similarity index 73% rename from patches/unapplied/server/Do-not-let-the-server-load-chunks-from-newer-version.patch rename to patches/server/Do-not-let-the-server-load-chunks-from-newer-version.patch index c586b2fd77..96bfc9f520 100644 --- a/patches/unapplied/server/Do-not-let-the-server-load-chunks-from-newer-version.patch +++ b/patches/server/Do-not-let-the-server-load-chunks-from-newer-version.patch @@ -13,19 +13,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java @@ -0,0 +0,0 @@ public class ChunkSerializer { - holder.tasks.forEach(Runnable::run); return holder.protoChunk; } -+ + + // Paper start -+ private static final int CURRENT_DATA_VERSION = SharedConstants.getCurrentVersion().getWorldVersion(); ++ private static final int CURRENT_DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); + private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); + // Paper end -+ - public static InProgressChunkHolder loadChunk(ServerLevel world, StructureManager structureManager, PoiManager poiStorage, ChunkPos pos, CompoundTag nbt, boolean distinguish) { + public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) { java.util.ArrayDeque tasksToExecuteOnMain = new java.util.ArrayDeque<>(); // Paper end - ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); + // Paper start - Do NOT attempt to load chunks saved with newer versions + if (nbt.contains("DataVersion", 99)) { + int dataVersion = nbt.getInt("DataVersion"); @@ -35,6 +32,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + // Paper end - BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource(); - CompoundTag nbttagcompound1 = nbt.getCompound("Level"); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate - ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate + ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate + + if (!Objects.equals(chunkPos, chunkcoordintpair1)) { diff --git a/patches/unapplied/server/Don-t-require-FACING-data.patch b/patches/server/Don-t-require-FACING-data.patch similarity index 100% rename from patches/unapplied/server/Don-t-require-FACING-data.patch rename to patches/server/Don-t-require-FACING-data.patch diff --git a/patches/unapplied/server/Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch b/patches/server/Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch similarity index 98% rename from patches/unapplied/server/Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch rename to patches/server/Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch index c88cd68850..7a5830b64a 100644 --- a/patches/unapplied/server/Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch +++ b/patches/server/Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch @@ -52,7 +52,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 protected abstract T createInstance(JsonObject obj, EntityPredicate.Composite playerPredicate, DeserializationContext predicateDeserializer); @@ -0,0 +0,0 @@ public abstract class SimpleCriterionTrigger tester) { + protected void trigger(ServerPlayer player, Predicate predicate) { PlayerAdvancements playerAdvancements = player.getAdvancements(); - Set> set = this.players.get(playerAdvancements); + Set> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak diff --git a/patches/unapplied/server/Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch b/patches/server/Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch similarity index 100% rename from patches/unapplied/server/Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch rename to patches/server/Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch diff --git a/patches/unapplied/server/Fix-SPIGOT-5885-Unable-to-disable-advancements.patch b/patches/server/Fix-SPIGOT-5885-Unable-to-disable-advancements.patch similarity index 100% rename from patches/unapplied/server/Fix-SPIGOT-5885-Unable-to-disable-advancements.patch rename to patches/server/Fix-SPIGOT-5885-Unable-to-disable-advancements.patch diff --git a/patches/unapplied/server/Fix-SPIGOT-5989.patch b/patches/server/Fix-SPIGOT-5989.patch similarity index 98% rename from patches/unapplied/server/Fix-SPIGOT-5989.patch rename to patches/server/Fix-SPIGOT-5989.patch index e6bc6385cc..6edd088aa3 100644 --- a/patches/unapplied/server/Fix-SPIGOT-5989.patch +++ b/patches/server/Fix-SPIGOT-5989.patch @@ -54,7 +54,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } else { @@ -0,0 +0,0 @@ public abstract class PlayerList { } - // entityplayer1.syncInventory(); + // entityplayer1.initInventoryMenu(); entityplayer1.setHealth(entityplayer1.getHealth()); - if (flag2) { - entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 1.0F, 1.0F)); diff --git a/patches/unapplied/server/Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/patches/server/Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch similarity index 100% rename from patches/unapplied/server/Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch rename to patches/server/Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch diff --git a/patches/unapplied/server/Fix-arrows-never-despawning-MC-125757.patch b/patches/server/Fix-arrows-never-despawning-MC-125757.patch similarity index 100% rename from patches/unapplied/server/Fix-arrows-never-despawning-MC-125757.patch rename to patches/server/Fix-arrows-never-despawning-MC-125757.patch diff --git a/patches/unapplied/server/Fix-regex-mistake-in-CB-NBT-int-deserialization.patch b/patches/server/Fix-regex-mistake-in-CB-NBT-int-deserialization.patch similarity index 100% rename from patches/unapplied/server/Fix-regex-mistake-in-CB-NBT-int-deserialization.patch rename to patches/server/Fix-regex-mistake-in-CB-NBT-int-deserialization.patch diff --git a/patches/unapplied/server/Fix-some-rails-connecting-improperly.patch b/patches/server/Fix-some-rails-connecting-improperly.patch similarity index 100% rename from patches/unapplied/server/Fix-some-rails-connecting-improperly.patch rename to patches/server/Fix-some-rails-connecting-improperly.patch diff --git a/patches/unapplied/server/Move-range-check-for-block-placing-up.patch b/patches/server/Move-range-check-for-block-placing-up.patch similarity index 96% rename from patches/unapplied/server/Move-range-check-for-block-placing-up.patch rename to patches/server/Move-range-check-for-block-placing-up.patch index 415085a586..74213c4fbe 100644 --- a/patches/unapplied/server/Move-range-check-for-block-placing-up.patch +++ b/patches/server/Move-range-check-for-block-placing-up.patch @@ -40,7 +40,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 int i = this.player.level.getMaxBuildHeight(); if (blockposition.getY() < i) { - if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract((net.minecraft.world.entity.player.Player) this.player, blockposition)) { + if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) { // CraftBukkit start - Check if we can actually do something over this large a distance - Location eyeLoc = this.getCraftPlayer().getEyeLocation(); - double reachDistance = NumberConversions.square(eyeLoc.getX() - blockposition.getX()) + NumberConversions.square(eyeLoc.getY() - blockposition.getY()) + NumberConversions.square(eyeLoc.getZ() - blockposition.getZ()); diff --git a/patches/unapplied/server/Optimize-NetworkManager-Exception-Handling.patch b/patches/server/Optimize-NetworkManager-Exception-Handling.patch similarity index 99% rename from patches/unapplied/server/Optimize-NetworkManager-Exception-Handling.patch rename to patches/server/Optimize-NetworkManager-Exception-Handling.patch index 6047abf763..8aa72b112a 100644 --- a/patches/unapplied/server/Optimize-NetworkManager-Exception-Handling.patch +++ b/patches/server/Optimize-NetworkManager-Exception-Handling.patch @@ -25,7 +25,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public class Varint21FrameDecoder extends ByteToMessageDecoder { + private final byte[] lenBuf = new byte[3]; // Paper - @Override ++ @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) { + // Paper start - if channel is not active just discard the packet + if (!channelHandlerContext.channel().isActive()) { diff --git a/patches/unapplied/server/Optimize-redstone-algorithm.patch b/patches/server/Optimize-redstone-algorithm.patch similarity index 100% rename from patches/unapplied/server/Optimize-redstone-algorithm.patch rename to patches/server/Optimize-redstone-algorithm.patch diff --git a/patches/unapplied/server/Optimize-the-advancement-data-player-iteration-to-be.patch b/patches/server/Optimize-the-advancement-data-player-iteration-to-be.patch similarity index 100% rename from patches/unapplied/server/Optimize-the-advancement-data-player-iteration-to-be.patch rename to patches/server/Optimize-the-advancement-data-player-iteration-to-be.patch diff --git a/patches/unapplied/server/Prevent-headless-pistons-from-being-created.patch b/patches/server/Prevent-headless-pistons-from-being-created.patch similarity index 84% rename from patches/unapplied/server/Prevent-headless-pistons-from-being-created.patch rename to patches/server/Prevent-headless-pistons-from-being-created.patch index e4e8aa196a..701e0cfe37 100644 --- a/patches/unapplied/server/Prevent-headless-pistons-from-being-created.patch +++ b/patches/server/Prevent-headless-pistons-from-being-created.patch @@ -18,22 +18,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + config.set("settings.unsupported-settings.allow-headless-pistons-readme", "This setting controls if players should be able to create headless pistons."); + allowHeadlessPistons = getBoolean("settings.unsupported-settings.allow-headless-pistons", false); + } -+ - public static int playerAutoSaveRate = -1; - public static int maxPlayerAutoSavePerTick = 10; - private static void playerAutoSaveRate() { + } diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/Explosion.java +++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -0,0 +0,0 @@ import java.util.Random; - import java.util.Set; - import javax.annotation.Nullable; - import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; - import net.minecraft.core.Vec3i; - import net.minecraft.core.particles.ParticleTypes; - import net.minecraft.server.level.ServerLevel; @@ -0,0 +0,0 @@ import net.minecraft.world.level.block.BaseFireBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; @@ -51,7 +40,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + if (!com.destroystokyo.paper.PaperConfig.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { + BlockEntity extension = this.level.getBlockEntity(blockposition); + if (extension instanceof PistonMovingBlockEntity && ((PistonMovingBlockEntity) extension).isSourcePiston()) { -+ Direction direction = iblockdata.getValue(PistonHeadBlock.FACING); ++ net.minecraft.core.Direction direction = iblockdata.getValue(PistonHeadBlock.FACING); + set.add(blockposition.relative(direction.getOpposite())); + } + } diff --git a/patches/unapplied/server/Thread-Safe-Vanilla-Command-permission-checking.patch b/patches/server/Thread-Safe-Vanilla-Command-permission-checking.patch similarity index 84% rename from patches/unapplied/server/Thread-Safe-Vanilla-Command-permission-checking.patch rename to patches/server/Thread-Safe-Vanilla-Command-permission-checking.patch index 0ba62f8040..2e1859b82b 100644 --- a/patches/unapplied/server/Thread-Safe-Vanilla-Command-permission-checking.patch +++ b/patches/server/Thread-Safe-Vanilla-Command-permission-checking.patch @@ -29,23 +29,12 @@ diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/ index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/commands/CommandSourceStack.java +++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -0,0 +0,0 @@ import com.mojang.brigadier.suggestion.Suggestions; - import com.mojang.brigadier.suggestion.SuggestionsBuilder; - import java.util.Collection; - import java.util.Iterator; -+import java.util.Map; - import java.util.Set; - import java.util.concurrent.CompletableFuture; -+import java.util.concurrent.ConcurrentHashMap; - import java.util.function.BinaryOperator; - import java.util.stream.Stream; - import javax.annotation.Nullable; @@ -0,0 +0,0 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy private final ResultConsumer consumer; private final EntityAnchorArgument.Anchor anchor; private final Vec2 rotation; - public volatile CommandNode currentCommand; // CraftBukkit -+ public Map currentCommand = new ConcurrentHashMap<>(); // CraftBukkit // Paper ++ public java.util.Map currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) { this(output, pos, rot, world, level, name, displayName, server, entity, false, (commandcontext, flag, j) -> { diff --git a/patches/unapplied/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/patches/unapplied/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch deleted file mode 100644 index 31e7b70c5e..0000000000 --- a/patches/unapplied/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch +++ /dev/null @@ -1,1547 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 11 Apr 2020 03:56:07 -0400 -Subject: [PATCH] Implement Chunk Priority / Urgency System for Chunks - -Mark chunks that are blocking main thread for world generation as urgent - -Implements a general priority system so that chunks that are sorted in -the generator queues can prioritize certain chunks over another. - -Urgent chunks will jump to the front of the line, ensuring that a -sync chunk load on an ungenerated chunk does not lag the server for -a long period of time if the servers generator queues are filled with -lots of chunks already. - -This massively reduces the lag spikes from sync chunk gens. - -Then we further prioritize loading order so nearby chunks have higher -priority than distant chunks, reducing the pressure a high no tick -view distance holds on you. - -Chunks in front of the player have higher priority, to help with -fast traveling players keep up with their movement. - -diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java -+++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java -@@ -0,0 +0,0 @@ public final class ChunkTaskManager { - } - - static void dumpChunkInfo(Set seenChunks, ChunkHolder chunkHolder, int x, int z) { -- dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1); -+ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 4); // Paper - 1->4 - } - - static void dumpChunkInfo(Set seenChunks, ChunkHolder chunkHolder, int x, int z, int indent, int maxDepth) { -@@ -0,0 +0,0 @@ public final class ChunkTaskManager { - PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getStatus().toString())); - PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Ticket Status - " + ChunkHolder.getStatus(chunkHolder.getTicketLevel())); - PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Status - " + ((holderStatus == null) ? "null" : holderStatus.toString())); -+ // Paper start -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Priority - " + chunkHolder.queueLevel); -+ -+ if (!chunkHolder.neighbors.isEmpty()) { -+ if (indent >= maxDepth) { -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: (Can't show, too deeply nested)"); -+ return; -+ } -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: "); -+ for (ChunkHolder neighbor : chunkHolder.neighbors.keySet()) { -+ ChunkStatus status = neighbor.getChunkHolderStatus(); -+ if (status != null && status.isOrAfter(ChunkHolder.getStatus(neighbor.getTicketLevel()))) { -+ continue; -+ } -+ int nx = neighbor.pos.x; -+ int nz = neighbor.pos.z; -+ if (seenChunks.contains(neighbor)) { -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + " (CIRCULAR)"); -+ continue; -+ } -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + ":"); -+ dumpChunkInfo(seenChunks, neighbor, nx, nz, indent + 1, maxDepth); -+ } -+ } -+ // Paper end - } - } - -diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/MCUtil.java -+++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -0,0 +0,0 @@ public final class MCUtil { - chunkData.addProperty("x", playerChunk.pos.x); - chunkData.addProperty("z", playerChunk.pos.z); - chunkData.addProperty("ticket-level", playerChunk.getTicketLevel()); -+ chunkData.addProperty("priority", playerChunk.queueLevel); // Paper - priority - chunkData.addProperty("state", ChunkHolder.getFullChunkStatus(playerChunk.getTicketLevel()).toString()); - chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.longKey)); - chunkData.addProperty("status", status == null ? "unloaded" : status.toString()); -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -0,0 +0,0 @@ public class ChunkHolder { - private final DebugBuffer chunkToSaveHistory; - public int oldTicketLevel; - private int ticketLevel; -- private int queueLevel; -+ public volatile int queueLevel; // Paper - private->public, make volatile since this is concurrently accessed - public final ChunkPos pos; - private boolean hasChangedSections; - private final ShortSet[] changedBlocksPerSection; -@@ -0,0 +0,0 @@ public class ChunkHolder { - - boolean isUpdateQueued = false; // Paper - private final ChunkMap chunkMap; // Paper -+ public ServerLevel getWorld() { return chunkMap.level; } // Paper - // Paper start - no-tick view distance - public final LevelChunk getSendingChunk() { - // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used -@@ -0,0 +0,0 @@ public class ChunkHolder { - } - // Paper end - -+ // Paper start - Chunk gen/load priority system -+ volatile int neighborPriority = -1; -+ volatile int priorityBoost = 0; -+ public final java.util.concurrent.ConcurrentHashMap neighbors = new java.util.concurrent.ConcurrentHashMap<>(); -+ public final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap neighborPriorities = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); -+ int requestedPriority = ChunkMap.MAX_CHUNK_DISTANCE + 1; // this priority is possible pending, but is used to ensure needless updates are not queued -+ -+ private int getDemandedPriority() { -+ int priority = neighborPriority; // if we have a neighbor priority, use it -+ int myPriority = getMyPriority(); -+ -+ if (priority == -1 || (ticketLevel <= 33 && priority > myPriority)) { -+ priority = myPriority; -+ } -+ -+ return Math.max(1, Math.min(Math.max(ticketLevel, ChunkMap.MAX_CHUNK_DISTANCE), priority)); -+ } -+ -+ private int getMyPriority() { -+ if (priorityBoost == DistanceManager.URGENT_PRIORITY) { -+ return 2; // Urgent - ticket level isn't always 31 so 33-30 = 3, but allow 1 more tasks to go below this for dependents -+ } -+ return ticketLevel - priorityBoost; -+ } -+ -+ private int getNeighborsPriority() { -+ return (neighborPriorities.isEmpty() ? getMyPriority() : getDemandedPriority()) + 1; -+ } -+ -+ public void onNeighborRequest(ChunkHolder neighbor, ChunkStatus status) { -+ neighbor.setNeighborPriority(this, getNeighborsPriority()); -+ this.neighbors.compute(neighbor, (playerChunk, currentWantedStatus) -> { -+ if (currentWantedStatus == null || !currentWantedStatus.isOrAfter(status)) { -+ //System.out.println(this + " request " + neighbor + " at " + status + " currently " + currentWantedStatus); -+ return status; -+ } else { -+ //System.out.println(this + " requested " + neighbor + " at " + status + " but thats lower than other wanted status " + currentWantedStatus); -+ return currentWantedStatus; -+ } -+ }); -+ -+ } -+ -+ public void onNeighborDone(ChunkHolder neighbor, ChunkStatus chunkstatus, ChunkAccess chunk) { -+ this.neighbors.compute(neighbor, (playerChunk, wantedStatus) -> { -+ if (wantedStatus != null && chunkstatus.isOrAfter(wantedStatus)) { -+ //System.out.println(this + " neighbor done at " + neighbor + " for status " + chunkstatus + " wanted " + wantedStatus); -+ neighbor.removeNeighborPriority(this); -+ return null; -+ } else { -+ //System.out.println(this + " neighbor finished our previous request at " + neighbor + " for status " + chunkstatus + " but we now want instead " + wantedStatus); -+ return wantedStatus; -+ } -+ }); -+ } -+ -+ private void removeNeighborPriority(ChunkHolder requester) { -+ synchronized (neighborPriorities) { -+ neighborPriorities.remove(requester.pos.toLong()); -+ recalcNeighborPriority(); -+ } -+ checkPriority(); -+ } -+ -+ -+ private void setNeighborPriority(ChunkHolder requester, int priority) { -+ synchronized (neighborPriorities) { -+ if (!Integer.valueOf(priority).equals(neighborPriorities.put(requester.pos.toLong(), Integer.valueOf(priority)))) { -+ recalcNeighborPriority(); -+ } -+ } -+ checkPriority(); -+ } -+ -+ private void recalcNeighborPriority() { -+ neighborPriority = -1; -+ if (!neighborPriorities.isEmpty()) { -+ synchronized (neighborPriorities) { -+ for (Integer neighbor : neighborPriorities.values()) { -+ if (neighbor < neighborPriority || neighborPriority == -1) { -+ neighborPriority = neighbor; -+ } -+ } -+ } -+ } -+ } -+ private void checkPriority() { -+ if (this.requestedPriority != getDemandedPriority()) this.chunkMap.queueHolderUpdate(this); -+ } -+ -+ public final double getDistance(ServerPlayer player) { -+ return getDistance(player.getX(), player.getZ()); -+ } -+ public final double getDistance(double blockX, double blockZ) { -+ int cx = net.minecraft.server.MCUtil.fastFloor(blockX) >> 4; -+ int cz = net.minecraft.server.MCUtil.fastFloor(blockZ) >> 4; -+ final double x = pos.x - cx; -+ final double z = pos.z - cz; -+ return (x * x) + (z * z); -+ } -+ -+ public final double getDistanceFrom(BlockPos pos) { -+ return getDistance(pos.getX(), pos.getZ()); -+ } -+ -+ public static ChunkStatus getNextStatus(ChunkStatus status) { -+ if (status == ChunkStatus.FULL) { -+ return status; -+ } -+ return CHUNK_STATUSES.get(status.getIndex() + 1); -+ } -+ public CompletableFuture> getStatusFutureUncheckedMain(ChunkStatus chunkstatus) { -+ return ensureMain(getFutureIfPresentUnchecked(chunkstatus)); -+ } -+ public CompletableFuture ensureMain(CompletableFuture future) { -+ return future.thenApplyAsync(r -> r, chunkMap.mainInvokingExecutor); -+ } -+ -+ @Override -+ public String toString() { -+ return "PlayerChunk{" + -+ "location=" + pos + -+ ", ticketLevel=" + ticketLevel + "/" + getStatus(this.ticketLevel) + -+ ", chunkHolderStatus=" + getChunkHolderStatus() + -+ ", neighborPriority=" + getNeighborsPriority() + -+ ", priority=(" + ticketLevel + " - " + priorityBoost +" vs N " + neighborPriority + ") = " + getDemandedPriority() + " A " + queueLevel + -+ '}'; -+ } -+ // Paper end -+ - // Paper start - optimise isOutsideOfRange - // cached here to avoid a map lookup - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; -@@ -0,0 +0,0 @@ public class ChunkHolder { - }); - } - -+ // Paper start -+ private boolean loadCallbackScheduled = false; -+ private boolean unloadCallbackScheduled = false; -+ // Paper end -+ - private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) { - this.pendingFullStateConfirmation.cancel(false); - playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state); - } - - protected void updateFutures(ChunkMap chunkStorage, Executor executor) { -+ io.papermc.paper.util.TickThread.ensureTickThread("Async ticket level update"); // Paper - ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel); - ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel); - boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; -@@ -0,0 +0,0 @@ public class ChunkHolder { - // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. - if (playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { - this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { -+ io.papermc.paper.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Paper - LevelChunk chunk = (LevelChunk)either.left().orElse(null); -- if (chunk != null) { -+ if (chunk != null && chunk.wasLoadCallbackInvoked() && ChunkHolder.this.ticketLevel > 33) { // Paper - only invoke unload if load was called -+ // Paper start - only schedule once, now the future is no longer completed as RIGHT if unloaded... -+ if (ChunkHolder.this.unloadCallbackScheduled) { -+ return; -+ } -+ ChunkHolder.this.unloadCallbackScheduled = true; -+ // Paper end - only schedule once, now the future is no longer completed as RIGHT if unloaded... - chunkStorage.callbackExecutor.execute(() -> { -+ // Paper start - only schedule once, now the future is no longer completed as RIGHT if unloaded... -+ ChunkHolder.this.unloadCallbackScheduled = false; -+ if (ChunkHolder.this.ticketLevel <= 33) { -+ return; -+ } -+ // Paper end - only schedule once, now the future is no longer completed as RIGHT if unloaded... - // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick - // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag. - // These actions may however happen deferred, so we manually set the needsSaving flag already here. -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, ChunkHolder.FullChunkStatus.BORDER); - // Paper start - cache ticking ready status - this.fullChunkFuture.thenAccept(either -> { -+ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper - final Optional left = either.left(); - if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { - // note: Here is a very good place to add callbacks to logic waiting on this. - LevelChunk fullChunk = either.left().get(); - ChunkHolder.this.isFullChunkReady = true; - fullChunk.playerChunk = ChunkHolder.this; -+ this.chunkMap.distanceManager.clearPriorityTickets(pos); - } - }); - this.updateChunkToSave(this.fullChunkFuture, "full"); -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, ChunkHolder.FullChunkStatus.TICKING); - // Paper start - cache ticking ready status - this.tickingChunkFuture.thenAccept(either -> { -+ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper - either.ifLeft(chunk -> { - // note: Here is a very good place to add callbacks to logic waiting on this. - ChunkHolder.this.isTickingReady = true; -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, ChunkHolder.FullChunkStatus.ENTITY_TICKING); - // Paper start - cache ticking ready status - this.entityTickingChunkFuture.thenAccept(either -> { -+ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper - either.ifLeft(chunk -> { - ChunkHolder.this.isEntityTickingReady = true; - // Paper start - entity ticking chunk set -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.demoteFullChunk(chunkStorage, playerchunk_state1); - } - -- this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); -+ //this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); -+ // Paper start - raise IO/load priority if priority changes, use our preferred priority -+ priorityBoost = chunkMap.distanceManager.getChunkPriority(pos); -+ int currRequestedPriority = this.requestedPriority; -+ int priority = getDemandedPriority(); -+ int newRequestedPriority = this.requestedPriority = priority; -+ if (this.queueLevel > priority) { -+ int ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY; -+ if (priority <= 10) { -+ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; -+ } else if (priority <= 20) { -+ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; -+ } -+ chunkMap.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, ioPriority); -+ chunkMap.level.getChunkSource().getLightEngine().queue.changePriority(pos.toLong(), this.queueLevel, priority); // Paper // Restore this in chunk priority later? -+ } -+ if (currRequestedPriority != newRequestedPriority) { -+ this.onLevelChange.onLevelChange(this.pos, () -> this.queueLevel, priority, p -> this.queueLevel = p); // use preferred priority -+ int neighborsPriority = getNeighborsPriority(); -+ this.neighbors.forEach((neighbor, neighborDesired) -> neighbor.setNeighborPriority(this, neighborsPriority)); -+ } -+ // Paper end - this.oldTicketLevel = this.ticketLevel; - // CraftBukkit start - // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. - if (!playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { - this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { -+ io.papermc.paper.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Paper - LevelChunk chunk = (LevelChunk)either.left().orElse(null); -- if (chunk != null) { -+ if (chunk != null && ChunkHolder.this.oldTicketLevel <= 33 && !chunk.wasLoadCallbackInvoked()) { // Paper - ensure ticket level is set to loaded before calling, as now this can complete with ticket level > 33 -+ // Paper start - only schedule once, now the future is no longer completed as RIGHT if unloaded... -+ if (ChunkHolder.this.loadCallbackScheduled) { -+ return; -+ } -+ ChunkHolder.this.loadCallbackScheduled = true; -+ // Paper end - only schedule once, now the future is no longer completed as RIGHT if unloaded... - chunkStorage.callbackExecutor.execute(() -> { -- chunk.loadCallback(); -+ ChunkHolder.this.loadCallbackScheduled = false; // Paper - only schedule once, now the future is no longer completed as RIGHT if unloaded... -+ if (ChunkHolder.this.oldTicketLevel <= 33) chunk.loadCallback(); // Paper " - }); - } - }).exceptionally((throwable) -> { -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public final ServerLevel level; - private final ThreadedLevelLightEngine lightEngine; - private final BlockableEventLoop mainThreadExecutor; -+ final java.util.concurrent.Executor mainInvokingExecutor; // Paper - public final ChunkGenerator generator; - public final Supplier overworldDataStorage; - private final PoiManager poiManager; -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.level = world; - this.generator = chunkGenerator; - this.mainThreadExecutor = mainThreadExecutor; -+ // Paper start -+ this.mainInvokingExecutor = (run) -> { -+ if (MCUtil.isMainThread()) { -+ run.run(); -+ } else { -+ mainThreadExecutor.execute(run); -+ } -+ }; -+ // Paper end - ProcessorMailbox threadedmailbox = ProcessorMailbox.create(executor, "worldgen"); - - Objects.requireNonNull(mainThreadExecutor); -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper end - optimise PlayerChunkMap#isOutsideRange - } - -+ // Paper start - Chunk Prioritization -+ public void queueHolderUpdate(ChunkHolder playerchunk) { -+ Runnable runnable = () -> { -+ if (isUnloading(playerchunk)) { -+ return; // unloaded -+ } -+ distanceManager.pendingChunkUpdates.add(playerchunk); -+ if (!distanceManager.pollingPendingChunkUpdates) { -+ level.getChunkSource().runDistanceManagerUpdates(); -+ } -+ }; -+ if (MCUtil.isMainThread()) { -+ // We can't use executor here because it will not execute tasks if its currently in the middle of executing tasks... -+ runnable.run(); -+ } else { -+ mainThreadExecutor.execute(runnable); -+ } -+ } -+ -+ private boolean isUnloading(ChunkHolder playerchunk) { -+ return playerchunk == null || toDrop.contains(playerchunk.pos.toLong()); -+ } -+ -+ private void updateChunkPriorityMap(it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap map, long chunk, int level) { -+ int prev = map.getOrDefault(chunk, -1); -+ if (level > prev) { -+ map.put(chunk, level); -+ } -+ } -+ -+ public void checkHighPriorityChunks(ServerPlayer player) { -+ int currentTick = MinecraftServer.currentTick; -+ if (currentTick - player.lastHighPriorityChecked < 20 || !player.isRealPlayer) { // weed out fake players -+ return; -+ } -+ player.lastHighPriorityChecked = currentTick; -+ it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap priorities = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(); -+ -+ int viewDistance = getEffectiveNoTickViewDistance(); -+ net.minecraft.core.BlockPos.MutableBlockPos pos = new net.minecraft.core.BlockPos.MutableBlockPos(); -+ -+ // Prioritize circular near -+ double playerChunkX = Mth.floor(player.getX()) >> 4; -+ double playerChunkZ = Mth.floor(player.getZ()) >> 4; -+ pos.set(player.getX(), 0, player.getZ()); -+ double twoThirdModifier = 2D / 3D; -+ MCUtil.getSpiralOutChunks(pos, Math.min(6, viewDistance)).forEach(coord -> { -+ if (shouldSkipPrioritization(coord)) return; -+ -+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); -+ // Prioritize immediate -+ if (dist <= 4) { -+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (27 - dist)); -+ return; -+ } -+ -+ // Prioritize nearby chunks -+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (20 - dist * twoThirdModifier)); -+ }); -+ -+ // Prioritize Frustum near 3 -+ ChunkPos front3 = player.getChunkInFront(3); -+ pos.set(front3.x << 4, 0, front3.z << 4); -+ MCUtil.getSpiralOutChunks(pos, Math.min(5, viewDistance)).forEach(coord -> { -+ if (shouldSkipPrioritization(coord)) return; -+ -+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); -+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier)); -+ }); -+ -+ // Prioritize Frustum near 5 -+ if (viewDistance > 4) { -+ ChunkPos front5 = player.getChunkInFront(5); -+ pos.set(front5.x << 4, 0, front5.z << 4); -+ MCUtil.getSpiralOutChunks(pos, 4).forEach(coord -> { -+ if (shouldSkipPrioritization(coord)) return; -+ -+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); -+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier)); -+ }); -+ } -+ -+ // Prioritize Frustum far 7 -+ if (viewDistance > 6) { -+ ChunkPos front7 = player.getChunkInFront(7); -+ pos.set(front7.x << 4, 0, front7.z << 4); -+ MCUtil.getSpiralOutChunks(pos, 3).forEach(coord -> { -+ if (shouldSkipPrioritization(coord)) { -+ return; -+ } -+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); -+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier)); -+ }); -+ } -+ -+ if (priorities.isEmpty()) return; -+ distanceManager.delayDistanceManagerTick = true; -+ priorities.long2IntEntrySet().fastForEach(entry -> distanceManager.markHighPriority(new ChunkPos(entry.getLongKey()), entry.getIntValue())); -+ distanceManager.delayDistanceManagerTick = false; -+ level.getChunkSource().runDistanceManagerUpdates(); -+ -+ } -+ -+ private boolean shouldSkipPrioritization(ChunkPos coord) { -+ if (playerViewDistanceNoTickMap.getObjectsInRange(coord.toLong()) == null) return true; -+ ChunkHolder chunk = getUpdatingChunkIfPresent(coord.toLong()); -+ return chunk != null && (chunk.isFullChunkReady()); -+ } -+ // Paper end -+ - // Paper start - public void updatePlayerMobTypeMap(Entity entity) { - if (!this.level.paperConfig.perPlayerMobSpawns) { -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - List>> list = Lists.newArrayList(); - int j = centerChunk.x; - int k = centerChunk.z; -+ ChunkHolder requestingNeighbor = getUpdatingChunkIfPresent(centerChunk.toLong()); // Paper - - for (int l = -margin; l <= margin; ++l) { - for (int i1 = -margin; i1 <= margin; ++i1) { -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - ChunkStatus chunkstatus = (ChunkStatus) distanceToStatus.apply(j1); - CompletableFuture> completablefuture = playerchunk.getOrScheduleFuture(chunkstatus, this); -+ // Paper start -+ if (requestingNeighbor != null && requestingNeighbor != playerchunk && !completablefuture.isDone()) { -+ requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); -+ completablefuture.thenAccept(either -> { -+ requestingNeighbor.onNeighborDone(playerchunk, chunkstatus, either.left().orElse(null)); -+ }); -+ } -+ // Paper end - - list.add(completablefuture); - } -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - if (requiredStatus == ChunkStatus.EMPTY) { - return this.scheduleChunkLoad(chunkcoordintpair); - } else { -+ // Paper start - revert 1.17 chunk system changes -+ CompletableFuture> future = holder.getOrScheduleFuture(requiredStatus.getParent(), this); -+ return future.thenComposeAsync((either) -> { -+ Optional optional = either.left(); -+ if (!optional.isPresent()) { -+ return CompletableFuture.completedFuture(either); -+ } -+ // Paper end - revert 1.17 chunk system changes - if (requiredStatus == ChunkStatus.LIGHT) { - this.distanceManager.addTicket(TicketType.LIGHT, chunkcoordintpair, 33 + ChunkStatus.getDistance(ChunkStatus.LIGHT), chunkcoordintpair); - } - -- Optional optional = ((Either) holder.getOrScheduleFuture(requiredStatus.getParent(), this).getNow(ChunkHolder.UNLOADED_CHUNK)).left(); -+ // Paper - revert 1.17 chunk system changes - - if (optional.isPresent() && ((ChunkAccess) optional.get()).getStatus().isOrAfter(requiredStatus)) { - CompletableFuture> completablefuture = requiredStatus.load(this.level, this.structureManager, this.lightEngine, (ichunkaccess) -> { -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } else { - return this.scheduleChunkGeneration(holder, requiredStatus); - } -+ }, this.mainThreadExecutor).thenComposeAsync(CompletableFuture::completedFuture, this.mainThreadExecutor); // Paper - revert 1.17 chunk system changes - } - } - -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - }; - - CompletableFuture chunkSaveFuture = this.level.asyncChunkTaskManager.getChunkSaveFuture(pos.x, pos.z); -+ // Paper start -+ ChunkHolder playerChunk = getUpdatingChunkIfPresent(pos.toLong()); -+ int chunkPriority = playerChunk != null ? playerChunk.requestedPriority : 33; -+ int priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY; -+ -+ if (chunkPriority <= 10) { -+ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; -+ } else if (chunkPriority <= 20) { -+ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; -+ } -+ boolean isHighestPriority = priority == com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; -+ // Paper end - if (chunkSaveFuture != null) { -- this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, -- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture); -- this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY); -+ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, priority, chunkHolderConsumer, isHighestPriority, chunkSaveFuture); // Paper - } else { -- this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, -- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false); -+ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, priority, chunkHolderConsumer, isHighestPriority); // Paper - } -+ this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, priority); // Paper - return ret; - // Paper end - } -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.releaseLightTicket(chunkcoordintpair); - return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); - }); -- }, executor); -+ }, executor).thenComposeAsync((either) -> { // Paper start - force competion on the main thread -+ return CompletableFuture.completedFuture(either); -+ }, this.mainThreadExecutor); // use the main executor, we want to ensure only one chunk callback can be completed per runnable execute -+ // Paper end - force competion on the main thread - } - - protected void releaseLightTicket(ChunkPos pos) { -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - long i = playerchunk.getPos().toLong(); - - Objects.requireNonNull(playerchunk); -- mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, playerchunk::getTicketLevel)); -+ mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, () -> 1)); // Paper - final loads are always urgent! - }); - } - -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - } - - private static int getTicketLevelAt(SortedArraySet> arraysetsorted) { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::getLowestTicketLevel"); // Paper - return !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.first()).getTicketLevel() : ChunkMap.MAX_CHUNK_DISTANCE + 1; - } - -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - - public boolean runAllUpdates(ChunkMap playerchunkmap) { - //this.f.a(); // Paper - no longer used -+ org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper - this.playerTicketManager.runAllUpdates(); - int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); - boolean flag = i != 0; -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - - // Paper start - if (!this.pendingChunkUpdates.isEmpty()) { -+ this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority - while(!this.pendingChunkUpdates.isEmpty()) { - ChunkHolder remove = this.pendingChunkUpdates.remove(); - remove.isUpdateQueued = false; - remove.updateFutures(playerchunkmap, this.mainThreadExecutor); - } -+ } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority - // Paper end - return true; - } else { -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - return flag; - } - } -+ boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority - - boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper - SortedArraySet> arraysetsorted = this.getTickets(i); - int j = DistanceManager.getTicketLevelAt(arraysetsorted); - Ticket ticket1 = (Ticket) arraysetsorted.addOrGet(ticket); // CraftBukkit - decompile error -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - } - - boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper - SortedArraySet> arraysetsorted = this.getTickets(i); -+ int oldLevel = getTicketLevelAt(arraysetsorted); // Paper - - boolean removed = false; // CraftBukkit - if (arraysetsorted.remove(ticket)) { -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - this.tickets.remove(i); - } - -- this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false); -+ // Paper start - Chunk priority -+ int newLevel = getTicketLevelAt(arraysetsorted); -+ if (newLevel > oldLevel) { -+ this.ticketTracker.update(i, newLevel, false); -+ } -+ // Paper end - return removed; // CraftBukkit - } - -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - }); - } - -+ // Paper start - Chunk priority -+ public static final int PRIORITY_TICKET_LEVEL = ChunkMap.MAX_CHUNK_DISTANCE; -+ public static final int URGENT_PRIORITY = 29; -+ public boolean delayDistanceManagerTick = false; -+ public boolean markUrgent(ChunkPos coords) { -+ return addPriorityTicket(coords, TicketType.URGENT, URGENT_PRIORITY); -+ } -+ public boolean markHighPriority(ChunkPos coords, int priority) { -+ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority)); -+ return addPriorityTicket(coords, TicketType.PRIORITY, priority); -+ } -+ -+ public void markAreaHighPriority(ChunkPos center, int priority, int radius) { -+ delayDistanceManagerTick = true; -+ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority)); -+ int finalPriority = priority; -+ net.minecraft.server.MCUtil.getSpiralOutChunks(center.getWorldPosition(), radius).forEach(coords -> { -+ addPriorityTicket(coords, TicketType.PRIORITY, finalPriority); -+ }); -+ delayDistanceManagerTick = false; -+ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); -+ } -+ -+ public void clearAreaPriorityTickets(ChunkPos center, int radius) { -+ delayDistanceManagerTick = true; -+ net.minecraft.server.MCUtil.getSpiralOutChunks(center.getWorldPosition(), radius).forEach(coords -> { -+ this.removeTicket(coords.toLong(), new Ticket(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords)); -+ }); -+ delayDistanceManagerTick = false; -+ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); -+ } -+ -+ private boolean hasPlayerTicket(ChunkPos coords, int level) { -+ SortedArraySet> tickets = this.tickets.get(coords.toLong()); -+ if (tickets == null || tickets.isEmpty()) { -+ return false; -+ } -+ for (Ticket ticket : tickets) { -+ if (ticket.getType() == TicketType.PLAYER && ticket.getTicketLevel() == level) { -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ -+ private boolean addPriorityTicket(ChunkPos coords, TicketType ticketType, int priority) { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket"); -+ long pair = coords.toLong(); -+ ChunkHolder chunk = chunkMap.getUpdatingChunkIfPresent(pair); -+ boolean needsTicket = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(pair) != null && !hasPlayerTicket(coords, 33); -+ -+ if (needsTicket) { -+ Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, coords); -+ this.ticketsToRelease.add(pair); -+ addTicket(pair, ticket); -+ } -+ if ((chunk != null && chunk.isFullChunkReady())) { -+ if (needsTicket) { -+ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); -+ } -+ return needsTicket; -+ } -+ -+ boolean success; -+ if (!(success = updatePriorityTicket(coords, ticketType, priority))) { -+ Ticket ticket = new Ticket(ticketType, PRIORITY_TICKET_LEVEL, coords); -+ ticket.priority = priority; -+ success = this.addTicket(pair, ticket); -+ } else { -+ if (chunk == null) { -+ chunk = chunkMap.getUpdatingChunkIfPresent(pair); -+ } -+ chunkMap.queueHolderUpdate(chunk); -+ } -+ -+ //chunkMap.world.getWorld().spawnParticle(priority <= 15 ? org.bukkit.Particle.EXPLOSION_HUGE : org.bukkit.Particle.EXPLOSION_NORMAL, chunkMap.world.getWorld().getPlayers(), null, coords.x << 4, 70, coords.z << 4, 2, 0, 0, 0, 1, null, true); -+ -+ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); -+ -+ return success; -+ } -+ -+ private boolean updatePriorityTicket(ChunkPos coords, TicketType type, int priority) { -+ SortedArraySet> tickets = this.tickets.get(coords.toLong()); -+ if (tickets == null) { -+ return false; -+ } -+ for (Ticket ticket : tickets) { -+ if (ticket.getType() == type) { -+ // We only support increasing, not decreasing, too complicated -+ ticket.setCreatedTick(this.ticketTickCounter); -+ ticket.priority = Math.max(ticket.priority, priority); -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ -+ public int getChunkPriority(ChunkPos coords) { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::getChunkPriority"); -+ SortedArraySet> tickets = this.tickets.get(coords.toLong()); -+ if (tickets == null) { -+ return 0; -+ } -+ for (Ticket ticket : tickets) { -+ if (ticket.getType() == TicketType.URGENT) { -+ return URGENT_PRIORITY; -+ } -+ } -+ for (Ticket ticket : tickets) { -+ if (ticket.getType() == TicketType.PRIORITY && ticket.priority > 0) { -+ return ticket.priority; -+ } -+ } -+ return 0; -+ } -+ -+ public void clearPriorityTickets(ChunkPos coords) { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::clearPriority"); -+ this.removeTicket(coords.toLong(), new Ticket(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords)); -+ } -+ -+ public void clearUrgent(ChunkPos coords) { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::clearUrgent"); -+ this.removeTicket(coords.toLong(), new Ticket(TicketType.URGENT, PRIORITY_TICKET_LEVEL, coords)); -+ } -+ // Paper end -+ - protected void updateChunkForced(ChunkPos pos, boolean forced) { - Ticket ticket = new Ticket<>(TicketType.FORCED, 31, pos); - -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - - public void updateViewDistance(int watchDistance) { - ObjectIterator objectiterator = this.chunks.long2ByteEntrySet().iterator(); -+ // Paper start - set the view distance before scheduling chunk loads/unloads -+ int lastViewDistance = this.viewDistance; -+ this.viewDistance = watchDistance; -+ // Paper end - - while (objectiterator.hasNext()) { - it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry it_unimi_dsi_fastutil_longs_long2bytemap_entry = (it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry) objectiterator.next(); - byte b0 = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getByteValue(); - long j = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getLongKey(); - -- this.onLevelChange(j, b0, this.haveTicketFor(b0), b0 <= watchDistance - 2); -+ this.onLevelChange(j, b0, b0 <= lastViewDistance - 2, this.haveTicketFor(b0)); // Paper - } - -- this.viewDistance = watchDistance; -+ // this.viewDistance = watchDistance; // Paper - view distance is now set further up - } - - private void onLevelChange(long pos, int distance, boolean oldWithinViewDistance, boolean withinViewDistance) { - if (oldWithinViewDistance != withinViewDistance) { -- Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkPos(pos)); // Paper - no-tick view distance -+ ChunkPos coords = new ChunkPos(pos); // Paper - reuse variable -+ Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, coords); // Paper - no-tick view distance - - if (withinViewDistance) { -+ scheduleChunkLoad(pos, net.minecraft.server.MinecraftServer.currentTick, distance, (priority) -> { // Paper - smarter ticket delay based on frustum and distance -+ // Paper start - recheck its still valid if not cancel -+ if (!isChunkInRange(pos)) { -+ DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { -+ DistanceManager.this.mainThreadExecutor.execute(() -> { -+ DistanceManager.this.removeTicket(pos, ticket); -+ DistanceManager.this.clearPriorityTickets(coords); -+ }); -+ }, pos, false)); -+ return; -+ } -+ // abort early if we got a ticket already -+ if (hasPlayerTicket(coords, 33)) return; -+ // skip player ticket throttle for near chunks -+ if (priority <= 3) { -+ DistanceManager.this.addTicket(pos, ticket); -+ DistanceManager.this.ticketsToRelease.add(pos); -+ return; -+ } -+ // Paper end - DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> { - DistanceManager.this.mainThreadExecutor.execute(() -> { -- if (this.haveTicketFor(this.getLevel(pos))) { -+ if (isChunkInRange(pos)) { if (!hasPlayerTicket(coords, 33)) { // Paper - high priority might of already added it - DistanceManager.this.addTicket(pos, ticket); - DistanceManager.this.ticketsToRelease.add(pos); -- } else { -+ }} else { // Paper - DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { - }, pos, false)); - } - - }); - }, pos, () -> { -- return distance; -+ return Math.min(ChunkMap.MAX_CHUNK_DISTANCE, priority); // Paper - Chunk priority - })); -+ }); // Paper - } else { - DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { - DistanceManager.this.mainThreadExecutor.execute(() -> { - DistanceManager.this.removeTicket(pos, ticket); -+ DistanceManager.this.clearPriorityTickets(coords); // Paper - Chunk priority - }); - }, pos, true)); - } -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - private boolean haveTicketFor(int distance) { - return distance <= this.viewDistance - 2; - } -+ -+ // Paper start - smart scheduling of player tickets -+ private boolean isChunkInRange(long i) { -+ return this.haveTicketFor(this.getLevel(i)); -+ } -+ public void scheduleChunkLoad(long i, long startTick, int initialDistance, java.util.function.Consumer task) { -+ long elapsed = net.minecraft.server.MinecraftServer.currentTick - startTick; -+ ChunkPos chunkPos = new ChunkPos(i); -+ ChunkHolder updatingChunk = chunkMap.getUpdatingChunkIfPresent(i); -+ if ((updatingChunk != null && updatingChunk.isFullChunkReady()) || !isChunkInRange(i) || getChunkPriority(chunkPos) > 0) { // Copied from above -+ // no longer needed -+ task.accept(1); -+ return; -+ } -+ -+ int desireDelay = 0; -+ double minDist = Double.MAX_VALUE; -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(i); -+ if (elapsed == 0 && initialDistance <= 4) { -+ // Aim for no delay on initial 6 chunk radius tickets save on performance of the below code to only > 6 -+ minDist = initialDistance; -+ } else if (players != null) { -+ Object[] backingSet = players.getBackingSet(); -+ -+ net.minecraft.core.BlockPos blockPos = chunkPos.getWorldPosition(); -+ -+ boolean isFront = false; -+ net.minecraft.core.BlockPos.MutableBlockPos pos = new net.minecraft.core.BlockPos.MutableBlockPos(); -+ for (int index = 0, len = backingSet.length; index < len; ++index) { -+ if (!(backingSet[index] instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer) backingSet[index]; -+ -+ ChunkPos pointInFront = player.getChunkInFront(5); -+ pos.set(pointInFront.x << 4, 0, pointInFront.z << 4); -+ double frontDist = net.minecraft.server.MCUtil.distanceSq(pos, blockPos); -+ -+ pos.set(player.getX(), 0, player.getZ()); -+ double center = net.minecraft.server.MCUtil.distanceSq(pos, blockPos); -+ -+ double dist = Math.min(frontDist, center); -+ if (!isFront) { -+ ChunkPos pointInBack = player.getChunkInFront(-7); -+ pos.set(pointInBack.x << 4, 0, pointInBack.z << 4); -+ double backDist = net.minecraft.server.MCUtil.distanceSq(pos, blockPos); -+ if (frontDist < backDist) { -+ isFront = true; -+ } -+ } -+ if (dist < minDist) { -+ minDist = dist; -+ } -+ } -+ if (minDist == Double.MAX_VALUE) { -+ minDist = 15; -+ } else { -+ minDist = Math.sqrt(minDist) / 16; -+ } -+ if (minDist > 4) { -+ int desiredTimeDelayMax = isFront ? -+ (minDist < 10 ? 7 : 15) : // Front -+ (minDist < 10 ? 15 : 45); // Back -+ desireDelay += (desiredTimeDelayMax * 20) * (minDist / 32); -+ } -+ } else { -+ minDist = initialDistance; -+ desireDelay = 1; -+ } -+ long delay = desireDelay - elapsed; -+ if (delay <= 0 && minDist > 4 && minDist < Double.MAX_VALUE) { -+ boolean hasAnyNeighbor = false; -+ for (int x = -1; x <= 1; x++) { -+ for (int z = -1; z <= 1; z++) { -+ if (x == 0 && z == 0) continue; -+ long pair = ChunkPos.asLong(chunkPos.x + x, chunkPos.z + z); -+ ChunkHolder neighbor = chunkMap.getUpdatingChunkIfPresent(pair); -+ ChunkStatus current = neighbor != null ? neighbor.getChunkHolderStatus() : null; -+ if (current != null && current.isOrAfter(ChunkStatus.LIGHT)) { -+ hasAnyNeighbor = true; -+ } -+ } -+ } -+ if (!hasAnyNeighbor) { -+ delay += 20; -+ } -+ } -+ if (delay <= 0) { -+ task.accept((int) minDist); -+ } else { -+ int taskDelay = (int) Math.min(delay, minDist >= 10 ? 40 : (minDist < 6 ? 5 : 20)); -+ net.minecraft.server.MCUtil.scheduleTask(taskDelay, () -> scheduleChunkLoad(i, startTick, initialDistance, task), "Player Ticket Delayer"); -+ } -+ } -+ // Paper end - } - } -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - public void removeTicketAtLevel(TicketType ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) { - this.distanceManager.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier); - } -+ -+ public boolean markUrgent(ChunkPos coords) { -+ return this.distanceManager.markUrgent(coords); -+ } -+ -+ public boolean markHighPriority(ChunkPos coords, int priority) { -+ return this.distanceManager.markHighPriority(coords, priority); -+ } -+ -+ public void markAreaHighPriority(ChunkPos center, int priority, int radius) { -+ this.distanceManager.markAreaHighPriority(center, priority, radius); -+ } -+ -+ public void clearAreaPriorityTickets(ChunkPos center, int radius) { -+ this.distanceManager.clearAreaPriorityTickets(center, radius); -+ } -+ -+ public void clearPriorityTickets(ChunkPos coords) { -+ this.distanceManager.clearPriorityTickets(coords); -+ } - // Paper end - async chunk io - - @Nullable -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - Objects.requireNonNull(completablefuture); - if (!completablefuture.isDone()) { // Paper - // Paper start - async chunk io/loading -+ ChunkPos pair = new ChunkPos(x1, z1); // Paper - Chunk priority -+ this.distanceManager.markUrgent(pair); // Paper - Chunk priority - this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); - com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1); - // Paper end -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - chunkproviderserver_a.managedBlock(completablefuture::isDone); - com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug - this.level.timings.syncChunkLoad.stopTiming(); // Paper -+ this.distanceManager.clearPriorityTickets(pair); // Paper - Chunk priority -+ this.distanceManager.clearUrgent(pair); // Paper - Chunk priority - } // Paper - ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { - return ichunkaccess1; -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - if (create && !currentlyUnloading) { - // CraftBukkit end - this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); -+ if (isUrgent) this.distanceManager.markUrgent(chunkcoordintpair); // Paper - Chunk priority - if (this.chunkAbsent(playerchunk, l)) { - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); - - gameprofilerfiller.push("chunkLoad"); -+ distanceManager.delayDistanceManagerTick = false; // Paper - Chunk priority - ensure this is never false - this.runDistanceManagerUpdates(); - playerchunk = this.getVisibleChunkIfPresent(k); - gameprofilerfiller.pop(); -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - } - } - -- return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap); -+ // Paper start - Chunk priority -+ CompletableFuture> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap); -+ if (isUrgent) { -+ future.thenAccept(either -> this.distanceManager.clearUrgent(chunkcoordintpair)); -+ } -+ return future; -+ // Paper end - } - - private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - } - - public boolean runDistanceManagerUpdates() { -+ if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority - boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); - boolean flag1 = this.chunkMap.promoteChunkMap(); - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -0,0 +0,0 @@ public class ServerPlayer extends Player { - private int lastRecordedArmor = Integer.MIN_VALUE; - private int lastRecordedLevel = Integer.MIN_VALUE; - private int lastRecordedExperience = Integer.MIN_VALUE; -+ // Paper start - Chunk priority -+ public long lastHighPriorityChecked; -+ public void forceCheckHighPriority() { -+ lastHighPriorityChecked = -1; -+ getLevel().getChunkSource().chunkMap.checkHighPriorityChunks(this); -+ } -+ public boolean isRealPlayer; -+ // Paper end - private float lastSentHealth = -1.0E8F; - private int lastSentFood = -99999999; - private boolean lastFoodSaturationZero = true; -@@ -0,0 +0,0 @@ public class ServerPlayer extends Player { - this.maxHealthCache = this.getMaxHealth(); - this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper - } -+ // Paper start - Chunk priority -+ public BlockPos getPointInFront(double inFront) { -+ double rads = Math.toRadians(net.minecraft.server.MCUtil.normalizeYaw(this.yRot + 90)); // MC rotates yaw 90 for some odd reason -+ final double x = getX() + inFront * Math.cos(rads); -+ final double z = getZ() + inFront * Math.sin(rads); -+ return new BlockPos(x, getY(), z); -+ } -+ -+ public ChunkPos getChunkInFront(double inFront) { -+ double rads = Math.toRadians(net.minecraft.server.MCUtil.normalizeYaw(this.yRot + 90)); // MC rotates yaw 90 for some odd reason -+ final double x = getX() + (inFront * 16) * Math.cos(rads); -+ final double z = getZ() + (inFront * 16) * Math.sin(rads); -+ return new ChunkPos(Mth.floor(x) >> 4, Mth.floor(z) >> 4); -+ } -+ // Paper end - - // Yes, this doesn't match Vanilla, but it's the best we can do for now. - // If this is an issue, PRs are welcome -@@ -0,0 +0,0 @@ public class ServerPlayer extends Player { - if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) - super.tick(); - } -+ if (valid && isAlive() && connection != null) ((ServerLevel)level).getChunkSource().chunkMap.checkHighPriorityChunks(this); // Paper - Chunk priority - - for (int i = 0; i < this.getInventory().getContainerSize(); ++i) { - ItemStack itemstack = this.getInventory().getItem(i); -diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -@@ -0,0 +0,0 @@ - package net.minecraft.server.level; - - import com.mojang.datafixers.util.Pair; -+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; // Paper - import it.unimi.dsi.fastutil.objects.ObjectArrayList; - import it.unimi.dsi.fastutil.objects.ObjectList; - import it.unimi.dsi.fastutil.objects.ObjectListIterator; -@@ -0,0 +0,0 @@ import net.minecraft.util.thread.ProcessorMailbox; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.LightLayer; - import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; - import net.minecraft.world.level.chunk.DataLayer; - import net.minecraft.world.level.chunk.LevelChunkSection; - import net.minecraft.world.level.chunk.LightChunkGetter; -@@ -0,0 +0,0 @@ import org.apache.logging.log4j.Logger; - public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { - private static final Logger LOGGER = LogManager.getLogger(); - private final ProcessorMailbox taskMailbox; -- private final ObjectList> lightTasks = new ObjectArrayList<>(); -- private final ChunkMap chunkMap; -+ // Paper start -+ private static final int MAX_PRIORITIES = ChunkMap.MAX_CHUNK_DISTANCE + 2; -+ -+ static class ChunkLightQueue { -+ public boolean shouldFastUpdate; -+ java.util.ArrayDeque pre = new java.util.ArrayDeque(); -+ java.util.ArrayDeque post = new java.util.ArrayDeque(); -+ -+ ChunkLightQueue(long chunk) {} -+ } -+ -+ static class PendingLightTask { -+ long chunkId; -+ IntSupplier priority; -+ Runnable pre; -+ Runnable post; -+ boolean fastUpdate; -+ -+ public PendingLightTask(long chunkId, IntSupplier priority, Runnable pre, Runnable post, boolean fastUpdate) { -+ this.chunkId = chunkId; -+ this.priority = priority; -+ this.pre = pre; -+ this.post = post; -+ this.fastUpdate = fastUpdate; -+ } -+ } -+ -+ -+ // Retain the chunks priority level for queued light tasks -+ class LightQueue { -+ private int size = 0; -+ private final Long2ObjectLinkedOpenHashMap[] buckets = new Long2ObjectLinkedOpenHashMap[MAX_PRIORITIES]; -+ private final java.util.concurrent.ConcurrentLinkedQueue pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); -+ private final java.util.concurrent.ConcurrentLinkedQueue priorityChanges = new java.util.concurrent.ConcurrentLinkedQueue<>(); -+ -+ private LightQueue() { -+ for (int i = 0; i < buckets.length; i++) { -+ buckets[i] = new Long2ObjectLinkedOpenHashMap<>(); -+ } -+ } -+ -+ public void changePriority(long pair, int currentPriority, int priority) { -+ this.priorityChanges.add(() -> { -+ ChunkLightQueue remove = this.buckets[currentPriority].remove(pair); -+ if (remove != null) { -+ ChunkLightQueue existing = this.buckets[Math.max(1, priority)].put(pair, remove); -+ if (existing != null) { -+ remove.pre.addAll(existing.pre); -+ remove.post.addAll(existing.post); -+ } -+ } -+ }); -+ } -+ -+ public final void addChunk(long chunkId, IntSupplier priority, Runnable pre, Runnable post) { -+ pendingTasks.add(new PendingLightTask(chunkId, priority, pre, post, true)); -+ tryScheduleUpdate(); -+ } -+ -+ public final void add(long chunkId, IntSupplier priority, ThreadedLevelLightEngine.TaskType type, Runnable run) { -+ pendingTasks.add(new PendingLightTask(chunkId, priority, type == TaskType.PRE_UPDATE ? run : null, type == TaskType.POST_UPDATE ? run : null, false)); -+ } -+ public final void add(PendingLightTask update) { -+ int priority = update.priority.getAsInt(); -+ ChunkLightQueue lightQueue = this.buckets[priority].computeIfAbsent(update.chunkId, ChunkLightQueue::new); -+ -+ if (update.pre != null) { -+ this.size++; -+ lightQueue.pre.add(update.pre); -+ } -+ if (update.post != null) { -+ this.size++; -+ lightQueue.post.add(update.post); -+ } -+ if (update.fastUpdate) { -+ lightQueue.shouldFastUpdate = true; -+ } -+ } -+ -+ public final boolean isEmpty() { -+ return this.size == 0 && this.pendingTasks.isEmpty(); -+ } -+ -+ public final int size() { -+ return this.size; -+ } -+ -+ public boolean poll(java.util.List pre, java.util.List post) { -+ PendingLightTask pending; -+ while ((pending = pendingTasks.poll()) != null) { -+ add(pending); -+ } -+ Runnable run; -+ while ((run = priorityChanges.poll()) != null) { -+ run.run(); -+ } -+ boolean hasWork = false; -+ Long2ObjectLinkedOpenHashMap[] buckets = this.buckets; -+ int priority = 0; -+ while (priority < MAX_PRIORITIES && !isEmpty()) { -+ Long2ObjectLinkedOpenHashMap bucket = buckets[priority]; -+ if (bucket.isEmpty()) { -+ priority++; -+ if (hasWork) { -+ return true; -+ } else { -+ continue; -+ } -+ } -+ ChunkLightQueue queue = bucket.removeFirst(); -+ this.size -= queue.pre.size() + queue.post.size(); -+ pre.addAll(queue.pre); -+ post.addAll(queue.post); -+ queue.pre.clear(); -+ queue.post.clear(); -+ hasWork = true; -+ if (queue.shouldFastUpdate) { -+ return true; -+ } -+ } -+ return hasWork; -+ } -+ } -+ -+ final LightQueue queue = new LightQueue(); -+ // Paper end -+ private final ChunkMap chunkMap; private final ChunkMap playerChunkMap; // Paper - private final ProcessorHandle> sorterMailbox; - private volatile int taskPerBatch = 5; - private final AtomicBoolean scheduled = new AtomicBoolean(); - - public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { - super(chunkProvider, true, hasBlockLight); -- this.chunkMap = chunkStorage; -+ this.chunkMap = chunkStorage; this.playerChunkMap = chunkMap; // Paper - this.sorterMailbox = executor; - this.taskMailbox = processor; - } -@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) { -- this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> { -- this.lightTasks.add(Pair.of(stage, task)); -- if (this.lightTasks.size() >= this.taskPerBatch) { -- this.runUpdate(); -- } -- -- }, ChunkPos.asLong(x, z), completedLevelSupplier)); -+ // Paper start - replace method -+ this.queue.add(ChunkPos.asLong(x, z), completedLevelSupplier, stage, task); -+ // Paper end - } - - @Override -@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { - ChunkPos chunkPos = chunk.getPos(); -- chunk.setLightCorrect(false); -- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -+ // Paper start -+ //ichunkaccess.b(false); // Don't need to disable this -+ long pair = chunkPos.toLong(); -+ CompletableFuture future = new CompletableFuture<>(); -+ IntSupplier prioritySupplier = playerChunkMap.getChunkQueueLevel(pair); -+ boolean[] skippedPre = {false}; -+ this.queue.addChunk(pair, prioritySupplier, Util.name(() -> { -+ // Paper end - LevelChunkSection[] levelChunkSections = chunk.getSections(); - - for(int i = 0; i < chunk.getSectionsCount(); ++i) { -@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - }, () -> { - return "lightChunk " + chunkPos + " " + excludeBlocks; -- })); -- return CompletableFuture.supplyAsync(() -> { -+ // Paper start - merge the 2 together -+ }), () -> { -+ this.chunkMap.releaseLightTicket(chunkPos); // Paper - moved from below, we want to call this even when returning early -+ if (skippedPre[0]) return; // Paper - future's already complete - chunk.setLightCorrect(true); - super.retainData(chunkPos, false); -- this.chunkMap.releaseLightTicket(chunkPos); -- return chunk; -- }, (runnable) -> { -- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, runnable); -+ //this.chunkMap.releaseLightTicket(chunkPos); // Paper - moved up -+ future.complete(chunk); - }); -+ return future; -+ // Paper end - } - - public void tryScheduleUpdate() { -- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { -+ if ((!this.queue.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { // Paper - this.taskMailbox.tell(() -> { - this.runUpdate(); - this.scheduled.set(false); -+ tryScheduleUpdate(); // Paper - if we still have work to do, do it! - }); - } - - } - -+ // Paper start - replace impl -+ private final java.util.List pre = new java.util.ArrayList<>(); -+ private final java.util.List post = new java.util.ArrayList<>(); - private void runUpdate() { -- int i = Math.min(this.lightTasks.size(), this.taskPerBatch); -- ObjectListIterator> objectListIterator = this.lightTasks.iterator(); -- -- int j; -- for(j = 0; objectListIterator.hasNext() && j < i; ++j) { -- Pair pair = objectListIterator.next(); -- if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) { -- pair.getSecond().run(); -- } -+ if (queue.poll(pre, post)) { -+ pre.forEach(Runnable::run); -+ pre.clear(); -+ super.runUpdates(Integer.MAX_VALUE, true, true); -+ post.forEach(Runnable::run); -+ post.clear(); -+ } else { -+ // might have level updates to go still -+ super.runUpdates(Integer.MAX_VALUE, true, true); - } -- -- objectListIterator.back(j); -- super.runUpdates(Integer.MAX_VALUE, true, true); -- -- for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) { -- Pair pair2 = objectListIterator.next(); -- if (pair2.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) { -- pair2.getSecond().run(); -- } -- -- objectListIterator.remove(); -- } -- -+ // Paper end - } - - public void setTaskPerBatch(int taskBatchSize) { -diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/Ticket.java -+++ b/src/main/java/net/minecraft/server/level/Ticket.java -@@ -0,0 +0,0 @@ public final class Ticket implements Comparable> { - public final T key; - public long createdTick; - public long delayUnloadBy; // Paper -+ public int priority; // Paper - Chunk priority - - protected Ticket(TicketType type, int level, T argument) { - this.type = type; -diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/TicketType.java -+++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -0,0 +0,0 @@ import net.minecraft.world.level.ChunkPos; - public class TicketType { - public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper - public static final TicketType ASYNC_LOAD = create("async_load", Long::compareTo); // Paper -+ public static final TicketType PRIORITY = create("priority", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper -+ public static final TicketType URGENT = create("urgent", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper - - private final String name; - private final Comparator comparator; -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - - this.awaitingTeleportTime = this.tickCount; - this.player.absMoveTo(d0, d1, d2, f, f1); -+ this.player.forceCheckHighPriority(); // Paper - this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport, flag)); - } - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -0,0 +0,0 @@ public abstract class PlayerList { - } - - public void placeNewPlayer(Connection connection, ServerPlayer player) { -+ player.isRealPlayer = true; // Paper - Chunk priority - ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);// Paper - if (prev != null) { - disconnectPendingPlayer(prev); -@@ -0,0 +0,0 @@ public abstract class PlayerList { - net.minecraft.server.level.ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap; - net.minecraft.server.level.DistanceManager distanceManager = playerChunkMap.distanceManager; - distanceManager.addTicketAtLevel(net.minecraft.server.level.TicketType.LOGIN, pos, 31, pos.toLong()); -- worldserver1.getChunkSource().runDistanceManagerUpdates(); -- worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> { -+ worldserver1.getChunkSource().markAreaHighPriority(pos, 28, 3); // Paper - Chunk priority -+ worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, false).thenApply(chunk -> { // Paper - Chunk priority - net.minecraft.server.level.ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pos.toLong()); - if (updatingChunk != null) { - return updatingChunk.getEntityTickingChunkFuture(); -@@ -0,0 +0,0 @@ public abstract class PlayerList { - // CraftBukkit end - - worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper -+ entityplayer1.forceCheckHighPriority(); // Player - Chunk priority - while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { - entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); - } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - private Vec3 position; - private BlockPos blockPosition; - private Vec3 deltaMovement; -- private float yRot; -+ public float yRot; // Paper - private->public - private float xRot; - public float yRotO; - public float xRotO; -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess { - return NEIGHBOUR_CACHE_RADIUS; - } - -- boolean loadedTicketLevel; -+ boolean loadedTicketLevel; public final boolean wasLoadCallbackInvoked() { return this.loadedTicketLevel; } // Paper - public accessor - private long neighbourChunksLoadedBitset; - private final LevelChunk[] loadedNeighbourChunks = new LevelChunk[(NEIGHBOUR_CACHE_RADIUS * 2 + 1) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)]; - -@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess { - - // CraftBukkit start - public void loadCallback() { -+ if (this.loadedTicketLevel) { LOGGER.error("Double calling chunk load!", new Throwable()); } // Paper - // Paper start - neighbour cache - int chunkX = this.chunkPos.x; - int chunkZ = this.chunkPos.z; -@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess { - } - - public void unloadCallback() { -+ if (!this.loadedTicketLevel) { LOGGER.error("Double calling chunk unload!", new Throwable()); } // Paper - org.bukkit.Server server = this.level.getCraftServer(); - org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(this.bukkitChunk, this.isUnsaved()); - server.getPluginManager().callEvent(unloadEvent); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return future; - } - -+ // Paper start - Chunk priority -+ if (!urgent) { -+ // If not urgent, at least use a slightly boosted priority -+ world.getChunkSource().markHighPriority(new ChunkPos(x, z), 1); -+ } -+ // Paper end - return this.world.getChunkSource().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { - net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); - if (chunk != null) addTicket(x, z); // Paper -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); - } - -+ // Paper start - Chunk priority -+ @Override -+ public java.util.concurrent.CompletableFuture teleportAsync(Location loc, @javax.annotation.Nonnull PlayerTeleportEvent.TeleportCause cause) { -+ ((CraftWorld)loc.getWorld()).getHandle().getChunkSource().markAreaHighPriority( -+ new net.minecraft.world.level.ChunkPos(net.minecraft.util.Mth.floor(loc.getX()) >> 4, -+ net.minecraft.util.Mth.floor(loc.getZ()) >> 4), 28, 3); // Load area high priority -+ return super.teleportAsync(loc, cause); -+ } -+ // Paper end -+ - @Override - public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { - Preconditions.checkArgument(location != null, "location"); diff --git a/patches/unapplied/server/Improve-Chunk-Status-Transition-Speed.patch b/patches/unapplied/server/Improve-Chunk-Status-Transition-Speed.patch deleted file mode 100644 index 8606e368f0..0000000000 --- a/patches/unapplied/server/Improve-Chunk-Status-Transition-Speed.patch +++ /dev/null @@ -1,81 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 29 May 2020 23:32:14 -0400 -Subject: [PATCH] Improve Chunk Status Transition Speed - -When a chunk is loaded from disk that has already been generated, -the server has to promote the chunk through the system to reach -it's current desired status level. - -This results in every single status transition going from the main thread -to the world gen threads, only to discover it has no work it actually -needs to do.... and then it returns back to main. - -This back and forth costs a lot of time and can really delay chunk loads -when the server is under high TPS due to their being a lot of time in -between chunk load times, as well as hogs up the chunk threads from doing -actual generation and light work. - -Additionally, the whole task system uses a lot of CPU on the server threads anyways. - -So by optimizing status transitions for status's that are already complete, -we can run them to the desired level while on main thread (where it has -to happen anyways) instead of ever jumping to world gen thread. - -This will improve chunk loading effeciency to be reduced down to the following -scenario / path: - -1) MAIN: Chunk Requested, Load Request sent to ChunkTaskManager / IO Queue -2) IO: Once position in queue comes, submit read IO data and schedule to chunk task thread -3) CHUNK: Once IO is loaded and position in queue comes, deserialize the chunk data, process conversions, submit to main queue -4) MAIN: next Chunk Task process (Mid Tick or End Of Tick), load chunk data into world (POI, main thread tasks) -5) MAIN: process status transitions all the way to LIGHT, light schedules Threaded task -6) SERVER: Light tasks register light enablement for chunk and any lighting needing to be done -7) MAIN: Task returns to main, finish processing to FULL/TICKING status - -Previously would have hopped to SERVER around 12+ times there extra. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -0,0 +0,0 @@ public class ChunkHolder { - // Paper end - optimise isOutsideOfRange - long lastAutoSaveTime; // Paper - incremental autosave - long inactiveTimeStart; // Paper - incremental autosave -+ // Paper start - optimize chunk status progression without jumping through thread pool -+ public boolean canAdvanceStatus() { -+ ChunkStatus status = getChunkHolderStatus(); -+ ChunkAccess chunk = getAvailableChunkNow(); -+ return chunk != null && (status == null || chunk.getStatus().isOrAfter(getNextStatus(status))); -+ } -+ // Paper end - - public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { - this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - return either.mapLeft((list) -> { - return (LevelChunk) list.get(list.size() / 2); - }); -- }, this.mainThreadExecutor); -+ }, this.mainInvokingExecutor); // Paper - } - - @Nullable -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - return "chunkGenerate " + requiredStatus.getName(); - }); - Executor executor = (runnable) -> { -+ // Paper start - optimize chunk status progression without jumping through thread pool -+ if (holder.canAdvanceStatus()) { -+ this.mainInvokingExecutor.execute(runnable); -+ return; -+ } -+ // Paper end - this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); - }; -