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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -0,0 +0,0 @@ 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; @@ -0,0 +0,0 @@ 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 @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/TicketType.java +++ b/src/main/java/net/minecraft/server/level/TicketType.java @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -0,0 +0,0 @@ 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 @@ -0,0 +0,0 @@ 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(); @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -0,0 +0,0 @@ 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; @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -0,0 +0,0 @@ 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; @@ -0,0 +0,0 @@ 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; @@ -0,0 +0,0 @@ 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; @@ -0,0 +0,0 @@ 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(); @@ -0,0 +0,0 @@ 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) { @@ -0,0 +0,0 @@ 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; } @@ -0,0 +0,0 @@ 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 @@ -0,0 +0,0 @@ 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 @@ -0,0 +0,0 @@ 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) @@ -0,0 +0,0 @@ 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})); @@ -0,0 +0,0 @@ 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 @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -0,0 +0,0 @@ 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) {