diff --git a/leaf-server/minecraft-patches/features/0105-Cache-chunk-key.patch b/leaf-server/minecraft-patches/features/0105-Cache-chunk-key.patch index a66e140d..a9932a9c 100644 --- a/leaf-server/minecraft-patches/features/0105-Cache-chunk-key.patch +++ b/leaf-server/minecraft-patches/features/0105-Cache-chunk-key.patch @@ -84,7 +84,7 @@ index 571db5f9bf94745a8afe2cd313e593fb15db5e37..1487b7d8be435b3fbad2aabd05796965 valueInMap = new ServerChunkTasks( keyInMap, ServerLightQueue.this.lightInterface, ServerLightQueue.this, priority diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 268c463d379528b8242f1628e97e67ea638b7ced..20ded514a74652685b2f785c7fe5fda19e36b2a5 100644 +index 5423d8228c1da56135ae32b958f432d5b94707ed..95bed1e67758543a7aec12eee1229ee2c4057c88 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -508,7 +508,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe diff --git a/leaf-server/minecraft-patches/features/0114-Cache-supporting-block-check.patch b/leaf-server/minecraft-patches/features/0114-Cache-supporting-block-check.patch index 044b101e..133644d2 100644 --- a/leaf-server/minecraft-patches/features/0114-Cache-supporting-block-check.patch +++ b/leaf-server/minecraft-patches/features/0114-Cache-supporting-block-check.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Cache supporting block check diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 80ad278eac81aac72d6ec7737287ad018eff7c54..9bc978ca290ca772b0367e89b69fe16b502b0cd2 100644 +index 4544dd876d3cbcdb9b774b4a1f0c4737f3124bc5..6ca446fd9ab38329ba505526a56f8e4f64a9a639 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -1083,12 +1083,36 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess diff --git a/leaf-server/minecraft-patches/features/0124-Rewrite-queue-on-Connection.flushQueue.patch b/leaf-server/minecraft-patches/features/0124-Rewrite-queue-on-Connection.flushQueue.patch deleted file mode 100644 index bf118f9e..00000000 --- a/leaf-server/minecraft-patches/features/0124-Rewrite-queue-on-Connection.flushQueue.patch +++ /dev/null @@ -1,116 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Fri, 21 Feb 2025 15:52:42 +0100 -Subject: [PATCH] Rewrite queue on Connection.flushQueue - - -diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java -index 00a82873d226f113278632a53c0faca420dd67d4..9aeadf792eb7c6c39044df00fa7ff80017ba72d0 100644 ---- a/net/minecraft/network/Connection.java -+++ b/net/minecraft/network/Connection.java -@@ -85,7 +85,7 @@ public class Connection extends SimpleChannelInboundHandler> { - private static final ProtocolInfo INITIAL_PROTOCOL = HandshakeProtocols.SERVERBOUND; - private final PacketFlow receiving; - private volatile boolean sendLoginDisconnect = true; -- private final Queue pendingActions = Queues.newConcurrentLinkedQueue(); // Paper - Optimize network -+ private final Queue pendingActions = org.dreeam.leaf.config.modules.network.ConnectionFlushQueueRewrite.enabled ? new java.util.ArrayDeque<>() : Queues.newConcurrentLinkedQueue(); // Paper - Optimize network // Leaf - Rewrite queue on Connection.flushQueue - public Channel channel; - public SocketAddress address; - // Spigot start -@@ -541,9 +541,17 @@ public class Connection extends SimpleChannelInboundHandler> { - if (io.papermc.paper.util.MCUtil.isMainThread()) { - return this.processQueue(); - } else if (this.isPending) { -- // Should only happen during login/status stages -- synchronized (this.pendingActions) { -- return this.processQueue(); -+ // Leaf start - Rewrite queue on Connection.flushQueue -+ if (org.dreeam.leaf.config.modules.network.ConnectionFlushQueueRewrite.enabled) { -+ // Submit to the event loop to ensure thread confinement -+ this.channel.eventLoop().execute(this::processQueue); -+ return false; -+ } else { -+ // Original Paper behavior -+ synchronized (this.pendingActions) { -+ return this.processQueue(); -+ // Leaf end - Rewrite queue on Connection.flushQueue -+ } - } - } - return false; -@@ -554,6 +562,24 @@ public class Connection extends SimpleChannelInboundHandler> { - return true; - } - -+ // Leaf start - Rewrite queue on Connection.flushQueue -+ if (org.dreeam.leaf.config.modules.network.ConnectionFlushQueueRewrite.enabled) { -+ WrappedConsumer queued; -+ while ((queued = this.pendingActions.poll()) != null) { -+ if (queued instanceof PacketSendAction packetSendAction) { -+ final Packet packet = packetSendAction.packet; -+ if (!packet.isReady()) { -+ // Re-add to the front and exit -+ this.pendingActions.add(queued); -+ return false; -+ } -+ } -+ -+ if (queued.tryMarkConsumed()) { -+ queued.accept(this); -+ } -+ } -+ } else { - // If we are on main, we are safe here in that nothing else should be processing queue off main anymore - // But if we are not on main due to login/status, the parent is synchronized on packetQueue - final java.util.Iterator iterator = this.pendingActions.iterator(); -@@ -581,6 +607,8 @@ public class Connection extends SimpleChannelInboundHandler> { - queued.accept(this); - } - } -+ } -+ // Leaf end - Rewrite queue on Connection.flushQueue - return true; - } - // Paper end - Optimize network -@@ -913,6 +941,31 @@ public class Connection extends SimpleChannelInboundHandler> { - // Paper start - Optimize network - public void clearPacketQueue() { - final net.minecraft.server.level.ServerPlayer player = getPlayer(); -+ -+ // Leaf start - Rewrite queue on Connection.flushQueue -+ if (org.dreeam.leaf.config.modules.network.ConnectionFlushQueueRewrite.enabled) { -+ // When using Leaf's queue rewrite, ensure thread safety via event loop -+ if (this.channel != null && !this.channel.eventLoop().inEventLoop()) { -+ this.channel.eventLoop().execute(() -> this.clearPacketQueue()); -+ return; -+ } -+ -+ // Take a snapshot to avoid ConcurrentModificationException -+ java.util.List> queueSnapshot = new java.util.ArrayList<>(this.pendingActions); -+ -+ for (Consumer queuedAction : queueSnapshot) { -+ if (queuedAction instanceof PacketSendAction packetSendAction) { -+ final Packet packet = packetSendAction.packet; -+ if (packet.hasFinishListener()) { -+ packet.onPacketDispatchFinish(player, null); -+ } -+ } -+ } -+ -+ this.pendingActions.clear(); -+ } else { -+ // Original Paper behavior - use synchronization -+ synchronized (this.pendingActions) { - for (final Consumer queuedAction : this.pendingActions) { - if (queuedAction instanceof PacketSendAction packetSendAction) { - final Packet packet = packetSendAction.packet; -@@ -922,6 +975,9 @@ public class Connection extends SimpleChannelInboundHandler> { - } - } - this.pendingActions.clear(); -+ } -+ } -+ // Leaf end - Rewrite queue on Connection.flushQueue - } - - private static class InnerUtil { // Attempt to hide these methods from ProtocolLib, so it doesn't accidently pick them up. diff --git a/leaf-server/minecraft-patches/features/0125-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch b/leaf-server/minecraft-patches/features/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch similarity index 91% rename from leaf-server/minecraft-patches/features/0125-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch rename to leaf-server/minecraft-patches/features/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch index 8d4399d3..36b4be10 100644 --- a/leaf-server/minecraft-patches/features/0125-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch +++ b/leaf-server/minecraft-patches/features/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch @@ -6,7 +6,7 @@ Subject: [PATCH] ShreddedPaper: Don't block main thread in diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java -index 9aeadf792eb7c6c39044df00fa7ff80017ba72d0..2aba58b4c1afb843b61d9a5113ed058b06278348 100644 +index 00a82873d226f113278632a53c0faca420dd67d4..5b46036868b6c9d082e35591e58735e16adaae62 100644 --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java @@ -325,6 +325,7 @@ public class Connection extends SimpleChannelInboundHandler> { diff --git a/leaf-server/minecraft-patches/features/0126-Use-ensureCapacity-to-pre-populate-the-size-of-ticki.patch b/leaf-server/minecraft-patches/features/0125-Use-ensureCapacity-to-pre-populate-the-size-of-ticki.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0126-Use-ensureCapacity-to-pre-populate-the-size-of-ticki.patch rename to leaf-server/minecraft-patches/features/0125-Use-ensureCapacity-to-pre-populate-the-size-of-ticki.patch diff --git a/leaf-server/minecraft-patches/features/0127-Directly-use-the-pre-filtered-ticking-chunks-list-as.patch b/leaf-server/minecraft-patches/features/0126-Directly-use-the-pre-filtered-ticking-chunks-list-as.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0127-Directly-use-the-pre-filtered-ticking-chunks-list-as.patch rename to leaf-server/minecraft-patches/features/0126-Directly-use-the-pre-filtered-ticking-chunks-list-as.patch diff --git a/leaf-server/minecraft-patches/features/0128-Bulk-writes-to-writeLongArray-during-chunk-loading.patch b/leaf-server/minecraft-patches/features/0127-Bulk-writes-to-writeLongArray-during-chunk-loading.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0128-Bulk-writes-to-writeLongArray-during-chunk-loading.patch rename to leaf-server/minecraft-patches/features/0127-Bulk-writes-to-writeLongArray-during-chunk-loading.patch diff --git a/leaf-server/minecraft-patches/features/0129-Optimize-AABB.patch b/leaf-server/minecraft-patches/features/0128-Optimize-AABB.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0129-Optimize-AABB.patch rename to leaf-server/minecraft-patches/features/0128-Optimize-AABB.patch diff --git a/leaf-server/minecraft-patches/features/0130-Improve-sorting-in-SortedArraySet.patch b/leaf-server/minecraft-patches/features/0129-Improve-sorting-in-SortedArraySet.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0130-Improve-sorting-in-SortedArraySet.patch rename to leaf-server/minecraft-patches/features/0129-Improve-sorting-in-SortedArraySet.patch diff --git a/leaf-server/minecraft-patches/features/0131-Make-removeIf-slightly-faster.patch b/leaf-server/minecraft-patches/features/0130-Make-removeIf-slightly-faster.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0131-Make-removeIf-slightly-faster.patch rename to leaf-server/minecraft-patches/features/0130-Make-removeIf-slightly-faster.patch diff --git a/leaf-server/minecraft-patches/features/0132-Optimize-LinearPalette.patch b/leaf-server/minecraft-patches/features/0131-Optimize-LinearPalette.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0132-Optimize-LinearPalette.patch rename to leaf-server/minecraft-patches/features/0131-Optimize-LinearPalette.patch diff --git a/leaf-server/minecraft-patches/features/0133-Slightly-optimized-VarInt-write.patch b/leaf-server/minecraft-patches/features/0132-Slightly-optimized-VarInt-write.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0133-Slightly-optimized-VarInt-write.patch rename to leaf-server/minecraft-patches/features/0132-Slightly-optimized-VarInt-write.patch diff --git a/leaf-server/minecraft-patches/features/0134-Rewrite-ClientboundLightUpdatePacketData.patch b/leaf-server/minecraft-patches/features/0133-Rewrite-ClientboundLightUpdatePacketData.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0134-Rewrite-ClientboundLightUpdatePacketData.patch rename to leaf-server/minecraft-patches/features/0133-Rewrite-ClientboundLightUpdatePacketData.patch diff --git a/leaf-server/minecraft-patches/features/0135-Some-Optimizations-on-SerializableChunkData.patch b/leaf-server/minecraft-patches/features/0134-Some-Optimizations-on-SerializableChunkData.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0135-Some-Optimizations-on-SerializableChunkData.patch rename to leaf-server/minecraft-patches/features/0134-Some-Optimizations-on-SerializableChunkData.patch diff --git a/leaf-server/minecraft-patches/features/0136-Rework-ChunkHolderManager.patch b/leaf-server/minecraft-patches/features/0135-Rework-ChunkHolderManager.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0136-Rework-ChunkHolderManager.patch rename to leaf-server/minecraft-patches/features/0135-Rework-ChunkHolderManager.patch diff --git a/leaf-server/minecraft-patches/features/0137-Optimize-chunkUnload.patch b/leaf-server/minecraft-patches/features/0136-Optimize-chunkUnload.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0137-Optimize-chunkUnload.patch rename to leaf-server/minecraft-patches/features/0136-Optimize-chunkUnload.patch diff --git a/leaf-server/minecraft-patches/features/0138-Async-chunk-sending.patch b/leaf-server/minecraft-patches/features/0137-Async-chunk-sending.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0138-Async-chunk-sending.patch rename to leaf-server/minecraft-patches/features/0137-Async-chunk-sending.patch diff --git a/leaf-server/minecraft-patches/features/0139-Spawner-Configurations.patch b/leaf-server/minecraft-patches/features/0138-Spawner-Configurations.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0139-Spawner-Configurations.patch rename to leaf-server/minecraft-patches/features/0138-Spawner-Configurations.patch diff --git a/leaf-server/minecraft-patches/features/0140-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/minecraft-patches/features/0139-SparklyPaper-Parallel-world-ticking.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0140-SparklyPaper-Parallel-world-ticking.patch rename to leaf-server/minecraft-patches/features/0139-SparklyPaper-Parallel-world-ticking.patch diff --git a/leaf-server/minecraft-patches/features/0141-SparklyPaper-Track-each-world-MSPT.patch b/leaf-server/minecraft-patches/features/0140-SparklyPaper-Track-each-world-MSPT.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0141-SparklyPaper-Track-each-world-MSPT.patch rename to leaf-server/minecraft-patches/features/0140-SparklyPaper-Track-each-world-MSPT.patch diff --git a/leaf-server/minecraft-patches/features/0142-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch b/leaf-server/minecraft-patches/features/0141-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0142-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch rename to leaf-server/minecraft-patches/features/0141-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch diff --git a/leaf-server/minecraft-patches/features/0143-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch b/leaf-server/minecraft-patches/features/0142-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0143-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch rename to leaf-server/minecraft-patches/features/0142-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch diff --git a/leaf-server/minecraft-patches/features/0144-Remove-streams-on-InsideBrownianWalk.patch b/leaf-server/minecraft-patches/features/0143-Remove-streams-on-InsideBrownianWalk.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0144-Remove-streams-on-InsideBrownianWalk.patch rename to leaf-server/minecraft-patches/features/0143-Remove-streams-on-InsideBrownianWalk.patch diff --git a/leaf-server/minecraft-patches/features/0145-Use-BFS-on-getSlopeDistance.patch b/leaf-server/minecraft-patches/features/0144-Use-BFS-on-getSlopeDistance.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0145-Use-BFS-on-getSlopeDistance.patch rename to leaf-server/minecraft-patches/features/0144-Use-BFS-on-getSlopeDistance.patch diff --git a/leaf-server/minecraft-patches/features/0145-Validate-recipe-display-index-before-retrieving-it.patch b/leaf-server/minecraft-patches/features/0145-Validate-recipe-display-index-before-retrieving-it.patch new file mode 100644 index 00000000..97bc0eee --- /dev/null +++ b/leaf-server/minecraft-patches/features/0145-Validate-recipe-display-index-before-retrieving-it.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Wed, 19 Mar 2025 13:32:39 -0400 +Subject: [PATCH] Validate recipe display index before retrieving it + + +diff --git a/net/minecraft/world/item/crafting/RecipeManager.java b/net/minecraft/world/item/crafting/RecipeManager.java +index 4bd1b514f91c0a2c9261b41211a4a341f784a995..5b69c4927174611d62f0f4698215ab97c827c7f5 100644 +--- a/net/minecraft/world/item/crafting/RecipeManager.java ++++ b/net/minecraft/world/item/crafting/RecipeManager.java +@@ -206,7 +206,11 @@ public class RecipeManager extends SimplePreparableReloadListener imp + + @Nullable + public RecipeManager.ServerDisplayInfo getRecipeFromDisplay(RecipeDisplayId display) { +- return this.allDisplays.get(display.index()); ++ // Leaf start - Validate recipe display index before retrieving it ++ final int index = display.index(); ++ ++ return this.allDisplays.size() > index ? this.allDisplays.get(index) : null; ++ // Leaf end - Validate recipe display index before retrieving it + } + + public void listDisplaysForRecipe(ResourceKey> recipe, Consumer output) { diff --git a/leaf-server/minecraft-patches/features/0156-AsyncPacketSending.patch b/leaf-server/minecraft-patches/features/0156-AsyncPacketSending.patch new file mode 100644 index 00000000..29db1654 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0156-AsyncPacketSending.patch @@ -0,0 +1,281 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Tue, 25 Mar 2025 00:00:36 +0100 +Subject: [PATCH] AsyncPacketSending + + +diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java +index 5b46036868b6c9d082e35591e58735e16adaae62..b352523e08e212ca4086904042b921af32cd3172 100644 +--- a/net/minecraft/network/Connection.java ++++ b/net/minecraft/network/Connection.java +@@ -66,6 +66,16 @@ import org.slf4j.Logger; + import org.slf4j.Marker; + import org.slf4j.MarkerFactory; + ++ ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.LinkedBlockingQueue; ++import java.util.concurrent.ThreadPoolExecutor; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.ThreadFactory; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.atomic.AtomicBoolean; ++import org.dreeam.leaf.config.modules.async.AsyncPacketSending; ++ + public class Connection extends SimpleChannelInboundHandler> { + private static final float AVERAGE_PACKETS_SMOOTHING = 0.75F; + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -128,6 +138,35 @@ public class Connection extends SimpleChannelInboundHandler> { + return null; + } + // Paper end - add utility methods ++ ++ private static final ExecutorService PACKET_EXECUTOR; ++ private static final AtomicInteger PACKET_THREAD_ID = new AtomicInteger(0); ++ private final AtomicBoolean processingScheduled = new AtomicBoolean(false); ++ ++ static { ++ // Initialize the packet executor only if async sending is enabled ++ if (AsyncPacketSending.enabled) { ++ ThreadFactory threadFactory = r -> { ++ Thread thread = new Thread(r, "Leaf - Async-Packet-Sender-" + PACKET_THREAD_ID.incrementAndGet()); ++ thread.setDaemon(true); ++ thread.setPriority(Thread.NORM_PRIORITY - 1); // Slightly lower priority ++ return thread; ++ }; ++ ++ // Use bounded queue to prevent memory issues ++ PACKET_EXECUTOR = new ThreadPoolExecutor( ++ Math.max(1, AsyncPacketSending.threadPoolSize), ++ Math.max(1, AsyncPacketSending.threadPoolSize), ++ 60L, TimeUnit.SECONDS, ++ new LinkedBlockingQueue<>(AsyncPacketSending.queueCapacity), ++ threadFactory, ++ new ThreadPoolExecutor.CallerRunsPolicy() // Fallback to caller thread if queue is full ++ ); ++ } else { ++ PACKET_EXECUTOR = null; ++ } ++ } ++ + // Paper start - packet limiter + protected final Object PACKET_LIMIT_LOCK = new Object(); + protected final @Nullable io.papermc.paper.util.IntervalledCounter allPacketCounts = io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.isEnabled() ? new io.papermc.paper.util.IntervalledCounter( +@@ -474,11 +513,82 @@ public class Connection extends SimpleChannelInboundHandler> { + + private void sendPacket(Packet packet, @Nullable PacketSendListener sendListener, boolean flush) { + this.sentPackets++; ++ ++ // Fast path: if we're already in the event loop, execute directly + if (this.channel.eventLoop().inEventLoop()) { + this.doSendPacket(packet, sendListener, flush); +- } else { ++ return; ++ } ++ ++ // Early return if async sending is disabled or executor not initialized ++ if (!AsyncPacketSending.enabled || PACKET_EXECUTOR == null) { ++ this.channel.eventLoop().execute(() -> this.doSendPacket(packet, sendListener, flush)); ++ return; ++ } ++ ++ // Handle high priority packets directly on Netty event loop ++ if (isHighPriorityPacket(packet)) { + this.channel.eventLoop().execute(() -> this.doSendPacket(packet, sendListener, flush)); ++ return; ++ } ++ ++ // For regular packets, use our async executor ++ PACKET_EXECUTOR.execute(() -> { ++ try { ++ // Wait for packet to be ready if needed ++ if (AsyncPacketSending.spinWaitForReadyPackets && !packet.isReady()) { ++ long startTime = System.nanoTime(); ++ while (!packet.isReady() && ++ System.nanoTime() - startTime < AsyncPacketSending.spinTimeNanos) { ++ Thread.onSpinWait(); ++ } ++ } ++ ++ // We still need to execute on the event loop for the actual channel operations ++ // as Netty requires channel operations to be on its event loop ++ this.channel.eventLoop().execute(() -> this.doSendPacket(packet, sendListener, flush)); ++ } catch (RejectedExecutionException e) { ++ // If the event loop is shutting down ++ LOGGER.debug("Failed to schedule packet send, event loop may be shutting down", e); ++ if (packet.hasFinishListener()) { ++ packet.onPacketDispatchFinish(this.getPlayer(), null); ++ } ++ } catch (Exception e) { ++ LOGGER.error("Error in async packet sending", e); ++ if (packet.hasFinishListener()) { ++ packet.onPacketDispatchFinish(this.getPlayer(), null); ++ } ++ } ++ }); ++ } ++ ++ // Helper method to determine if a packet should have high priority ++ private boolean isHighPriorityPacket(Packet packet) { ++ // Critical packets that should never be delayed ++ if (packet instanceof net.minecraft.network.protocol.common.ClientboundKeepAlivePacket || ++ packet instanceof net.minecraft.network.protocol.common.ClientboundDisconnectPacket) { ++ return true; + } ++ ++ // Movement packets if prioritization is enabled ++ if (AsyncPacketSending.prioritizeMovementPackets && ( ++ packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket ++ )) { ++ return true; ++ } ++ ++ // Chat packets if prioritization is enabled ++ if (AsyncPacketSending.prioritizeChatPackets && ( ++ packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundSystemChatPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket ++ )) { ++ return true; ++ } ++ ++ return false; + } + + private void doSendPacket(Packet packet, @Nullable PacketSendListener sendListener, boolean flush) { +@@ -539,15 +649,106 @@ public class Connection extends SimpleChannelInboundHandler> { + if (!this.isConnected()) { + return true; + } +- if (io.papermc.paper.util.MCUtil.isMainThread()) { +- return this.processQueue(); +- } else if (this.isPending) { +- // Should only happen during login/status stages ++ ++ // If async sending is disabled or we're in a special case, use original logic ++ if (!AsyncPacketSending.enabled || PACKET_EXECUTOR == null || this.pendingActions.isEmpty()) { ++ if (io.papermc.paper.util.MCUtil.isMainThread()) { ++ return this.processQueue(); ++ } else if (this.isPending) { ++ // Should only happen during login/status stages ++ synchronized (this.pendingActions) { ++ return this.processQueue(); ++ } ++ } ++ return false; ++ } ++ ++ // For login/status stages, stick with synchronous processing ++ if (this.isPending) { + synchronized (this.pendingActions) { + return this.processQueue(); + } + } +- return false; ++ ++ // Schedule async processing if not already scheduled ++ if (processingScheduled.compareAndSet(false, true)) { ++ PACKET_EXECUTOR.execute(this::processQueueAsync); ++ } ++ ++ return true; ++ } ++ ++ private void processQueueAsync() { ++ try { ++ if (AsyncPacketSending.batchProcessing) { ++ processQueueBatched(); ++ } else { ++ processQueue(); ++ } ++ } finally { ++ // Allow scheduling again ++ processingScheduled.set(false); ++ ++ // If there are still items in the queue, schedule another processing round ++ if (!this.pendingActions.isEmpty()) { ++ if (processingScheduled.compareAndSet(false, true)) { ++ PACKET_EXECUTOR.execute(this::processQueueAsync); ++ } ++ } ++ } ++ } ++ ++ // Process queue in batches for better efficiency ++ private void processQueueBatched() { ++ int maxToProcess = AsyncPacketSending.batchSize; ++ int processed = 0; ++ ++ while (processed < maxToProcess && !this.pendingActions.isEmpty()) { ++ WrappedConsumer action = this.pendingActions.poll(); ++ if (action == null) { ++ break; ++ } ++ ++ processed++; ++ ++ if (action.isConsumed()) { ++ continue; ++ } ++ ++ // Check if packet is ready for sending ++ if (action instanceof PacketSendAction packetSendAction) { ++ Packet packet = packetSendAction.packet; ++ if (!packet.isReady()) { ++ // Not ready, put back in queue ++ this.pendingActions.add(action); ++ continue; ++ } ++ } ++ ++ // Execute the action ++ if (action.tryMarkConsumed()) { ++ try { ++ action.accept(this); ++ } catch (Exception e) { ++ LOGGER.error("Error processing packet action", e); ++ } ++ } ++ } ++ } ++ ++ // Add a shutdown method for clean shutdown ++ public static void shutdownAsyncPacketSender() { ++ if (PACKET_EXECUTOR != null) { ++ PACKET_EXECUTOR.shutdown(); ++ try { ++ if (!PACKET_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS)) { ++ PACKET_EXECUTOR.shutdownNow(); ++ } ++ } catch (InterruptedException e) { ++ PACKET_EXECUTOR.shutdownNow(); ++ Thread.currentThread().interrupt(); ++ } ++ } + } + + private boolean processQueue() { +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 7739b4955dcb489c6bba9c9db65ba87025f7c669..e8b4be3d9f4838d8bad8bed5270c725141b7368f 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -70,6 +70,7 @@ import net.minecraft.core.RegistryAccess; + import net.minecraft.core.registries.Registries; + import net.minecraft.data.worldgen.features.MiscOverworldFeatures; + import net.minecraft.gametest.framework.GameTestTicker; ++import net.minecraft.network.Connection; + import net.minecraft.network.chat.ChatDecorator; + import net.minecraft.network.chat.ChatType; + import net.minecraft.network.chat.Component; +@@ -1018,6 +1019,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop