TemporaryPlayer doesn't support getName(), so use getAddress() for maps

In addition, adding a useful ConcurrentPlayerMap that doesn't store 
player instances directly, and can even handle TemporaryPlayers.
This commit is contained in:
Kristian S. Stangeland 2013-11-23 05:00:34 +01:00
parent 00b297475c
commit 125bfbad30
3 changed files with 334 additions and 12 deletions

View File

@ -19,13 +19,14 @@ package com.comphenix.protocol.async;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.ConcurrentPlayerMap;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.SortedPacketListenerList;
@ -37,9 +38,8 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
* @author Kristian
*/
class PlayerSendingHandler {
private ErrorReporter reporter;
private ConcurrentHashMap<String, QueueContainer> playerSendingQueues;
private ConcurrentMap<Player, QueueContainer> playerSendingQueues;
// Timeout listeners
private SortedPacketListenerList serverTimeoutListeners;
@ -105,7 +105,7 @@ class PlayerSendingHandler {
this.clientTimeoutListeners = clientTimeoutListeners;
// Initialize storage of queues
this.playerSendingQueues = new ConcurrentHashMap<String, QueueContainer>();
this.playerSendingQueues = ConcurrentPlayerMap.usingAddress();
}
/**
@ -137,15 +137,14 @@ class PlayerSendingHandler {
* @return The server or client sending queue the packet belongs to.
*/
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
String name = packet.getPlayer().getName();
QueueContainer queues = playerSendingQueues.get(name);
QueueContainer queues = playerSendingQueues.get(packet.getPlayer());
// Safe concurrent initialization
if (queues == null && createNew) {
final QueueContainer newContainer = new QueueContainer();
// Attempt to map the queue
queues = playerSendingQueues.putIfAbsent(name, newContainer);
queues = playerSendingQueues.putIfAbsent(packet.getPlayer(), newContainer);
if (queues == null) {
queues = newContainer;
@ -258,9 +257,7 @@ class PlayerSendingHandler {
* @param player - the player that just logged out.
*/
public void removePlayer(Player player) {
String name = player.getName();
// Every packet will be dropped - there's nothing we can do
playerSendingQueues.remove(name);
playerSendingQueues.remove(player);
}
}

View File

@ -0,0 +1,325 @@
package com.comphenix.protocol.concurrency;
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;
import java.util.concurrent.ExecutionException;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.google.common.base.Function;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.UncheckedExecutionException;
/**
* 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.getAddress();
}
},
/**
* Use a player's {@link Player#getName()} as key in the map.
*/
NAME {
@Override
public Object apply(Player player) {
return player.getName();
}
},
}
/**
* An internal map of player keys to values.
*/
protected ConcurrentMap<Object, TValue> valueLookup = createValueMap();
/**
* A cache of the associated keys for each player.
*/
protected Cache<Object, Player> keyLookup = createKeyCache();
/**
* The method used to retrieve a unique key for a player.
*/
protected final Function<Player, Object> keyMethod;
/**
* Construct a new concurrent player map that uses each player's address as key.
* @return Concurrent player map.
*/
public static <T> ConcurrentPlayerMap<T> usingAddress() {
return new ConcurrentPlayerMap<T>(PlayerKey.ADDRESS);
}
/**
* Construct a new concurrent player map that uses each player's name as key.
* @return Concurrent player map.
*/
public static <T> ConcurrentPlayerMap<T> usingName() {
return new ConcurrentPlayerMap<T>(PlayerKey.NAME);
}
/**
* Construct a new concurrent player map using the given standard key method.
* @param standardMethod - the standard key method.
*/
public 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.
*/
protected ConcurrentMap<Object, TValue> createValueMap() {
return Maps.newConcurrentMap();
}
/**
* Construct a cache of keys and the associated player.
* @return The key map.
*/
protected Cache<Object, Player> createKeyCache() {
return CacheBuilder.newBuilder().
weakValues().
removalListener(
new RemovalListener<Object, Player>() {
@Override
public void onRemoval(RemovalNotification<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.
* @param player - the value that was evicted or collected.
*/
private void onCacheEvicted(Object key) {
Player newPlayer = findOnlinePlayer(key);
if (newPlayer != null) {
// Update the reference
keyLookup.asMap().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.
*/
protected 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.
* @throws ExecutionException
*/
protected Player lookupPlayer(Object key) {
try {
return keyLookup.get(key);
} catch (ExecutionException e) {
return null;
} 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.
*/
protected Object cachePlayerKey(Player player) {
Object key = keyMethod.apply(player);
keyLookup.asMap().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);
TValue value = valueLookup.remove(playerKey);
keyLookup.asMap().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 (valueLookup.remove(playerKey, value)) {
keyLookup.asMap().remove(playerKey);
return true;
}
}
return false;
}
@Override
public TValue get(Object key) {
if (key instanceof Player)
return valueLookup.get(keyMethod.apply((Player) key));
return null;
}
@Override
public boolean containsKey(Object key) {
if (key instanceof Player)
return valueLookup.containsKey(keyMethod.apply((Player) key));
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.invalidateAll();
}
};
}
/**
* 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.asMap().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>>() {
public boolean hasNext() {
return filtered.hasNext();
}
public Entry<Player, TValue> next() {
return filtered.next();
}
public void remove() {
source.remove();
}
};
}
}

View File

@ -545,7 +545,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} else {
throw new PlayerLoggedOutException(String.format(
"Unable to send packet %s (%s): Player %s has logged out.",
packet.getID(), packet, reciever.getName()
packet.getID(), packet, reciever
));
}
}
@ -567,7 +567,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
else
throw new PlayerLoggedOutException(String.format(
"Unable to receieve packet %s. Player %s has logged out.",
mcPacket, player.getName()
mcPacket, player
));
}