aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0168-AsyncTabCompleteEvent.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/0168-AsyncTabCompleteEvent.patch')
-rw-r--r--patches/server/0168-AsyncTabCompleteEvent.patch185
1 files changed, 185 insertions, 0 deletions
diff --git a/patches/server/0168-AsyncTabCompleteEvent.patch b/patches/server/0168-AsyncTabCompleteEvent.patch
new file mode 100644
index 0000000000..ddced42488
--- /dev/null
+++ b/patches/server/0168-AsyncTabCompleteEvent.patch
@@ -0,0 +1,185 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Sun, 26 Nov 2017 13:19:58 -0500
+Subject: [PATCH] AsyncTabCompleteEvent
+
+Let plugins be able to control tab completion of commands and chat async.
+
+This will be useful for frameworks like ACF so we can define async safe completion handlers,
+and avoid going to main for tab completions.
+
+Especially useful if you need to query a database in order to obtain the results for tab
+completion, such as offline players.
+
+Also adds isCommand and getLocation to the sync TabCompleteEvent
+
+Co-authored-by: Aikar <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index e40eeb5e04d96fb55283ded82cea0a5539a2fad5..90bd5c1a010a3a9d24328e5c719053603e206626 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -784,12 +784,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
+
+ }
+
++ // Paper start
++ private static final java.util.concurrent.ExecutorService TAB_COMPLETE_EXECUTOR = java.util.concurrent.Executors.newFixedThreadPool(4,
++ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Tab Complete Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build());
++ // Paper end
+ @Override
+ public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {
+- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel());
++ // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); // Paper - run this async
+ // CraftBukkit start
+ if (this.chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) {
+- this.disconnect(Component.translatable("disconnect.spam"));
++ server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]))); // Paper
+ return;
+ }
+ // Paper start
+@@ -800,18 +804,45 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
+ }
+ // Paper end
+ // CraftBukkit end
++ // Paper start - async tab completion
++ TAB_COMPLETE_EXECUTOR.execute(() -> {
+ StringReader stringreader = new StringReader(packet.getCommand());
+
+ if (stringreader.canRead() && stringreader.peek() == '/') {
+ stringreader.skip();
+ }
++ final String command = packet.getCommand();
++ final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getCraftPlayer(), command, true, null);
++ event.callEvent();
++ final java.util.List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions();
++ // If the event isn't handled, we can assume that we have no completions, and so we'll ask the server
++ if (!event.isHandled()) {
++ if (!event.isCancelled()) {
+
+- ParseResults<CommandSourceStack> parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack());
++ this.server.scheduleOnMain(() -> { // This needs to be on main
++ ParseResults<CommandSourceStack> parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack());
+
+- this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> {
+- if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [<args>] from showing for plugins with nothing more to offer
+- this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions));
++ this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> {
++ if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [<args>] from showing for plugins with nothing more to offer
++ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions));
++ });
++ });
++ }
++ } else if (!completions.isEmpty()) {
++ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(command, stringreader.getTotalLength());
++ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1);
++ completions.forEach(completion -> {
++ final Integer intSuggestion = com.google.common.primitives.Ints.tryParse(completion.suggestion());
++ if (intSuggestion != null) {
++ builder.suggest(intSuggestion, PaperAdventure.asVanilla(completion.tooltip()));
++ } else {
++ builder.suggest(completion.suggestion(), PaperAdventure.asVanilla(completion.tooltip()));
++ }
++ });
++ player.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), builder.buildFuture().join()));
++ }
+ });
++ // Paper end - async tab completion
+ }
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 7013b11fc0cd5fbb5a7e62be45d84d54d3052237..ca6bb66e8ba1e17f025b82091910ca223185ad3b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -2086,7 +2086,7 @@ public final class CraftServer implements Server {
+ offers = this.tabCompleteChat(player, message);
+ }
+
+- TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers);
++ TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers, message.startsWith("/") || forceCommand, pos != null ? net.minecraft.server.MCUtil.toLocation(((CraftWorld) player.getWorld()).getHandle(), new BlockPos(pos)) : null); // Paper
+ this.getPluginManager().callEvent(tabEvent);
+
+ return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions();
+diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
+index b996fde481cebbbcce80a6c267591136db7cc0bc..14cd8ae69d9b25dc5edad4ff96ff4a9acb1f22cb 100644
+--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
+@@ -28,6 +28,61 @@ public class ConsoleCommandCompleter implements Completer {
+ public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
+ final CraftServer server = this.server.server;
+ final String buffer = line.line();
++ // Async Tab Complete
++ final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event =
++ new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(server.getConsoleSender(), buffer, true, null);
++ event.callEvent();
++ final List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions();
++
++ if (event.isCancelled() || event.isHandled()) {
++ // Still fire sync event with the provided completions, if someone is listening
++ if (!event.isCancelled() && TabCompleteEvent.getHandlerList().getRegisteredListeners().length > 0) {
++ List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> finalCompletions = new java.util.ArrayList<>(completions);
++ Waitable<List<String>> syncCompletions = new Waitable<List<String>>() {
++ @Override
++ protected List<String> evaluate() {
++ org.bukkit.event.server.TabCompleteEvent syncEvent = new org.bukkit.event.server.TabCompleteEvent(server.getConsoleSender(), buffer,
++ finalCompletions.stream()
++ .map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::suggestion)
++ .collect(java.util.stream.Collectors.toList()));
++ return syncEvent.callEvent() ? syncEvent.getCompletions() : com.google.common.collect.ImmutableList.of();
++ }
++ };
++ server.getServer().processQueue.add(syncCompletions);
++ try {
++ final List<String> legacyCompletions = syncCompletions.get();
++ completions.removeIf(it -> !legacyCompletions.contains(it.suggestion())); // remove any suggestions that were removed
++ // add any new suggestions
++ for (final String completion : legacyCompletions) {
++ if (notNewSuggestion(completions, completion)) {
++ continue;
++ }
++ completions.add(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion(completion));
++ }
++ } catch (InterruptedException | ExecutionException e1) {
++ e1.printStackTrace();
++ }
++ }
++
++ if (!completions.isEmpty()) {
++ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) {
++ if (completion.suggestion().isEmpty()) {
++ continue;
++ }
++ candidates.add(new Candidate(
++ completion.suggestion(),
++ completion.suggestion(),
++ null,
++ io.papermc.paper.adventure.PaperAdventure.PLAIN.serializeOr(completion.tooltip(), null),
++ null,
++ null,
++ false
++ ));
++ }
++ }
++ return;
++ }
++
+ // Paper end
+ Waitable<List<String>> waitable = new Waitable<List<String>>() {
+ @Override
+@@ -73,4 +128,15 @@ public class ConsoleCommandCompleter implements Completer {
+ Thread.currentThread().interrupt();
+ }
+ }
++
++ // Paper start
++ private boolean notNewSuggestion(final List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> completions, final String completion) {
++ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion it : completions) {
++ if (it.suggestion().equals(completion)) {
++ return true;
++ }
++ }
++ return false;
++ }
++ // Paper end
+ }