Skip to content

Commit 96a7014

Browse files
authored
Merge pull request CommandAPI#560 from JorelAli/dev/example/paper-command-registration
Delegate Bukkit command registering logic to `CommandRegistrationStrategy`
2 parents 81e1dd4 + ce75d6b commit 96a7014

File tree

32 files changed

+1094
-1164
lines changed

32 files changed

+1094
-1164
lines changed

commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java

Lines changed: 21 additions & 311 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package dev.jorel.commandapi;
2+
3+
import com.mojang.brigadier.CommandDispatcher;
4+
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
5+
import com.mojang.brigadier.tree.CommandNode;
6+
import com.mojang.brigadier.tree.LiteralCommandNode;
7+
import com.mojang.brigadier.tree.RootCommandNode;
8+
import dev.jorel.commandapi.preprocessor.RequireField;
9+
10+
import java.util.HashSet;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.function.Predicate;
14+
15+
@RequireField(in = CommandNode.class, name = "children", ofType = Map.class)
16+
@RequireField(in = CommandNode.class, name = "literals", ofType = Map.class)
17+
@RequireField(in = CommandNode.class, name = "arguments", ofType = Map.class)
18+
public abstract class CommandRegistrationStrategy<Source> {
19+
// Reflection
20+
// I'd like to make the Maps here `Map<String, CommandNode<Source>>`, but these static fields cannot use the type
21+
// parameter Source. We still need to cast to that signature for map, so Map is raw.
22+
static final SafeVarHandle<CommandNode<?>, Map> commandNodeChildren;
23+
private static final SafeVarHandle<CommandNode<?>, Map> commandNodeLiterals;
24+
private static final SafeVarHandle<CommandNode<?>, Map> commandNodeArguments;
25+
26+
// Compute all var handles all in one go so we don't do this during main server runtime
27+
static {
28+
commandNodeChildren = SafeVarHandle.ofOrNull(CommandNode.class, "children", "children", Map.class);
29+
commandNodeLiterals = SafeVarHandle.ofOrNull(CommandNode.class, "literals", "literals", Map.class);
30+
commandNodeArguments = SafeVarHandle.ofOrNull(CommandNode.class, "arguments", "arguments", Map.class);
31+
}
32+
33+
// Utility methods
34+
protected void removeBrigadierCommands(RootCommandNode<Source> root, String commandName,
35+
boolean unregisterNamespaces, Predicate<CommandNode<Source>> extraCheck) {
36+
Map<String, CommandNode<Source>> children = (Map<String, CommandNode<Source>>) commandNodeChildren.get(root);
37+
Map<String, CommandNode<Source>> literals = (Map<String, CommandNode<Source>>) commandNodeLiterals.get(root);
38+
Map<String, CommandNode<Source>> arguments = (Map<String, CommandNode<Source>>) commandNodeArguments.get(root);
39+
40+
removeCommandFromMapIfCheckPasses(children, commandName, extraCheck);
41+
removeCommandFromMapIfCheckPasses(literals, commandName, extraCheck);
42+
// Commands should really only be represented as literals, but it is technically possible
43+
// to put an ArgumentCommandNode in the root, so we'll check
44+
removeCommandFromMapIfCheckPasses(arguments, commandName, extraCheck);
45+
46+
if (unregisterNamespaces) {
47+
removeCommandNamespace(children, commandName, extraCheck);
48+
removeCommandNamespace(literals, commandName, extraCheck);
49+
removeCommandNamespace(arguments, commandName, extraCheck);
50+
}
51+
}
52+
53+
protected static <T> void removeCommandNamespace(Map<String, T> map, String commandName, Predicate<T> extraCheck) {
54+
for (String key : new HashSet<>(map.keySet())) {
55+
if (!isThisTheCommandButNamespaced(commandName, key)) continue;
56+
57+
removeCommandFromMapIfCheckPasses(map, key, extraCheck);
58+
}
59+
}
60+
61+
protected static <T> void removeCommandFromMapIfCheckPasses(Map<String, T> map, String key, Predicate<T> extraCheck) {
62+
T element = map.get(key);
63+
if (element == null) return;
64+
if (extraCheck.test(element)) map.remove(key);
65+
}
66+
67+
protected static boolean isThisTheCommandButNamespaced(String commandName, String key) {
68+
if (!key.contains(":")) return false;
69+
String[] split = key.split(":");
70+
if (split.length < 2) return false;
71+
return split[1].equalsIgnoreCase(commandName);
72+
}
73+
74+
// Behavior methods
75+
public abstract CommandDispatcher<Source> getBrigadierDispatcher();
76+
77+
public abstract void runTasksAfterServerStart();
78+
79+
public abstract void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode<Source> resultantNode, List<LiteralCommandNode<Source>> aliasNodes);
80+
81+
public abstract LiteralCommandNode<Source> registerCommandNode(LiteralArgumentBuilder<Source> node, String namespace);
82+
83+
public abstract void unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit);
84+
85+
public abstract void preReloadDataPacks();
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package dev.jorel.commandapi;
2+
3+
import com.mojang.brigadier.CommandDispatcher;
4+
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
5+
import com.mojang.brigadier.tree.CommandNode;
6+
import com.mojang.brigadier.tree.LiteralCommandNode;
7+
import com.mojang.brigadier.tree.RootCommandNode;
8+
9+
import java.util.List;
10+
import java.util.function.Predicate;
11+
import java.util.function.Supplier;
12+
13+
/**
14+
* Handles logic for registering commands after Paper build 65, where <a href="https://github.com/PaperMC/Paper/pull/8235">https://github.com/PaperMC/Paper/pull/8235</a>
15+
* changed a bunch of the behind-the-scenes logic.
16+
*/
17+
public class PaperCommandRegistration<Source> extends CommandRegistrationStrategy<Source> {
18+
// References to necessary methods
19+
private final Supplier<CommandDispatcher<Source>> getBrigadierDispatcher;
20+
private final Predicate<CommandNode<Source>> isBukkitCommand;
21+
22+
// Store registered commands nodes for eventual reloads
23+
private final RootCommandNode<Source> registeredNodes = new RootCommandNode<>();
24+
25+
public PaperCommandRegistration(Supplier<CommandDispatcher<Source>> getBrigadierDispatcher, Predicate<CommandNode<Source>> isBukkitCommand) {
26+
this.getBrigadierDispatcher = getBrigadierDispatcher;
27+
this.isBukkitCommand = isBukkitCommand;
28+
}
29+
30+
// Provide access to internal functions that may be useful to developers
31+
/**
32+
* Checks if a Brigadier command node came from wrapping a Bukkit command
33+
*
34+
* @param node The CommandNode to check
35+
* @return true if the CommandNode is being handled by Paper's BukkitCommandNode
36+
*/
37+
public boolean isBukkitCommand(CommandNode<Source> node) {
38+
return isBukkitCommand.test(node);
39+
}
40+
41+
// Implement CommandRegistrationStrategy methods
42+
@Override
43+
public CommandDispatcher<Source> getBrigadierDispatcher() {
44+
return getBrigadierDispatcher.get();
45+
}
46+
47+
@Override
48+
public void runTasksAfterServerStart() {
49+
// Nothing to do
50+
}
51+
52+
@Override
53+
public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode<Source> resultantNode, List<LiteralCommandNode<Source>> aliasNodes) {
54+
// Nothing to do
55+
}
56+
57+
@Override
58+
public LiteralCommandNode<Source> registerCommandNode(LiteralArgumentBuilder<Source> node, String namespace) {
59+
LiteralCommandNode<Source> commandNode = getBrigadierDispatcher.get().register(node);
60+
LiteralCommandNode<Source> namespacedCommandNode = CommandAPIHandler.getInstance().namespaceNode(commandNode, namespace);
61+
62+
// Add to registered command nodes
63+
registeredNodes.addChild(commandNode);
64+
registeredNodes.addChild(namespacedCommandNode);
65+
66+
// Namespace is not empty on Bukkit forks
67+
getBrigadierDispatcher.get().getRoot().addChild(namespacedCommandNode);
68+
69+
return commandNode;
70+
}
71+
72+
@Override
73+
public void unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit) {
74+
// Remove nodes from the dispatcher
75+
removeBrigadierCommands(getBrigadierDispatcher.get().getRoot(), commandName, unregisterNamespaces,
76+
// If we are unregistering a Bukkit command, ONLY unregister BukkitCommandNodes
77+
// If we are unregistering a Vanilla command, DO NOT unregister BukkitCommandNodes
78+
c -> !unregisterBukkit ^ isBukkitCommand.test(c));
79+
80+
// CommandAPI commands count as non-Bukkit
81+
if (!unregisterBukkit) {
82+
// Don't add nodes back after a reload
83+
removeBrigadierCommands(registeredNodes, commandName, unregisterNamespaces, c -> true);
84+
}
85+
86+
// Update the dispatcher file
87+
CommandAPIHandler.getInstance().writeDispatcherToFile();
88+
}
89+
90+
@Override
91+
public void preReloadDataPacks() {
92+
RootCommandNode<Source> root = getBrigadierDispatcher.get().getRoot();
93+
for (CommandNode<Source> commandNode : registeredNodes.getChildren()) {
94+
root.addChild(commandNode);
95+
}
96+
}
97+
}

commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public void registerReloadHandler(Plugin plugin) {
5858

5959
@EventHandler
6060
public void onServerReloadResources(ServerResourcesReloadedEvent event) {
61+
// This event is called after Paper is done with everything command related
62+
// which means we can put commands back
63+
CommandAPIBukkit.get().getCommandRegistrationStrategy().preReloadDataPacks();
6164
CommandAPI.logNormal("/minecraft:reload detected. Reloading CommandAPI commands!");
6265
nmsInstance.reloadDataPacks();
6366
}

0 commit comments

Comments
 (0)