Paper/patches/server/0161-AsyncTabCompleteEvent.patch
Nassim Jahnke dd11ef8441
Updated Upstream (Bukkit/CraftBukkit/Spigot) (#11102)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
3a3bea52 SPIGOT-7829: Increase maximum outgoing plugin message size to match Vanilla intention
5cd1c8cb SPIGOT-7831: Add CreatureSpawnEvent.SpawnReason#POTION_EFFECT
a8e278f0 SPIGOT-7827: Sync EntityPortalEvent with PlayerPortalEvent since non-players can now create portals
53729d12 Remove spurious ApiStatus.Internal annotation
b9f57486 SPIGOT-7799, PR-1039: Expose explosion world interaction in EntityExplodeEvent and BlockExplodeEvent
7983b966 PR-1029: Trial changing a small number of inner enums to classes/interfaces to better support custom values

CraftBukkit Changes:
403accd56 SPIGOT-7831: Add CreatureSpawnEvent.SpawnReason#POTION_EFFECT
812761660 Increase outdated build delay
bed1e3ff6 SPIGOT-7827: Sync EntityPortalEvent with PlayerPortalEvent since non-players can now create portals
2444c8b23 SPIGOT-7823: Suspicious sand and gravel material are not marked as having gravity correctly
aceddcd0b SPIGOT-7820: Enum changes - duplicate method name
a0d2d6a84 SPIGOT-7813: Material#isInteractable() always returns false
8fd64b091 SPIGOT-7806: Handle both loot and inventory item drop behaviour in PlayerDeathEvent
a4ee40b74 SPIGOT-7799, PR-1436: Expose explosion world interaction in EntityExplodeEvent and BlockExplodeEvent
082aa51c5 PR-1424: Trial changing a small number of inner enums to classes/interfaces to better support custom values
66e78a96b SPIGOT-7815: Consider EntityDamageEvent status for Wolf armor damage

Spigot Changes:
5bbef5ad SPIGOT-7834: Modify max value for generic.max_absorption
2024-07-18 10:13:20 +02:00

177 lines
11 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
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 <aikar@aikar.co>
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 7906e163f8d03ba39480526d0293ad48534f11bf..ec80a9138260497d0deccf3ade3f44fc849de1d5 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -711,21 +711,58 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
}
+ // Paper start - AsyncTabCompleteEvent
+ 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 - AsyncTabCompleteEvent
@Override
public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {
- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+ // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async
// CraftBukkit start
if (this.chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) {
this.disconnect(Component.translatable("disconnect.spam"));
return;
}
// CraftBukkit end
+ // Paper start - AsyncTabCompleteEvent
+ TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet));
+ }
+
+ private void handleCustomCommandSuggestions0(final ServerboundCommandSuggestionPacket packet) {
StringReader stringreader = new StringReader(packet.getCommand());
if (stringreader.canRead() && stringreader.peek() == '/') {
stringreader.skip();
}
+ final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getCraftPlayer(), packet.getCommand(), 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 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()) {
+ return;
+ }
+
+ // This needs to be on main
+ this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringreader));
+ } else if (!completions.isEmpty()) {
+ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringreader.getTotalLength());
+ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1);
+ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) {
+ 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()));
+ }
+ }
+ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), builder.buildFuture().join()));
+ }
+ }
+
+ private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) {
+ // Paper end - AsyncTabCompleteEvent
ParseResults<CommandSourceStack> parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack());
this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 4280bd6288ce9522d041ec2dc2105d3908514a2f..5d8eb3b728359be5f92a255c9637d71713403cc8 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -2292,7 +2292,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 ? io.papermc.paper.util.MCUtil.toLocation(((CraftWorld) player.getWorld()).getHandle(), BlockPos.containing(pos)) : null); // Paper - AsyncTabCompleteEvent
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 d24acf28f5ed023acc550bcf877e4b9800ec8c9f..8f82041f0482df22a6a9ea38d50d56228131775d 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
}