diff --git a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java index f3229fb0c..bbc30ea9a 100644 --- a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java @@ -15,12 +15,14 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerFishEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; @@ -264,6 +266,34 @@ public class PlayerListener implements Listener { BleedTimerTask.bleedOut(player); // Bleed it out } + /** + * Start user data prefetch. + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onFirstLogin(AsyncPlayerPreLoginEvent event) { + UserManager.prefetchUserData(event.getName()); + } + + /** + * Cancel user data prefetch if another plugin kicks them. + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false) + public void onPreLoginComplete(AsyncPlayerPreLoginEvent event) { + if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { + UserManager.discardPrefetch(event.getName()); + } + } + + /** + * Cancel user data prefetch if they're banned or a plugin kicks them. + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false) + public void onLoginComplete(PlayerLoginEvent event) { + if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { + UserManager.discardPrefetch(event.getPlayer().getName()); + } + } + /** * Monitor PlayerJoin events. * diff --git a/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoader.java b/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoader.java new file mode 100644 index 000000000..fb6465c3e --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoader.java @@ -0,0 +1,19 @@ +package com.gmail.nossr50.runnables.player; + +import java.util.concurrent.Callable; + +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.datatypes.player.PlayerProfile; + +public class PlayerProfileLoader implements Callable { + private final String playerName; + + public PlayerProfileLoader(String player) { + this.playerName = player; + } + + @Override + public PlayerProfile call() { + return mcMMO.getDatabaseManager().loadPlayerProfile(playerName, true); + } +} diff --git a/src/main/java/com/gmail/nossr50/util/player/UserManager.java b/src/main/java/com/gmail/nossr50/util/player/UserManager.java index b547dfa9d..202ab84cf 100644 --- a/src/main/java/com/gmail/nossr50/util/player/UserManager.java +++ b/src/main/java/com/gmail/nossr50/util/player/UserManager.java @@ -1,22 +1,55 @@ package com.gmail.nossr50.util.player; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.runnables.player.PlayerProfileLoader; public final class UserManager { - private final static Map players = new HashMap(); + private final static Map players = new HashMap(); + private final static Map> loadTasks = new HashMap>(); + private final static ExecutorService loadExecutor = Executors.newCachedThreadPool(); private UserManager() {}; /** - * Add a new user. + * Asynchronously pre-fetch information about the player. This is intended + * to expedite the PlayerJoinEvent. + * + * @param playerName The player name + */ + public static void prefetchUserData(String playerName) { + loadTasks.put(playerName, loadExecutor.submit(new PlayerProfileLoader(playerName))); + } + + /** + * Discard the information from the prefetch - for example, due to the + * user being banned. + * + * @param playerName The player name + */ + public static void discardPrefetch(String playerName) { + Future oldTask = loadTasks.remove(playerName); + if (oldTask != null) { + oldTask.cancel(false); + } + } + + /** + * Add a new user. If the prefetched player information is available, it + * will be used. * * @param player The player to create a user record for * @return the player's {@link McMMOPlayer} object @@ -29,7 +62,22 @@ public final class UserManager { mcMMOPlayer.setPlayer(player); // The player object is different on each reconnection and must be updated } else { + Future task = loadTasks.remove(playerName); + if (task != null && !task.isCancelled()) { + try { + mcMMOPlayer = new McMMOPlayer(player, task.get()); + // TODO copy any additional post-processing here + players.put(playerName, mcMMOPlayer); + return mcMMOPlayer; + } + catch (ExecutionException e) { + } + catch (InterruptedException e) { + } + } + // Did not return - load on main thread mcMMOPlayer = new McMMOPlayer(player); + // (start post-processing that must be copied above) players.put(playerName, mcMMOPlayer); } @@ -43,12 +91,14 @@ public final class UserManager { */ public static void remove(String playerName) { players.remove(playerName); + discardPrefetch(playerName); } /** * Clear all users. */ public static void clearAll() { + discardAllPrefetch(); players.clear(); } @@ -56,11 +106,23 @@ public final class UserManager { * Save all users. */ public static void saveAll() { + discardAllPrefetch(); for (McMMOPlayer mcMMOPlayer : players.values()) { mcMMOPlayer.getProfile().save(); } } + /** + * Discard / cancel all data prefetching. + */ + public static void discardAllPrefetch() { + Iterator> taskIter = loadTasks.values().iterator(); + while (taskIter.hasNext()) { + taskIter.next().cancel(false); + taskIter.remove(); + } + } + public static Map getPlayers() { return players; }