浏览代码

SQLDatabaseManager optimizations, async profile loading -t00thpick1, zreed
This commit changes our shared connection into a connection pool utility to prevent
thread locks from multiple actions attempting to access the database at the same time.
In additon, profile loading has been moved off the main thread at login time, to
allieviate the performance issues caused by it.

Fixes #2138, Fixes #2119, Fixes #1982, Fixes #1953

t00thpick1 11 年之前
父节点
当前提交
857e12b96e

+ 1 - 0
Changelog.txt

@@ -10,6 +10,7 @@ Key:
 Version 1.5.01-dev
 Version 1.5.01-dev
  + Added new child skill; Salvage
  + Added new child skill; Salvage
  + Added UUID support!
  + Added UUID support!
+ + Added SQL connection pooling and async loading!
  + Added new feature to Herbalism. Instantly-regrown crops are protected from being broken for 1 second
  + Added new feature to Herbalism. Instantly-regrown crops are protected from being broken for 1 second
  + Added option to config.yml to show the /mcstats scoreboard automatically after logging in
  + Added option to config.yml to show the /mcstats scoreboard automatically after logging in
  + Added option to config.yml for Alchemy. Skills.Alchemy.Prevent_Hopper_Transfer_Bottles
  + Added option to config.yml for Alchemy. Skills.Alchemy.Prevent_Hopper_Transfer_Bottles

+ 15 - 0
pom.xml

@@ -76,6 +76,8 @@
                     <artifactSet>
                     <artifactSet>
                         <includes>
                         <includes>
                             <include>com.turt2live.metrics:MetricsExtension</include>
                             <include>com.turt2live.metrics:MetricsExtension</include>
+                            <include>commons-logging:commons-logging</include>
+                            <include>net.snaq:dbpool</include>
                         </includes>
                         </includes>
                     </artifactSet>
                     </artifactSet>
                     <relocations>
                     <relocations>
@@ -83,6 +85,14 @@
                             <pattern>com.turt2live.metrics</pattern>
                             <pattern>com.turt2live.metrics</pattern>
                             <shadedPattern>com.gmail.nossr50.metrics.mcstats</shadedPattern>
                             <shadedPattern>com.gmail.nossr50.metrics.mcstats</shadedPattern>
                         </relocation>
                         </relocation>
+                        <relocation>
+                            <pattern>org.apache.commons.logging</pattern>
+                            <shadedPattern>com.gmail.nossr50.commons.logging</shadedPattern>
+                        </relocation>
+                        <relocation>
+                            <pattern>net.snaq</pattern>
+                            <shadedPattern>com.gmail.nossr50.dbpool</shadedPattern>
+                        </relocation>
                     </relocations>
                     </relocations>
                 </configuration>
                 </configuration>
                 <executions>
                 <executions>
@@ -136,6 +146,11 @@
             <artifactId>MetricsExtension</artifactId>
             <artifactId>MetricsExtension</artifactId>
             <version>0.0.5-SNAPSHOT</version>
             <version>0.0.5-SNAPSHOT</version>
         </dependency>
         </dependency>
+        <dependency>
+            <groupId>net.snaq</groupId>
+            <artifactId>dbpool</artifactId>
+            <version>5.1</version>
+        </dependency>
     </dependencies>
     </dependencies>
     <distributionManagement>
     <distributionManagement>
         <repository>
         <repository>

+ 2 - 2
src/main/java/com/gmail/nossr50/api/ExperienceAPI.java

@@ -930,7 +930,7 @@ public final class ExperienceAPI {
     }
     }
 
 
     private static PlayerProfile getOfflineProfile(UUID uuid) {
     private static PlayerProfile getOfflineProfile(UUID uuid) {
-        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, false);
+        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
 
 
         if (!profile.isLoaded()) {
         if (!profile.isLoaded()) {
             throw new InvalidPlayerException();
             throw new InvalidPlayerException();
@@ -942,7 +942,7 @@ public final class ExperienceAPI {
     @Deprecated
     @Deprecated
     private static PlayerProfile getOfflineProfile(String playerName) {
     private static PlayerProfile getOfflineProfile(String playerName) {
         UUID uuid = mcMMO.p.getServer().getOfflinePlayer(playerName).getUniqueId();
         UUID uuid = mcMMO.p.getServer().getOfflinePlayer(playerName).getUniqueId();
-        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, false);
+        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
 
 
         if (!profile.isLoaded()) {
         if (!profile.isLoaded()) {
             throw new InvalidPlayerException();
             throw new InvalidPlayerException();

+ 3 - 2
src/main/java/com/gmail/nossr50/commands/database/ConvertDatabaseCommand.java

@@ -12,6 +12,7 @@ import com.gmail.nossr50.datatypes.database.DatabaseType;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.runnables.database.DatabaseConversionTask;
 import com.gmail.nossr50.runnables.database.DatabaseConversionTask;
+import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.player.UserManager;
 
 
 public class ConvertDatabaseCommand implements CommandExecutor {
 public class ConvertDatabaseCommand implements CommandExecutor {
@@ -55,13 +56,13 @@ public class ConvertDatabaseCommand implements CommandExecutor {
                 UserManager.clearAll();
                 UserManager.clearAll();
 
 
                 for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
                 for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
-                    PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getUniqueId(), false);
+                    PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getUniqueId());
 
 
                     if (profile.isLoaded()) {
                     if (profile.isLoaded()) {
                         mcMMO.getDatabaseManager().saveUser(profile);
                         mcMMO.getDatabaseManager().saveUser(profile);
                     }
                     }
 
 
-                    UserManager.addUser(player);
+                    new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
                 }
                 }
 
 
                 new DatabaseConversionTask(oldDatabase, sender, previousType.toString(), newType.toString()).runTaskAsynchronously(mcMMO.p);
                 new DatabaseConversionTask(oldDatabase, sender, previousType.toString(), newType.toString()).runTaskAsynchronously(mcMMO.p);

+ 2 - 1
src/main/java/com/gmail/nossr50/commands/experience/ConvertExperienceCommand.java

@@ -9,6 +9,7 @@ import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.datatypes.experience.FormulaType;
 import com.gmail.nossr50.datatypes.experience.FormulaType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.runnables.database.FormulaConversionTask;
 import com.gmail.nossr50.runnables.database.FormulaConversionTask;
+import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.player.UserManager;
 
 
 public class ConvertExperienceCommand implements CommandExecutor {
 public class ConvertExperienceCommand implements CommandExecutor {
@@ -37,7 +38,7 @@ public class ConvertExperienceCommand implements CommandExecutor {
                 new FormulaConversionTask(sender, newType).runTaskLater(mcMMO.p, 1);
                 new FormulaConversionTask(sender, newType).runTaskLater(mcMMO.p, 1);
 
 
                 for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
                 for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
-                    UserManager.addUser(player);
+                    new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
                 }
                 }
 
 
                 return true;
                 return true;

+ 3 - 0
src/main/java/com/gmail/nossr50/config/Config.java

@@ -232,6 +232,7 @@ public class Config extends AutoUpdateConfigLoader {
     /* General Settings */
     /* General Settings */
     public String getLocale() { return config.getString("General.Locale", "en_us"); }
     public String getLocale() { return config.getString("General.Locale", "en_us"); }
     public boolean getMOTDEnabled() { return config.getBoolean("General.MOTD_Enabled", true); }
     public boolean getMOTDEnabled() { return config.getBoolean("General.MOTD_Enabled", true); }
+    public boolean getShowProfileLoadedMessage() { return config.getBoolean("General.Show_Profile_Loaded", true); }
     public boolean getDonateMessageEnabled() { return config.getBoolean("Commands.mcmmo.Donate_Message", true); }
     public boolean getDonateMessageEnabled() { return config.getBoolean("Commands.mcmmo.Donate_Message", true); }
     public int getSaveInterval() { return config.getInt("General.Save_Interval", 10); }
     public int getSaveInterval() { return config.getInt("General.Save_Interval", 10); }
     public boolean getStatsTrackingEnabled() { return config.getBoolean("General.Stats_Tracking", true); }
     public boolean getStatsTrackingEnabled() { return config.getBoolean("General.Stats_Tracking", true); }
@@ -313,6 +314,8 @@ public class Config extends AutoUpdateConfigLoader {
     public int getMySQLServerPort() { return config.getInt("MySQL.Server.Port", 3306); }
     public int getMySQLServerPort() { return config.getInt("MySQL.Server.Port", 3306); }
     public String getMySQLServerName() { return config.getString("MySQL.Server.Address", "localhost"); }
     public String getMySQLServerName() { return config.getString("MySQL.Server.Address", "localhost"); }
     public String getMySQLUserPassword() { return getStringIncludingInts("MySQL.Database.User_Password"); }
     public String getMySQLUserPassword() { return getStringIncludingInts("MySQL.Database.User_Password"); }
+    public int getMySQLMaxConnections() { return config.getInt("MySQL.Database.MaxConnections"); }
+    public int getMySQLMaxPoolSize() { return config.getInt("MySQL.Database.MaxPoolSize"); }
 
 
     private String getStringIncludingInts(String key) {
     private String getStringIncludingInts(String key) {
         String str = config.getString(key);
         String str = config.getString(key);

+ 7 - 5
src/main/java/com/gmail/nossr50/database/DatabaseManager.java

@@ -73,7 +73,7 @@ public interface DatabaseManager {
     /**
     /**
      * Load a player from the database.
      * Load a player from the database.
      *
      *
-     * @deprecated replaced by {@link #loadPlayerProfile(UUID uuid, boolean createNew)}
+     * @deprecated replaced by {@link #loadPlayerProfile(String playerName, UUID uuid, boolean createNew)}
      *
      *
      * @param playerName The name of the player to load from the database
      * @param playerName The name of the player to load from the database
      * @param createNew Whether to create a new record if the player is not
      * @param createNew Whether to create a new record if the player is not
@@ -88,12 +88,9 @@ public interface DatabaseManager {
      * Load a player from the database.
      * Load a player from the database.
      *
      *
      * @param uuid The uuid of the player to load from the database
      * @param uuid The uuid of the player to load from the database
-     * @param createNew Whether to create a new record if the player is not
-     *          found
      * @return The player's data, or an unloaded PlayerProfile if not found
      * @return The player's data, or an unloaded PlayerProfile if not found
-     *          and createNew is false
      */
      */
-    public PlayerProfile loadPlayerProfile(UUID uuid, boolean createNew);
+    public PlayerProfile loadPlayerProfile(UUID uuid);
 
 
     /**
     /**
      * Load a player from the database. Attempt to use uuid, fall back on playername
      * Load a player from the database. Attempt to use uuid, fall back on playername
@@ -132,4 +129,9 @@ public interface DatabaseManager {
      * @return The type of database
      * @return The type of database
      */
      */
     public DatabaseType getDatabaseType();
     public DatabaseType getDatabaseType();
+
+    /**
+     * Called when the plugin disables
+     */
+    public void onDisable();
 }
 }

+ 156 - 39
src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java

@@ -2,7 +2,6 @@ package com.gmail.nossr50.database;
 
 
 import java.io.BufferedReader;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.BufferedWriter;
-import java.io.Closeable;
 import java.io.File;
 import java.io.File;
 import java.io.FileReader;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.FileWriter;
@@ -10,6 +9,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Comparator;
+import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.List;
@@ -98,8 +98,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
             }
             }
             finally {
             finally {
-                tryClose(in);
-                tryClose(out);
+                if (in != null) {
+                    try {
+                        in.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                if (out != null) {
+                    try {
+                        out.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
             }
             }
         }
         }
 
 
@@ -164,8 +178,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
             }
             }
             finally {
             finally {
-                tryClose(in);
-                tryClose(out);
+                if (in != null) {
+                    try {
+                        in.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                if (out != null) {
+                    try {
+                        out.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
             }
             }
         }
         }
 
 
@@ -203,8 +231,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
             }
             }
             finally {
             finally {
-                tryClose(in);
-                tryClose(out);
+                if (in != null) {
+                    try {
+                        in.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                if (out != null) {
+                    try {
+                        out.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
             }
             }
         }
         }
 
 
@@ -294,8 +336,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 return false;
                 return false;
             }
             }
             finally {
             finally {
-                tryClose(in);
-                tryClose(out);
+                if (in != null) {
+                    try {
+                        in.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                if (out != null) {
+                    try {
+                        out.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
             }
             }
         }
         }
     }
     }
@@ -311,7 +367,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
     public Map<SkillType, Integer> readRank(String playerName) {
     public Map<SkillType, Integer> readRank(String playerName) {
         updateLeaderboards();
         updateLeaderboards();
 
 
-        Map<SkillType, Integer> skills = new HashMap<SkillType, Integer>();
+        Map<SkillType, Integer> skills = new EnumMap<SkillType, Integer>(SkillType.class);
 
 
         for (SkillType skill : SkillType.NON_CHILD_SKILLS) {
         for (SkillType skill : SkillType.NON_CHILD_SKILLS) {
             skills.put(skill, getPlayerRank(playerName, playerStatHash.get(skill)));
             skills.put(skill, getPlayerRank(playerName, playerStatHash.get(skill)));
@@ -381,18 +437,25 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 e.printStackTrace();
                 e.printStackTrace();
             }
             }
             finally {
             finally {
-                tryClose(out);
+                if (out != null) {
+                    try {
+                        out.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
             }
             }
         }
         }
     }
     }
 
 
     @Deprecated
     @Deprecated
     public PlayerProfile loadPlayerProfile(String playerName, boolean create) {
     public PlayerProfile loadPlayerProfile(String playerName, boolean create) {
-        return loadPlayerProfile(playerName, "", create);
+        return loadPlayerProfile(playerName, "", false);
     }
     }
 
 
-    public PlayerProfile loadPlayerProfile(UUID uuid, boolean create) {
-        return loadPlayerProfile("", uuid.toString(), create);
+    public PlayerProfile loadPlayerProfile(UUID uuid) {
+        return loadPlayerProfile("", uuid.toString(), false);
     }
     }
 
 
     public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean create) {
     public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean create) {
@@ -448,7 +511,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                         in.close();
                         in.close();
                     }
                     }
                     catch (IOException e) {
                     catch (IOException e) {
-                        e.printStackTrace();
+                        // Ignore
                     }
                     }
                 }
                 }
             }
             }
@@ -491,7 +554,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 e.printStackTrace();
                 e.printStackTrace();
             }
             }
             finally {
             finally {
-                tryClose(in);
+                if (in != null) {
+                    try {
+                        in.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
             }
             }
         }
         }
     }
     }
@@ -532,8 +602,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
             }
             }
             finally {
             finally {
-                tryClose(in);
-                tryClose(out);
+                if (in != null) {
+                    try {
+                        in.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                if (out != null) {
+                    try {
+                        out.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
             }
             }
         }
         }
 
 
@@ -573,8 +657,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
             }
             }
             finally {
             finally {
-                tryClose(in);
-                tryClose(out);
+                if (in != null) {
+                    try {
+                        in.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                if (out != null) {
+                    try {
+                        out.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
             }
             }
         }
         }
 
 
@@ -601,7 +699,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 e.printStackTrace();
                 e.printStackTrace();
             }
             }
             finally {
             finally {
-                tryClose(in);
+                if (in != null) {
+                    try {
+                        in.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
             }
             }
         }
         }
         return users;
         return users;
@@ -671,7 +776,14 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " during user " + playerName + " (Are you sure you formatted it correctly?) " + e.toString());
                 mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " during user " + playerName + " (Are you sure you formatted it correctly?) " + e.toString());
             }
             }
             finally {
             finally {
-                tryClose(in);
+                if (in != null) {
+                    try {
+                        in.close();
+                    }
+                    catch (IOException e) {
+                        // Ignore
+                    }
+                }
             }
             }
         }
         }
 
 
@@ -897,8 +1009,22 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                     mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
                     mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
                 }
                 }
                 finally {
                 finally {
-                    tryClose(in);
-                    tryClose(out);
+                    if (in != null) {
+                        try {
+                            in.close();
+                        }
+                        catch (IOException e) {
+                            // Ignore
+                        }
+                    }
+                    if (out != null) {
+                        try {
+                            out.close();
+                        }
+                        catch (IOException e) {
+                            // Ignore
+                        }
+                    }
                 }
                 }
             }
             }
 
 
@@ -923,18 +1049,6 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
         }
         }
     }
     }
 
 
-    private void tryClose(Closeable c) {
-        if (c == null) {
-            return;
-        }
-        try {
-            c.close();
-        }
-        catch (IOException e) {
-            e.printStackTrace();
-        }
-    }
-
     private Integer getPlayerRank(String playerName, List<PlayerStat> statsList) {
     private Integer getPlayerRank(String playerName, List<PlayerStat> statsList) {
         if (statsList == null) {
         if (statsList == null) {
             return null;
             return null;
@@ -967,8 +1081,8 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
 
 
     private PlayerProfile loadFromLine(String[] character) {
     private PlayerProfile loadFromLine(String[] character) {
         Map<SkillType, Integer>   skills     = getSkillMapFromLine(character);      // Skill levels
         Map<SkillType, Integer>   skills     = getSkillMapFromLine(character);      // Skill levels
-        Map<SkillType, Float>     skillsXp   = new HashMap<SkillType, Float>();     // Skill & XP
-        Map<AbilityType, Integer> skillsDATS = new HashMap<AbilityType, Integer>(); // Ability & Cooldown
+        Map<SkillType, Float>     skillsXp   = new EnumMap<SkillType, Float>(SkillType.class);     // Skill & XP
+        Map<AbilityType, Integer> skillsDATS = new EnumMap<AbilityType, Integer>(AbilityType.class); // Ability & Cooldown
         MobHealthbarType mobHealthbarType;
         MobHealthbarType mobHealthbarType;
 
 
         // TODO on updates, put new values in a try{} ?
         // TODO on updates, put new values in a try{} ?
@@ -1019,7 +1133,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
     }
     }
 
 
     private Map<SkillType, Integer> getSkillMapFromLine(String[] character) {
     private Map<SkillType, Integer> getSkillMapFromLine(String[] character) {
-        Map<SkillType, Integer> skills = new HashMap<SkillType, Integer>();   // Skill & Level
+        Map<SkillType, Integer> skills = new EnumMap<SkillType, Integer>(SkillType.class);   // Skill & Level
 
 
         skills.put(SkillType.TAMING, Integer.valueOf(character[24]));
         skills.put(SkillType.TAMING, Integer.valueOf(character[24]));
         skills.put(SkillType.MINING, Integer.valueOf(character[1]));
         skills.put(SkillType.MINING, Integer.valueOf(character[1]));
@@ -1041,4 +1155,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
     public DatabaseType getDatabaseType() {
     public DatabaseType getDatabaseType() {
         return DatabaseType.FLATFILE;
         return DatabaseType.FLATFILE;
     }
     }
+
+    @Override
+    public void onDisable() { }
 }
 }

文件差异内容过多而无法显示
+ 537 - 273
src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java


+ 2 - 68
src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java

@@ -7,12 +7,10 @@ import java.util.UUID;
 
 
 import org.bukkit.GameMode;
 import org.bukkit.GameMode;
 import org.bukkit.Location;
 import org.bukkit.Location;
-import org.bukkit.Server;
 import org.bukkit.Sound;
 import org.bukkit.Sound;
 import org.bukkit.entity.Player;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.metadata.FixedMetadataValue;
 import org.bukkit.metadata.FixedMetadataValue;
-import org.bukkit.scheduler.BukkitRunnable;
 
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.AdvancedConfig;
@@ -93,13 +91,13 @@ public class McMMOPlayer {
     private boolean isUsingUnarmed;
     private boolean isUsingUnarmed;
     private final FixedMetadataValue playerMetadata;
     private final FixedMetadataValue playerMetadata;
 
 
-    public McMMOPlayer(Player player) {
+    public McMMOPlayer(Player player, PlayerProfile profile) {
         String playerName = player.getName();
         String playerName = player.getName();
         UUID uuid = player.getUniqueId();
         UUID uuid = player.getUniqueId();
 
 
         this.player = player;
         this.player = player;
         playerMetadata = new FixedMetadataValue(mcMMO.p, playerName);
         playerMetadata = new FixedMetadataValue(mcMMO.p, playerName);
-        profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName, uuid, true);
+        this.profile = profile;
         party = PartyManager.getPlayerParty(playerName);
         party = PartyManager.getPlayerParty(playerName);
         ptpRecord = new PartyTeleportRecord();
         ptpRecord = new PartyTeleportRecord();
 
 
@@ -130,70 +128,6 @@ public class McMMOPlayer {
         for (ToolType toolType : ToolType.values()) {
         for (ToolType toolType : ToolType.values()) {
             toolMode.put(toolType, false);
             toolMode.put(toolType, false);
         }
         }
-
-        if (!profile.isLoaded()) {
-            mcMMO.p.getLogger().warning("Unable to load the PlayerProfile for " + playerName + ". Will retry over the next several seconds.");
-            new RetryProfileLoadingTask().runTaskTimerAsynchronously(mcMMO.p, 11L, 31L);
-        }
-    }
-
-    private class RetryProfileLoadingTask extends BukkitRunnable {
-        private static final int MAX_TRIES = 5;
-        private final String playerName = McMMOPlayer.this.player.getName();
-        private final UUID uniqueId = McMMOPlayer.this.player.getUniqueId();
-        private int attempt = 0;
-
-        // WARNING: ASYNC TASK
-        // DO NOT MODIFY THE McMMOPLAYER FROM THIS CODE
-        @Override
-        public void run() {
-            // Quit if they logged out
-            if (!player.isOnline()) {
-                mcMMO.p.getLogger().info("Aborting profile loading recovery for " + playerName + " - player logged out");
-                this.cancel();
-                return;
-            }
-
-            // Send the message that we're doing the recovery
-            if (attempt == 0) {
-                player.sendMessage(LocaleLoader.getString("Recovery.Notice"));
-            }
-
-            // Increment attempt counter and try
-            attempt++;
-            PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uniqueId, true);
-            // If successful, schedule the apply
-            if (profile.isLoaded()) {
-                new ApplySuccessfulProfile(profile).runTask(mcMMO.p);
-                player.sendMessage(LocaleLoader.getString("Recovery.Success"));
-                this.cancel();
-                return;
-            }
-
-            // If we've failed five times, give up
-            if (attempt >= MAX_TRIES) {
-                mcMMO.p.getLogger().severe("Giving up on attempting to load the PlayerProfile for " + playerName);
-                mcMMO.p.getServer().broadcast(LocaleLoader.getString("Recovery.AdminFailureNotice", playerName), Server.BROADCAST_CHANNEL_ADMINISTRATIVE);
-                player.sendMessage(LocaleLoader.getString("Recovery.Failure").split("\n"));
-                this.cancel();
-                return;
-            }
-        }
-    }
-
-    private class ApplySuccessfulProfile extends BukkitRunnable {
-        private final PlayerProfile profile;
-
-        private ApplySuccessfulProfile(PlayerProfile profile) {
-            this.profile = profile;
-        }
-
-        // Synchronized task
-        // No database access permitted
-        @Override
-        public void run() {
-            McMMOPlayer.this.profile = profile;
-        }
     }
     }
 
 
     public AcrobaticsManager getAcrobaticsManager() {
     public AcrobaticsManager getAcrobaticsManager() {

+ 2 - 9
src/main/java/com/gmail/nossr50/listeners/PlayerListener.java

@@ -41,7 +41,7 @@ import com.gmail.nossr50.datatypes.skills.AbilityType;
 import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.party.ShareHandler;
 import com.gmail.nossr50.party.ShareHandler;
-import com.gmail.nossr50.runnables.commands.McScoreboardKeepTask;
+import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
 import com.gmail.nossr50.runnables.skills.BleedTimerTask;
 import com.gmail.nossr50.runnables.skills.BleedTimerTask;
 import com.gmail.nossr50.skills.fishing.FishingManager;
 import com.gmail.nossr50.skills.fishing.FishingManager;
 import com.gmail.nossr50.skills.herbalism.HerbalismManager;
 import com.gmail.nossr50.skills.herbalism.HerbalismManager;
@@ -387,9 +387,7 @@ public class PlayerListener implements Listener {
             return;
             return;
         }
         }
 
 
-        McMMOPlayer mcMMOPlayer = UserManager.addUser(player);
-        mcMMOPlayer.actualizeRespawnATS();
-        ScoreboardManager.setupPlayer(player);
+        new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
 
 
         if (Config.getInstance().getMOTDEnabled() && Permissions.motd(player)) {
         if (Config.getInstance().getMOTDEnabled() && Permissions.motd(player)) {
             Motd.displayAll(player);
             Motd.displayAll(player);
@@ -403,11 +401,6 @@ public class PlayerListener implements Listener {
             player.sendMessage(LocaleLoader.getString("UpdateChecker.Outdated"));
             player.sendMessage(LocaleLoader.getString("UpdateChecker.Outdated"));
             player.sendMessage(LocaleLoader.getString("UpdateChecker.NewAvailable"));
             player.sendMessage(LocaleLoader.getString("UpdateChecker.NewAvailable"));
         }
         }
-
-        if (Config.getInstance().getShowStatsAfterLogin()) {
-            ScoreboardManager.enablePlayerStatsScoreboard(player);
-            new McScoreboardKeepTask(player).runTaskLater(mcMMO.p, 1 * Misc.TICK_CONVERSION_FACTOR);
-        }
     }
     }
 
 
     /**
     /**

+ 3 - 2
src/main/java/com/gmail/nossr50/mcMMO.java

@@ -38,6 +38,7 @@ import com.gmail.nossr50.runnables.UpdaterResultAsyncTask;
 import com.gmail.nossr50.runnables.backups.CleanBackupsTask;
 import com.gmail.nossr50.runnables.backups.CleanBackupsTask;
 import com.gmail.nossr50.runnables.database.UserPurgeTask;
 import com.gmail.nossr50.runnables.database.UserPurgeTask;
 import com.gmail.nossr50.runnables.party.PartyAutoKickTask;
 import com.gmail.nossr50.runnables.party.PartyAutoKickTask;
+import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
 import com.gmail.nossr50.runnables.player.PowerLevelUpdatingTask;
 import com.gmail.nossr50.runnables.player.PowerLevelUpdatingTask;
 import com.gmail.nossr50.runnables.skills.BleedTimerTask;
 import com.gmail.nossr50.runnables.skills.BleedTimerTask;
 import com.gmail.nossr50.skills.alchemy.Alchemy;
 import com.gmail.nossr50.skills.alchemy.Alchemy;
@@ -167,8 +168,7 @@ public class mcMMO extends JavaPlugin {
             holidayManager = new HolidayManager();
             holidayManager = new HolidayManager();
 
 
             for (Player player : getServer().getOnlinePlayers()) {
             for (Player player : getServer().getOnlinePlayers()) {
-                UserManager.addUser(player); // In case of reload add all users back into UserManager
-                ScoreboardManager.setupPlayer(player);
+                new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
             }
             }
 
 
             debug("Version " + getDescription().getVersion() + " is enabled!");
             debug("Version " + getDescription().getVersion() + " is enabled!");
@@ -242,6 +242,7 @@ public class mcMMO extends JavaPlugin {
             }
             }
         }
         }
 
 
+        databaseManager.onDisable();
         debug("Was disabled."); // How informative!
         debug("Was disabled."); // How informative!
     }
     }
 
 

+ 0 - 39
src/main/java/com/gmail/nossr50/runnables/database/SQLDatabaseKeepaliveTask.java

@@ -1,39 +0,0 @@
-package com.gmail.nossr50.runnables.database;
-
-import java.lang.ref.WeakReference;
-
-import org.bukkit.scheduler.BukkitRunnable;
-
-import com.gmail.nossr50.database.SQLDatabaseManager;
-
-/**
- * This task is in charge of sending a MySQL ping over the MySQL connection
- * every hour to prevent the connection from timing out and losing players'
- * data when they join.
- * <p/>
- * A WeakReference is used to keep the database instance, because
- * {@link com.gmail.nossr50.commands.database.ConvertDatabaseCommand database
- * conversion} may create a SQLDatabaseManager that will be thrown out. If a
- * normal reference was used, the conversion would cause a combined data and
- * resource leak through this task.
- */
-public class SQLDatabaseKeepaliveTask extends BukkitRunnable {
-    WeakReference<SQLDatabaseManager> databaseInstance;
-
-    public SQLDatabaseKeepaliveTask(SQLDatabaseManager dbman) {
-        databaseInstance = new WeakReference<SQLDatabaseManager>(dbman);
-    }
-
-    public void run() {
-        SQLDatabaseManager dbman = databaseInstance.get();
-        if (dbman != null) {
-            dbman.checkConnected();
-        }
-        else {
-            // This happens when the database was started for a conversion,
-            // or discarded by its creator for any other reason. If this code
-            // was not present, we would leak the connection resources.
-            this.cancel();
-        }
-    }
-}

+ 0 - 22
src/main/java/com/gmail/nossr50/runnables/database/SQLReconnectTask.java

@@ -1,22 +0,0 @@
-package com.gmail.nossr50.runnables.database;
-
-import org.bukkit.entity.Player;
-import org.bukkit.scheduler.BukkitRunnable;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.database.SQLDatabaseManager;
-import com.gmail.nossr50.util.player.UserManager;
-
-public class SQLReconnectTask extends BukkitRunnable {
-    @Override
-    public void run() {
-        if (((SQLDatabaseManager) mcMMO.getDatabaseManager()).checkConnected()) {
-            UserManager.saveAll();  // Save all profiles
-            UserManager.clearAll(); // Clear the profiles
-
-            for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
-                UserManager.addUser(player); // Add in new profiles, forcing them to 'load' again from MySQL
-            }
-        }
-    }
-}

+ 0 - 51
src/main/java/com/gmail/nossr50/runnables/database/UUIDFetcherRunnable.java

@@ -1,51 +0,0 @@
-package com.gmail.nossr50.runnables.database;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.UUID;
-
-import org.bukkit.scheduler.BukkitRunnable;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.util.uuid.UUIDFetcher;
-
-public class UUIDFetcherRunnable extends BukkitRunnable {
-    private List<String> names;
-
-    public UUIDFetcherRunnable(List<String> names) {
-        this.names = names;
-    }
-
-    public UUIDFetcherRunnable(String name) {
-        this.names = new ArrayList<String>();
-        this.names.add(name);
-    }
-
-    @Override
-    public void run() {
-        try {
-            Map<String, UUID> returns = new UUIDFetcher(this.names).call();
-            new CacheReturnedNames(returns).runTask(mcMMO.p);
-        }
-        catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    private class CacheReturnedNames extends BukkitRunnable {
-        private Map<String, UUID> returns;
-
-        public CacheReturnedNames(Map<String, UUID> returns) {
-            this.returns = returns;
-        }
-
-        @Override
-        public void run() {
-            for (Entry<String, UUID> entry : this.returns.entrySet()) {
-                mcMMO.getDatabaseManager().saveUserUUID(entry.getKey(), entry.getValue());
-            }
-        }
-    }
-}

+ 108 - 0
src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoadingTask.java

@@ -0,0 +1,108 @@
+package com.gmail.nossr50.runnables.player;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.bukkit.Server;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.runnables.commands.McScoreboardKeepTask;
+import com.gmail.nossr50.util.Misc;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.scoreboards.ScoreboardManager;
+
+public class PlayerProfileLoadingTask extends BukkitRunnable {
+    private static final int MAX_TRIES = 5;
+    private final Player player;
+    private int attempt = 0;
+    private ReentrantLock lock = new ReentrantLock();
+    private boolean cancelled = false;
+
+    public PlayerProfileLoadingTask(Player player) {
+        this.player = player;
+    }
+
+    // WARNING: ASYNC TASK
+    // DO NOT MODIFY THE McMMOPLAYER FROM THIS CODE
+    @Override
+    public void run() {
+        lock.lock();
+
+        if (this.cancelled) {
+            return;
+        }
+
+        // Quit if they logged out
+        if (!player.isOnline()) {
+            mcMMO.p.getLogger().info("Aborting profile loading recovery for " + player.getName() + " - player logged out");
+            this.cancel();
+            cancelled = true;
+            lock.unlock();
+            return;
+        }
+
+        // Increment attempt counter and try
+        attempt++;
+
+        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(player.getName(), player.getUniqueId(), true);
+        // If successful, schedule the apply
+        if (profile.isLoaded()) {
+            new ApplySuccessfulProfile(profile).runTask(mcMMO.p);
+            this.cancel();
+            cancelled = true;
+            lock.unlock();
+            return;
+        }
+
+        // If we've failed five times, give up
+        if (attempt >= MAX_TRIES) {
+            mcMMO.p.getLogger().severe("Giving up on attempting to load the PlayerProfile for " + player.getName());
+            mcMMO.p.getServer().broadcast(LocaleLoader.getString("Profile.Loading.AdminFailureNotice", player.getName()), Server.BROADCAST_CHANNEL_ADMINISTRATIVE);
+            player.sendMessage(LocaleLoader.getString("Profile.Loading.Failure").split("\n"));
+            this.cancel();
+            cancelled = true;
+            lock.unlock();
+            return;
+        }
+        lock.unlock();
+    }
+
+    private class ApplySuccessfulProfile extends BukkitRunnable {
+        private final PlayerProfile profile;
+
+        private ApplySuccessfulProfile(PlayerProfile profile) {
+            this.profile = profile;
+        }
+
+        // Synchronized task
+        // No database access permitted
+        @Override
+        public void run() {
+            if (!player.isOnline()) {
+                mcMMO.p.getLogger().info("Aborting profile loading recovery for " + player.getName() + " - player logged out");
+                return;
+            }
+
+            McMMOPlayer mcMMOPlayer = new McMMOPlayer(player, profile);
+            UserManager.track(mcMMOPlayer);
+            mcMMOPlayer.actualizeRespawnATS();
+            ScoreboardManager.setupPlayer(player);
+
+            if (Config.getInstance().getShowProfileLoadedMessage()) {
+                player.sendMessage(LocaleLoader.getString("Profile.Loading.Success"));
+            }
+
+            if (Config.getInstance().getShowStatsAfterLogin()) {
+                ScoreboardManager.enablePlayerStatsScoreboard(player);
+                new McScoreboardKeepTask(player).runTaskLater(mcMMO.p, 1 * Misc.TICK_CONVERSION_FACTOR);
+            }
+        }
+    }
+}
+
+

+ 2 - 1
src/main/java/com/gmail/nossr50/util/Misc.java

@@ -13,6 +13,7 @@ import org.bukkit.inventory.ItemStack;
 
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.events.items.McMMOItemSpawnEvent;
 import com.gmail.nossr50.events.items.McMMOItemSpawnEvent;
+import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.player.UserManager;
 
 
 public final class Misc {
 public final class Misc {
@@ -112,7 +113,7 @@ public final class Misc {
 
 
         if (player != null) {
         if (player != null) {
             UserManager.remove(player);
             UserManager.remove(player);
-            UserManager.addUser(player);
+            new PlayerProfileLoadingTask(player).runTaskTimerAsynchronously(mcMMO.p, 1, 20); // 1 Tick delay to ensure the player is marked as online before we begin loading
         }
         }
     }
     }
 
 

+ 4 - 8
src/main/java/com/gmail/nossr50/util/player/UserManager.java

@@ -18,16 +18,12 @@ public final class UserManager {
     private UserManager() {}
     private UserManager() {}
 
 
     /**
     /**
-     * Add a new user.
+     * Track a new user.
      *
      *
-     * @param player The player to create a user record for
-     * @return the player's {@link McMMOPlayer} object
+     * @param mcMMOPlayer the player profile to start tracking
      */
      */
-    public static McMMOPlayer addUser(Player player) {
-        McMMOPlayer mcMMOPlayer = new McMMOPlayer(player);
-        player.setMetadata(mcMMO.playerDataKey, new FixedMetadataValue(mcMMO.p, mcMMOPlayer));
-
-        return mcMMOPlayer;
+    public static void track(McMMOPlayer mcMMOPlayer) {
+        mcMMOPlayer.getPlayer().setMetadata(mcMMO.playerDataKey, new FixedMetadataValue(mcMMO.p, mcMMOPlayer));
     }
     }
 
 
     /**
     /**

+ 8 - 0
src/main/resources/config.yml

@@ -10,6 +10,8 @@
 General:
 General:
     Locale: en_US
     Locale: en_US
     MOTD_Enabled: true
     MOTD_Enabled: true
+    # Send a message to the player when his profile was successfully loaded
+    Show_Profile_Loaded: false
     # Amount of time (in minutes) to wait between saves of player information
     # Amount of time (in minutes) to wait between saves of player information
     Save_Interval: 10
     Save_Interval: 10
     # Allow mcMMO to report on basic anonymous usage
     # Allow mcMMO to report on basic anonymous usage
@@ -122,6 +124,12 @@ MySQL:
         User_Password: UserPassword
         User_Password: UserPassword
         Name: DataBaseName
         Name: DataBaseName
         TablePrefix: mcmmo_
         TablePrefix: mcmmo_
+        # This setting is the max simultaneous mysql connections allowed at a time, needs to be
+        # high enough to support multiple player logins in quick succession
+        MaxConnections: 30
+        # This setting is the max size of the pool of cached connections that we hold available
+        # at any given time
+        MaxPoolSize: 20
     Server:
     Server:
         Port: 3306
         Port: 3306
         Address: localhost
         Address: localhost

+ 3 - 4
src/main/resources/locale/locale_en_US.properties

@@ -960,7 +960,6 @@ Scoreboard.Misc.Cooldown=[[LIGHT_PURPLE]]Cooldown
 Scoreboard.Misc.Overall=[[GOLD]]Overall
 Scoreboard.Misc.Overall=[[GOLD]]Overall
 
 
 #DATABASE RECOVERY
 #DATABASE RECOVERY
-Recovery.Notice=[[RED]]Notice: mcMMO was [[DARK_RED]]unable to load your data.[[RED]] Retrying 5 times...
-Recovery.Success=[[GREEN]]Success! Your mcMMO data was loaded.
-Recovery.Failure=[[RED]]mcMMO still cannot load your data. You may want to [[AQUA]]contact the server owner.\n[[YELLOW]]You can still play on the server, but you will have [[BOLD]]no mcMMO levels[[YELLOW]] and any XP you get [[BOLD]]will not be saved[[YELLOW]].
-Recovery.AdminFailureNotice=[[DARK_RED]][A][[RED]] mcMMO was unable to load the player data for [[YELLOW]]{0}[[RED]]. [[LIGHT_PURPLE]]Please inspect your database setup.
+Profile.Loading.Success=[[GREEN]]Your mcMMO profile has been loaded.
+Profile.Loading.Failure=[[RED]]mcMMO still cannot load your data. You may want to [[AQUA]]contact the server owner.\n[[YELLOW]]You can still play on the server, but you will have [[BOLD]]no mcMMO levels[[YELLOW]] and any XP you get [[BOLD]]will not be saved[[YELLOW]].
+Profile.Loading.AdminFailureNotice=[[DARK_RED]][A][[RED]] mcMMO was unable to load the player data for [[YELLOW]]{0}[[RED]]. [[LIGHT_PURPLE]]Please inspect your database setup.

部分文件因为文件数量过多而无法显示