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/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java index 0080a0cbe58d1a81c434ef97659428c8bf1ec290..eebd4c50a7324250d3ebe7060739a71af4243f72 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -604,7 +604,7 @@ public class ChunkProviderServer extends IChunkProvider { return this.serverThreadQueue.executeNext(); } - private boolean tickDistanceManager() { + public boolean tickDistanceManager() { // Paper - private -> public boolean flag = this.chunkMapDistance.a(this.playerChunkMap); boolean flag1 = this.playerChunkMap.b(); diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index c879e5d9acd400db5b7fdb770e5f8cc419e3bb23..228666eccfb924d2844c911e734eef4b0a6b3afa 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -1326,7 +1326,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke this.pitch = MathHelper.a(f1, -90.0F, 90.0F) % 360.0F; this.lastYaw = this.yaw; this.lastPitch = this.pitch; - world.getChunkAt((int) Math.floor(this.locX()) >> 4, (int) Math.floor(this.locZ()) >> 4); // CraftBukkit + if (valid) world.getChunkAt((int) Math.floor(this.locX()) >> 4, (int) Math.floor(this.locZ()) >> 4); // CraftBukkit // Paper } public void c(Vec3D vec3d) { diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java index 56cf64709ae520287f6687fe7c2f55040cc769a1..0a5f784e851414f84e5a94b53f5616531f533109 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -43,6 +43,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { private static final Logger LOGGER = LogManager.getLogger(); public PlayerConnection playerConnection; + public NetworkManager networkManager; // Paper public final MinecraftServer server; public final PlayerInteractManager playerInteractManager; public final Deque removeQueue = new ArrayDeque<>(); // Paper @@ -105,6 +106,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { 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/LoginListener.java b/src/main/java/net/minecraft/server/LoginListener.java index 50163f7291474062bde81631c9220e8a9f5bb718..ebab6961b752ca0b1c1b28f85abb33c7b4d70e3c 100644 --- a/src/main/java/net/minecraft/server/LoginListener.java +++ b/src/main/java/net/minecraft/server/LoginListener.java @@ -66,7 +66,7 @@ public class LoginListener implements PacketLoginInListener { } // Paper end } else if (this.g == LoginListener.EnumProtocolState.DELAY_ACCEPT) { - EntityPlayer entityplayer = this.server.getPlayerList().getPlayer(this.i.getId()); + EntityPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.i.getId()); // Paper if (entityplayer == null) { this.g = LoginListener.EnumProtocolState.READY_TO_ACCEPT; @@ -165,7 +165,7 @@ public class LoginListener implements PacketLoginInListener { } this.networkManager.sendPacket(new PacketLoginOutSuccess(this.i)); - EntityPlayer entityplayer = this.server.getPlayerList().getPlayer(this.i.getId()); + EntityPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.i.getId()); // Paper if (entityplayer != null) { this.g = LoginListener.EnumProtocolState.DELAY_ACCEPT; diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java index 1b9a089eddacbaf1a7d7e19478ee756b4ab601b2..4f37401909f1845d0f18aa497a6561b705e2fd14 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -69,6 +69,7 @@ public class PlayerConnection implements PacketListenerPlayIn { private static final Logger LOGGER = LogManager.getLogger(); public final NetworkManager networkManager; private final MinecraftServer minecraftServer; + Runnable playerJoinReady; // Paper public EntityPlayer player; private int e; private long lastKeepAlive = SystemUtils.getMonotonicMillis(); private void setLastPing(long lastPing) { this.lastKeepAlive = lastPing;}; private long getLastPing() { return this.lastKeepAlive;}; // Paper - OBFHELPER @@ -142,6 +143,15 @@ public class PlayerConnection implements PacketListenerPlayIn { // 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.syncPosition(); this.player.lastX = this.player.locX(); this.player.lastY = this.player.locY(); @@ -183,7 +193,7 @@ public class PlayerConnection implements PacketListenerPlayIn { this.r = null; this.D = false; this.E = 0; - } + }} // Paper - end if (valid) this.minecraftServer.getMethodProfiler().enter("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/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java index 0f50aef7ff3c06cad0afa22924da170e04780053..f7c23ec053b261a5e7e069fb61dd803c385d6758 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nullable; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -54,11 +55,12 @@ public abstract class PlayerList { private static final SimpleDateFormat g = 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 j = Maps.newHashMap(); + private final Map j = Maps.newHashMap();Map getUUIDMap() { return j; } // Paper - OBFHELPER private final GameProfileBanList k; private final IpBanList l; private final OpList operators; private final WhiteList whitelist; + private final Map pendingPlayers = Maps.newHashMap(); // Paper // CraftBukkit start // private final Map o; // private final Map p; @@ -97,6 +99,11 @@ public abstract class PlayerList { } public void a(NetworkManager networkmanager, EntityPlayer entityplayer) { + EntityPlayer prev = pendingPlayers.put(entityplayer.getUniqueID(), entityplayer);// Paper + if (prev != null) { + disconnectPendingPlayer(prev); + } + entityplayer.networkManager = networkmanager; // Paper entityplayer.loginTime = System.currentTimeMillis(); // Paper GameProfile gameprofile = entityplayer.getProfile(); UserCache usercache = this.server.getUserCache(); @@ -110,7 +117,7 @@ public abstract class PlayerList { if (nbttagcompound != null && nbttagcompound.hasKey("bukkit")) { NBTTagCompound bukkit = nbttagcompound.getCompound("bukkit"); s = bukkit.hasKeyOfType("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s; - } + }String lastKnownName = s; // Paper // CraftBukkit end if (nbttagcompound != null) { @@ -185,6 +192,53 @@ public abstract class PlayerList { entityplayer.B().a(entityplayer); this.sendScoreboard(worldserver1.getScoreboard(), entityplayer); this.server.invalidatePingSample(); + // Paper start - async load spawn in chunk + WorldServer finalWorldserver = worldserver; + WorldServer finalWorldserver1 = worldserver1; + int chunkX = loc.getBlockX() >> 4; + int chunkZ = loc.getBlockZ() >> 4; + final ChunkCoordIntPair pos = new ChunkCoordIntPair(chunkX, chunkZ); + PlayerChunkMap playerChunkMap = finalWorldserver.getChunkProvider().playerChunkMap; + playerChunkMap.chunkDistanceManager.addTicketAtLevel(TicketType.LOGIN, pos, 31, pos.pair()); + worldserver.getChunkProvider().tickDistanceManager(); + worldserver.getChunkProvider().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> { + PlayerChunk updatingChunk = playerChunkMap.getUpdatingChunk(pos.pair()); + if (updatingChunk != null) { + return updatingChunk.getEntityTickingFuture(); + } else { + return CompletableFuture.completedFuture(chunk); + } + }).thenAccept(chunk -> { + playerconnection.playerJoinReady = () -> { + postChunkLoadJoin( + entityplayer, finalWorldserver, finalWorldserver1, networkmanager, playerconnection, + nbttagcompound, networkmanager.getSocketAddress().toString(), lastKnownName + ); + //playerChunkMap.chunkDistanceManager.removeTicketAtLevel(TicketType.LOGIN, pos, 31, pos.pair()); + }; + }); + } + + EntityPlayer getActivePlayer(UUID uuid) { + EntityPlayer player = this.getUUIDMap().get(uuid); + return player != null ? player : pendingPlayers.get(uuid); + } + + void disconnectPendingPlayer(EntityPlayer entityplayer) { + ChatMessage msg = new ChatMessage("multiplayer.disconnect.duplicate_login", new Object[0]); + entityplayer.networkManager.sendPacket(new PacketPlayOutKickDisconnect(msg), (future) -> { + entityplayer.networkManager.close(msg); + entityplayer.networkManager = null; + }); + } + + private void postChunkLoadJoin(EntityPlayer entityplayer, WorldServer worldserver, WorldServer worldserver1, NetworkManager networkmanager, PlayerConnection playerconnection, NBTTagCompound nbttagcompound, String s1, String s) { + pendingPlayers.remove(entityplayer.getUniqueID(), entityplayer); + if (!networkmanager.isConnected()) { + return; + } + entityplayer.didPlayerJoinEvent = true; + // Paper end ChatMessage chatmessage; if (entityplayer.getProfile().getName().equalsIgnoreCase(s)) { @@ -206,7 +260,7 @@ public abstract class PlayerList { entityplayer.supressTrackerForLogin = true; worldserver.addPlayerJoin(entityplayer); this.server.getBossBattleCustomData().a(entityplayer); // see commented out section below worldserver.addPlayerJoin(entityplayer); - mountSavedVehicle(entityplayer, worldserver, nbttagcompound); + mountSavedVehicle(entityplayer, worldserver, worldserver1, nbttagcompound); // Paper end // CraftBukkit start PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(cserver.getPlayer(entityplayer), joinMessage); @@ -243,7 +297,6 @@ public abstract class PlayerList { } entityplayer.sentListPacket = true; entityplayer.supressTrackerForLogin = false; // Paper - ((WorldServer)entityplayer.world).getChunkProvider().playerChunkMap.addEntity(entityplayer); // Paper - track entity now // CraftBukkit end entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityMetadata(entityplayer.getId(), entityplayer.datawatcher, true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn @@ -270,9 +323,10 @@ public abstract class PlayerList { } // Paper start - move vehicle into method so it can be called above - short circuit around that code + ((WorldServer)entityplayer.world).getChunkProvider().playerChunkMap.addEntity(entityplayer); // track entity now onPlayerJoinFinish(entityplayer, worldserver, s1); } - private void mountSavedVehicle(EntityPlayer entityplayer, WorldServer worldserver, NBTTagCompound nbttagcompound) { + private void mountSavedVehicle(EntityPlayer entityplayer, WorldServer worldserver, WorldServer worldserver1, NBTTagCompound nbttagcompound) { // Paper end if (nbttagcompound != null && nbttagcompound.hasKeyOfType("RootVehicle", 10)) { NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("RootVehicle"); @@ -421,6 +475,7 @@ public abstract class PlayerList { protected void savePlayerFile(EntityPlayer entityplayer) { if (!entityplayer.getBukkitEntity().isPersistent()) return; // CraftBukkit + if (!entityplayer.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.playerFileData.save(entityplayer); ServerStatisticManager serverstatisticmanager = (ServerStatisticManager) entityplayer.getStatisticManager(); // CraftBukkit @@ -445,7 +500,7 @@ public abstract class PlayerList { entityplayer.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), "\u00A7e" + entityplayer.getName() + " left the game"); - 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.isMainThread()) entityplayer.playerTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog) @@ -498,6 +553,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})); @@ -515,7 +577,7 @@ public abstract class PlayerList { cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); // CraftBukkit end - return playerQuitEvent.getQuitMessage(); // CraftBukkit + return entityplayer.didPlayerJoinEvent ? playerQuitEvent.getQuitMessage() : null; // CraftBukkit // Paper - don't print quit if we never printed join } // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer @@ -534,6 +596,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/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java index 8055f5998213ab1c6c10d03d88d2b14d220a5e40..d7b9d9fd3a3b607278a3d72b0b306b0be2aa30ad 100644 --- a/src/main/java/net/minecraft/server/TicketType.java +++ b/src/main/java/net/minecraft/server/TicketType.java @@ -17,6 +17,7 @@ public class TicketType { public static final TicketType FORCED = a("forced", Comparator.comparingLong(ChunkCoordIntPair::pair)); public static final TicketType LIGHT = a("light", Comparator.comparingLong(ChunkCoordIntPair::pair)); public static final TicketType PORTAL = a("portal", BaseBlockPosition::compareTo, 300); + public static final TicketType LOGIN = a("login", Long::compareTo, 100); // Paper public static final TicketType POST_TELEPORT = a("post_teleport", Integer::compareTo, 5); public static final TicketType UNKNOWN = a("unknown", Comparator.comparingLong(ChunkCoordIntPair::pair), 1); public static final TicketType PLUGIN = a("plugin", (a, b) -> 0); // CraftBukkit