From a17923602f55ae32f09c57cc7decfaebb27b33a8 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Fri, 23 Aug 2019 00:50:36 +0200 Subject: [PATCH] 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. --- BuildConfigUpdater/BuildConfigUpdater.iml | 2 + .../core/component/randomtp/RandomTP.java | 6 +- .../component/restart/RestartComponent.java | 1 - .../lib/architecture/Component.java | 7 +- .../java/buttondevteam/lib/chat/Command2.java | 177 ++++++++++-------- 5 files changed, 109 insertions(+), 84 deletions(-) diff --git a/BuildConfigUpdater/BuildConfigUpdater.iml b/BuildConfigUpdater/BuildConfigUpdater.iml index df2c815..cea34c4 100644 --- a/BuildConfigUpdater/BuildConfigUpdater.iml +++ b/BuildConfigUpdater/BuildConfigUpdater.iml @@ -12,6 +12,8 @@ + + diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/randomtp/RandomTP.java b/ButtonCore/src/main/java/buttondevteam/core/component/randomtp/RandomTP.java index a0612db..755a32a 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/randomtp/RandomTP.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/randomtp/RandomTP.java @@ -53,11 +53,15 @@ public class RandomTP extends TBMCCommandBase public void onEnable(Component component) { + System.out.println("Adding command"); TBMCChatAPI.AddCommand(component, this); + System.out.println("Getting world"); world = Bukkit.getWorld("World"); + System.out.println("Getting border"); 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) } /*================================================================================================*/ diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/restart/RestartComponent.java b/ButtonCore/src/main/java/buttondevteam/core/component/restart/RestartComponent.java index 6b431bb..d0104a0 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/restart/RestartComponent.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/restart/RestartComponent.java @@ -19,7 +19,6 @@ import org.bukkit.event.player.PlayerQuitEvent; public class RestartComponent extends Component implements Listener { @Override public void enable() { - //TODO: Permissions for the commands registerCommand(new ScheduledRestartCommand(this)); TBMCChatAPI.AddCommand(this, new PrimeRestartCommand(this)); registerListener(this); diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java index b98466b..a39db67 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java @@ -131,10 +131,15 @@ public abstract class Component { throw new UnregisteredComponentException(component); if (component.enabled == enabled) return; //Don't do anything if (component.enabled = enabled) { + //System.out.println("Updating config for "+component.getClassName()); updateConfig(component.getPlugin(), component); + //System.out.println("Enabling "+component.getClassName()); component.enable(); - if (ButtonPlugin.configGenAllowed(component)) + if (ButtonPlugin.configGenAllowed(component)) { + //System.out.println("Pregenning config for "+component.getClassName()); IHaveConfig.pregenConfig(component, null); + } + //System.out.println("Done enabling "+component.getClassName()); } else { component.disable(); component.plugin.saveConfig(); diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java index d2851fe..1e75596 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java @@ -126,91 +126,106 @@ public abstract class Command2 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)) { String subcommand = commandline.substring(0, i).toLowerCase(); SubcommandData sd = subcommands.get(subcommand); //O(1) if (sd == null) continue; - if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands - sender.sendMessage(sd.helpText); - return true; - } - if (!hasPermission(sender, sd.command, sd.method)) { - sender.sendMessage("§cYou don't have permission to use this command"); - return true; - } - val params = new ArrayList(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; - } + Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> { + try { + handleCommandAsync(sender, commandline, sd, subcommand); + } catch (Exception e) { + TBMCCoreAPI.SendException("Command execution failed for sender " + sender + " and message " + commandline, e); } - if (paramArr[i1].isVarArgs()) { - params.add(commandline.substring(j + 1).split(" +")); - continue; + }); + return true; //We found a method + } + 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 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(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) 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) 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 { 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) { @@ -218,12 +233,12 @@ public abstract class Command2 sender.sendMessage(sd.helpText); } else if (ret != null) throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret); - return true; //We found a method } catch (InvocationTargetException e) { 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 public abstract void registerCommand(TC command);