Slow startup debug, async command handling

The command argument processing and permission checking is done asynchronously, mainly because LuckPerms (rightfully) complains that loading player data (for unconnected DC users) should not be done on the main thread.
The actual command execution is still done on the main thread.
This commit is contained in:
Norbi Peti 2019-08-23 00:50:36 +02:00
parent feee6a0ebe
commit a17923602f
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
5 changed files with 109 additions and 84 deletions

View file

@ -12,6 +12,8 @@
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="ButtonCore (1) (com.github.TBMCPlugins.ButtonCore)" /> <orderEntry type="module" module-name="ButtonCore (1) (com.github.TBMCPlugins.ButtonCore)" />
<orderEntry type="module" module-name="ButtonCore (1) (com.github.TBMCPlugins.ButtonCore)" />
<orderEntry type="module" module-name="ButtonCore (1) (com.github.TBMCPlugins.ButtonCore)" />
<orderEntry type="library" name="Maven: org.reflections:reflections:0.9.10" level="project" /> <orderEntry type="library" name="Maven: org.reflections:reflections:0.9.10" level="project" />
<orderEntry type="library" name="Maven: com.google.code.findbugs:annotations:2.0.1" level="project" /> <orderEntry type="library" name="Maven: com.google.code.findbugs:annotations:2.0.1" level="project" />
<orderEntry type="library" name="Maven: org.javassist:javassist:3.20.0-GA" level="project" /> <orderEntry type="library" name="Maven: org.javassist:javassist:3.20.0-GA" level="project" />

View file

@ -53,11 +53,15 @@ public class RandomTP extends TBMCCommandBase
public void onEnable(Component component) public void onEnable(Component component)
{ {
System.out.println("Adding command");
TBMCChatAPI.AddCommand(component, this); TBMCChatAPI.AddCommand(component, this);
System.out.println("Getting world");
world = Bukkit.getWorld("World"); world = Bukkit.getWorld("World");
System.out.println("Getting border");
border = world.getWorldBorder(); border = world.getWorldBorder();
newLocation(); System.out.println("Getting new location");
System.out.println("Success: "+newLocation()); //TODO: It takes 10-30 seconds to find a location (newLocation() was there)
} }
/*================================================================================================*/ /*================================================================================================*/

View file

@ -19,7 +19,6 @@ import org.bukkit.event.player.PlayerQuitEvent;
public class RestartComponent extends Component<MainPlugin> implements Listener { public class RestartComponent extends Component<MainPlugin> implements Listener {
@Override @Override
public void enable() { public void enable() {
//TODO: Permissions for the commands
registerCommand(new ScheduledRestartCommand(this)); registerCommand(new ScheduledRestartCommand(this));
TBMCChatAPI.AddCommand(this, new PrimeRestartCommand(this)); TBMCChatAPI.AddCommand(this, new PrimeRestartCommand(this));
registerListener(this); registerListener(this);

View file

@ -131,10 +131,15 @@ public abstract class Component<TP extends JavaPlugin> {
throw new UnregisteredComponentException(component); throw new UnregisteredComponentException(component);
if (component.enabled == enabled) return; //Don't do anything if (component.enabled == enabled) return; //Don't do anything
if (component.enabled = enabled) { if (component.enabled = enabled) {
//System.out.println("Updating config for "+component.getClassName());
updateConfig(component.getPlugin(), component); updateConfig(component.getPlugin(), component);
//System.out.println("Enabling "+component.getClassName());
component.enable(); component.enable();
if (ButtonPlugin.configGenAllowed(component)) if (ButtonPlugin.configGenAllowed(component)) {
//System.out.println("Pregenning config for "+component.getClassName());
IHaveConfig.pregenConfig(component, null); IHaveConfig.pregenConfig(component, null);
}
//System.out.println("Done enabling "+component.getClassName());
} else { } else {
component.disable(); component.disable();
component.plugin.saveConfig(); component.plugin.saveConfig();

View file

@ -126,91 +126,106 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
paramConverters.put(cl, new ParamConverter<>(converter, errormsg)); paramConverters.put(cl, new ParamConverter<>(converter, errormsg));
} }
public boolean handleCommand(TP sender, String commandline) throws Exception { public boolean handleCommand(TP sender, String commandline) {
for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) { for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) {
String subcommand = commandline.substring(0, i).toLowerCase(); String subcommand = commandline.substring(0, i).toLowerCase();
SubcommandData<TC> sd = subcommands.get(subcommand); //O(1) SubcommandData<TC> sd = subcommands.get(subcommand); //O(1)
if (sd == null) continue; if (sd == null) continue;
if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> {
sender.sendMessage(sd.helpText); try {
return true; handleCommandAsync(sender, commandline, sd, subcommand);
} } catch (Exception e) {
if (!hasPermission(sender, sd.command, sd.method)) { TBMCCoreAPI.SendException("Command execution failed for sender " + sender + " and message " + commandline, e);
sender.sendMessage("§cYou don't have permission to use this command");
return true;
}
val params = new ArrayList<Object>(sd.method.getParameterCount());
int j = subcommand.length(), pj;
Class<?>[] parameterTypes = sd.method.getParameterTypes();
if (parameterTypes.length == 0)
throw new Exception("No sender parameter for method '" + sd.method + "'");
val sendertype = parameterTypes[0];
final ChromaGamerBase cg;
if (sendertype.isAssignableFrom(sender.getClass()))
params.add(sender); //The command either expects a CommandSender or it is a Player, or some other expected type
else if (sender instanceof Command2MCSender
&& sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass()))
params.add(((Command2MCSender) sender).getSender());
else if (ChromaGamerBase.class.isAssignableFrom(sendertype)
&& sender instanceof Command2MCSender
&& (cg = ChromaGamerBase.getFromSender(((Command2MCSender) sender).getSender())) != null
&& cg.getClass() == sendertype) //The command expects a user of our system
params.add(cg);
else {
sender.sendMessage("§cYou need to be a " + sendertype.getSimpleName() + " to use this command.");
return true;
}
val paramArr = sd.method.getParameters();
for (int i1 = 1; i1 < parameterTypes.length; i1++) {
Class<?> cl = parameterTypes[i1];
pj = j + 1; //Start index
if (pj == commandline.length() + 1) { //No param given
if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) {
if (cl.isPrimitive())
params.add(Defaults.defaultValue(cl));
else if (Number.class.isAssignableFrom(cl)
|| Number.class.isAssignableFrom(cl))
params.add(Defaults.defaultValue(Primitives.unwrap(cl)));
else
params.add(null);
continue; //Fill the remaining params with nulls
} else {
sender.sendMessage(sd.helpText); //Required param missing
return true;
}
} }
if (paramArr[i1].isVarArgs()) { });
params.add(commandline.substring(j + 1).split(" +")); return true; //We found a method
continue; }
return false;
}
//Needed because permission checking may load the (perhaps offline) sender's file which is disallowed on the main thread
public void handleCommandAsync(TP sender, String commandline, SubcommandData<TC> sd, String subcommand) throws Exception {
if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands
sender.sendMessage(sd.helpText);
return;
}
if (!hasPermission(sender, sd.command, sd.method)) {
sender.sendMessage("§cYou don't have permission to use this command");
return;
}
val params = new ArrayList<Object>(sd.method.getParameterCount());
int j = subcommand.length(), pj;
Class<?>[] parameterTypes = sd.method.getParameterTypes();
if (parameterTypes.length == 0)
throw new Exception("No sender parameter for method '" + sd.method + "'");
val sendertype = parameterTypes[0];
final ChromaGamerBase cg;
if (sendertype.isAssignableFrom(sender.getClass()))
params.add(sender); //The command either expects a CommandSender or it is a Player, or some other expected type
else if (sender instanceof Command2MCSender
&& sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass()))
params.add(((Command2MCSender) sender).getSender());
else if (ChromaGamerBase.class.isAssignableFrom(sendertype)
&& sender instanceof Command2MCSender
&& (cg = ChromaGamerBase.getFromSender(((Command2MCSender) sender).getSender())) != null
&& cg.getClass() == sendertype) //The command expects a user of our system
params.add(cg);
else {
sender.sendMessage("§cYou need to be a " + sendertype.getSimpleName() + " to use this command.");
return;
}
val paramArr = sd.method.getParameters();
for (int i1 = 1; i1 < parameterTypes.length; i1++) {
Class<?> cl = parameterTypes[i1];
pj = j + 1; //Start index
if (pj == commandline.length() + 1) { //No param given
if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) {
if (cl.isPrimitive())
params.add(Defaults.defaultValue(cl));
else if (Number.class.isAssignableFrom(cl)
|| Number.class.isAssignableFrom(cl))
params.add(Defaults.defaultValue(Primitives.unwrap(cl)));
else
params.add(null);
continue; //Fill the remaining params with nulls
} else {
sender.sendMessage(sd.helpText); //Required param missing
return;
} }
j = commandline.indexOf(' ', j + 1); //End index
if (j == -1 || paramArr[i1].isAnnotationPresent(TextArg.class)) //Last parameter
j = commandline.length();
String param = commandline.substring(pj, j);
if (cl == String.class) {
params.add(param);
continue;
} else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) {
try {
//noinspection unchecked
Number n = ThorpeUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class<? extends Number>) cl);
params.add(n);
} catch (ParseException e) {
sender.sendMessage("§c'" + param + "' is not a number.");
return true;
}
continue;
}
val conv = paramConverters.get(cl);
if (conv == null)
throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method.toString() + "'");
val cparam = conv.converter.apply(param);
if (cparam == null) {
sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found
return true;
}
params.add(cparam);
} }
if (paramArr[i1].isVarArgs()) {
params.add(commandline.substring(j + 1).split(" +"));
continue;
}
j = commandline.indexOf(' ', j + 1); //End index
if (j == -1 || paramArr[i1].isAnnotationPresent(TextArg.class)) //Last parameter
j = commandline.length();
String param = commandline.substring(pj, j);
if (cl == String.class) {
params.add(param);
continue;
} else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) {
try {
//noinspection unchecked
Number n = ThorpeUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class<? extends Number>) cl);
params.add(n);
} catch (ParseException e) {
sender.sendMessage("§c'" + param + "' is not a number.");
return;
}
continue;
}
val conv = paramConverters.get(cl);
if (conv == null)
throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method.toString() + "'");
val cparam = conv.converter.apply(param);
if (cparam == null) {
sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found
return;
}
params.add(cparam);
}
Bukkit.getScheduler().runTask(MainPlugin.Instance, () -> {
try { try {
val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time) val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time)
if (ret instanceof Boolean) { if (ret instanceof Boolean) {
@ -218,12 +233,12 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
sender.sendMessage(sd.helpText); sender.sendMessage(sd.helpText);
} else if (ret != null) } else if (ret != null)
throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret); throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret);
return true; //We found a method
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
TBMCCoreAPI.SendException("An error occurred in a command handler!", e.getCause()); TBMCCoreAPI.SendException("An error occurred in a command handler!", e.getCause());
} catch (Exception e) {
TBMCCoreAPI.SendException("Command handling failed for sender " + sender + " and subcommand " + subcommand, e);
} }
} });
return false; //Didn't handle
} //TODO: Add to the help } //TODO: Add to the help
public abstract void registerCommand(TC command); public abstract void registerCommand(TC command);