ProjectAres/API/minecraft/src/main/java/tc/oc/api/minecraft/users/UserStore.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;
}
}