8000 Executor Rewrite · CommandAPI/CommandAPI@11936f3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 11936f3

Browse files
committed
Executor Rewrite
Note: This commit makes this branch backwards incompatible, especially for non-DSL Kotlin code (see `commandapi-documentation-code/.../Examples.kt`). "Standard" Java and DSL API code usage looks the same, but I'm pretty sure plugins will need to recompile due to changes to the `FunctionalInterface`s. There are also some smaller public API changes, mentioned below. Notable changes: - Removed `AbstractCommandSender` and all its implemenations (i.e. the `dev.jorel.commandapi.commandsenders` package is completely gone) - Logic in `CommandAPIHandler#generateBrigadierRequirements` for checking if a sender satisfies a `CommandPermission` was moved to `CommandAPIPlatform#getPermissionCheck` - Previously, methods in `AbstractCommandSender` provided access to `hasPermission` and `isOp`. `CommandAPIBukkit` and `CommandAPIVelocity` now handle these definitions. - `CommandPermission.TRUE()` and `CommandPermission.FALSE()` added for computing short circuits when combining permissions and requirements - `PreviewInfo` now has the generic parameter `Player` rather than an `AbstractPlayer` - Backwards-incompatible (`(Player) player.getSource()` becomes `player`) - Generic parameter propogates to `PreviewableFunction` and `Previewable` - `CommandAPIPlatform` methods for convert Brigadier Source to CommandSender simplified - `getSenderForCommand` removed - just pass `CommandContext#getSource` into `getCommandSenderFromCommandSource` - `forceNative` parameter was only used on Bukkit, which is now handled by `NMS#getNativeProxyCommandSender` (more on that below) - `wrapCommandSender` removed - Logic from #580 removed, since wrapping the NullCommandSender no longer needs to be handled as a special case - Wrapping the sender no longer necessary for `getBrigadierSourceFromCommandSender` - `C EDBE ommandAPIVelocity` now does nothing in these methods :P - `CommandAPIExecutor` reworked - `ExecutorType` moved to platform modules (so Velocity can no longer try to define sender types that don't exist) - Detecting whether a certain executor can be run by the given class delegated to `BukkitTypedExecutor` and `VelocityTypedExecutor` - Priority of executors now depends on order of calling the `executes` methods (i.e the priority listed here https://commandapi.jorel.dev/9.4.1/normalexecutors.html#multiple-command-executor-implementations no longer applies) - Normal executors are no longer ignored if resulting executors exist (not sure if this was a bug or intended) - Tweaked `ExecutionInfo` - Added `CommandContext<Source> cmdCtx` to `ExecutionInfo` - This allows passing the information needed for a `NATIVE` executor on Bukkit to create a `NativeProxyCommandSender` - Uses `NMS#getNativeProxyCommandSender`, which was adapted from the removed `getSenderForCommand` method - Note: conflicts with #478, though should be easily resolved - Note: Velocity can define the `CommandSource` object, though Bukkit has it as `?`. This makes it hard for non DSL Kotlin to infer the type parameters for some reason, which is annoying because you now need to specify the type parameter. I don't know if there's a better way to handle that. - TODO: Make sure depdendents can still use `ExecutionInfo` without including Brigadier as a dependency - `ExecutionInfo` is now a record (`BukkitExecutionInfo` and `VelocityExecutionInfo` removed) - Simplified `dev.jorel.commandapi.executors` package - Platform and sender specific executor classes (e.g. `PlayerCommandExecutor` and `EntityResultingExecutionInfo`) removed - Just the functional interfaces `NormalExecutorInfo`, `NormalExecutor`, `ResultingExecutorInfo`, and `ResultingExecutor` now - `BukkitExecutable` and `VelocityExecutable` link different command sender classes as type parameters to the `ExecutorType` enum values TODO: - Add executor tests to `dev/dev` to ensure same behavior - Especially for `PROXY` and `NATIVE` senders - Especially for non-DSL Kotlin to see how its lamdba type inference works - Update documentation
1 parent 4b8880d commit 11936f3

File tree

147 files changed

+1311
-3978
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

147 files changed

+1311
-3978
lines changed

commandapi-core/src/main/java/dev/jorel/commandapi/Brigadier.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import dev.jorel.commandapi.arguments.AbstractArgument;
3333
import dev.jorel.commandapi.arguments.ArgumentSuggestions;
3434
import dev.jorel.commandapi.arguments.Literal;
35-
import dev.jorel.commandapi.commandsenders.AbstractCommandSender;
3635

3736
import java.util.Collections;
3837
import java.util.List;
@@ -206,19 +205,17 @@ Command fromCommand(AbstractCommandAPICommand<?, Argument, CommandSender> comman
206205
*/
207206
public static <CommandSender> Object getBrigadierSourceFromCommandSender(CommandSender sender) {
208207
CommandAPIPlatform<?, CommandSender, ?> platform = (CommandAPIPlatform<?, CommandSender, ?>) CommandAPIHandler.getInstance().getPlatform();
209-
return platform.getBrigadierSourceFromCommandSender(platform.wrapCommandSender(sender));
208+
return platform.getBrigadierSourceFromCommandSender(sender);
210209
}
211210

212211
/**
213-
* Returns a Bukkit CommandSender from a Brigadier CommandContext
212+
* Returns the current platform's command sender from a Brigadier CommandContext
214213
*
215214
* @param cmdCtx the command context to get the CommandSender from
216-
* @return a Bukkit CommandSender from the provided Brigadier CommandContext
215+
* @return a command sender from the provided Brigadier CommandContext
217216
*/
218-
public static <CommandSender> CommandSender getCommandSenderFromContext(CommandContext cmdCtx) {
219-
CommandAPIPlatform<?, CommandSender, ?> platform = (CommandAPIPlatform<?, CommandSender, ?>) CommandAPIHandler.getInstance().getPlatform();
220-
// For some reason putting this on one line doesn't work - very weird
221-
AbstractCommandSender<CommandSender> abstractSender = platform.getSenderForCommand(cmdCtx, false);
222-
return abstractSender.getSource();
217+
public static <CommandSender, Source> CommandSender getCommandSenderFromContext(CommandContext cmdCtx) {
218+
CommandAPIPlatform<?, CommandSender, Source> platform = (CommandAPIPlatform<?, CommandSender, Source>) CommandAPIHandler.getInstance().getPlatform();
219+
return platform.getCommandSenderFromCommandSource((Source) cmdCtx.getSource());
223220
}
224221
}

commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.mojang.brigadier.Message;
44
import com.mojang.brigadier.exceptions.CommandSyntaxException;
55
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
6-
import dev.jorel.commandapi.commandsenders.AbstractPlayer;
76
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
87

98
import java.util.ArrayList;
@@ -232,14 +231,13 @@ public static void reloadDatapacks() {
232231
}
233232

234233
/**
235-
* Updates the requirements required for a given player to execute a command.
234+
* Updates the player's view of the requirements for them to execute a command.
236235
*
237236
* @param player the player whose requirements should be updated
238237
*/
239238
public static <CommandSender, Player extends CommandSender> void updateRequirements(Player player) {
240-
@SuppressWarnings("unchecked")
241239
CommandAPIPlatform<?, CommandSender, ?> platform = (CommandAPIPlatform<?, CommandSender, ?>) CommandAPIHandler.getInstance().getPlatform();
242-
platform.updateRequirements((AbstractPlayer<?>) platform.wrapCommandSender(player));
240+
platform.updateRequirements(player);
243241
}
244242

245243
// Produce WrapperCommandSyntaxException

commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIExecutor.java

Lines changed: 42 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,14 @@
2222

2323
import java.util.ArrayList;
2424
import java.util.List;
25-
import java.util.NoSuchElementException;
25+
import java.util.Optional;
2626

2727
import com.mojang.brigadier.LiteralMessage;
2828
import com.mojang.brigadier.exceptions.CommandSyntaxException;
2929
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
3030

31-
import dev.jorel.commandapi.commandsenders.*;
3231
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
3332
import dev.jorel.commandapi.executors.ExecutionInfo;
34-
import dev.jorel.commandapi.executors.ExecutorType;
35-
import dev.jorel.commandapi.executors.NormalExecutor;
36-
import dev.jorel.commandapi.executors.ResultingExecutor;
3733
import dev.jorel.commandapi.executors.TypedExecutor;
3834

3935
/**
@@ -42,143 +38,67 @@
4238
* executors) and switches its execution implementation based on the provided
4339
* command executor types.
4440
*
45-
* @param <CommandSender> The CommandSender for this executor
46-
* @param <WrapperType> The AbstractCommandSender that wraps the CommandSender
41+
* @param <CommandSender> The class for running platform commands
4742
*/
48-
public class CommandAPIExecutor<CommandSender, WrapperType
49-
/// @cond DOX
50-
extends AbstractCommandSender<? extends CommandSender>
51-
/// @endcond
52-
> {
43+
public class CommandAPIExecutor<CommandSender> {
5344

54-
private List<NormalExecutor<CommandSender, WrapperType>> normalExecutors;
55-
private List<ResultingExecutor<CommandSender, WrapperType>> resultingExecutors;
45+
// Setup executors
46+
private List<TypedExecutor<CommandSender, ? extends CommandSender, ?>> executors;
5647

5748
public CommandAPIExecutor() {
58-
normalExecutors = new ArrayList<>();
59-
resultingExecutors = new ArrayList<>();
49+
this.executors = new ArrayList<>();
6050
}
6151

62-
@SuppressWarnings("unchecked")
63-
public void addNormalExecutor(NormalExecutor<?, ?> executor) {
64-
this.normalExecutors.add((NormalExecutor<CommandSender, WrapperType>) executor);
52+
public void addExecutor(TypedExecutor<CommandSender, ? extends CommandSender, ?> executor) {
53+
this.executors.add(executor);
6554
}
6655

67-
@SuppressWarnings("unchecked")
68-
public void addResultingExecutor(ResultingExecutor<?, ?> executor) {
69-
this.resultingExecutors.add((ResultingExecutor<CommandSender, WrapperType>) executor);
56+
public void setExecutors(List<TypedExecutor<CommandSender, ? extends CommandSender, ?>> executors) {
57+
this.executors = executors;
7058
}
7159

72-
public int execute(ExecutionInfo<CommandSender, WrapperType> info) throws CommandSyntaxException {
73-
// Parse executor type
74-
if (!resultingExecutors.isEmpty()) {
75-
// Run resulting executor
76-
try {
77-
return execute(resultingExecutors, info);
78-
} catch (WrapperCommandSyntaxException e) {
79-
throw e.getException();
80-
} catch (Throwable ex) {
81-
CommandAPI.getLogger().severe("Unhandled exception executing '" + info.args().fullInput() + "'", ex);
82-
if (ex instanceof Exception) {
83-
throw ex;
84-
} else {
85-
throw new RuntimeException(ex);
86-
}
87-
}
88-
} else {
89-
// Run normal executor
90-
try {
91-
return execute(normalExecutors, info);
92-
} catch (WrapperCommandSyntaxException e) {
93-
throw e.getException();
94-
} catch (Throwable ex) {
95-
CommandAPI.getLogger().severe("Unhandled exception executing '" + info.args().fullInput() + "'", ex);
96-
if (ex instanceof Exception) {
97-
throw ex;
98-
} else {
99-
throw new RuntimeException(ex);
100-
}
101-
}
102-
}
103-
}
104-
105-
private int execute(List<? extends TypedExecutor<CommandSender, WrapperType>> executors, ExecutionInfo<CommandSender, WrapperType> info)
106-
throws WrapperCommandSyntaxException {
107-
if (isForceNative()) {
108-
return execute(executors, info, ExecutorType.NATIVE);
109-
} else if (info.senderWrapper() instanceof AbstractPlayer && matches(executors, ExecutorType.PLAYER)) {
110-
return execute(executors, info, ExecutorType.PLAYER);
111-
} else if (info.senderWrapper() instanceof AbstractEntity && matches(executors, ExecutorType.ENTITY)) {
112-
return execute(executors, info, ExecutorType.ENTITY);
113-
} else if (info.senderWrapper() instanceof AbstractConsoleCommandSender && matches(executors, ExecutorType.CONSOLE)) {
114-
return execute(executors, info, ExecutorType.CONSOLE);
115-
} else if (info.senderWrapper() instanceof AbstractBlockCommandSender && matches(executors, ExecutorType.BLOCK)) {
116-
return execute(executors, info, ExecutorType.BLOCK);
117-
} else if (info.senderWrapper() instanceof AbstractProxiedCommandSender && matches(executors, ExecutorType.PROXY)) {
118-
return execute(executors, info, ExecutorType.PROXY);
119-
} else if (info.senderWrapper() instanceof AbstractRemoteConsoleCommandSender && matches(executors, ExecutorType.REMOTE)) {
120-
return execute(executors, info, ExecutorType.REMOTE);
121-
} else if (info.senderWrapper() instanceof AbstractFeedbackForwardingCommandSender && matches(executors, ExecutorType.FEEDBACK_FORWARDING)) {
122-
return execute(executors, info, ExecutorType.FEEDBACK_FORWARDING);
123-
} else if (matches(executors, ExecutorType.ALL)) {
124-
return execute(executors, info, ExecutorType.ALL);
125-
} else {
126-
throw new WrapperCommandSyntaxException(new SimpleCommandExceptionType(
127-
new LiteralMessage(CommandAPI.getConfiguration().getMissingImplementationMessage()
128-
.replace("%s", info.sender().getClass().getSimpleName().toLowerCase())
129-
.replace("%S", info.sender().getClass().getSimpleName()))).create());
130-
}
131-
}
132-
133-
private int execute(List<? extends TypedExecutor<CommandSender, WrapperType>> executors,
134-
ExecutionInfo<CommandSender, WrapperType> info, ExecutorType type) throws WrapperCommandSyntaxException {
135-
for (TypedExecutor<CommandSender, WrapperType> executor : executors) {
136-
if (executor.getType() == type) {
137-
return executor.executeWith(info);
138-
}
139-
}
140-
throw new NoSuchElementException("Executor had no valid executors for type " + type.toString());
141-
}
142-
143-
public List<NormalExecutor<CommandSender, WrapperType>> getNormalExecutors() {
144-
return normalExecutors;
145-
}
146-
147-
public List<ResultingExecutor<CommandSender, WrapperType>> getResultingExecutors() {
148-
return resultingExecutors;
60+
public List<TypedExecutor<CommandSender, ? extends CommandSender, ?>> getExecutors() {
61+
return this.executors;
14962
}
15063

15164
public boolean hasAnyExecutors() {
152-
return !(normalExecutors.isEmpty() && resultingExecutors.isEmpty());
153-
}
154-
155-
public boolean isForceNative() {
156-
return matches(normalExecutors, ExecutorType.NATIVE) || matches(resultingExecutors, ExecutorType.NATIVE);
65+
return !executors.isEmpty();
15766
}
15867

159-
private boolean matches(List<? extends TypedExecutor<?, ?>> executors, ExecutorType type) {
160-
for (TypedExecutor<?, ?> executor : executors) {
161-
if (executor.getType() == type) {
162-
return true;
68+
// Use executors
69+
public int execute(ExecutionInfo<CommandSender, ?> info) throws CommandSyntaxException {
70+
try {
71+
for (TypedExecutor<CommandSender, ? extends CommandSender, ?> executor : executors) {
72+
Optional<Integer> result = tryExecutor(executor, info);
73+
74+
if (result.isPresent()) return result.get();
75+
}
76+
} catch (WrapperCommandSyntaxException e) {
77+
throw e.getException();
78+
} catch (Throwable ex) {
79+
CommandAPI.getLogger().severe("Unhandled exception executing '" + info.args().fullInput() + "'", ex);
80+
if (ex instanceof Exception) {
81+
throw ex;
82+
} else {
83+
throw new RuntimeException(ex);
16384
}
16485
}
165-
return false;
166-
}
16786

168-
CommandAPIExecutor<CommandSender, WrapperType> mergeExecutor(CommandAPIExecutor<CommandSender, WrapperType> executor) {
169-
CommandAPIExecutor<CommandSender, WrapperType> result = new CommandAPIExecutor<>();
170-
result.normalExecutors = new ArrayList<>(normalExecutors);
171-
result.resultingExecutors = new ArrayList<>(resultingExecutors);
172-
result.normalExecutors.addAll(executor.normalExecutors);
173-
result.resultingExecutors.addAll(executor.resultingExecutors);
174-
return result;
87+
// Executor not found
88+
throw new SimpleCommandExceptionType(new LiteralMessage(
89+
CommandAPI.getConfiguration().getMissingImplementationMessage()
90+
.replace("%s", info.sender().getClass().getSimpleName().toLowerCase())
91+
.replace("%S", info.sender().getClass().getSimpleName())
92+
)).create();
17593
}
17694

177-
public void setNormalExecutors(List<NormalExecutor<CommandSender, WrapperType>> normalExecutors) {
178-
this.normalExecutors = normalExecutors;
179-
}
95+
// This needs to be extracted into another method to name the `Sender` and `Source` generic, which allows `executeWith` to accept the converted info
96+
private <Sender extends CommandSender, Source> Optional<Integer> tryExecutor(TypedExecutor<CommandSender, Sender, Source> executor, ExecutionInfo<CommandSender, ?> info) throws WrapperCommandSyntaxException {
97+
ExecutionInfo<Sender, Source> convertedInfo = executor.tryForSender((ExecutionInfo<CommandSender, Source>) info);
18098

181-
public void setResultingExecutors(List<ResultingExecutor<CommandSender, WrapperType>> resultingExecutors) {
182-
this.resultingExecutors = resultingExecutors;
99+
if (convertedInfo != null) {
100+
return Optional.of(executor.executeWith(convertedInfo));
101+
}
102+
return Optional.empty();
183103
}
184104
}

commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import com.mojang.brigadier.tree.ArgumentCommandNode;
4545
import com.mojang.brigadier.tree.LiteralCommandNode;
4646
import dev.jorel.commandapi.arguments.*;
47-
import dev.jorel.commandapi.commandsenders.AbstractCommandSender;
4847
import dev.jorel.commandapi.exceptions.CommandConflictException;
4948
import dev.jorel.commandapi.executors.CommandArguments;
5049
import dev.jorel.commandapi.executors.ExecutionInfo;
@@ -271,33 +270,18 @@ private void ensureNoCommandConflict(CommandNode<Source> nodeToRegister, Command
271270
* @param executor Code to run when the command is executed.
272271
* @return A Brigadier Command object that runs the given execution with the given arguments as input.
273272
*/
274-
public Command<Source> generateBrigadierCommand(List<Argument> args, CommandAPIExecutor<CommandSender, AbstractCommandSender<? extends CommandSender>> executor) {
273+
public Command<Source> generateBrigadierCommand(List<Argument> args, CommandAPIExecutor<CommandSender> executor) {
275274
// We need to make sure our arguments list is never changed
276275
// If we just used the reference to the list, the caller might add arguments that aren't actually previous
277276
// arguments for this suggestion node, and we would be confused because the new arguments don't exist
278277
List<Argument> immutableArguments = List.copyOf(args);
279278
// Generate our command from executor
280279
return cmdCtx -> {
281280
// Construct the execution info
282-
AbstractCommandSender<? extends CommandSender> sender = platform.getSenderForCommand(cmdCtx, executor.isForceNative());
281+
CommandSender sender = platform.getCommandSenderFromCommandSource(cmdCtx.getSource());
283282
CommandArguments commandArguments = argsToCommandArgs(cmdCtx, immutableArguments);
284283

285-
ExecutionInfo<CommandSender, AbstractCommandSender<? extends CommandSender>> executionInfo = new ExecutionInfo<>() {
286-
@Override
287-
public CommandSender sender() {
288-
return sender.getSource();
289-
}
290-
291-
@Override
292-
public AbstractCommandSender<? extends CommandSender> senderWrapper() {
293-
return sender;
294-
}
295-
296-
@Override
297-
public CommandArguments args() {
298-
return commandArguments;
299-
}
300-
};
284+
ExecutionInfo<CommandSender, Source> executionInfo = new ExecutionInfo<>(sender, commandArguments, cmdCtx);
301285

302286
// Apply the executor
303287
return executor.execute(executionInfo);
@@ -375,7 +359,7 @@ public SuggestionProvider<Source> generateBrigadierSuggestions(List<Argument> pr
375359
return (context, builder) -> {
376360
// Construct the suggestion info
377361
SuggestionInfo<CommandSender> suggestionInfo = new SuggestionInfo<>(
378-
platform.getCommandSenderFromCommandSource(context.getSource()).getSource(),
362+
platform.getCommandSenderFromCommandSource(context.getSource()),
379363
argsToCommandArgs(context, immutableArguments), builder.getInput(), builder.getRemaining()
380364
);
381365

@@ -393,44 +377,24 @@ public SuggestionProvider<Source> generateBrigadierSuggestions(List<Argument> pr
393377
* @return A Predicate that makes sure a Brigadier source object satisfies the given permission and arbitrary requirements.
394378
*/
395379
public Predicate<Source> generateBrigadierRequirements(CommandPermission permission, Predicate<CommandSender> requirements) {
380+
// If requirements are always false, result is always false
381+
if (requirements == CommandPermission.FALSE()) return CommandPermission.FALSE();
382+
396383
// Find the intial check for the given CommandPermission
397-
Predicate<AbstractCommandSender<? extends CommandSender>> senderCheck;
398-
if (permission.equals(CommandPermission.NONE)) {
399-
// No permissions always passes
400-
senderCheck = null;
401-
} else if (permission.equals(CommandPermission.OP)) {
402-
// Check op status
403-
senderCheck = AbstractCommandSender::isOp;
404-
} else {
405-
Optional<String> permissionStringWrapper = permission.getPermission();
406-
if (permissionStringWrapper.isPresent()) {
407-
String permissionString = permissionStringWrapper.get();
408-
// check permission
409-
senderCheck = sender -> sender.hasPermission(permissionString);
410-
} else {
411-
// No permission always passes
412-
senderCheck = null;
413-
}
414-
}
384+
Predicate<CommandSender> senderCheck = platform.getPermissionCheck(permission);
415385

416-
if (senderCheck == null) {
417-
// Short circuit permissions check if it doesn't depend on source
418-
if (permission.isNegated()) {
419-
// A negated NONE permission never passes
420-
return source -> false;
421-
} else {
422-
// Only need to check the requirements
423-
return source -> requirements.test(platform.getCommandSenderFromCommandSource(source).getSource());
424-
}
425-
}
386+
// Merge with requirements (unless its always true, which wouldn't add anything)
387+
if (requirements != CommandPermission.TRUE()) senderCheck = senderCheck.and(requirements);
426388

427-
// Negate permission check if appropriate
428-
Predicate<AbstractCommandSender<? extends CommandSender>> finalSenderCheck = permission.isNegated() ? senderCheck.negate() : senderCheck;
389+
// Short circuit if the final test is always true or false
390+
if (senderCheck == CommandPermission.TRUE()) return CommandPermission.TRUE();
391+
if (senderCheck == CommandPermission.FALSE()) return CommandPermission.FALSE();
429392

430-
// Merge permission check and requirements
393+
// Otherwise, convert Brigadier Source to CommandSender and run the check
394+
final Predicate<CommandSender> finalSenderCheck = senderCheck;
431395
return source -> {
432-
AbstractCommandSender<? extends CommandSender> sender = platform.getCommandSenderFromCommandSource(source);
433-
return finalSenderCheck.test(sender) && requirements.test(sender.getSource());
396+
CommandSender sender = platform.getCommandSenderFromCommandSource(source);
397+
return finalSenderCheck.test(sender);
434398
};
435399
}
436400

0 commit comments

Comments
 (0)
0