247 lines
7.5 KiB
Java
247 lines
7.5 KiB
Java
|
package tc.oc.api.minecraft.users;
|
||
|
|
||
|
import java.lang.ref.WeakReference;
|
||
|
import java.util.Collection;
|
||
|
import java.util.Map;
|
||
|
import java.util.Optional;
|
||
|
import java.util.UUID;
|
||
|
import java.util.WeakHashMap;
|
||
|
import java.util.concurrent.Executor;
|
||
|
import javax.annotation.Nullable;
|
||
|
import javax.inject.Inject;
|
||
|
import javax.inject.Provider;
|
||
|
|
||
|
import tc.oc.api.docs.PlayerId;
|
||
|
import tc.oc.api.docs.Session;
|
||
|
import tc.oc.api.docs.SimplePlayerId;
|
||
|
import tc.oc.api.docs.User;
|
||
|
import tc.oc.api.docs.UserId;
|
||
|
import tc.oc.api.model.ModelSync;
|
||
|
import tc.oc.commons.core.util.ProxyUtils;
|
||
|
import tc.oc.minecraft.api.entity.Player;
|
||
|
import tc.oc.minecraft.api.server.LocalServer;
|
||
|
|
||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||
|
|
||
|
public abstract class UserStore<P extends Player> implements OnlinePlayers<P> {
|
||
|
|
||
|
@Inject private LocalServer server;
|
||
|
@Inject private @ModelSync Executor modelSync;
|
||
|
|
||
|
private final Map<Player, User> users = new WeakHashMap<>();
|
||
|
private final Map<Player, Session> sessions = new WeakHashMap<>();
|
||
|
|
||
|
// Keys of this map are the actual proxies
|
||
|
private final WeakHashMap<UserId, EverfreshUser> proxies = new WeakHashMap<>();
|
||
|
|
||
|
private static class EverfreshUser implements Provider<User> {
|
||
|
User real;
|
||
|
WeakReference<User> proxy;
|
||
|
|
||
|
@Override
|
||
|
public User get() {
|
||
|
return real;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Collection<P> all() {
|
||
|
return (Collection<P>) server.getOnlinePlayers();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public @Nullable P find(String name) {
|
||
|
return (P) server.getPlayerExact(name);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public @Nullable P find(UUID uuid) {
|
||
|
return (P) server.getPlayer(uuid);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public @Nullable P find(UserId userId) {
|
||
|
if(userId instanceof PlayerId) {
|
||
|
return find(((PlayerId) userId).username());
|
||
|
} else {
|
||
|
final User user = tryUser(userId);
|
||
|
return user == null ? null : find(user.username());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected void updateUser(Player player, @Nullable User before, @Nullable User after) {
|
||
|
if(after == null) {
|
||
|
users.remove(player);
|
||
|
proxies.remove(before);
|
||
|
} else {
|
||
|
users.put(player, after);
|
||
|
|
||
|
final EverfreshUser proxy = proxies.get(after);
|
||
|
if(proxy != null) {
|
||
|
proxy.real = after;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void addUser(Player player, User user) {
|
||
|
checkNotNull(user);
|
||
|
updateUser(player, null, user);
|
||
|
}
|
||
|
|
||
|
public @Nullable User replaceUser(Player player, User user) {
|
||
|
checkNotNull(player);
|
||
|
checkNotNull(user);
|
||
|
final User old = users.get(player);
|
||
|
if(old != null) {
|
||
|
updateUser(player, old, user);
|
||
|
}
|
||
|
return old;
|
||
|
}
|
||
|
|
||
|
public void handleUpdate(User replacement) {
|
||
|
modelSync.execute(() -> {
|
||
|
final P player = find(replacement.uuid());
|
||
|
if(player != null) {
|
||
|
replaceUser(player, replacement);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public @Nullable User removeUser(Player player) {
|
||
|
final User old = users.get(player);
|
||
|
if(old != null) {
|
||
|
updateUser(player, old, null);
|
||
|
}
|
||
|
return old;
|
||
|
}
|
||
|
|
||
|
public boolean hasUser(Player player) {
|
||
|
return users.containsKey(player);
|
||
|
}
|
||
|
|
||
|
public Optional<User> user(UserId userId) {
|
||
|
return Optional.ofNullable(tryUser(userId));
|
||
|
}
|
||
|
|
||
|
public Optional<User> user(Player player) {
|
||
|
return Optional.ofNullable(tryUser(player));
|
||
|
}
|
||
|
|
||
|
public @Nullable User tryUser(UserId userId) {
|
||
|
// If the argument is a User already, return it
|
||
|
if(userId instanceof User) {
|
||
|
return (User) userId;
|
||
|
}
|
||
|
|
||
|
// Search the store for a full User
|
||
|
for(User user : users.values()) {
|
||
|
// User extends UserId, so they will compare equal if they are the same user
|
||
|
if(userId.equals(user)) return user;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public @Nullable User tryUser(Player player) {
|
||
|
return users.get(player);
|
||
|
}
|
||
|
|
||
|
public User getUser(UserId userId) {
|
||
|
final User user = tryUser(userId);
|
||
|
if(user == null) {
|
||
|
throw new IllegalStateException("User " + userId + " has no cached user document");
|
||
|
}
|
||
|
return user;
|
||
|
}
|
||
|
|
||
|
public User getUser(Player player) {
|
||
|
final User doc = tryUser(player);
|
||
|
if(doc == null) {
|
||
|
throw new IllegalStateException("Player " + player + " has no cached user document");
|
||
|
}
|
||
|
return doc;
|
||
|
}
|
||
|
|
||
|
public PlayerId playerId(UserId userId) {
|
||
|
if(userId instanceof PlayerId) {
|
||
|
return SimplePlayerId.copyOf((PlayerId) userId);
|
||
|
}
|
||
|
return SimplePlayerId.copyOf(getUser(userId));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a light-weight {@link PlayerId} for the given player.
|
||
|
*/
|
||
|
public PlayerId playerId(Player player) {
|
||
|
return SimplePlayerId.copyOf(getUser(player));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a dynamic {@link User} instance that stays in sync with the latest
|
||
|
* version of the document.
|
||
|
*
|
||
|
* The given player must be online when this method is called, but the returned
|
||
|
* document can be used even after they have disconnected, it just won't update.
|
||
|
* If they reconnect, the old document will resume updating.
|
||
|
*
|
||
|
* These proxies are stored in a weak collection, so don't hold references to
|
||
|
* them for any longer than necessary.
|
||
|
*/
|
||
|
public User getEverfreshUser(Player player) {
|
||
|
return getEverfreshUser(getUser(player));
|
||
|
}
|
||
|
|
||
|
public User getEverfreshUser(UserId user) {
|
||
|
// Look for an existing proxy
|
||
|
EverfreshUser provider = proxies.get(user);
|
||
|
if(provider != null) {
|
||
|
final User fake = provider.proxy.get();
|
||
|
if(fake != null) {
|
||
|
// If the proxy is still available, return it
|
||
|
return fake;
|
||
|
} else {
|
||
|
// If the proxy was garbage collected, remove
|
||
|
// the entry from the map, and create a new one.
|
||
|
proxies.remove(user);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create a new provider and store the latest User document in it.
|
||
|
// Don't store the document passed to the method, because we don't
|
||
|
// know how old it is, or whether or not it is a proxy itself.
|
||
|
provider = new EverfreshUser();
|
||
|
provider.real = getUser(get(user));
|
||
|
|
||
|
// Store the proxy User in the provider, so that we can look it up later.
|
||
|
// There is no fast way to get a key from a map, so we have to store
|
||
|
// a copy of it in the value. It has to be a weak reference or the entry
|
||
|
// in the weak map would never be released.
|
||
|
final User proxy = ProxyUtils.newProviderProxy(User.class, provider);
|
||
|
provider.proxy = new WeakReference<>(proxy);
|
||
|
|
||
|
// Store the provider with the proxy User as the (weak) key
|
||
|
proxies.put(proxy, provider);
|
||
|
return proxy;
|
||
|
}
|
||
|
|
||
|
public void setSession(Player player, Session session) {
|
||
|
sessions.put(player, checkNotNull(session));
|
||
|
}
|
||
|
|
||
|
public @Nullable Session removeSession(Player player) {
|
||
|
return sessions.remove(player);
|
||
|
}
|
||
|
|
||
|
public Optional<Session> session(Player player) {
|
||
|
return Optional.ofNullable(sessions.get(player));
|
||
|
}
|
||
|
|
||
|
public Session getSession(Player player) {
|
||
|
Session session = sessions.get(player);
|
||
|
if(session == null) {
|
||
|
throw new IllegalStateException("Player " + player.getName() + " has no session");
|
||
|
}
|
||
|
return session;
|
||
|
}
|
||
|
}
|