diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandContextKeys.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandContextKeys.java index 1ee67d29d..9b5f05b77 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandContextKeys.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandContextKeys.java @@ -26,6 +26,8 @@ import cloud.commandframework.keys.CloudKey; import io.leangen.geantyref.TypeToken; import java.util.Set; +import java.util.concurrent.Executor; +import org.apiguardian.api.API; import org.bukkit.command.CommandSender; /** @@ -56,6 +58,15 @@ public final class BukkitCommandContextKeys { } ); + /** + * Key used to store an {@link Executor} for the command sender's scheduler. + * + * @since 2.0.0 + */ + @API(status = API.Status.STABLE, since = "2.0.0") + public static final CloudKey SENDER_SCHEDULER_EXECUTOR = CloudKey.of( + "SenderSchedulerExecutor", Executor.class); + private BukkitCommandContextKeys() { } } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandPreprocessor.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandPreprocessor.java index 42f1b6a18..7838ff761 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandPreprocessor.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandPreprocessor.java @@ -27,6 +27,9 @@ import cloud.commandframework.bukkit.internal.BukkitBackwardsBrigadierSenderMapper; import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext; import cloud.commandframework.execution.preprocessor.CommandPreprocessor; +import java.util.concurrent.Executor; +import org.bukkit.Server; +import org.bukkit.plugin.Plugin; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -76,5 +79,22 @@ public void accept(final @NonNull CommandPreprocessingContext context) { BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES, this.commandManager.queryCapabilities() ); + + // Store if PaperCommandManager's preprocessor didn't already + if (!context.getCommandContext().contains(BukkitCommandContextKeys.SENDER_SCHEDULER_EXECUTOR)) { + context.getCommandContext().store(BukkitCommandContextKeys.SENDER_SCHEDULER_EXECUTOR, this.mainThreadExecutor()); + } + } + + private Executor mainThreadExecutor() { + final Plugin plugin = this.commandManager.getOwningPlugin(); + final Server server = plugin.getServer(); + return task -> { + if (server.isPrimaryThread()) { + task.run(); + return; + } + server.getScheduler().runTask(plugin, task); + }; } } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SelectorUtils.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SelectorUtils.java index 7931b6317..70b4c6d81 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SelectorUtils.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SelectorUtils.java @@ -79,7 +79,7 @@ private SelectorUtils() { if (CraftBukkitReflection.MAJOR_REVISION < 13) { return null; } - final ArgumentParser wrappedBrigParser = new WrappedBrigadierParser<>( + final WrappedBrigadierParser wrappedBrigParser = new WrappedBrigadierParser<>( () -> createEntityArgument(single, playersOnly), ArgumentParser.DEFAULT_ARGUMENT_COUNT, EntityArgumentParseFunction.INSTANCE @@ -267,11 +267,11 @@ protected PlayerSelectorParser(final boolean single) { private static class ModernSelectorParser implements ArgumentParser.FutureArgumentParser, SuggestionProvider { - private final ArgumentParser wrappedBrigadierParser; + private final WrappedBrigadierParser wrappedBrigadierParser; private final SelectorMapper mapper; ModernSelectorParser( - final ArgumentParser wrapperBrigParser, + final WrappedBrigadierParser wrapperBrigParser, final SelectorMapper mapper ) { this.wrappedBrigadierParser = wrapperBrigParser; @@ -279,27 +279,31 @@ private static class ModernSelectorParser implements ArgumentParser.Future } @Override + @SuppressWarnings("unchecked") public CompletableFuture> parseFuture( final CommandContext commandContext, final CommandInput commandInput ) { - final CommandInput originalCommandInput = commandInput.copy(); - return this.wrappedBrigadierParser.parseFuture(commandContext, commandInput) - .thenCompose(result -> { - try { - final String input = originalCommandInput.difference(commandInput); - return ArgumentParseResult.successFuture( - this.mapper.mapResult(input, new EntitySelectorWrapper(commandContext, result)) - ); - } catch (final CommandSyntaxException ex) { - commandInput.cursor(originalCommandInput.cursor()); - return ArgumentParseResult.failureFuture(ex); - } catch (final Exception ex) { - final CompletableFuture> future = new CompletableFuture<>(); - future.completeExceptionally(ex); - return future; - } - }); + return CompletableFuture.supplyAsync(() -> { + final CommandInput originalCommandInput = commandInput.copy(); + final ArgumentParseResult result = this.wrappedBrigadierParser.parse( + commandContext, + commandInput + ); + if (result.failure().isPresent()) { + return (ArgumentParseResult) result; + } + final String input = originalCommandInput.difference(commandInput); + try { + return ArgumentParseResult.success( + this.mapper.mapResult(input, new EntitySelectorWrapper(commandContext, result.parsedValue().get())) + ); + } catch (final CommandSyntaxException ex) { + return ArgumentParseResult.failure(ex); + } catch (final Exception ex) { + throw rethrow(ex); + } + }, commandContext.get(BukkitCommandContextKeys.SENDER_SCHEDULER_EXECUTOR)); } @Override diff --git a/cloud-minecraft/cloud-paper/build.gradle.kts b/cloud-minecraft/cloud-paper/build.gradle.kts index 6fc0ab472..8e3bc1182 100644 --- a/cloud-minecraft/cloud-paper/build.gradle.kts +++ b/cloud-minecraft/cloud-paper/build.gradle.kts @@ -2,6 +2,10 @@ plugins { id("cloud.base-conventions") } +java { + disableAutoTargetJvm() +} + dependencies { api(projects.cloudBukkit) compileOnly(libs.paperApi) diff --git a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperCommandManager.java b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperCommandManager.java index e50a7010e..704cf04ba 100644 --- a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperCommandManager.java +++ b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperCommandManager.java @@ -82,6 +82,8 @@ public PaperCommandManager( final @NonNull Function backwardsCommandSenderMapper ) throws Exception { super(owningPlugin, commandExecutionCoordinator, commandSenderMapper, backwardsCommandSenderMapper); + + this.registerCommandPreProcessor(new PaperCommandPreprocessor<>(this)); } /** diff --git a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperCommandPreprocessor.java b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperCommandPreprocessor.java new file mode 100644 index 000000000..fe6aa30e7 --- /dev/null +++ b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperCommandPreprocessor.java @@ -0,0 +1,89 @@ +// +// MIT License +// +// Copyright (c) 2022 Alexander Söderberg & Contributors +// +// 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 cloud.commandframework.paper; + +import cloud.commandframework.bukkit.BukkitCommandContextKeys; +import cloud.commandframework.bukkit.internal.CraftBukkitReflection; +import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext; +import cloud.commandframework.execution.preprocessor.CommandPreprocessor; +import java.util.concurrent.Executor; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +final class PaperCommandPreprocessor implements CommandPreprocessor { + + private static final boolean FOLIA = + CraftBukkitReflection.classExists("io.papermc.paper.threadedregions.RegionizedServer"); + + private final PaperCommandManager manager; + + PaperCommandPreprocessor(final PaperCommandManager manager) { + this.manager = manager; + } + + @Override + public void accept(final CommandPreprocessingContext ctx) { + // cloud-bukkit's preprocessor will store the main thread executor if we don't store anything. + if (FOLIA) { + ctx.getCommandContext().store( + BukkitCommandContextKeys.SENDER_SCHEDULER_EXECUTOR, + this.foliaExecutorFor(ctx.getCommandContext().sender()) + ); + } + } + + private Executor foliaExecutorFor(final C sender) { + final CommandSender commandSender = this.manager.getBackwardsCommandSenderMapper().apply(sender); + final Plugin plugin = this.manager.getOwningPlugin(); + if (commandSender instanceof Entity) { + return task -> { + ((Entity) commandSender).getScheduler().run( + plugin, + handle -> task.run(), + null + ); + }; + } else if (commandSender instanceof BlockCommandSender) { + final BlockCommandSender blockSender = (BlockCommandSender) commandSender; + return task -> { + blockSender.getServer().getRegionScheduler().run( + plugin, + blockSender.getBlock().getLocation(), + handle -> task.run() + ); + }; + } + return task -> { + plugin.getServer().getGlobalRegionScheduler().run( + plugin, + handle -> task.run() + ); + }; + } +} diff --git a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/builder/feature/minecraft/SelectorExample.java b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/builder/feature/minecraft/SelectorExample.java index 1256aea9a..57176198e 100644 --- a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/builder/feature/minecraft/SelectorExample.java +++ b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/builder/feature/minecraft/SelectorExample.java @@ -54,7 +54,9 @@ public void registerFeature( .handler(commandContext -> { final Player player = commandContext.sender(); final SingleEntitySelector singleEntitySelector = commandContext.get("entity"); - singleEntitySelector.single().teleport(player); + player.getServer().getScheduler().runTask(examplePlugin, () -> { + singleEntitySelector.single().teleport(player); + }); player.sendMessage(ChatColor.GREEN + "The entity was teleported to you!"); })); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d5b66a0b5..df705cdb6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,7 +42,7 @@ bungeecord = "1.8-SNAPSHOT" cloudburst = "1.0.0-SNAPSHOT" adventureApi = "4.14.0" adventurePlatform = "4.3.1" -paperApi = "1.16.5-R0.1-SNAPSHOT" +paperApi = "1.20.4-R0.1-SNAPSHOT" velocityApi = "3.1.1" spongeApi7 = "7.3.0" jetbrainsAnnotations = "24.1.0" @@ -102,8 +102,8 @@ adventureApi = { group = "net.kyori", name = "adventure-api", version.ref = "adv adventurePlatformBukkit = { group = "net.kyori", name = "adventure-platform-bukkit", version.ref = "adventurePlatform" } adventurePlatformBungeecord = { group = "net.kyori", name = "adventure-platform-bungeecord", version.ref = "adventurePlatform" } adventureTextSerializerPlain = { group = "net.kyori", name = "adventure-text-serializer-plain", version.ref = "adventureApi" } -paperApi = { group = "com.destroystokyo.paper", name = "paper-api", version.ref = "paperApi" } -paperMojangApi = { group = "com.destroystokyo.paper", name = "paper-mojangapi", version.ref = "paperApi" } +paperApi = { group = "io.papermc.paper", name = "paper-api", version.ref = "paperApi" } +paperMojangApi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "paperApi" } spongeApi7 = { group = "org.spongepowered", name = "spongeapi", version.ref = "spongeApi7" } velocityApi = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocityApi" } fabricMinecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabricMinecraft" }