Skip to content

Commit af09dd0

Browse files
authored
feat(commands): add command node and tree to better performance (#38)
* feat(commands): add command tree to remove complexity * feat(javadoc); add javadoc for the new element and add test for the new system * fix: invoker and tabulation with new tree * fix(tree): fix logic to keep parent command when no child exist * fix(tree): return the parent command if args is not sub command and parent have command
1 parent 6663cf7 commit af09dd0

File tree

26 files changed

+704
-305
lines changed

26 files changed

+704
-305
lines changed

build.gradle

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
plugins {
2-
id 'com.adarshr.test-logger' version '4.0.0'
3-
}
4-
51
allprojects {
62
group = 'fr.traqueur.commands'
73
version = property('version')
@@ -25,8 +21,6 @@ allprojects {
2521
subprojects {
2622
if (!project.name.contains('test-plugin')) {
2723

28-
apply plugin: 'com.adarshr.test-logger'
29-
3024
dependencies {
3125
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
3226
testImplementation 'org.mockito:mockito-core:5.3.1'
@@ -40,7 +34,8 @@ subprojects {
4034
}
4135

4236
testLogging {
43-
events("passed", "skipped", "failed")
37+
showStandardStreams = true
38+
events("passed", "skipped", "failed", "standardOut", "standardError")
4439
exceptionFormat "full"
4540
}
4641
}

core/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
id 'maven-publish'
3+
id("me.champeau.jmh") version "0.7.3"
34
}
45

56
java {
@@ -9,6 +10,11 @@ java {
910
withJavadocJar()
1011
}
1112

13+
dependencies {
14+
jmh 'org.openjdk.jmh:jmh-core:1.37'
15+
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
16+
}
17+
1218
def generatedResourcesDir = "$buildDir/generated-resources"
1319

1420
tasks.register('generateCommandsProperties') {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package fr.traqueur.commands;
2+
3+
import fr.traqueur.commands.api.models.Command;
4+
import fr.traqueur.commands.api.models.collections.CommandTree;
5+
import org.openjdk.jmh.annotations.*;
6+
7+
import java.util.*;
8+
import java.util.concurrent.ThreadLocalRandom;
9+
import java.util.concurrent.TimeUnit;
10+
11+
@State(Scope.Benchmark)
12+
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
13+
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
14+
@Fork(2)
15+
@BenchmarkMode(Mode.Throughput)
16+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
17+
public class CommandLookupBenchmark {
18+
19+
@Param({ "1000", "10000", "50000" })
20+
public int N;
21+
22+
@Param({ "1", "2", "3" })
23+
public int maxDepth;
24+
25+
private Map<String, DummyCommand> flatMap;
26+
private CommandTree<DummyCommand, Object> tree;
27+
private String[] rawLabels;
28+
29+
@Setup(Level.Trial)
30+
public void setup() {
31+
flatMap = new HashMap<>(N);
32+
tree = new CommandTree<>();
33+
34+
rawLabels = new String[N];
35+
ThreadLocalRandom rnd = ThreadLocalRandom.current();
36+
37+
for (int i = 0; i < N; i++) {
38+
int depth = 1 + rnd.nextInt(maxDepth);
39+
StringBuilder sb = new StringBuilder();
40+
for (int d = 0; d < depth; d++) {
41+
if (d > 0) sb.append('.');
42+
sb.append("cmd").append(rnd.nextInt(N * 10));
43+
}
44+
String label = sb.toString();
45+
rawLabels[i] = label;
46+
47+
DummyCommand cmd = new DummyCommand(label);
48+
flatMap.put(label, cmd);
49+
tree.addCommand(label, cmd);
50+
}
51+
}
52+
53+
@Benchmark
54+
public DummyCommand mapLookup() {
55+
String raw = rawLabels[ThreadLocalRandom.current().nextInt(N)];
56+
String[] segments = raw.split("\\.");
57+
for (int len = segments.length; len > 0; len--) {
58+
String key = String.join(".", Arrays.copyOf(segments, len));
59+
DummyCommand c = flatMap.get(key);
60+
if (c != null) {
61+
return c;
62+
}
63+
}
64+
return null;
65+
}
66+
67+
@Benchmark
68+
public CommandTree.MatchResult<DummyCommand, Object> treeLookup() {
69+
String raw = rawLabels[ThreadLocalRandom.current().nextInt(N)];
70+
String[] parts = raw.split("\\.");
71+
String base = parts[0];
72+
String[] sub = Arrays.copyOfRange(parts, 1, parts.length);
73+
return tree.findNode(base, sub).orElse(null);
74+
}
75+
76+
public static class DummyCommand extends Command<DummyCommand, Object> {
77+
public DummyCommand(String name) { super(null, name); }
78+
@Override public void execute(Object s, fr.traqueur.commands.api.arguments.Arguments a) {}
79+
}
80+
}

core/src/main/java/fr/traqueur/commands/api/CommandInvoker.java

Lines changed: 0 additions & 188 deletions
This file was deleted.

core/src/main/java/fr/traqueur/commands/api/CommandManager.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
import fr.traqueur.commands.api.arguments.Argument;
44
import fr.traqueur.commands.api.arguments.ArgumentConverter;
5+
import fr.traqueur.commands.api.arguments.Arguments;
56
import fr.traqueur.commands.api.arguments.TabCompleter;
67
import fr.traqueur.commands.api.exceptions.ArgumentIncorrectException;
78
import fr.traqueur.commands.api.exceptions.TypeArgumentNotExistException;
89
import fr.traqueur.commands.api.logging.Logger;
910
import fr.traqueur.commands.api.logging.MessageHandler;
11+
import fr.traqueur.commands.api.models.Command;
12+
import fr.traqueur.commands.api.models.CommandInvoker;
13+
import fr.traqueur.commands.api.models.CommandPlatform;
14+
import fr.traqueur.commands.api.models.collections.CommandTree;
1015
import fr.traqueur.commands.api.updater.Updater;
1116
import fr.traqueur.commands.impl.arguments.BooleanArgument;
1217
import fr.traqueur.commands.impl.arguments.DoubleArgument;
@@ -44,7 +49,7 @@ public abstract class CommandManager<T, S> {
4449
/**
4550
* The commands registered in the command manager.
4651
*/
47-
private final Map<String, Command<T,S>> commands;
52+
private final CommandTree<T,S> commands;
4853

4954
/**
5055
* The argument converters registered in the command manager.
@@ -86,7 +91,7 @@ public CommandManager(CommandPlatform<T,S> platform) {
8691
this.messageHandler = new InternalMessageHandler();
8792
this.logger = new InternalLogger(platform.getLogger());
8893
this.debug = false;
89-
this.commands = new HashMap<>();
94+
this.commands = new CommandTree<>();
9095
this.typeConverters = new HashMap<>();
9196
this.completers = new HashMap<>();
9297
this.invoker = new CommandInvoker<>(this);
@@ -165,10 +170,14 @@ public void unregisterCommand(String label) {
165170
* @param subcommands If the subcommands must be unregistered.
166171
*/
167172
public void unregisterCommand(String label, boolean subcommands) {
168-
if(this.commands.get(label) == null) {
169-
throw new IllegalArgumentException("The command " + label + " does not exist.");
173+
String[] rawArgs = label.split("\\.");
174+
Optional<Command<T,S>> commandOptional = this.commands.findNode(rawArgs)
175+
.flatMap(result -> result.node.getCommand());
176+
177+
if (!commandOptional.isPresent()) {
178+
throw new IllegalArgumentException("Command with label '" + label + "' does not exist.");
170179
}
171-
this.unregisterCommand(this.commands.get(label), subcommands);
180+
this.unregisterCommand(commandOptional.get(), subcommands);
172181
}
173182

174183
/**
@@ -252,7 +261,7 @@ public Arguments parse(Command<T,S> command, String[] args) throws TypeArgumentN
252261
* Get the commands of the command manager.
253262
* @return The commands of the command manager.
254263
*/
255-
public Map<String, Command<T,S>> getCommands() {
264+
public CommandTree<T, S> getCommands() {
256265
return commands;
257266
}
258267

@@ -327,7 +336,7 @@ private void unregisterSubCommands(String parentLabel, List<Command<T,S>> subcom
327336
*/
328337
private void removeCommand(String label, boolean subcommand) {
329338
this.platform.removeCommand(label, subcommand);
330-
this.commands.remove(label);
339+
this.commands.removeCommand(label, subcommand);
331340
this.completers.remove(label);
332341
}
333342

@@ -351,10 +360,8 @@ private void addCommand(Command<T,S> command, String label) throws TypeArgumentN
351360
}
352361

353362
command.setManager(this);
354-
355-
commands.put(label.toLowerCase(), command);
356-
357363
this.platform.addCommand(command, label);
364+
commands.addCommand(label, command);
358365

359366
this.addCompletionsForLabel(labelParts);
360367
this.addCompletionForArgs(label, labelSize, args);

0 commit comments

Comments
 (0)