Implement AsyncResult, a wrapper for CompletableFuture

This commit is contained in:
Ben Woo 2023-09-14 00:36:22 +08:00
parent 2642d940ba
commit 82c84c2c7c
No known key found for this signature in database
GPG Key ID: FB2A3645536E12C8
8 changed files with 220 additions and 55 deletions

View File

@ -1,6 +1,6 @@
package org.mvplugins.multiverse.core.commands;
import java.util.concurrent.CompletableFuture;
import java.util.Collections;
import co.aikar.commands.MessageType;
import co.aikar.commands.annotation.CommandAlias;
@ -24,6 +24,7 @@ import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.commandtools.queue.QueuedCommand;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.utils.result.AsyncResult;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
@ -80,9 +81,9 @@ class DeleteCommand extends MultiverseCommand {
private void runDeleteCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, ParsedCommandFlags parsedFlags) {
issuer.sendInfo(MVCorei18n.DELETE_DELETING, "{world}", world.getName());
CompletableFuture<Void> future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
? CompletableFuture.allOf(playerWorldTeleporter.removeFromWorld(world))
: CompletableFuture.completedFuture(null);
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
? playerWorldTeleporter.removeFromWorld(world)
: AsyncResult.completedFuture(Collections.emptyList());
future.thenRun(() -> doWorldDeleting(issuer, world));
}

View File

@ -3,7 +3,6 @@ package org.mvplugins.multiverse.core.commands;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import co.aikar.commands.MessageType;
import co.aikar.commands.annotation.CommandAlias;
@ -27,6 +26,7 @@ import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.commandtools.queue.QueuedCommand;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.utils.result.AsyncResult;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
@ -99,9 +99,9 @@ class RegenCommand extends MultiverseCommand {
issuer.sendInfo(MVCorei18n.REGEN_REGENERATING, "{world}", world.getName());
List<Player> worldPlayers = world.getPlayers().getOrElse(Collections.emptyList());
CompletableFuture<Void> future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
? CompletableFuture.allOf(playerWorldTeleporter.removeFromWorld(world))
: CompletableFuture.completedFuture(null);
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
? playerWorldTeleporter.removeFromWorld(world)
: AsyncResult.completedFuture(Collections.emptyList());
future.thenRun(() -> doWorldRegening(issuer, world, parsedFlags, worldPlayers));
}

View File

@ -1,6 +1,6 @@
package org.mvplugins.multiverse.core.commands;
import java.util.concurrent.CompletableFuture;
import java.util.Collections;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
@ -22,6 +22,7 @@ import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.utils.result.AsyncResult;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
@ -66,11 +67,11 @@ class RemoveCommand extends MultiverseCommand {
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
CompletableFuture<Void> future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
? worldManager.getLoadedWorld(worldName)
.map(world -> CompletableFuture.allOf(playerWorldTeleporter.removeFromWorld(world)))
.getOrElse(CompletableFuture.completedFuture(null))
: CompletableFuture.completedFuture(null);
.map(playerWorldTeleporter::removeFromWorld)
.getOrElse(AsyncResult.completedFuture(Collections.emptyList()))
: AsyncResult.completedFuture(Collections.emptyList());
future.thenRun(() -> doWorldRemoving(issuer, worldName));
}

View File

@ -1,7 +1,6 @@
package org.mvplugins.multiverse.core.commands;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
@ -23,6 +22,7 @@ import org.mvplugins.multiverse.core.destination.ParsedDestination;
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.utils.result.AsyncResult;
@Service
@CommandAlias("mv")
@ -72,10 +72,14 @@ class TeleportCommand extends MultiverseCommand {
issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
"{player}", playerName, "{destination}", destination.toString());
CompletableFuture
.allOf(safetyTeleporter.teleportSafely(issuer.getIssuer(), Arrays.stream(players).toList(),
destination))
.thenAccept(result -> Logging.fine("Async teleport result: %s", result));
AsyncResult.allOf(Arrays.stream(players)
.map(player -> safetyTeleporter.teleportSafely(issuer.getIssuer(), player, destination))
.toList())
.thenRun(() -> Logging.fine("Async teleport result: %s"))
.exceptionally(throwable -> {
Logging.severe("Error while teleporting %s to %s: %s",
playerName, destination, throwable.getMessage());
});
}
@Override

View File

@ -1,6 +1,6 @@
package org.mvplugins.multiverse.core.commands;
import java.util.concurrent.CompletableFuture;
import java.util.Collections;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
@ -20,6 +20,7 @@ import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.utils.result.AsyncResult;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
@ -70,9 +71,9 @@ class UnloadCommand extends MultiverseCommand {
issuer.sendInfo(MVCorei18n.UNLOAD_UNLOADING, "{world}", world.getAlias());
CompletableFuture<Void> future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
? CompletableFuture.allOf(playerWorldTeleporter.removeFromWorld(world))
: CompletableFuture.completedFuture(null);
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
? playerWorldTeleporter.removeFromWorld(world)
: AsyncResult.completedFuture(Collections.emptyList());
future.thenRun(() -> doWorldUnloading(issuer, world, parsedFlags));
}

View File

@ -1,7 +1,6 @@
package org.mvplugins.multiverse.core.teleportation;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import com.dumptruckman.minecraft.util.Logging;
import io.papermc.lib.PaperLib;
@ -17,6 +16,7 @@ import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.api.BlockSafety;
import org.mvplugins.multiverse.core.destination.ParsedDestination;
import org.mvplugins.multiverse.core.utils.result.AsyncResult;
import org.mvplugins.multiverse.core.utils.result.Result;
@SuppressWarnings("unchecked")
@ -33,97 +33,97 @@ public class AsyncSafetyTeleporter {
this.teleportQueue = teleportQueue;
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
public AsyncResult<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
@NotNull Entity teleportee,
@Nullable ParsedDestination<?> destination) {
return teleportSafely(null, teleportee, destination);
}
public <T extends Entity> CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>[] teleportSafely(
public <T extends Entity> AsyncResult<List<Result<TeleportResult.Success, TeleportResult.Failure>>> teleportSafely(
@Nullable CommandSender teleporter,
@NotNull List<T> teleportees,
@Nullable ParsedDestination<?> destination) {
return teleportees.stream()
return AsyncResult.allOf(teleportees.stream()
.map(teleportee -> teleportSafely(teleporter, teleportee, destination))
.toArray(CompletableFuture[]::new);
.toList());
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
public AsyncResult<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
@Nullable CommandSender teleporter,
@NotNull Entity teleportee,
@Nullable ParsedDestination<?> destination) {
if (destination == null) {
return CompletableFuture.completedFuture(Result.failure(TeleportResult.Failure.NULL_DESTINATION));
return AsyncResult.completedFuture(Result.failure(TeleportResult.Failure.NULL_DESTINATION));
}
return destination.getDestination().checkTeleportSafety()
? teleportSafely(teleporter, teleportee, destination.getLocation(teleportee))
: teleport(teleporter, teleportee, destination.getLocation(teleportee));
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
public AsyncResult<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
@NotNull Entity teleportee,
@Nullable Location location) {
return teleportSafely(null, teleportee, location);
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
public AsyncResult<Result<TeleportResult.Success, TeleportResult.Failure>> teleportSafely(
@Nullable CommandSender teleporter,
@NotNull Entity teleportee,
@Nullable Location location) {
if (location == null) {
return CompletableFuture.completedFuture(Result.failure(TeleportResult.Failure.NULL_LOCATION));
return AsyncResult.completedFuture(Result.failure(TeleportResult.Failure.NULL_LOCATION));
}
Location safeLocation = blockSafety.getSafeLocation(location);
if (safeLocation == null) {
return CompletableFuture.completedFuture(Result.failure(TeleportResult.Failure.UNSAFE_LOCATION));
return AsyncResult.completedFuture(Result.failure(TeleportResult.Failure.UNSAFE_LOCATION));
}
return teleport(teleporter, teleportee, safeLocation);
}
public <T extends Entity> CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>[] teleport(
public <T extends Entity> AsyncResult<List<Result<TeleportResult.Success, TeleportResult.Failure>>> teleport(
@NotNull List<T> teleportees,
@Nullable ParsedDestination<?> destination) {
return teleportees.stream()
return AsyncResult.allOf(teleportees.stream()
.map(teleportee -> teleport(teleportee, destination))
.toArray(CompletableFuture[]::new);
.toList());
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
public AsyncResult<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
@NotNull Entity teleportee,
@Nullable ParsedDestination<?> destination) {
return teleport(null, teleportee, destination);
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
public AsyncResult<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
@Nullable CommandSender teleporter,
@NotNull Entity teleportee,
@Nullable ParsedDestination<?> destination) {
if (destination == null) {
return CompletableFuture.completedFuture(Result.failure(TeleportResult.Failure.NULL_DESTINATION));
return AsyncResult.completedFuture(Result.failure(TeleportResult.Failure.NULL_DESTINATION));
}
return teleport(teleporter, teleportee, destination.getLocation(teleportee));
}
public <T extends Entity> CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>[] teleport(
public <T extends Entity> AsyncResult<List<Result<TeleportResult.Success, TeleportResult.Failure>>> teleport(
@NotNull List<T> teleportees,
@Nullable Location location) {
return teleportees.stream()
return AsyncResult.allOf(teleportees.stream()
.map(teleportee -> teleport(teleportee, location))
.toArray(CompletableFuture[]::new);
.toList());
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
public AsyncResult<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
@NotNull Entity teleportee,
@Nullable Location location) {
return teleport(null, teleportee, location);
}
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
public AsyncResult<Result<TeleportResult.Success, TeleportResult.Failure>> teleport(
@Nullable CommandSender teleporter,
@NotNull Entity teleportee,
@Nullable Location location) {
if (location == null) {
return CompletableFuture.completedFuture(Result.failure(TeleportResult.Failure.NULL_LOCATION));
return AsyncResult.completedFuture(Result.failure(TeleportResult.Failure.NULL_LOCATION));
}
boolean shouldAddToQueue = teleporter != null && teleportee instanceof Player;
@ -131,7 +131,7 @@ public class AsyncSafetyTeleporter {
teleportQueue.addToQueue(teleporter.getName(), teleportee.getName());
}
CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> future = new CompletableFuture<>();
AsyncResult<Result<TeleportResult.Success, TeleportResult.Failure>> future = new AsyncResult<>();
doAsyncTeleport(teleportee, location, future, shouldAddToQueue);
return future;
}
@ -139,7 +139,7 @@ public class AsyncSafetyTeleporter {
private void doAsyncTeleport(
@NotNull Entity teleportee,
@NotNull Location location,
CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>> future,
AsyncResult<Result<TeleportResult.Success, TeleportResult.Failure>> future,
boolean shouldAddToQueue) {
Try.run(() -> PaperLib.teleportAsync(teleportee, location).thenAccept(result -> {
Logging.finer("Teleported async %s to %s", teleportee.getName(), location);

View File

@ -0,0 +1,158 @@
package org.mvplugins.multiverse.core.utils.result;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
public final class AsyncResult<T> {
/**
* Returns a new AsyncResult that is completed when all of the given AsyncResult complete.
*
* @param results The results to wait for.
* @return A new AsyncResult that is completed when all of the given AsyncResult complete.
*/
public static AsyncResult<Void> allOf(AsyncResult<?>... results) {
return new AsyncResult<>(CompletableFuture.allOf(Arrays.stream(results)
.map(result -> result.future)
.toArray(CompletableFuture[]::new)));
}
/**
* Returns a new AsyncResult that is completed when all of the given AsyncResult with the same type complete.
*
* @param results The results to wait for.
* @param <T> The type of the AsyncResult.
* @return A new AsyncResult that is completed when all of the given AsyncResult complete.
*/
public static <T> AsyncResult<List<T>> allOf(List<AsyncResult<T>> results) {
return new AsyncResult<>(CompletableFuture.allOf(results.stream()
.map(result -> result.future)
.toArray(CompletableFuture[]::new))
.thenApply(v -> results.stream()
.map(result -> result.future.join())
.toList()));
}
/**
* Wraps a CompletableFuture in an AsyncResult.
*
* @param future The future to wrap.
* @param <T> The type of the future.
* @return A new AsyncResult that is completed when the given future completes.
*/
public static <T> AsyncResult<T> of(CompletableFuture<T> future) {
return new AsyncResult<>(future);
}
/**
* Returns a new AsyncResult that is already completed with the given value.
*
* @param value The value to complete the AsyncResult with.
* @param <T> The type of the value.
* @return The completed AsyncResult.
*/
public static <T> AsyncResult<T> completedFuture(T value) {
return new AsyncResult<>(CompletableFuture.completedFuture(value));
}
/**
* Returns a new CompletableFuture that is already completed exceptionally with the given exception.
*
* @param throwable The exception to complete the AsyncResult with.
* @param <T> The type of the value.
* @return The completed AsyncResult.
*/
public static <T> AsyncResult<T> failedFuture(Throwable throwable) {
return new AsyncResult<>(CompletableFuture.failedFuture(throwable));
}
private final CompletableFuture<T> future;
/**
* Creates a new AsyncResult.
*/
public AsyncResult() {
this(new CompletableFuture<>());
}
private AsyncResult(CompletableFuture<T> future) {
this.future = future;
}
/**
* If not already completed, sets the value returned by related methods to the given value.
*
* @param value The value to complete the AsyncResult with.
* @return true if this invocation caused this AsyncResult to transition to a completed state, else false.
*/
public boolean complete(T value) {
return future.complete(value);
}
/**
* If not already completed, causes invocations of related methods to throw the given exception.
*
* @param throwable The exception to complete the AsyncResult with.
* @return true if this invocation caused this AsyncResult to transition to a completed state, else false.
*/
public boolean completeExceptionally(Throwable throwable) {
return future.completeExceptionally(throwable);
}
/**
* Executes the given action when this AsyncResult completes.
*
* @param consumer The action to perform.
* @return This AsyncResult.
*/
public AsyncResult<Void> thenAccept(Consumer<T> consumer) {
return new AsyncResult<>(future.thenAccept(consumer));
}
/**
* Executes the given action when this AsyncResult completes.
*
* @param runnable The action to perform.
* @return This AsyncResult.
*/
public AsyncResult<Void> thenRun(Runnable runnable) {
return new AsyncResult<>(future.thenRun(runnable));
}
/**
* Executes the given action when this AsyncResult completes and returns a new AsyncResult with the new value.
*
* @param function The action to perform.
* @param <U> The type of the new value.
* @return A new AsyncResult with the new value.
*/
public <U> AsyncResult<U> thenApply(Function<T, U> function) {
return new AsyncResult<>(future.thenApply(function));
}
/**
* Executes the given action when this AsyncResult completes with an exception.
*
* @param consumer The action to perform.
* @return This AsyncResult.
*/
public AsyncResult<T> exceptionally(Consumer<Throwable> consumer) {
return new AsyncResult<>(future.exceptionally(throwable -> {
consumer.accept(throwable);
return null;
}));
}
/**
* Executes the given action when this AsyncResult completes with an exception and returns a new AsyncResult with the new value.
*
* @param function The action to perform.
* @return A new AsyncResult with the new value.
*/
public AsyncResult<T> exceptionally(Function<Throwable, T> function) {
return new AsyncResult<>(future.exceptionally(function));
}
}

View File

@ -1,8 +1,6 @@
package org.mvplugins.multiverse.core.world.helpers;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import jakarta.inject.Inject;
import org.bukkit.Bukkit;
@ -14,6 +12,7 @@ import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
import org.mvplugins.multiverse.core.teleportation.TeleportResult;
import org.mvplugins.multiverse.core.utils.result.AsyncResult;
import org.mvplugins.multiverse.core.utils.result.Result;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
@ -34,7 +33,7 @@ public class PlayerWorldTeleporter {
*
* @param world The world to remove all players from.
*/
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>[]
public AsyncResult<List<Result<TeleportResult.Success, TeleportResult.Failure>>>
removeFromWorld(@NotNull LoadedMultiverseWorld world) {
// TODO: Better handling of fallback world
World toWorld = Bukkit.getWorlds().get(0);
@ -47,7 +46,7 @@ public class PlayerWorldTeleporter {
* @param from The world to transfer players from.
* @param to The location to transfer players to.
*/
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>[]
public AsyncResult<List<Result<TeleportResult.Success, TeleportResult.Failure>>>
transferFromWorldTo(@NotNull LoadedMultiverseWorld from, @NotNull LoadedMultiverseWorld to) {
return transferAllFromWorldToLocation(from, to.getSpawnLocation());
}
@ -58,7 +57,7 @@ public class PlayerWorldTeleporter {
* @param from The world to transfer players from.
* @param to The world to transfer players to.
*/
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>[]
public AsyncResult<List<Result<TeleportResult.Success, TeleportResult.Failure>>>
transferFromWorldTo(@NotNull LoadedMultiverseWorld from, @NotNull World to) {
return transferAllFromWorldToLocation(from, to.getSpawnLocation());
}
@ -70,11 +69,12 @@ public class PlayerWorldTeleporter {
* @param location The location to transfer players to.
* @return A list of futures that represent the teleportation of each player.
*/
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>[]
public AsyncResult<List<Result<TeleportResult.Success, TeleportResult.Failure>>>
transferAllFromWorldToLocation(@NotNull LoadedMultiverseWorld world, @NotNull Location location) {
return world.getPlayers()
.map(players -> safetyTeleporter.teleport(players, location))
.getOrElse(new CompletableFuture[0]);
.getOrElse(() -> AsyncResult.failedFuture(
new IllegalStateException("Unable to get players from world" + world.getName())));
}
/**
@ -83,7 +83,7 @@ public class PlayerWorldTeleporter {
* @param players The players to teleport.
* @param world The world to teleport players to.
*/
public CompletableFuture<Result<TeleportResult.Success, TeleportResult.Failure>>[]
public AsyncResult<List<Result<TeleportResult.Success, TeleportResult.Failure>>>
teleportPlayersToWorld(@NotNull List<Player> players, @NotNull LoadedMultiverseWorld world) {
Location spawnLocation = world.getSpawnLocation();
return safetyTeleporter.teleport(players, spawnLocation);