diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/MinecraftServerMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/MinecraftServerMixin.java index 411766e5..f0383155 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/MinecraftServerMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/MinecraftServerMixin.java @@ -1,24 +1,99 @@ package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks; +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.serialization.Dynamic; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.TicketType; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.Mth; +import net.minecraft.util.Unit; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.storage.WorldData; +import org.apache.commons.lang3.tuple.Pair; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.duck.ISpawnTrackingMinecraftServer; 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.Redirect; +import javax.annotation.Nullable; + @Mixin(value = MinecraftServer.class, priority = 1100) -public class MinecraftServerMixin { +public abstract class MinecraftServerMixin implements ISpawnTrackingMinecraftServer { + @Shadow + public abstract boolean isDedicatedServer(); + + @Shadow + public abstract WorldData getWorldData(); + + @Shadow + @Nullable + public abstract ServerLevel getLevel(ResourceKey dimension); + + private Pair, ChunkPos> mfix$initialSpawnLocation; + + private @Nullable Pair, ChunkPos> loadPlayerSpawnLocation() { + CompoundTag player = this.getWorldData().getLoadedPlayerTag(); + + if (player == null) { + return null; + } + + ListTag pos = player.getList("Pos", CompoundTag.TAG_DOUBLE); + double x = pos.getDouble(0); + double z = pos.getDouble(2); + + // Dimension + ResourceKey dimension = DimensionType.parseLegacy( + new Dynamic<>(NbtOps.INSTANCE, player.get("Dimension")) + ).resultOrPartial(ModernFix.LOGGER::error).orElse(Level.OVERWORLD); + + return Pair.of(dimension, new ChunkPos(SectionPos.blockToSectionCoord(Mth.floor(x)), SectionPos.blockToSectionCoord(Mth.floor(z)))); + } + @Redirect(method = "prepareLevels", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V")) - private void addSpawnChunkTicket(ServerChunkCache cache, TicketType type, ChunkPos pos, int distance, Object o) { - // load first chunk - cache.getChunk(pos.x, pos.z, ChunkStatus.FULL, true); + private void addSpawnChunkTicket(ServerChunkCache cache, TicketType type, ChunkPos pos, int distance, Object o, @Local(ordinal = 0, argsOnly = true) ChunkProgressListener listener) { + if (!this.isDedicatedServer()) { + // Temporarily create a START ticket around the player to load the world in parallel with client join + // We remove it once the player has joined the world + var pair = this.mfix$initialSpawnLocation = loadPlayerSpawnLocation(); + if (pair != null) { + var level = this.getLevel(pair.getLeft()); + if (level != null) { + cache = level.getChunkSource(); + pos = pair.getRight(); + } + } + + listener.updateSpawnPos(pos); + cache.addRegionTicket(TicketType.START, pos, 0, Unit.INSTANCE); + } else { + // just trigger sync load of initial spawn once + // TODO: figure out if this magic is still needed + cache.getChunk(pos.x, pos.z, true); + } } @Redirect(method = "prepareLevels", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;getTickingGenerated()I"), require = 0) private int getGenerated(ServerChunkCache cache) { return 441; } + + @Override + public Pair, ChunkPos> mfix$getInitialStartTicketLocation() { + var pair = this.mfix$initialSpawnLocation; + this.mfix$initialSpawnLocation = null; + return pair; + } } diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/PlayerListMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/PlayerListMixin.java new file mode 100644 index 00000000..b5c4f746 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/PlayerListMixin.java @@ -0,0 +1,26 @@ +package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks; + +import net.minecraft.network.Connection; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.TicketType; +import net.minecraft.server.players.PlayerList; +import net.minecraft.util.Unit; +import org.embeddedt.modernfix.duck.ISpawnTrackingMinecraftServer; +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) +public class PlayerListMixin { + @Inject(method = "placeNewPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;addNewPlayer(Lnet/minecraft/server/level/ServerPlayer;)V", shift = At.Shift.AFTER)) + private void removeStartTicket(Connection netManager, ServerPlayer player, CallbackInfo ci) { + var initial = ((ISpawnTrackingMinecraftServer)player.server).mfix$getInitialStartTicketLocation(); + if (initial != null) { + var level = player.server.getLevel(initial.getLeft()); + if (level != null) { + level.getChunkSource().removeRegionTicket(TicketType.START, initial.getRight(), 0, Unit.INSTANCE); + } + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/ServerChunkCacheAccessor.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/ServerChunkCacheAccessor.java deleted file mode 100644 index 1ae8cbaa..00000000 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/ServerChunkCacheAccessor.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks; - -import net.minecraft.server.level.DistanceManager; -import net.minecraft.server.level.ServerChunkCache; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(ServerChunkCache.class) -public interface ServerChunkCacheAccessor { - @Accessor("distanceManager") - DistanceManager getDistanceManager(); -} diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/ServerPlayerMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/ServerPlayerMixin.java new file mode 100644 index 00000000..74eda4a7 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/remove_spawn_chunks/ServerPlayerMixin.java @@ -0,0 +1,20 @@ +package org.embeddedt.modernfix.common.mixin.perf.remove_spawn_chunks; + +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(ServerPlayer.class) +public class ServerPlayerMixin { + /** + * @author embeddedt + * @reason do not waste time loading the wrong chunks and placing the player there just to correct it later + */ + @WrapWithCondition(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;fudgeSpawnLocation(Lnet/minecraft/server/level/ServerLevel;)V")) + private boolean skipFudgingForSPOwner(ServerPlayer player, ServerLevel targetLevel) { + return targetLevel.getServer().getWorldData().getLoadedPlayerTag() == null + || !targetLevel.getServer().isSingleplayerOwner(player.getGameProfile()); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/duck/ISpawnTrackingMinecraftServer.java b/src/main/java/org/embeddedt/modernfix/duck/ISpawnTrackingMinecraftServer.java new file mode 100644 index 00000000..46fb5b0b --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/duck/ISpawnTrackingMinecraftServer.java @@ -0,0 +1,10 @@ +package org.embeddedt.modernfix.duck; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import org.apache.commons.lang3.tuple.Pair; + +public interface ISpawnTrackingMinecraftServer { + Pair, ChunkPos> mfix$getInitialStartTicketLocation(); +}