Luminol-Core/luminol-server/minecraft-patches/features/0026-Async-protocol-switching-optimization.patch
2026-06-30 18:32:29 +08:00

292 lines
18 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
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<Packet<?>> {
}
}
+ // Luminol start - async protocol switcher
+ public <T extends PacketListener> void setupInboundProtocolAsync(ProtocolInfo<T> 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 <T extends PacketListener> void setupInboundProtocol(ProtocolInfo<T> 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
}
}