From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar Date: Sun, 19 Apr 2020 04:28:29 -0400 Subject: [PATCH] Load Chunks for Login Asynchronously diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index b3ca4300b280a24f3ed2acaffdd6ae2cdffd140d..97a582614ad28f9fa864ae9be4860658e5979214 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -145,7 +145,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider private final ProcessorHandle> worldgenMailbox; private final ProcessorHandle> mainThreadMailbox; public final ChunkProgressListener progressListener; - public final ChunkMap.ChunkDistanceManager distanceManager; + public final ChunkMap.ChunkDistanceManager distanceManager; public final DistanceManager getChunkDistanceManager() { return this.distanceManager; } // Paper - OBFHELPER private final AtomicInteger tickingGenerated; public final StructureManager structureManager; // Paper - private -> public private final File storageFolder; diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index b45fe750c8ca838e1beebff4077e5819eec2836c..79fb63c40dd0543a6f629e78f390f23f34992ba1 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -629,7 +629,7 @@ public class ServerChunkCache extends ChunkSource { return this.mainThreadProcessor.pollTask(); } - private boolean runDistanceManagerUpdates() { + public boolean runDistanceManagerUpdates() { // Paper - private -> public boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); boolean flag1 = this.chunkMap.promoteChunkMap(); diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index 75a095e0c2177dc1b46b080597ff8f12f1480acc..24c508ade61a6ad90b0ef73cdc995f531ef18263 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -32,6 +32,7 @@ import net.minecraft.core.Vec3i; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.network.Connection; import net.minecraft.network.chat.ChatType; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.HoverEvent; @@ -172,6 +173,7 @@ public class ServerPlayer extends Player implements ContainerListener { private static final Logger LOGGER = LogManager.getLogger(); public ServerGamePacketListenerImpl connection; + public Connection networkManager; // Paper public final MinecraftServer server; public final ServerPlayerGameMode gameMode; public final Deque removeQueue = new ArrayDeque<>(); // Paper @@ -238,6 +240,7 @@ public class ServerPlayer extends Player implements ContainerListener { public boolean joining = true; public boolean sentListPacket = false; public boolean supressTrackerForLogin = false; // Paper + public boolean didPlayerJoinEvent = false; // Paper public Integer clientViewDistance; // CraftBukkit end public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java index d09e4857b6c40410d134fa81b48e95919a7373bd..583587457790df826a8a3239a4bd1d0f1dcab1da 100644 --- a/src/main/java/net/minecraft/server/level/TicketType.java +++ b/src/main/java/net/minecraft/server/level/TicketType.java @@ -21,6 +21,7 @@ public class TicketType { public static final TicketType FORCED = create("forced", Comparator.comparingLong(ChunkPos::toLong)); public static final TicketType LIGHT = create("light", Comparator.comparingLong(ChunkPos::toLong)); public static final TicketType PORTAL = create("portal", Vec3i::compareTo, 300); + public static final TicketType LOGIN = create("login", Long::compareTo, 100); // Paper public static final TicketType POST_TELEPORT = create("post_teleport", Integer::compareTo, 5); public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); public static final TicketType PLUGIN = create("plugin", (a, b) -> 0); // CraftBukkit diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java index 09a663cc53cdf8ae45352b280200c8170dbbcdfc..1bed6e69bf3cc1ab9b0c1259de4f643bf58371aa 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -220,6 +220,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { private static final Logger LOGGER = LogManager.getLogger(); public final Connection connection; private final MinecraftServer server; + public Runnable playerJoinReady; // Paper public ServerPlayer player; private int tickCount; private long keepAliveTime = Util.getMillis(); private void setLastPing(long lastPing) { this.keepAliveTime = lastPing;}; private long getLastPing() { return this.keepAliveTime;}; // Paper - OBFHELPER @@ -298,6 +299,15 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { // CraftBukkit end public void tick() { + // Paper start - login async + Runnable playerJoinReady = this.playerJoinReady; + if (playerJoinReady != null) { + this.playerJoinReady = null; + playerJoinReady.run(); + } + // Don't tick if not valid (dead), otherwise we load chunks below + if (this.player.valid) { + // Paper end this.resetPosition(); this.player.xo = this.player.getX(); this.player.yo = this.player.getY(); @@ -339,7 +349,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { this.lastVehicle = null; this.clientVehicleIsFloating = false; this.aboveGroundVehicleTickCount = 0; - } + }} // Paper - end if (valid) this.server.getProfiler().push("keepAlive"); // Paper Start - give clients a longer time to respond to pings as per pre 1.12.2 timings diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index 9631fa93b821c7f6bc6dc707c2c82cce2ae8291e..e229c7735ba88be3d8721440104958408a2a075e 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -86,7 +86,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener } // Paper end } else if (this.state == ServerLoginPacketListenerImpl.State.DELAY_ACCEPT) { - ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId()); + ServerPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.gameProfile.getId()); // Paper if (entityplayer == null) { this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT; @@ -186,7 +186,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener } this.connection.send(new ClientboundGameProfilePacket(this.gameProfile)); - ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId()); + ServerPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.gameProfile.getId()); // Paper if (entityplayer != null) { this.state = ServerLoginPacketListenerImpl.State.DELAY_ACCEPT; diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index 454d60566743e02e7e55868c7bb45e30583dfa8f..ffc8c9ee8b1768dd809189858ee45658fb9bf1c5 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -36,6 +36,7 @@ import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket; import net.minecraft.network.protocol.game.ClientboundChatPacket; import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.game.ClientboundDisconnectPacket; import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; import net.minecraft.network.protocol.game.ClientboundGameEventPacket; import net.minecraft.network.protocol.game.ClientboundLoginPacket; @@ -59,6 +60,8 @@ import net.minecraft.server.MCUtil; import net.minecraft.server.MinecraftServer; import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.ServerScoreboard; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayerGameMode; @@ -124,11 +127,12 @@ public abstract class PlayerList { private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); private final MinecraftServer server; public final List players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety - private final Map playersByUUID = Maps.newHashMap(); + private final Map playersByUUID = Maps.newHashMap();Map getUUIDMap() { return playersByUUID; } // Paper - OBFHELPER private final UserBanList bans; private final IpBanList ipBans; private final ServerOpList ops; private final UserWhiteList whitelist; + private final Map pendingPlayers = Maps.newHashMap(); // Paper // CraftBukkit start // private final Map o; // private final Map p; @@ -167,6 +171,11 @@ public abstract class PlayerList { } public void placeNewPlayer(Connection connection, ServerPlayer player) { + ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);// Paper + if (prev != null) { + disconnectPendingPlayer(prev); + } + player.networkManager = connection; // Paper player.loginTime = System.currentTimeMillis(); // Paper GameProfile gameprofile = player.getGameProfile(); GameProfileCache usercache = this.server.getProfileCache(); @@ -180,7 +189,7 @@ public abstract class PlayerList { if (nbttagcompound != null && nbttagcompound.contains("bukkit")) { CompoundTag bukkit = nbttagcompound.getCompound("bukkit"); s = bukkit.contains("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s; - } + }String lastKnownName = s; // Paper // CraftBukkit end if (nbttagcompound != null) { @@ -255,34 +264,79 @@ public abstract class PlayerList { player.getRecipeBook().sendInitialRecipeBook(player); this.updateEntireScoreboard(worldserver1.getScoreboard(), player); this.server.invalidateStatus(); + // Paper start - async load spawn in chunk + ServerLevel finalWorldserver = worldserver1; + int chunkX = loc.getBlockX() >> 4; + int chunkZ = loc.getBlockZ() >> 4; + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); + ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap; + playerChunkMap.getChunkDistanceManager().addTicketAtLevel(TicketType.LOGIN, pos, 31, pos.toLong()); + worldserver1.getChunkSource().runDistanceManagerUpdates(); + worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> { + ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pos.toLong()); + if (updatingChunk != null) { + return updatingChunk.getEntityTickingFuture(); + } else { + return java.util.concurrent.CompletableFuture.completedFuture(chunk); + } + }).thenAccept(chunk -> { + playerconnection.playerJoinReady = () -> { + postChunkLoadJoin( + player, finalWorldserver, connection, playerconnection, + nbttagcompound, connection.getRemoteAddress().toString(), lastKnownName + ); + }; + }); + } + + public ServerPlayer getActivePlayer(UUID uuid) { + ServerPlayer player = this.getUUIDMap().get(uuid); + return player != null ? player : pendingPlayers.get(uuid); + } + + void disconnectPendingPlayer(ServerPlayer entityplayer) { + TranslatableComponent msg = new TranslatableComponent("multiplayer.disconnect.duplicate_login", new Object[0]); + entityplayer.networkManager.send(new ClientboundDisconnectPacket(msg), (future) -> { + entityplayer.networkManager.disconnect(msg); + entityplayer.networkManager = null; + }); + } + + private void postChunkLoadJoin(ServerPlayer entityplayer, ServerLevel worldserver1, Connection networkmanager, ServerGamePacketListenerImpl playerconnection, CompoundTag nbttagcompound, String s1, String s) { + pendingPlayers.remove(entityplayer.getUUID(), entityplayer); + if (!networkmanager.isConnected()) { + return; + } + entityplayer.didPlayerJoinEvent = true; + // Paper end TranslatableComponent chatmessage; - if (player.getGameProfile().getName().equalsIgnoreCase(s)) { - chatmessage = new TranslatableComponent("multiplayer.player.joined", new Object[]{player.getDisplayName()}); + if (entityplayer.getGameProfile().getName().equalsIgnoreCase(s)) { + chatmessage = new TranslatableComponent("multiplayer.player.joined", new Object[]{entityplayer.getDisplayName()}); } else { - chatmessage = new TranslatableComponent("multiplayer.player.joined.renamed", new Object[]{player.getDisplayName(), s}); + chatmessage = new TranslatableComponent("multiplayer.player.joined.renamed", new Object[]{entityplayer.getDisplayName(), s}); } // CraftBukkit start chatmessage.withStyle(ChatFormatting.YELLOW); Component joinMessage = chatmessage; // Paper - Adventure - playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.yRot, player.xRot); - this.players.add(player); - this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot - this.playersByUUID.put(player.getUUID(), player); + playerconnection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.yRot, entityplayer.xRot); + this.players.add(entityplayer); + this.playersByName.put(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT), entityplayer); // Spigot + this.playersByUUID.put(entityplayer.getUUID(), entityplayer); // this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, new EntityPlayer[]{entityplayer})); // CraftBukkit - replaced with loop below // Paper start - correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks - player.supressTrackerForLogin = true; - worldserver1.addNewPlayer(player); - this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer); - mountSavedVehicle(player, worldserver1, nbttagcompound); + entityplayer.supressTrackerForLogin = true; + worldserver1.addNewPlayer(entityplayer); + this.server.getCustomBossEvents().onPlayerConnect(entityplayer); // see commented out section below worldserver.addPlayerJoin(entityplayer); + mountSavedVehicle(entityplayer, worldserver1, nbttagcompound); // Paper end // CraftBukkit start - PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(cserver.getPlayer(player), PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure + PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(cserver.getPlayer(entityplayer), PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure cserver.getPluginManager().callEvent(playerJoinEvent); - if (!player.connection.connection.isConnected()) { + if (!entityplayer.connection.connection.isConnected()) { return; } @@ -297,51 +351,51 @@ public abstract class PlayerList { // CraftBukkit end // CraftBukkit start - sendAll above replaced with this loop - ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, player); + ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, entityplayer); for (int i = 0; i < this.players.size(); ++i) { ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); - if (entityplayer1.getBukkitEntity().canSee(player.getBukkitEntity())) { + if (entityplayer1.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) { entityplayer1.connection.send(packet); } - if (!player.getBukkitEntity().canSee(entityplayer1.getBukkitEntity())) { + if (!entityplayer.getBukkitEntity().canSee(entityplayer1.getBukkitEntity())) { continue; } - player.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, new ServerPlayer[] { entityplayer1})); + entityplayer.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, new ServerPlayer[] { entityplayer1})); } - player.sentListPacket = true; - player.supressTrackerForLogin = false; // Paper - ((ServerLevel)player.level).getChunkSource().chunkMap.addEntity(player); // Paper - track entity now + entityplayer.sentListPacket = true; + entityplayer.supressTrackerForLogin = false; // Paper + ((ServerLevel)entityplayer.level).getChunkSource().chunkMap.addEntity(entityplayer); // Paper - track entity now // CraftBukkit end - player.connection.send(new ClientboundSetEntityDataPacket(player.getId(), player.getEntityData(), true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn + entityplayer.connection.send(new ClientboundSetEntityDataPacket(entityplayer.getId(), entityplayer.getEntityData(), true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn // CraftBukkit start - Only add if the player wasn't moved in the event - if (player.level == worldserver1 && !worldserver1.players().contains(player)) { - worldserver1.addNewPlayer(player); - this.server.getCustomBossEvents().onPlayerConnect(player); + if (entityplayer.level == worldserver1 && !worldserver1.players().contains(entityplayer)) { + worldserver1.addNewPlayer(entityplayer); + this.server.getCustomBossEvents().onPlayerConnect(entityplayer); } - worldserver1 = player.getLevel(); // CraftBukkit - Update in case join event changed it + worldserver1 = entityplayer.getLevel(); // CraftBukkit - Update in case join event changed it // CraftBukkit end - this.sendLevelInfo(player, worldserver1); + this.sendLevelInfo(entityplayer, worldserver1); if (!this.server.getResourcePack().isEmpty()) { - player.sendTexturePack(this.server.getResourcePack(), this.server.getResourcePackHash()); + entityplayer.sendTexturePack(this.server.getResourcePack(), this.server.getResourcePackHash()); } - Iterator iterator = player.getActiveEffects().iterator(); + Iterator iterator = entityplayer.getActiveEffects().iterator(); while (iterator.hasNext()) { MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); - playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect)); + playerconnection.send(new ClientboundUpdateMobEffectPacket(entityplayer.getId(), mobeffect)); } // Paper start - move vehicle into method so it can be called above - short circuit around that code - onPlayerJoinFinish(player, worldserver1, s1); + onPlayerJoinFinish(entityplayer, worldserver1, s1); } private void mountSavedVehicle(ServerPlayer entityplayer, ServerLevel worldserver1, CompoundTag nbttagcompound) { // Paper end @@ -492,6 +546,7 @@ public abstract class PlayerList { protected void save(ServerPlayer player) { if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit + if (!player.didPlayerJoinEvent) return; // Paper - If we never fired PJE, we disconnected during login. Data has not changed, and additionally, our saved vehicle is not loaded! If we save now, we will lose our vehicle (CraftBukkit bug) this.playerIo.save(player); ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit @@ -519,7 +574,7 @@ public abstract class PlayerList { } PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, com.destroystokyo.paper.PaperConfig.useDisplayNameInQuit ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); - cserver.getPluginManager().callEvent(playerQuitEvent); + if (entityplayer.didPlayerJoinEvent) cserver.getPluginManager().callEvent(playerQuitEvent); // Paper - if we disconnected before join ever fired, don't fire quit entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); if (server.isSameThread()) entityplayer.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog) @@ -572,6 +627,13 @@ public abstract class PlayerList { // this.p.remove(uuid); // CraftBukkit end } + // Paper start + entityplayer1 = pendingPlayers.get(uuid); + if (entityplayer1 == entityplayer) { + pendingPlayers.remove(uuid); + } + entityplayer.networkManager = null; + // Paper end // CraftBukkit start // this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, new EntityPlayer[]{entityplayer})); @@ -589,7 +651,7 @@ public abstract class PlayerList { cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); // CraftBukkit end - return playerQuitEvent.quitMessage(); // Paper - Adventure + return entityplayer.didPlayerJoinEvent ? playerQuitEvent.quitMessage() : null; // CraftBukkit // Paper - Adventure // Paper - don't print quit if we never printed join } // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer @@ -608,6 +670,13 @@ public abstract class PlayerList { list.add(entityplayer); } } + // Paper start - check pending players too + entityplayer = pendingPlayers.get(uuid); + if (entityplayer != null) { + this.pendingPlayers.remove(uuid); + disconnectPendingPlayer(entityplayer); + } + // Paper end Iterator iterator = list.iterator(); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index b8dcc91a191f25ca578e0858abf6c1b874fee15d..9f0371282f5829d26dc9618c3d466bccaa4cd3af 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -1371,7 +1371,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s this.yo = y; this.zo = d4; this.setPos(d3, y, d4); - level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit + if (valid) level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit // Paper } public void moveTo(Vec3 vec3d) {