From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MrHua269 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 index c3922310ef1fce26eca60793b75bcb480683778a..cc9459273762cde3e6b87a7eaf225607f527315c 100644 --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java @@ -1108,4 +1108,127 @@ public class Connection extends SimpleChannelInboundHandler> { } } // Paper end - Optimize network + // Shiroha start - async protocol switcher + public void setupInboundProtocolAsync( + ProtocolInfo 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(); + } + } + // Shiroha end + } diff --git a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java index 0485ce2a56adedc200cc0cd441df7cce88da66a8..344d2aa66f1fb720ca7abcc426ef438e8651ecd4 100644 --- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java @@ -197,8 +197,9 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis 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()))); + // this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()))); // Shiroha - Async protocol switch - move down + Runnable afterSwitch = () -> { // Shiroha - Async protocol switch try { PlayerList playerList = this.server.getPlayerList(); if (playerList.getPlayer(this.gameProfile.id()) != null) { @@ -244,6 +245,18 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis LOGGER.error("Couldn't place player in world", e); this.disconnect(DISCONNECT_REASON_INVALID_DATA); } + // Shiroha start - Async protocol switch + }; + if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { + 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 + } + // Shiroha end } @Override diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java index c86b1fd5e200c478d178be32791b6e8c960d47e2..862a764889ed9ca985afdf672aebeb342c18b0c2 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -2898,7 +2898,13 @@ public class ServerGamePacketListenerImpl } // 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); + if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Shiroha - Async protocol switch this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); + // Shiroha start - Async protcol switch + } else { + this.connection.setupOutboundProtocolAsync(ConfigurationProtocols.CLIENTBOUND, null, true); + } + // Shiroha end } @Override @@ -3783,12 +3789,26 @@ public class ServerGamePacketListenerImpl } final ServerConfigurationPacketListenerImpl listener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())); // Paper + if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Shiroha - Async protocol switch this.connection .setupInboundProtocol( ConfigurationProtocols.SERVERBOUND, listener // Paper ); new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper + } // Shiroha - Async protocol switch - add "{" + // Shiroha start - Async protocol switch - move up + else + this.connection.setupInboundProtocolAsync( + ConfigurationProtocols.SERVERBOUND, + listener, + () -> { + new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper + }, + true + ); + // Shiroha end + // new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper // Shiroha - Async protocol switch - move up } @Override diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index 95787d6d29ddb8dca78506bb2a115fa612fdaa29..8c4b60e7c7c44f20e13e917bc0bc3b75e98a36c1 100644 --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -439,12 +439,30 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, 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); + /*this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); // Shiroha - Async protocol switch - Rewrite 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; + this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;*/ // Shiroha - Async protocol switch - Rewrite + + // Shiroha start - Async protocol switch + 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 + + if (!io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { + 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 + // Shiroha end } @Override diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java index 259eb7574841b1b885093b534440f8a40db4e8e2..fcfa2b7b6e8f4c00763b8c23d6eb2699aced3e75 100644 --- 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 level.getCurrentWorldData().addConnection(player); // Folia end - rewrite login process + 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 connection.setupInboundProtocol( GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()), playerConnection), playerConnection ); + } // Shiroha - Async protocol switch 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 + // Shiroha start - Async protocol switch + if (io.nanachiyo0721.shiroha.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { + // 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 + ); + } + // Shiroha end } public void updateEntireScoreboard(final ServerScoreboard scoreboard, final ServerPlayer player) {