289 lines
16 KiB
Diff
289 lines
16 KiB
Diff
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
|
|
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<Packet<?>> {
|
|
}
|
|
}
|
|
// Paper end - Optimize network
|
|
+ // Shiroha start - async protocol switcher
|
|
+ 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();
|
|
+ }
|
|
+ }
|
|
+ // 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) {
|