mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-09-30 16:17:31 +02:00
a2bf242097
* Replace guava Charsets with StandardCharsets. * Use try-with-resources * Faster Util asList, inline getOnlinePlayers. * Use direct ArrayList allocation. * Use new instead of Lists#... * Use new instead of Lists#... * Faster looping. * Use switch. * Remove diamond operators. * Use ArrayDeque instead of LinkedList. * Actually conform to the documentation and always use an ArrayList as backing list. * Potentially breaking change: Use switch instead of startsWith as this e.g. causes "n" to be interpreted as "names" and any future new commands starting with "n" will be ignored. * Use addAll(). * Remove IntegerSet. Unused and also covered by fastutils IntSet. * Much faster boolean parsing. Might have breaking changes as parameterName is now checked after the false block. * Make most fields final, fix JavaDoc and remove diamond operators. * Make fields final. * Much cleaner getAllInterfaces() method. Co-authored-by: Dan Mulloy <dev@dmulloy2.net>
331 lines
8.4 KiB
Java
331 lines
8.4 KiB
Java
package com.comphenix.protocol.concurrency;
|
|
|
|
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
|
import com.comphenix.protocol.utility.Util;
|
|
import com.google.common.base.Function;
|
|
import com.google.common.base.Preconditions;
|
|
import com.google.common.cache.CacheLoader;
|
|
import com.google.common.cache.RemovalListener;
|
|
import com.google.common.collect.AbstractIterator;
|
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
|
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.entity.Player;
|
|
|
|
import java.util.AbstractMap;
|
|
import java.util.AbstractSet;
|
|
import java.util.Iterator;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ConcurrentMap;
|
|
|
|
/**
|
|
* Represents a concurrent player map.
|
|
* <p>
|
|
* This map may use player addresses as keys.
|
|
* @author Kristian
|
|
*/
|
|
public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> implements ConcurrentMap<Player, TValue> {
|
|
/**
|
|
* Represents the different standard player keys,
|
|
* @author Kristian
|
|
*/
|
|
public enum PlayerKey implements Function<Player, Object> {
|
|
/**
|
|
* Use a player's {@link Player#getAddress()} as key in the map.
|
|
*/
|
|
ADDRESS {
|
|
@Override
|
|
public Object apply(Player player) {
|
|
return player == null ? null : player.getAddress();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Use a player's {@link Player#getName()} as key in the map.
|
|
*/
|
|
NAME {
|
|
@Override
|
|
public Object apply(Player player) {
|
|
return player == null ? null : player.getName();
|
|
}
|
|
},
|
|
}
|
|
|
|
/**
|
|
* An internal map of player keys to values.
|
|
*/
|
|
private ConcurrentMap<Object, TValue> valueLookup = createValueMap();
|
|
|
|
/**
|
|
* A cache of the associated keys for each player.
|
|
*/
|
|
|
|
private ConcurrentMap<Object, Player> keyLookup = createKeyCache();
|
|
/**
|
|
* The method used to retrieve a unique key for a player.
|
|
*/
|
|
private final Function<Player, Object> keyMethod;
|
|
|
|
/**
|
|
* Construct a new concurrent player map that uses each player's address as key.
|
|
* @param <T> Parameter type
|
|
* @return Concurrent player map.
|
|
*/
|
|
public static <T> ConcurrentPlayerMap<T> usingAddress() {
|
|
return new ConcurrentPlayerMap<>(PlayerKey.ADDRESS);
|
|
}
|
|
|
|
/**
|
|
* Construct a new concurrent player map that uses each player's name as key.
|
|
* @param <T> Parameter type
|
|
* @return Concurrent player map.
|
|
*/
|
|
public static <T> ConcurrentPlayerMap<T> usingName() {
|
|
return new ConcurrentPlayerMap<>(PlayerKey.NAME);
|
|
}
|
|
|
|
/**
|
|
* Construct a new concurrent player map using the given standard key method.
|
|
* @param standardMethod - the standard key method.
|
|
*/
|
|
private ConcurrentPlayerMap(PlayerKey standardMethod) {
|
|
this.keyMethod = standardMethod;
|
|
}
|
|
|
|
/**
|
|
* Construct a new concurrent player map using the given custom key method.
|
|
* @param method - custom key method.
|
|
*/
|
|
public ConcurrentPlayerMap(Function<Player, Object> method) {
|
|
this.keyMethod = method;
|
|
}
|
|
|
|
/**
|
|
* Construct the map that will store the associated values.
|
|
* <p>
|
|
* The default implementation uses a {@link ConcurrentHashMap}.
|
|
* @return The value map.
|
|
*/
|
|
private ConcurrentMap<Object, TValue> createValueMap() {
|
|
return new ConcurrentHashMap<>();
|
|
}
|
|
|
|
/**
|
|
* Construct a cache of keys and the associated player.
|
|
* @return The key map.
|
|
*/
|
|
private ConcurrentMap<Object, Player> createKeyCache() {
|
|
return SafeCacheBuilder.newBuilder().
|
|
weakValues().
|
|
removalListener(
|
|
(RemovalListener<Object, Player>) removed -> {
|
|
// We ignore explicit removal
|
|
if (removed.wasEvicted()) {
|
|
onCacheEvicted(removed.getKey());
|
|
}
|
|
}).
|
|
build(
|
|
new CacheLoader<Object, Player>() {
|
|
@Override
|
|
public Player load(Object key) throws Exception {
|
|
Player player = findOnlinePlayer(key);
|
|
|
|
if (player != null)
|
|
return player;
|
|
|
|
// Per the contract, this method should not return NULL
|
|
throw new IllegalArgumentException(
|
|
"Unable to find a player associated with: " + key);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Invoked when an entry in the cache has been evicted, typically by the garbage collector.
|
|
* @param key - the key.
|
|
*/
|
|
private void onCacheEvicted(Object key) {
|
|
Player newPlayer = findOnlinePlayer(key);
|
|
|
|
if (newPlayer != null) {
|
|
// Update the reference
|
|
keyLookup.put(key, newPlayer);
|
|
} else {
|
|
valueLookup.remove(key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find an online player from the given key.
|
|
* @param key - a non-null key.
|
|
* @return The player with the given key, or NULL if not found.
|
|
*/
|
|
private Player findOnlinePlayer(Object key) {
|
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
|
if (key.equals(keyMethod.apply(player))) {
|
|
return player;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Lookup a player by key in the cache, optionally searching every online player.
|
|
* @param key - the key of the player we are locating.
|
|
* @return The player, or NULL if not found.
|
|
*/
|
|
private Player lookupPlayer(Object key) {
|
|
try {
|
|
return keyLookup.get(key);
|
|
} catch (UncheckedExecutionException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the key of a particular player, ensuring it is cached.
|
|
* @param player - the player whose key we want to retrieve.
|
|
* @return The key.
|
|
*/
|
|
private Object cachePlayerKey(Player player) {
|
|
Preconditions.checkNotNull(player, "player cannot be null");
|
|
|
|
Object key = keyMethod.apply(player);
|
|
|
|
keyLookup.put(key, player);
|
|
return key;
|
|
}
|
|
|
|
@Override
|
|
public TValue put(Player key, TValue value) {
|
|
return valueLookup.put(cachePlayerKey(key), value);
|
|
}
|
|
|
|
@Override
|
|
public TValue putIfAbsent(Player key, TValue value) {
|
|
return valueLookup.putIfAbsent(cachePlayerKey(key), value);
|
|
}
|
|
|
|
@Override
|
|
public TValue replace(Player key, TValue value) {
|
|
return valueLookup.replace(cachePlayerKey(key), value);
|
|
}
|
|
|
|
@Override
|
|
public boolean replace(Player key, TValue oldValue, TValue newValue) {
|
|
return valueLookup.replace(cachePlayerKey(key), oldValue, newValue);
|
|
}
|
|
|
|
@Override
|
|
public TValue remove(Object key) {
|
|
if (key instanceof Player) {
|
|
Object playerKey = keyMethod.apply((Player) key);
|
|
|
|
if (playerKey != null) {
|
|
TValue value = valueLookup.remove(playerKey);
|
|
|
|
keyLookup.remove(playerKey);
|
|
return value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public boolean remove(Object key, Object value) {
|
|
if (key instanceof Player) {
|
|
Object playerKey = keyMethod.apply((Player) key);
|
|
|
|
if (playerKey != null && valueLookup.remove(playerKey, value)) {
|
|
keyLookup.remove(playerKey);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public TValue get(Object key) {
|
|
if (key instanceof Player) {
|
|
Object playerKey = keyMethod.apply((Player) key);
|
|
return playerKey != null ? valueLookup.get(playerKey) : null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public boolean containsKey(Object key) {
|
|
if (key instanceof Player) {
|
|
Object playerKey = keyMethod.apply((Player) key);
|
|
return playerKey != null && valueLookup.containsKey(playerKey);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public Set<Entry<Player, TValue>> entrySet() {
|
|
return new AbstractSet<Entry<Player,TValue>>() {
|
|
@Override
|
|
public Iterator<Entry<Player, TValue>> iterator() {
|
|
return entryIterator();
|
|
}
|
|
|
|
@Override
|
|
public int size() {
|
|
return valueLookup.size();
|
|
}
|
|
|
|
@Override
|
|
public void clear() {
|
|
valueLookup.clear();
|
|
keyLookup.clear();
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Retrieve an iterator of entries that supports removal of elements.
|
|
* @return Entry iterator.
|
|
*/
|
|
private Iterator<Entry<Player, TValue>> entryIterator() {
|
|
// Skip entries with stale data
|
|
final Iterator<Entry<Object, TValue>> source = valueLookup.entrySet().iterator();
|
|
final AbstractIterator<Entry<Player,TValue>> filtered =
|
|
new AbstractIterator<Entry<Player,TValue>>() {
|
|
@Override
|
|
protected Entry<Player, TValue> computeNext() {
|
|
while (source.hasNext()) {
|
|
Entry<Object, TValue> entry = source.next();
|
|
Player player = lookupPlayer(entry.getKey());
|
|
|
|
if (player == null) {
|
|
// Remove entries that cannot be found
|
|
source.remove();
|
|
keyLookup.remove(entry.getKey());
|
|
} else {
|
|
return new SimpleEntry<Player, TValue>(player, entry.getValue());
|
|
}
|
|
}
|
|
return endOfData();
|
|
}
|
|
};
|
|
|
|
// We can't return AbstractIterator directly, as it doesn't permitt the remove() method
|
|
return new Iterator<Entry<Player, TValue>>() {
|
|
@Override
|
|
public boolean hasNext() {
|
|
return filtered.hasNext();
|
|
}
|
|
@Override
|
|
public Entry<Player, TValue> next() {
|
|
return filtered.next();
|
|
}
|
|
@Override
|
|
public void remove() {
|
|
source.remove();
|
|
}
|
|
};
|
|
}
|
|
}
|