[Class] No more "Don't store players" crap: PlayerKeyHashMap<V> & PlayerValueHashMap<K>

Discussion in 'Resources' started by TomFromCollege, Aug 18, 2013.

Thread Status:
Not open for further replies.
  1. Offline

    TomFromCollege

    Hi guys,

    I wrote these 2 little classes to enable storing players without having to worry about removing players from it.

    You use a normal HashMap like this:
    HashMap<Key,Value> - HashMap<String,Integer>

    They are used in the following way:
    Code:
    PlayerKeyHashMap<String> simplePlayerNameStringMap = new PlayerKeyHashMap<String>(plugin);
    PlayerKeyHashMap<String> playerDisplayNameMap = new PlayerKeyHashMap<String>(plugin, PlayerKeyHashMap.PlayerSyncField.DISPLAY_NAME,true);
    
    Setting the last boolean to false causes the class to not register the listener and therefore - Doesn't automatically remove the Entry when a Player Disconnects

    Code:java
    1.  
    2. import java.util.HashMap;
    3. import org.bukkit.entity.Player;
    4. import org.bukkit.event.EventHandler;
    5. import org.bukkit.event.Listener;
    6. import org.bukkit.event.player.PlayerQuitEvent;
    7. import org.bukkit.plugin.Plugin;
    8.  
    9. /**
    10. *
    11. * @author Thomas Nairn
    12. * @author TomFromCollege
    13. */
    14. public class PlayerKeyHashMap<V> extends HashMap<String, V> implements Listener {
    15.  
    16. private final PlayerSyncField playerSyncField;
    17.  
    18. /**
    19. *
    20. * @param Plugin The plugin used to register the listener
    21. * @param playerSyncField An Enum contained within this class that allows
    22. * you to sync more than just the Player Name
    23. */
    24. public PlayerKeyHashMap(Plugin plugin, PlayerSyncField playerSyncField, boolean autoRemove) {
    25. if (playerSyncField == null) {
    26. throw new NullPointerException("PlayerSyncField must be set.");
    27. }
    28. this.playerSyncField = playerSyncField;
    29. if (autoRemove) {
    30. plugin.getServer().getPluginManager().registerEvents(this, plugin);
    31. }
    32. }
    33.  
    34. /**
    35. * <p>This constructor should be used only when wanting to sync the
    36. * "PlayerName" field </p>
    37. */
    38. public PlayerKeyHashMap(Plugin plugin) {
    39. this(plugin, PlayerSyncField.NAME, true);
    40. }
    41.  
    42. @EventHandler
    43. private void onPlayerDisconnect(PlayerQuitEvent e) {
    44. synchronized (this) {
    45. remove(getPlayerField(e.getPlayer()));
    46. }
    47. }
    48.  
    49. public synchronized V put(Player player, V value) {
    50. return put(getPlayerField(player), value);
    51. }
    52.  
    53. public synchronized V get(Player player) {
    54. return get(getPlayerField(player));
    55. }
    56.  
    57. private String getPlayerField(Player player) {
    58. switch (playerSyncField) {
    59. case NAME:
    60. return player.getName();
    61. case DISPLAY_NAME:
    62. return player.getDisplayName();
    63. case LIST_NAME:
    64. return player.getPlayerListName();
    65. }
    66. return player.getName();
    67. }
    68.  
    69. /**
    70. *
    71. */
    72. public static enum PlayerSyncField {
    73.  
    74. NAME, DISPLAY_NAME, ADDRESS, LIST_NAME;
    75. }
    76. }
    77.  


    This next one I wouldn't really suggest using it as any player disconnect will cause it loop through the players in the List, it was really more for completion and for people requiring the use of a custom Key.

    Code:java
    1.  
    2.  
    3. import java.util.HashMap;
    4. import java.util.Iterator;
    5. import java.util.Map.Entry;
    6. import org.bukkit.entity.Player;
    7. import org.bukkit.event.EventHandler;
    8. import org.bukkit.event.Listener;
    9. import org.bukkit.event.player.PlayerQuitEvent;
    10. import org.bukkit.plugin.Plugin;
    11.  
    12. /**
    13. *
    14. * I Wouldn't really suggest using this one, as removing the values can be a
    15. * little slow due to the necessity of looping through the values to find key
    16. *
    17. * @author Thomas Nairn
    18. * @author TomFromCollege
    19. *
    20. *
    21. */
    22. public class PlayerValueHashMap<K> extends HashMap<K, String> implements Listener {
    23.  
    24. private final PlayerKeyHashMap.PlayerSyncField playerSyncField;
    25.  
    26. /**
    27. *
    28. * @param Plugin The plugin used to register the listener
    29. * @param playerSyncField An Enum contained within this class that allows
    30. * you to sync more than just the Player Name
    31. */
    32. public PlayerValueHashMap(Plugin plugin, PlayerKeyHashMap.PlayerSyncField playerSyncField, boolean autoRemove) {
    33. if (playerSyncField == null) {
    34. throw new NullPointerException("PlayerSyncField must be set.");
    35. }
    36. this.playerSyncField = playerSyncField;
    37. if (autoRemove) {
    38. plugin.getServer().getPluginManager().registerEvents(this, plugin);
    39. }
    40. }
    41.  
    42. /**
    43. * <p>This constructor should be used only when wanting to sync the
    44. * "PlayerName" field </p>
    45. */
    46. public PlayerValueHashMap(Plugin plugin) {
    47. this(plugin, PlayerKeyHashMap.PlayerSyncField.NAME, true);
    48. }
    49.  
    50. @EventHandler
    51. private void onPlayerDisconnect(PlayerQuitEvent e) {
    52. synchronized (this) {
    53. remove(get(e.getPlayer()));
    54. }
    55. }
    56.  
    57. public synchronized String put(K key, Player player) {
    58. return put(key, getPlayerField(player));
    59. }
    60.  
    61. /**
    62. * I wouldn't really use this, it's only really here for completion...
    63. */
    64. public synchronized K get(Player player) {
    65. Iterator<Entry<K, String>> it = entrySet().iterator();
    66. String field = getPlayerField(player);
    67. while (it.hasNext()) {
    68. Entry<K, String> val = it.next();
    69. if (val.getValue().equals(field)) {
    70. return val.getKey();
    71. }
    72. }
    73. return null;
    74. }
    75.  
    76. private String getPlayerField(Player player) {
    77. switch (playerSyncField) {
    78. case NAME:
    79. return player.getName();
    80. case DISPLAY_NAME:
    81. return player.getDisplayName();
    82. case LIST_NAME:
    83. return player.getPlayerListName();
    84. }
    85. return player.getName();
    86. }
    87.  
    88. /**
    89. *
    90. */
    91. public static enum PlayerSyncField {
    92.  
    93. NAME, DISPLAY_NAME, ADDRESS, LIST_NAME;
    94. }
    95. }
    96.  
     
  2. Offline

    bloodless2010

    Pardon my ignorance, but, why would you need to store players in a hashmap?
     
    AstramG and TheGreenGamerHD like this.
  3. Offline

    ZeusAllMighty11

    That's nice and all, but if people were thinking about going through this much trouble to store a player object, they'd probably more satisfied with the name storage and getting the player back from the string.

    I'd also like to know, why would you need to store player objects?
     
  4. Well I think this is cool lol. Seems pretty useful
     
  5. Offline

    TomFromCollege

    TheGreenGamerHD
    I don't think you realize what this does. Or even read it?
    It handles ALL that for you. I mean come on. You have to have wanted to save a value per player? Be it cooldown or I-Don't-Know-What

    It allows you to store an object per player, without having to go about the whole storing names thing.
    It also ensures you don't bloat your server with player disconnects and connects. I can save a value for a player (A config value, permission maybe?) and not have to worry about removing it when the player disconnects
     
  6. Offline

    ZeusAllMighty11

    I know what it does -_-

    I'm just saying it's silly to go through all that work when you really don't have to.


    Regardless, nice I guess
     
  7. Offline

    TomFromCollege

    Haha I see what you're saying. But.. I use PlayerName HashMaps through about 9 different plugins and it occurred to me today that this would make my life easier, it took about 20 minutes and the hard work is done! :D
     
  8. Offline

    Comphenix

    If you need a concurrent map of players, why not use Guava's MapMaker and store the players with weak references?
    Code:java
    1. import java.util.concurrent.ConcurrentMap;
    2.  
    3. import org.bukkit.entity.Player;
    4.  
    5. import com.google.common.collect.ForwardingConcurrentMap;
    6. import com.google.common.collect.MapMaker;
    7.  
    8. /**
    9.  * Represents a concurrent hash-map using a Bukkit player object as key. The player keys are
    10.  * referenced weakly, and will be properly collected once each player has logged out.
    11.  * @author Kristian
    12.  * @param <TValue> Type of the stored value.
    13.  */
    14. public class PlayerHashMap<TValue> extends ForwardingConcurrentMap<Player, TValue> {
    15. protected ConcurrentMap<Player, TValue> delegate;
    16.  
    17. /**
    18.   * Construct a new player-based concurrent hash map.
    19.   */
    20. public PlayerHashMap() {
    21. this.delegate = new MapMaker().weakKeys().makeMap();
    22. }
    23.  
    24. /**
    25.   * Construct a new player-based concurrent hash map.
    26.   * @param concurrencyLevel - guides the allowed concurrency among update operations.
    27.   */
    28. public PlayerHashMap(int concurrencyLevel) {
    29. this.delegate = new MapMaker().weakKeys().concurrencyLevel(concurrencyLevel).makeMap();
    30. }
    31.  
    32. // You could also register a listener and remove players explicitly.
    33. @Override
    34. protected ConcurrentMap<Player, TValue> delegate() {
    35. return delegate;
    36. }
    37. }

    I don't think there's a very compelling reason to use player names as keys when you can use weak references. Though, keep in mind that the key-values will not be immediately evicted after a player has logged out, but for most uses this shouldn't be an issue. You can always check if a player has logged out, or register an event listener like in TomFromCollege's version.

    I would much prefer to use a concurrent hash map over a synchronized map, as they won't block during retrieval operations, avoiding the dreaded deadlock, and they're generally faster. You can get better consistency with blocking, but that requires proper care and use of concurrency primitives. Besides, you can use putIfAbsent and get atomic insertion.

    But of course, that's only if need to worry about concurrency. Most plugins won't run on multiple threads (async event/async tasks), so the synchronized key word will just waste performance. In those cases, especially if performance is critical, you may use the second constructor that allows you to specify a concurrency level. Use one (1) if you know you'll never use it in a different thread.
     
  9. Offline

    TomFromCollege

    And today I learnt something! :)
    Never even heard of weak keys. I literally implemented a work around for the issue I was having.
    I know this is going to sound stupid due to Bukkits dependency on Guava, but I really do hate the idea of depending on it.
     
  10. Offline

    Comphenix

    You're welcome. :p

    It's definitely something more people should be aware of. I don't really see a point to the whole Map<String, Object> pattern, except that it's very easy for beginners to understand. But it could be abstracted away in a PlayerHashMap class.

    Well, that's fine, but Guava will always be bundled with Bukkit, or any Bukkit derivatives. There's nothing bad about taking advantage of it. The more you use it, the more you can't be without it.

    The only thing you should watch out for is using depreciated methods or beta classes. While CraftBukkit itself may not upgrade Guava from version 10, other custom implementations have done so already.
     
  11. Offline

    TomFromCollege

    Oh my god. Guava is beautiful. Comphenix, post your code in a resources snippet and allow me to scrap my horrendous code? :)
     
  12. Offline

    Comphenix

    Hm. Perhaps you could just link to my post somewhere in the first thread post? It seems a bit wasteful to make another thread. :p

    By the way, I should probably mention that MapMaker (and ConcurrentHashMap in Java 7 and earlier) does use locking as well, but it can accomodate multiple writers simultaneously by locking different sections of the hash table. However, I wouldn't recommend using synchronized methods, as someone else could synchronize against the same object and cause a deadlock. It's very unlikely, but it's better to be safe than sorry.
     
Thread Status:
Not open for further replies.

Share This Page