2026-07-09 00:03:09 +08:00
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Wed, 8 Jul 2026 22:23:29 +0800
Subject: [PATCH] Async protocol switching optimization
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
2026-08-11 15:18:51 +08:00
index c3922310ef1fce26eca60793b75bcb480683778a..cc9459273762cde3e6b87a7eaf225607f527315c 100644
2026-07-09 00:03:09 +08:00
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
2026-08-11 15:18:51 +08:00
@@ -1108,4 +1108,127 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
2026-07-09 00:03:09 +08:00
}
}
// Paper end - Optimize network
2026-07-14 11:32:51 +08:00
+ // Shiroha start - async protocol switcher
2026-07-09 00:03:09 +08:00
+ public <T extends PacketListener> void setupInboundProtocolAsync(
+ ProtocolInfo<T> protocol,
+ T packetListener,
+ @Nullable Runnable callback,
+ boolean resumeAutoReading
+ ) {
+ this.validateListener(protocol, packetListener);
+ if (protocol.flow() != this.getReceiving()) {
+ throw new IllegalStateException("Invalid inbound protocol: " + protocol.id());
+ } else {
+ this.packetListener = packetListener;
+ this.disconnectListener = null;
+
+ UnconfiguredPipelineHandler.InboundConfigurationTask configMessage = UnconfiguredPipelineHandler.setupInboundProtocol(protocol);
+ BundlerInfo bundlerInfo = protocol.bundlerInfo();
+ if (bundlerInfo != null) {
+ PacketBundlePacker newBundler = new PacketBundlePacker(bundlerInfo);
+ configMessage = configMessage.andThen(context -> context.pipeline().addAfter("decoder", "bundler", newBundler));
+ }
+
+ // Here we could execute it in event loop async to prevent waiting io task on main
+ // stop reading new packets to prevent some packets came into pipeline too early
+ this.channel.config().setAutoRead(false);
+
+ // do our configuration task
+ final UnconfiguredPipelineHandler.InboundConfigurationTask finalInboundConfigurationTask = configMessage;
+ Runnable toExecute = () -> this.channel.writeAndFlush(finalInboundConfigurationTask).addListener(future -> {
+ try {
+ if (future.isSuccess()) {
+ if (callback != null) callback.run(); // retire callback if there have one
+ return;
+ }
+
+ final Throwable ex = future.cause();
+
+ // here we process our exceptions like that blocking one
+ if (ex instanceof ClosedChannelException) {
+ LOGGER.info("Connection closed during protocol change");
+ } else {
+ this.channel.pipeline().fireExceptionCaught(ex);
+ }
+ }finally {
+ // reset auto back and resume reading if needed
+ if (resumeAutoReading) {
+ this.channel.config().setAutoRead(true);
+ this.channel.read();
+ }
+ }
+ });
+
+ // we need to do this inside the event loop
+ if (!this.channel.eventLoop().inEventLoop()) {
+ this.channel.eventLoop().execute(toExecute);
+ return;
+ }
+
+ toExecute.run();
+ }
+ }
+
+ public void setupOutboundProtocolAsync(
+ ProtocolInfo<?> protocol,
+ @Nullable Runnable callback,
+ boolean resumeAutoReading
+ ) {
+ if (protocol.flow() != this.getSending()) {
+ throw new IllegalStateException("Invalid outbound protocol: " + protocol.id());
+ } else {
+ UnconfiguredPipelineHandler.OutboundConfigurationTask configMessage = UnconfiguredPipelineHandler.setupOutboundProtocol(protocol);
+ BundlerInfo bundlerInfo = protocol.bundlerInfo();
+ if (bundlerInfo != null) {
+ PacketBundleUnpacker newUnbundler = new PacketBundleUnpacker(bundlerInfo);
+ configMessage = configMessage.andThen(
+ context -> context.pipeline().addAfter("encoder", "unbundler", newUnbundler)
+ );
+ }
+
+ boolean isLoginProtocol = protocol.id() == ConnectionProtocol.LOGIN;
+
+ // Here we could execute it in event loop async to prevent waiting io task on main
+ // stop reading new packets to prevent some packets came into pipeline too early
+ this.channel.config().setAutoRead(false);
+
+ // do our configuration task
+ final UnconfiguredPipelineHandler.OutboundConfigurationTask finalOutboundConfigurationTask = configMessage;
+ final Runnable writeTask = () -> this.channel.writeAndFlush(
+ finalOutboundConfigurationTask.andThen(context -> this.sendLoginDisconnect = isLoginProtocol)
+ ).addListener(future -> {
+ try {
+ if (future.isSuccess()) {
+ if (callback != null) callback.run(); // retire callback if there have one
+ return;
+ }
+
+ final Throwable ex = future.cause();
+
+ // here we process our exceptions like that blocking one
+ if (ex instanceof ClosedChannelException) {
+ LOGGER.info("Connection closed during protocol change");
+ } else {
+ this.channel.pipeline().fireExceptionCaught(ex);
+ }
+ }finally {
+ // reset auto back and resume reading if needed
+ if (resumeAutoReading) {
+ this.channel.config().setAutoRead(true);
+ this.channel.read(); // read once
+ }
+ }
+ });
+
+ // we need to do this inside the event loop
+ if (!this.channel.eventLoop().inEventLoop()) {
+ this.channel.eventLoop().execute(writeTask);
+ return;
+ }
+
+ writeTask.run();
+ }
+ }
2026-07-14 11:32:51 +08:00
+ // Shiroha end
2026-07-09 00:03:09 +08:00
+
}
diff --git a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
2026-08-08 11:46:48 +08:00
index 0485ce2a56adedc200cc0cd441df7cce88da66a8..344d2aa66f1fb720ca7abcc426ef438e8651ecd4 100644
2026-07-09 00:03:09 +08:00
--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
2026-08-08 11:46:48 +08:00
@@ -197,8 +197,9 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis
2026-07-09 00:03:09 +08:00
public void handleConfigurationFinished(final ServerboundFinishConfigurationPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.server.packetProcessor());
this.finishCurrentTask(JoinWorldTask.TYPE);
- this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess())));
2026-07-14 11:32:51 +08:00
+ // this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()))); // Shiroha - Async protocol switch - move down
2026-07-09 00:03:09 +08:00
2026-07-14 11:32:51 +08:00
+ Runnable afterSwitch = () -> { // Shiroha - Async protocol switch
2026-07-09 00:03:09 +08:00
try {
PlayerList playerList = this.server.getPlayerList();
if (playerList.getPlayer(this.gameProfile.id()) != null) {
2026-08-08 11:46:48 +08:00
@@ -244,6 +245,18 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis
2026-07-09 00:03:09 +08:00
LOGGER.error("Couldn't place player in world", e);
this.disconnect(DISCONNECT_REASON_INVALID_DATA);
}
2026-07-14 11:32:51 +08:00
+ // Shiroha start - Async protocol switch
2026-07-09 00:03:09 +08:00
+ };
2026-07-14 12:22:17 +08:00
+ if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) {
2026-07-09 00:03:09 +08:00
+ this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess())));
+ afterSwitch.run(); // directly run callback as we won't process any packet this time
+ } else {
+ this.connection.setupOutboundProtocolAsync(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess())), () -> {
+ // push back
+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(afterSwitch);
+ }, false); // we will start auto read once we set up inbound handler at placeNewPlayer in PlayerList
+ }
2026-07-14 11:32:51 +08:00
+ // Shiroha end
2026-07-09 00:03:09 +08:00
}
@Override
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
2026-08-08 11:46:48 +08:00
index c86b1fd5e200c478d178be32791b6e8c960d47e2..862a764889ed9ca985afdf672aebeb342c18b0c2 100644
2026-07-09 00:03:09 +08:00
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
2026-08-08 11:46:48 +08:00
@@ -2898,7 +2898,13 @@ public class ServerGamePacketListenerImpl
2026-07-09 00:03:09 +08:00
} // Folia end - rewrite login process - move connection ownership to global region
this.waitingForSwitchToConfig = true; // Folia - rewrite login process - fix bad ordering of this field write - moved down
this.send(ClientboundStartConfigurationPacket.INSTANCE);
2026-07-14 12:22:17 +08:00
+ if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Shiroha - Async protocol switch
2026-07-09 00:03:09 +08:00
this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND);
2026-07-14 11:32:51 +08:00
+ // Shiroha start - Async protcol switch
2026-07-09 00:03:09 +08:00
+ } else {
+ this.connection.setupOutboundProtocolAsync(ConfigurationProtocols.CLIENTBOUND, null, true);
+ }
2026-07-14 11:32:51 +08:00
+ // Shiroha end
2026-07-09 00:03:09 +08:00
}
@Override
2026-08-08 11:46:48 +08:00
@@ -3783,12 +3789,26 @@ public class ServerGamePacketListenerImpl
2026-07-09 00:03:09 +08:00
}
final ServerConfigurationPacketListenerImpl listener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())); // Paper
2026-07-14 12:22:17 +08:00
+ if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Shiroha - Async protocol switch
2026-07-09 00:03:09 +08:00
this.connection
.setupInboundProtocol(
ConfigurationProtocols.SERVERBOUND,
listener // Paper
);
new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper
2026-07-14 11:32:51 +08:00
+ } // Shiroha - Async protocol switch - add "{"
+ // Shiroha start - Async protocol switch - move up
2026-07-09 00:03:09 +08:00
+ else
+ this.connection.setupInboundProtocolAsync(
+ ConfigurationProtocols.SERVERBOUND,
+ listener,
+ () -> {
+ new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper
+ },
+ true
+ );
2026-07-14 11:32:51 +08:00
+ // Shiroha end
+ // new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper // Shiroha - Async protocol switch - move up
2026-07-09 00:03:09 +08:00
}
@Override
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
2026-08-11 15:18:51 +08:00
index 95787d6d29ddb8dca78506bb2a115fa612fdaa29..8c4b60e7c7c44f20e13e917bc0bc3b75e98a36c1 100644
2026-07-09 00:03:09 +08:00
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
2026-08-11 15:18:51 +08:00
@@ -439,12 +439,30 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
2026-07-09 00:03:09 +08:00
public void handleLoginAcknowledgement(final ServerboundLoginAcknowledgedPacket packet) {
net.minecraft.network.protocol.PacketUtils.ensureRunningOnSameThread(packet, this, this.server.packetProcessor()); // CraftBukkit
Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet");
- this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND);
2026-07-14 11:32:51 +08:00
+ /*this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); // Shiroha - Async protocol switch - Rewrite
2026-07-09 00:03:09 +08:00
CommonListenerCookie cookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred);
ServerConfigurationPacketListenerImpl configPacketListener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, cookie);
this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, configPacketListener);
configPacketListener.startConfiguration();
- this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;
2026-07-14 11:32:51 +08:00
+ this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;*/ // Shiroha - Async protocol switch - Rewrite
2026-07-09 00:03:09 +08:00
+
2026-07-14 11:32:51 +08:00
+ // Shiroha start - Async protocol switch
2026-07-09 00:03:09 +08:00
+ CommonListenerCookie cookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred);
+ ServerConfigurationPacketListenerImpl configPacketListener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, cookie);
+
+ Runnable afterSwitch = () -> io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(configPacketListener::startConfiguration); // push back to main thread
+
2026-07-14 12:22:17 +08:00
+ if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) {
2026-07-09 00:03:09 +08:00
+ this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND);
+ this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, configPacketListener);
+ afterSwitch.run();
+ return;
+ }
+
+ this.connection.setupInboundProtocolAsync(ConfigurationProtocols.SERVERBOUND, configPacketListener, () -> {
+ this.connection.setupOutboundProtocolAsync(ConfigurationProtocols.CLIENTBOUND, afterSwitch, true); // start auto read when everything is ready
+ }, false); // we will resume auto reading once the outbound protocol is also setup
2026-07-14 11:32:51 +08:00
+ // Shiroha end
2026-07-09 00:03:09 +08:00
}
@Override
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
2026-08-12 17:52:01 +08:00
index c70d3169002681c5f2131ca8e98ef7d42c6dc4fa..ac5f82eaa9965f7b610f7df2d0cd4cb028951d65 100644
2026-07-09 00:03:09 +08:00
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -237,9 +237,11 @@ public abstract class PlayerList {
// only after setting the connection listener to game type, add the connection to this regions list
2026-08-08 11:46:48 +08:00
level.getCurrentWorldData().addConnection(player);
2026-07-09 00:03:09 +08:00
// Folia end - rewrite login process
2026-07-14 12:22:17 +08:00
+ if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Shiroha - Async protocol switch // we will run async switch once these main thread logics became done
2026-07-09 00:03:09 +08:00
connection.setupInboundProtocol(
GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()), playerConnection), playerConnection
);
2026-07-14 11:32:51 +08:00
+ } // Shiroha - Async protocol switch
2026-07-09 00:03:09 +08:00
playerConnection.suspendFlushing();
GameRules gameRules = level.getGameRules();
boolean immediateRespawn = gameRules.get(GameRules.IMMEDIATE_RESPAWN);
@@ -403,6 +405,17 @@ public abstract class PlayerList {
);
}
// Paper end - Send empty chunk
2026-07-14 11:32:51 +08:00
+ // Shiroha start - Async protocol switch
2026-07-14 12:22:17 +08:00
+ if (io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) {
2026-07-09 00:03:09 +08:00
+ // auto read will be enabled once the async switch is done
+ connection.setupInboundProtocolAsync(
+ GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()), playerConnection),
+ playerConnection,
+ null, // we don't need to do anything more
+ true // start auto read which we have disabled in configuration handler
+ );
+ }
2026-07-14 11:32:51 +08:00
+ // Shiroha end
2026-07-09 00:03:09 +08:00
}
public void updateEntireScoreboard(final ServerScoreboard scoreboard, final ServerPlayer player) {