Make integrated server treat game as paused while singleplayer client is still loading

This commit is contained in:
embeddedt 2026-03-14 10:44:04 -04:00
parent 38288d5e6a
commit f79eae8b83
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
4 changed files with 105 additions and 0 deletions

View File

@ -0,0 +1,36 @@
package org.embeddedt.modernfix.common.mixin.perf.suspend_integrated_server_during_load;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.suspend_integrated_server_during_load.IDeferrableIntegratedServer;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPacketListener.class)
@ClientOnlyMixin
public class ClientPacketListenerMixin {
@Shadow
@Final
private Minecraft minecraft;
@Inject(method = "handleCustomPayload", at = @At("HEAD"), cancellable = true)
private void detectClientLoadSentinel(ClientboundCustomPayloadPacket packet, CallbackInfo ci) {
if (packet.getIdentifier().equals(IDeferrableIntegratedServer.CLIENT_LOAD_SENTINEL)) {
// Important: flag must be changed on the client thread, as later packets can start decoding
// while earlier ones are still being applied.
this.minecraft.executeIfPossible(() -> {
packet.getData().release();
if (this.minecraft.hasSingleplayerServer()) {
((IDeferrableIntegratedServer)this.minecraft.getSingleplayerServer()).mfix$markClientLoadFinished();
}
});
ci.cancel();
}
}
}

View File

@ -0,0 +1,33 @@
package org.embeddedt.modernfix.common.mixin.perf.suspend_integrated_server_during_load;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.Minecraft;
import net.minecraft.client.server.IntegratedServer;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.suspend_integrated_server_during_load.IDeferrableIntegratedServer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.util.concurrent.atomic.AtomicBoolean;
@Mixin(IntegratedServer.class)
@ClientOnlyMixin
public abstract class IntegratedServerMixin implements IDeferrableIntegratedServer {
private final AtomicBoolean mfix$hasPrimaryClientJoined = new AtomicBoolean(false);
/**
* @author embeddedt
* @reason Wait to be finished processing all expensive packets (recipes, tags, etc.)
* before continuing to tick the integrated server.
*/
@WrapOperation(method = "tickServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;isPaused()Z", ordinal = 0))
private boolean preventTicks(Minecraft instance, Operation<Boolean> original) {
return !mfix$hasPrimaryClientJoined.get() || original.call(instance);
}
@Override
public void mfix$markClientLoadFinished() {
mfix$hasPrimaryClientJoined.set(true);
}
}

View File

@ -0,0 +1,26 @@
package org.embeddedt.modernfix.common.mixin.perf.suspend_integrated_server_during_load;
import io.netty.buffer.Unpooled;
import net.minecraft.network.Connection;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.PlayerList;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.duck.suspend_integrated_server_during_load.IDeferrableIntegratedServer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerList.class)
@ClientOnlyMixin
public class PlayerListMixin {
@Inject(method = "placeNewPlayer", at = @At("RETURN"))
private void sendConfigFinishedSentinelPacket(Connection connection, ServerPlayer player, CallbackInfo ci) {
if (connection.isMemoryConnection()) {
FriendlyByteBuf friendlybytebuf = new FriendlyByteBuf(Unpooled.buffer());
player.connection.send(new ClientboundCustomPayloadPacket(IDeferrableIntegratedServer.CLIENT_LOAD_SENTINEL, friendlybytebuf));
}
}
}

View File

@ -0,0 +1,10 @@
package org.embeddedt.modernfix.duck.suspend_integrated_server_during_load;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.ModernFix;
public interface IDeferrableIntegratedServer {
ResourceLocation CLIENT_LOAD_SENTINEL = new ResourceLocation(ModernFix.MODID, "mark_client_load_finished");
void mfix$markClientLoadFinished();
}