From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MrHua269 Date: Mon, 14 Jul 2025 18:52:13 +0800 Subject: [PATCH] Async protocol switching optimization diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java index 3161dbf57cae0c934809bea816b87546bc230907..66ec0424a46dcd49cf44467357d80b1a2d84d3b2 100644 --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java @@ -373,6 +373,116 @@ public class Connection extends SimpleChannelInboundHandler> { } } + // Luminol start - async protocol switcher + public void setupInboundProtocolAsync(ProtocolInfo protocolInfo, T packetInfo, @Nullable Runnable callback, boolean releaseAutoRead) { + this.validateListener(protocolInfo, packetInfo); + if (protocolInfo.flow() != this.getReceiving()) { + throw new IllegalStateException("Invalid inbound protocol: " + protocolInfo.id()); + } else { + this.packetListener = packetInfo; + this.disconnectListener = null; + + UnconfiguredPipelineHandler.InboundConfigurationTask inboundConfigurationTask = UnconfiguredPipelineHandler.setupInboundProtocol(protocolInfo); + BundlerInfo bundlerInfo = protocolInfo.bundlerInfo(); + if (bundlerInfo != null) { + PacketBundlePacker packetBundlePacker = new PacketBundlePacker(bundlerInfo); + inboundConfigurationTask = inboundConfigurationTask.andThen(context -> context.pipeline().addAfter("decoder", "bundler", packetBundlePacker)); + } + + // Here we could execute it in event loop async to prevent waiting io task on main + this.channel.config().setAutoRead(false); // stop reading new packets to prevent some packets came into pipeline too early + final UnconfiguredPipelineHandler.InboundConfigurationTask finalInboundConfigurationTask = inboundConfigurationTask; + Runnable writeTask = () -> { + final ChannelFuture future = this.channel.writeAndFlush(finalInboundConfigurationTask); + + future.addListener(orginalFuture -> { + try { + if (orginalFuture.isSuccess()) { + if (callback != null) callback.run(); // retire callback if there have one + return; + } + + final Throwable ex = orginalFuture.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 { + if (releaseAutoRead) { + this.channel.config().setAutoRead(true); + this.channel.read(); + } + } + }); + }; + + if (!this.channel.eventLoop().inEventLoop()) { + this.channel.eventLoop().execute(writeTask); + return; + } + + writeTask.run(); + } + } + + public void setupOutboundProtocolAsync(ProtocolInfo protocolInfo, @Nullable Runnable callback, boolean releaseAutoRead) { + if (protocolInfo.flow() != this.getSending()) { + throw new IllegalStateException("Invalid outbound protocol: " + protocolInfo.id()); + } else { + UnconfiguredPipelineHandler.OutboundConfigurationTask outboundConfigurationTask = UnconfiguredPipelineHandler.setupOutboundProtocol(protocolInfo); + BundlerInfo bundlerInfo = protocolInfo.bundlerInfo(); + if (bundlerInfo != null) { + PacketBundleUnpacker packetBundleUnpacker = new PacketBundleUnpacker(bundlerInfo); + outboundConfigurationTask = outboundConfigurationTask.andThen( + context -> context.pipeline().addAfter("encoder", "unbundler", packetBundleUnpacker) + ); + } + + boolean flag = protocolInfo.id() == ConnectionProtocol.LOGIN; + + // Here we could execute it in event loop async to prevent waiting io task on main + this.channel.config().setAutoRead(false); // stop reading new packets to prevent some packets came into pipeline too early + final UnconfiguredPipelineHandler.OutboundConfigurationTask finalOutboundConfigurationTask = outboundConfigurationTask; + final Runnable writeTask = () -> { + final ChannelFuture future = this.channel.writeAndFlush(finalOutboundConfigurationTask.andThen(context -> this.sendLoginDisconnect = flag)); + + future.addListener(orginalFuture -> { + try { + if (orginalFuture.isSuccess()) { + if (callback != null) callback.run(); // retire callback if there have one + return; + } + + final Throwable ex = orginalFuture.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 { + if (releaseAutoRead) { + this.channel.config().setAutoRead(true); + this.channel.read(); // read once + } + } + }); + }; + + if (!this.channel.eventLoop().inEventLoop()) { + this.channel.eventLoop().execute(writeTask); + return; + } + + writeTask.run(); + } + } + // Luminol end + public void setupInboundProtocol(ProtocolInfo protocolInfo, T packetInfo) { this.validateListener(protocolInfo, packetInfo); if (protocolInfo.flow() != this.getReceiving()) { diff --git a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java index 884b7b8c4581742a1ca965d453773dc10b76d5f9..e8f0c81b31569d610fb88f10d93c4e68fb5a24a1 100644 --- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java @@ -158,8 +158,9 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis public void handleConfigurationFinished(ServerboundFinishConfigurationPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.server); 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()))); // Luminol - Async protocol switch - move down + Runnable afterSwitch = () -> { // Luminol - Async protocol switch try { PlayerList playerList = this.server.getPlayerList(); if (playerList.getPlayer(this.gameProfile.getId()) != null) { @@ -247,6 +248,18 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis this.connection.send(new ClientboundDisconnectPacket(DISCONNECT_REASON_INVALID_DATA)); this.connection.disconnect(DISCONNECT_REASON_INVALID_DATA); } + // Luminol start - Async protocol switch + }; + if (!me.earthme.luminol.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 + } + // Luminol end } @Override diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java index b489756075906f4a46fc36a57aca6c71ad5383fe..cd873b03c553ba42b85b6165f99f5d4fba34ec3b 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -2785,7 +2785,7 @@ public class ServerGamePacketListenerImpl // removed from the world // Folia end - rewrite login process - fix bad ordering of this field write - move after removed from world try { // Folia - rewrite login process - move connection ownership to global region - this.hackSwitchingConfig = true; // Folia - rewrite login process - avoid adding logout ticket here + this.hackSwitchingConfig = true; // Folia - rewrite login process - avoid adding logout ticket here and retiring scheduler this.removePlayerFromWorld(); } finally { // Folia start - rewrite login process - move connection ownership to global region io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.player.level().getCurrentWorldData(); @@ -2794,7 +2794,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 (!me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Luminol - Async protocol switch this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); + // Luminol start - Async protcol switch + } else { + this.connection.setupOutboundProtocolAsync(ConfigurationProtocols.CLIENTBOUND, null, true); + } + // Luminol end } @Override @@ -3631,12 +3637,25 @@ public class ServerGamePacketListenerImpl throw new IllegalStateException("Client acknowledged config, but none was requested"); } else { final ServerConfigurationPacketListenerImpl listener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())); // Paper + if (!me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled){ // Luminol - Async protocol switch this.connection .setupInboundProtocol( ConfigurationProtocols.SERVERBOUND, listener // Paper ); - new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper + new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); }// Paper // Luminol - Async protocol switch - add "{" + // Luminol 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 + ); + // Luminol end + // new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper // Luminol - Async protocol switch - move up } } diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index d114935b0879f5d83ad6c03395d565b6b92c6679..7a19bbcc74205035dee111d3fff53446bf2aad04 100644 --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -414,14 +414,35 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket packet) { net.minecraft.network.protocol.PacketUtils.ensureRunningOnSameThread(packet, this, this.server); // CraftBukkit Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet"); - this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); + /*this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); // Luminol - Async protocol switch - Rewrite CommonListenerCookie commonListenerCookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred); ServerConfigurationPacketListenerImpl serverConfigurationPacketListenerImpl = new ServerConfigurationPacketListenerImpl( this.server, this.connection, commonListenerCookie ); this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, serverConfigurationPacketListenerImpl); serverConfigurationPacketListenerImpl.startConfiguration(); + this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;*/ // Luminol - Async protocol switch - Rewrite + + // Luminol start - Async protocol switch this.state = ServerLoginPacketListenerImpl.State.ACCEPTED; + final CommonListenerCookie commonListenerCookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred); + final ServerConfigurationPacketListenerImpl serverConfigurationPacketListenerImpl = new ServerConfigurationPacketListenerImpl( + this.server, this.connection, commonListenerCookie + ); + + Runnable afterSwitch = () -> io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(serverConfigurationPacketListenerImpl::startConfiguration); // push back to main thread + + if (!me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { + this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); + this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, serverConfigurationPacketListenerImpl); + afterSwitch.run(); + return; + } + + this.connection.setupInboundProtocolAsync(ConfigurationProtocols.SERVERBOUND, serverConfigurationPacketListenerImpl, () -> { + this.connection.setupOutboundProtocolAsync(ConfigurationProtocols.CLIENTBOUND, afterSwitch, true); // start auto read when everything is ready + }, false); + // Luminol end } @Override diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java index 976a8982a0b0a9ae459cf08e13d69031d4238eca..482f6e26556377891bb682cdcc73193bc997a67d 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -342,10 +342,12 @@ public abstract class PlayerList { // only after setting the connection listener to game type, add the connection to this regions list serverLevel.getCurrentWorldData().connections.add(connection); // Folia end - rewrite login process + if (!me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Luminol - 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()), serverGamePacketListenerImpl), serverGamePacketListenerImpl ); + } // Luminol - Async protocol switch GameRules gameRules = serverLevel.getGameRules(); boolean _boolean = gameRules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN); boolean _boolean1 = gameRules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); @@ -497,6 +499,17 @@ public abstract class PlayerList { ); } // Paper end - Send empty chunk + // Luminol start - Async protocol switch + if (me.earthme.luminol.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()), serverGamePacketListenerImpl), + serverGamePacketListenerImpl, + null, // we don't need do anything more + true // start auto read which we have disabled in configuration handler + ); + } + // Luminol end } }