Skip to content

Commit

Permalink
Change the UserManager to use CompletableFutures to avoid on-thread IO
Browse files Browse the repository at this point in the history
* EventContextKeys that were users are now UUIDs.
  • Loading branch information
dualspiral committed Jul 17, 2021
1 parent 5c78552 commit 5dc6a2b
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -655,8 +655,8 @@ static Parameter.Value.Builder<URL> url() {
*
* @return A {@link Parameter.Value.Builder}
*/
static Parameter.Value.Builder<User> user() {
return Parameter.builder(User.class, ResourceKeyedValueParameters.USER);
static Parameter.Value.Builder<UUID> user() {
return Parameter.builder(UUID.class, ResourceKeyedValueParameters.USER);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ default Optional<? extends T> parseValue(
* you wish to do so, implement {@link ValueParameter} instead.
*
* {@inheritDoc}
* @return
*/
@Override
default List<CommandCompletion> complete(final CommandContext context, final String currentInput) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.spongepowered.api.registry.RegistryScope;
import org.spongepowered.api.registry.RegistryScopes;
import org.spongepowered.api.registry.RegistryTypes;
import org.spongepowered.api.user.UserManager;
import org.spongepowered.api.util.Color;
import org.spongepowered.api.world.Locatable;
import org.spongepowered.api.world.server.ServerLocation;
Expand Down Expand Up @@ -402,14 +403,27 @@ public final class ResourceKeyedValueParameters {
public static final DefaultedRegistryReference<ResourceKeyedValueParameter<URL>> URL = ResourceKeyedValueParameters.key(ResourceKey.sponge("url"));

/**
* Expect an argument to represent a player who has been online at some
* point, as a {@link User}.
* Expect an argument to represent the UUID of a player who has been online
* at some point - that is, a {@link UUID} where either:
*
* <ul>
* <li>The player is online; or</li>
* <li>The user with the returned {@link UUID} has user data available
* in the {@link UserManager} (that is, for this UUID,
* {@link UserManager#exists(java.util.UUID)} will return {@code true}).
* </li>
* </ul>
*
* <p>As {@link User} objects are potentially slow to load, they are not
* created or returned here. In general, you will want to run
* {@link UserManager#load(java.util.UUID)} on the returned UUID to actually
* obtain the user.</p>
*
* <p>This parameter accepts selectors (to obtain players).</p>
*
* <p>Returns a {@link User}.</p>
* <p>Returns a {@link UUID}.</p>
*/
public static final DefaultedRegistryReference<ResourceKeyedValueParameter<User>> USER = ResourceKeyedValueParameters.key(ResourceKey.sponge("user"));
public static final DefaultedRegistryReference<ResourceKeyedValueParameter<UUID>> USER = ResourceKeyedValueParameters.key(ResourceKey.sponge("user"));

/**
* Expect an argument to represent a {@link UUID}
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/org/spongepowered/api/entity/living/player/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
import org.spongepowered.api.item.inventory.ArmorEquipable;
import org.spongepowered.api.item.inventory.Carrier;
import org.spongepowered.api.item.inventory.Inventory;
import org.spongepowered.api.item.inventory.entity.PlayerInventory;
import org.spongepowered.api.item.inventory.entity.UserInventory;
import org.spongepowered.api.item.inventory.type.CarriedInventory;
import org.spongepowered.api.profile.GameProfile;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.api.util.annotation.DoNotStore;
Expand Down Expand Up @@ -129,12 +131,18 @@ public interface User extends DataHolder.Mutable, ArmorEquipable, Tamer, Subject
*/
Vector3d rotation();

/**
* {@inheritDoc}
*
* Note that this may be either a {@link PlayerInventory} or
* {@link UserInventory}, depending on whether the user is online or not.
*/
@Override
UserInventory inventory();
CarriedInventory<? extends Carrier> inventory();

/**
* Gets the {@link Inventory} available for this Player's shared {@link EnderChest}
* contents.
* Gets the {@link Inventory} available for this Player's shared
* {@link EnderChest} contents.
*
* @return The ender chest inventory
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
import org.spongepowered.math.vector.Vector3d;
import org.spongepowered.plugin.PluginContainer;

import java.util.UUID;

/**
* Standard keys for use within {@link EventContext}s.
*/
Expand Down Expand Up @@ -111,7 +113,7 @@ public final class EventContextKeys {
/**
* Represents the creator of an {@link Entity} or a {@link BlockState} at a {@link Location}
*/
public static final EventContextKey<User> CREATOR = EventContextKeys.key(ResourceKey.sponge("creator"), User.class);
public static final EventContextKey<UUID> CREATOR = EventContextKeys.key(ResourceKey.sponge("creator"), UUID.class);

/**
* Represents the {@link DamageType} to an entity.
Expand Down Expand Up @@ -199,9 +201,9 @@ public final class EventContextKeys {
public static final EventContextKey<BlockSnapshot> NEIGHBOR_NOTIFY_SOURCE = EventContextKeys.key(ResourceKey.sponge("neighbor_notify_source"), BlockSnapshot.class);

/**
* Represents the {@link User} that notified a block.
* Represents the {@link UUID} of a {@link User} that notified a block.
*/
public static final EventContextKey<User> NOTIFIER = EventContextKeys.key(ResourceKey.sponge("notifier"), User.class);
public static final EventContextKey<UUID> NOTIFIER = EventContextKeys.key(ResourceKey.sponge("notifier"), UUID.class);

/**
* Used when a {@link BlockTypes#PISTON_HEAD} extends.
Expand Down
111 changes: 56 additions & 55 deletions src/main/java/org/spongepowered/api/user/UserManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@
import org.spongepowered.api.profile.GameProfile;
import org.spongepowered.api.profile.GameProfileManager;

import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;

/**
* Stores the persistent {@link User} data of a {@link Player}.
*
* <p>Any {@link User}s retrieved from this manager should not be stored, as
* they may become invalid at any time.</p>
*/
public interface UserManager {

Expand All @@ -45,7 +48,7 @@ public interface UserManager {
* @param uniqueId The UUID of the user
* @return {@link User} or Optional.empty() if not found
*/
Optional<User> find(UUID uniqueId);
CompletableFuture<Optional<User>> load(UUID uniqueId);

/**
* Gets the data of a {@link User} by their last known user name
Expand All @@ -57,58 +60,76 @@ public interface UserManager {
* @param lastKnownName The user name
* @return {@link User} or Optional.empty() if not found
*/
Optional<User> find(String lastKnownName);
CompletableFuture<Optional<User>> load(String lastKnownName);

/**
* Gets the data of a {@link User} by their {@link GameProfile}.
*
* @param profile The profile
* @return {@link User} or Optional.empty() if not found
*/
Optional<User> find(GameProfile profile);
CompletableFuture<Optional<User>> load(GameProfile profile);

/**
* Gets or creates a persistent {@link User} associated with the given
* {@link GameProfile}.
*
* <p>To obtain a {@link GameProfile}, use the {@link GameProfileManager}.
* </p>
* Gets or creates a persistent {@link User} with the given UUID.
*
* @param profile The profile
* @param uuid The {@link UUID} of the player to load or create.
* @return The user object
*/
User findOrCreate(GameProfile profile);
CompletableFuture<User> loadOrCreate(UUID uuid);

/**
* Gets the collection of all {@link GameProfile}s with stored {@link User}
* data.
* Deletes the data associated with a {@link User}, if the player is
* offline.
*
* <p>This method may be resource intensive, particularly for servers that
* have a large number of {@link User}s. If you require a subset of this
* {@link Collection}, use {@link #streamOfMatches(String)} or
* {@link #streamAll()} and use {@link Stream} operations for your queries
* instead.</p>
* @param uuid The uuid of the user to delete
* @return true if the deletion was successful
*/
CompletableFuture<Boolean> delete(UUID uuid);

/**
* If the implementation supports caching user objects, this will hint
* to the implementation that the user with the given UUID should no
* longer be cached. Any {@link User} objects held that this point
* will become invalid (though developers should not be storing
* users).
*
* <p>This {@link Stream} may contain profiles that only hold a result for
* {@link GameProfile#uniqueId()}, that is, do not return a user's name.
* Such profiles should thus be treated as incomplete and are no more than
* an indicator that a {@link User} associated with the given {@link UUID}
* exists.</p>
* <p>Be aware, any changes that have been made to the user may not
* be saved.</p>
*
* <p>Similarly, for {@link GameProfile}s that are filled and thus contain
* name data, the profile information is based on the latest information
* the server holds and no attempt is made to update this information.</p>
* <p>Users that are online will not be affected by this call.</p>
*
* <p>If you require up to date {@link GameProfile}s, use the appropriate
* methods on the {@link GameProfileManager} and/or its associated
* {@link GameProfileManager}.</p>
* @param uuid The UUID of the user to save.
* @return {@code true} if the user was removed from a cache.
*/
boolean removeFromCache(UUID uuid);

/**
* If the implementation supports caching user objects, this will hint
* to the implementation that the user with the given UUID should be saved
* to the disk immediately.
*
* <p>Use {@link #find(GameProfile)} to load the {@link User} data associated
* with the associated {@link GameProfile}.</p>
* <p>If an exception is encountered during save, the completed future
* will be exceptional and the boolean will be {@code null}. It is therefore
* recommended that you check for any exceptions this future holds.</p>
*
* @return A {@link Stream} of {@link GameProfile}s
* @param uuid The user to attempt to save.
* @return A completed future that returns {@code true} if the implementation
* saved the user.
*/
CompletableFuture<Boolean> forceSave(UUID uuid);

/**
* Returns whether data to create a {@link User} exists for a given
* player with a specified {@link UUID}.
*
* <p>If this is {@code false}, then {@link #load(UUID)} will return
* an {@linkplain Optional#empty() empty optional}.</p>
*
* @param playerUuid The {@link UUID} of the player to check.
* @return If the player has existing user data that can be loaded.
*/
Collection<GameProfile> all();
boolean exists(UUID playerUuid);

/**
* Gets a {@link Stream} that returns a {@link GameProfile} for each stored
Expand All @@ -128,33 +149,13 @@ public interface UserManager {
* methods on the {@link GameProfileManager} and/or its associated
* {@link GameProfileManager}.</p>
*
* <p>Use {@link #find(GameProfile)} to load the {@link User} data associated
* <p>Use {@link #load(GameProfile)} to load the {@link User} data associated
* with the associated {@link GameProfile}.</p>
*
* @return A {@link Stream} of {@link GameProfile}s
*/
Stream<GameProfile> streamAll();

/**
* Deletes the data associated with a {@link User}.
*
* <p>This may not work if the user is logged in.</p>
*
* @param profile The profile of the user to delete
* @return true if the deletion was successful
*/
boolean delete(GameProfile profile);

/**
* Deletes the data associated with a {@link User}.
*
* <p>This may not work if the user is logged in.</p>
*
* @param user The user to delete
* @return true if the deletion was successful
*/
boolean delete(User user);

/**
* Gets a {@link Stream} that returns a {@link GameProfile} for each stored
* {@link User} whose last known user names start with the given string
Expand All @@ -168,7 +169,7 @@ public interface UserManager {
* methods on the {@link GameProfileManager} and/or its associated
* {@link GameProfileManager}.</p>
*
* <p>Use {@link #find(GameProfile)} to load associated {@link User} data.
* <p>Use {@link #load(GameProfile)} to load associated {@link User} data.
* </p>
*
* @param lastKnownName The name to check for
Expand Down

0 comments on commit 5dc6a2b

Please sign in to comment.