diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/execution/AsynchronousCommandExecutionCoordinator.java b/cloud-core/src/main/java/com/intellectualsites/commands/execution/AsynchronousCommandExecutionCoordinator.java new file mode 100644 index 00000000..96d05725 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/execution/AsynchronousCommandExecutionCoordinator.java @@ -0,0 +1,156 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package com.intellectualsites.commands.execution; + +import com.intellectualsites.commands.Command; +import com.intellectualsites.commands.CommandTree; +import com.intellectualsites.commands.context.CommandContext; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Execution coordinator parses and/or executes commands on a separate thread from the calling thread + * + * @param Command sender type + */ +public final class AsynchronousCommandExecutionCoordinator extends CommandExecutionCoordinator { + + private final Executor executor; + private final boolean synchronizeParsing; + + private AsynchronousCommandExecutionCoordinator(@Nullable final Executor executor, + final boolean synchronizeParsing, + @Nonnull final CommandTree commandTree) { + super(commandTree); + this.executor = executor; + this.synchronizeParsing = synchronizeParsing; + } + + /** + * Create a new {@link Builder} instance + * + * @param Command sender type + * @return Builder + */ + @Nonnull + public static Builder newBuilder() { + return new Builder<>(); + } + + @Override + public CompletableFuture> coordinateExecution(@Nonnull final CommandContext commandContext, + @Nonnull final Queue input) { + + final Supplier> supplier; + if (this.synchronizeParsing) { + final Optional> commandOptional = this.getCommandTree().parse(commandContext, input); + supplier = () -> { + commandOptional.ifPresent(command -> command.getCommandExecutionHandler().execute(commandContext)); + return new CommandResult<>(commandContext); + }; + } else { + supplier = () -> { + this.getCommandTree().parse(commandContext, input).ifPresent( + command -> command.getCommandExecutionHandler().execute(commandContext)); + return new CommandResult<>(commandContext); + }; + } + if (this.executor != null) { + return CompletableFuture.supplyAsync(supplier, this.executor); + } else { + return CompletableFuture.supplyAsync(supplier); + } + } + + + /** + * Builder for {@link AsynchronousCommandExecutionCoordinator} instances + * + * @param Command sender type + */ + public static final class Builder { + + private Executor executor = null; + private boolean synchronizeParsing = false; + + private Builder() { + } + + /** + * This forces the command parsing to run on the calling thread, + * and only the actual command execution will run using the executor + * + * @return Builder instance + */ + @Nonnull + public Builder withSynchronousParsing() { + this.synchronizeParsing = true; + return this; + } + + /** + * Both command parsing and execution will run using the executor + * + * @return Builder instance + */ + @Nonnull + public Builder withAsynchronousParsing() { + this.synchronizeParsing = false; + return this; + } + + /** + * Specify an executor that will be used to coordinate tasks. + * By default the executor uses {@link java.util.concurrent.ForkJoinPool#commonPool()} + * + * @param executor Executor to use + * @return Builder instance + */ + @Nonnull + public Builder withExecutor(@Nonnull final Executor executor) { + this.executor = executor; + return this; + } + + /** + * Builder a function that generates a command execution coordinator + * using the options specified in this builder + * + * @return Function that builds the coordinator + */ + @Nonnull + public Function, CommandExecutionCoordinator> build() { + return tree -> new AsynchronousCommandExecutionCoordinator<>(this.executor, this.synchronizeParsing, tree); + } + + } + +} diff --git a/cloud-minecraft/cloud-bukkit-test/src/main/java/com/intellectualsites/commands/BukkitTest.java b/cloud-minecraft/cloud-bukkit-test/src/main/java/com/intellectualsites/commands/BukkitTest.java index fc170e82..e7dad8c0 100644 --- a/cloud-minecraft/cloud-bukkit-test/src/main/java/com/intellectualsites/commands/BukkitTest.java +++ b/cloud-minecraft/cloud-bukkit-test/src/main/java/com/intellectualsites/commands/BukkitTest.java @@ -41,6 +41,7 @@ import com.intellectualsites.commands.bukkit.BukkitCommandManager; import com.intellectualsites.commands.bukkit.BukkitCommandMetaBuilder; import com.intellectualsites.commands.bukkit.CloudBukkitCapabilities; import com.intellectualsites.commands.bukkit.parsers.WorldArgument; +import com.intellectualsites.commands.execution.AsynchronousCommandExecutionCoordinator; import com.intellectualsites.commands.execution.CommandExecutionCoordinator; import com.intellectualsites.commands.meta.SimpleCommandMeta; import com.intellectualsites.commands.paper.PaperCommandManager; @@ -72,10 +73,11 @@ public final class BukkitTest extends JavaPlugin { @Override public void onEnable() { try { + final Function, CommandExecutionCoordinator> executionCoordinatorFunction = + AsynchronousCommandExecutionCoordinator.newBuilder().build(); mgr = new PaperCommandManager<>( this, - CommandExecutionCoordinator - .simpleCoordinator(), + executionCoordinatorFunction, Function.identity(), Function.identity() ); @@ -191,6 +193,7 @@ public final class BukkitTest extends JavaPlugin { } Bukkit.broadcastMessage(ChatColor.GRAY + "Using Registration Manager: " + this.mgr.getCommandRegistrationHandler().getClass().getSimpleName()); + Bukkit.broadcastMessage(ChatColor.GRAY + "Calling Thread: " + Thread.currentThread().getName()); } @Nonnull