package net.minecraft.server.commands; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.ContextChain; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.logging.LogUtils; import java.io.IOException; import java.io.PrintWriter; import java.io.UncheckedIOException; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.Locale; import net.minecraft.Util; import net.minecraft.commands.CommandResultCallback; import net.minecraft.commands.CommandSource; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.FunctionInstantiationException; import net.minecraft.commands.arguments.item.FunctionArgument; import net.minecraft.commands.execution.ChainModifiers; import net.minecraft.commands.execution.CustomCommandExecutor; import net.minecraft.commands.execution.ExecutionContext; import net.minecraft.commands.execution.ExecutionControl; import net.minecraft.commands.execution.Frame; import net.minecraft.commands.execution.TraceCallbacks; import net.minecraft.commands.execution.tasks.CallFunction; import net.minecraft.commands.functions.CommandFunction; import net.minecraft.commands.functions.InstantiatedFunction; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.util.TimeUtil; import net.minecraft.util.profiling.ProfileResults; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; public class DebugCommand { static final Logger LOGGER = LogUtils.getLogger(); private static final SimpleCommandExceptionType ERROR_NOT_RUNNING = new SimpleCommandExceptionType(Component.translatable("commands.debug.notRunning")); private static final SimpleCommandExceptionType ERROR_ALREADY_RUNNING = new SimpleCommandExceptionType(Component.translatable("commands.debug.alreadyRunning")); static final SimpleCommandExceptionType NO_RECURSIVE_TRACES = new SimpleCommandExceptionType(Component.translatable("commands.debug.function.noRecursion")); static final SimpleCommandExceptionType NO_RETURN_RUN = new SimpleCommandExceptionType(Component.translatable("commands.debug.function.noReturnRun")); public static void register(CommandDispatcher p_136906_) { p_136906_.register( Commands.literal("debug") .requires(p_180073_ -> p_180073_.hasPermission(3)) .then(Commands.literal("start").executes(p_180069_ -> start(p_180069_.getSource()))) .then(Commands.literal("stop").executes(p_136918_ -> stop(p_136918_.getSource()))) .then( Commands.literal("function") .requires(p_180071_ -> p_180071_.hasPermission(3)) .then( Commands.argument("name", FunctionArgument.functions()) .suggests(FunctionCommand.SUGGEST_FUNCTION) .executes(new DebugCommand.TraceCustomExecutor()) ) ) ); } private static int start(CommandSourceStack p_136910_) throws CommandSyntaxException { MinecraftServer minecraftserver = p_136910_.getServer(); if (minecraftserver.isTimeProfilerRunning()) { throw ERROR_ALREADY_RUNNING.create(); } else { minecraftserver.startTimeProfiler(); p_136910_.sendSuccess(() -> Component.translatable("commands.debug.started"), true); return 0; } } private static int stop(CommandSourceStack p_136916_) throws CommandSyntaxException { MinecraftServer minecraftserver = p_136916_.getServer(); if (!minecraftserver.isTimeProfilerRunning()) { throw ERROR_NOT_RUNNING.create(); } else { ProfileResults profileresults = minecraftserver.stopTimeProfiler(); double d0 = (double)profileresults.getNanoDuration() / (double)TimeUtil.NANOSECONDS_PER_SECOND; double d1 = (double)profileresults.getTickDuration() / d0; p_136916_.sendSuccess( () -> Component.translatable( "commands.debug.stopped", String.format(Locale.ROOT, "%.2f", d0), profileresults.getTickDuration(), String.format(Locale.ROOT, "%.2f", d1) ), true ); return (int)d1; } } static class TraceCustomExecutor extends CustomCommandExecutor.WithErrorHandling implements CustomCommandExecutor.CommandAdapter { public void runGuarded( CommandSourceStack p_309819_, ContextChain p_311173_, ChainModifiers p_312111_, ExecutionControl p_311988_ ) throws CommandSyntaxException { if (p_312111_.isReturn()) { throw DebugCommand.NO_RETURN_RUN.create(); } else if (p_311988_.tracer() != null) { throw DebugCommand.NO_RECURSIVE_TRACES.create(); } else { CommandContext commandcontext = p_311173_.getTopContext(); Collection> collection = FunctionArgument.getFunctions(commandcontext, "name"); MinecraftServer minecraftserver = p_309819_.getServer(); String s = "debug-trace-" + Util.getFilenameFormattedDateTime() + ".txt"; CommandDispatcher commanddispatcher = p_309819_.getServer().getFunctions().getDispatcher(); int i = 0; try { Path path = minecraftserver.getFile("debug"); Files.createDirectories(path); final PrintWriter printwriter = new PrintWriter(Files.newBufferedWriter(path.resolve(s), StandardCharsets.UTF_8)); DebugCommand.Tracer debugcommand$tracer = new DebugCommand.Tracer(printwriter); p_311988_.tracer(debugcommand$tracer); for (final CommandFunction commandfunction : collection) { try { CommandSourceStack commandsourcestack = p_309819_.withSource(debugcommand$tracer).withMaximumPermission(2); InstantiatedFunction instantiatedfunction = commandfunction.instantiate(null, commanddispatcher); p_311988_.queueNext((new CallFunction(instantiatedfunction, CommandResultCallback.EMPTY, false) { public void execute(CommandSourceStack p_310186_, ExecutionContext p_311250_, Frame p_311262_) { printwriter.println(commandfunction.id()); super.execute(p_310186_, p_311250_, p_311262_); } }).bind(commandsourcestack)); i += instantiatedfunction.entries().size(); } catch (FunctionInstantiationException functioninstantiationexception) { p_309819_.sendFailure(functioninstantiationexception.messageComponent()); } } } catch (IOException | UncheckedIOException uncheckedioexception) { DebugCommand.LOGGER.warn("Tracing failed", (Throwable)uncheckedioexception); p_309819_.sendFailure(Component.translatable("commands.debug.function.traceFailed")); } int j = i; p_311988_.queueNext( (p_311688_, p_310332_) -> { if (collection.size() == 1) { p_309819_.sendSuccess( () -> Component.translatable( "commands.debug.function.success.single", j, Component.translationArg(collection.iterator().next().id()), s ), true ); } else { p_309819_.sendSuccess(() -> Component.translatable("commands.debug.function.success.multiple", j, collection.size(), s), true); } } ); } } } static class Tracer implements CommandSource, TraceCallbacks { public static final int INDENT_OFFSET = 1; private final PrintWriter output; private int lastIndent; private boolean waitingForResult; Tracer(PrintWriter p_180079_) { this.output = p_180079_; } private void indentAndSave(int p_180082_) { this.printIndent(p_180082_); this.lastIndent = p_180082_; } private void printIndent(int p_180098_) { for (int i = 0; i < p_180098_ + 1; i++) { this.output.write(" "); } } private void newLine() { if (this.waitingForResult) { this.output.println(); this.waitingForResult = false; } } @Override public void onCommand(int p_180084_, String p_180085_) { this.newLine(); this.indentAndSave(p_180084_); this.output.print("[C] "); this.output.print(p_180085_); this.waitingForResult = true; } @Override public void onReturn(int p_180087_, String p_180088_, int p_180089_) { if (this.waitingForResult) { this.output.print(" -> "); this.output.println(p_180089_); this.waitingForResult = false; } else { this.indentAndSave(p_180087_); this.output.print("[R = "); this.output.print(p_180089_); this.output.print("] "); this.output.println(p_180088_); } } @Override public void onCall(int p_180091_, ResourceLocation p_180092_, int p_180093_) { this.newLine(); this.indentAndSave(p_180091_); this.output.print("[F] "); this.output.print(p_180092_); this.output.print(" size="); this.output.println(p_180093_); } @Override public void onError(String p_180101_) { this.newLine(); this.indentAndSave(this.lastIndent + 1); this.output.print("[E] "); this.output.print(p_180101_); } @Override public void sendSystemMessage(Component p_214427_) { this.newLine(); this.printIndent(this.lastIndent + 1); this.output.print("[M] "); this.output.println(p_214427_.getString()); } @Override public boolean acceptsSuccess() { return true; } @Override public boolean acceptsFailure() { return true; } @Override public boolean shouldInformAdmins() { return false; } @Override public boolean alwaysAccepts() { return true; } @Override public void close() { IOUtils.closeQuietly((Writer)this.output); } } }