minecraft-src/net/minecraft/server/commands/DebugCommand.java
2025-07-04 03:15:13 +03:00

254 lines
9.1 KiB
Java

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.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.ExecutionControl;
import net.minecraft.commands.execution.TraceCallbacks;
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.server.commands.DebugCommand.TraceCustomExecutor.1;
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<CommandSourceStack> dispatcher) {
dispatcher.register(
Commands.literal("debug")
.requires(commandSourceStack -> commandSourceStack.hasPermission(3))
.then(Commands.literal("start").executes(commandContext -> start(commandContext.getSource())))
.then(Commands.literal("stop").executes(commandContext -> stop(commandContext.getSource())))
.then(
Commands.literal("function")
.requires(commandSourceStack -> commandSourceStack.hasPermission(3))
.then(Commands.argument("name", FunctionArgument.functions()).suggests(FunctionCommand.SUGGEST_FUNCTION).executes(new DebugCommand.TraceCustomExecutor()))
)
);
}
private static int start(CommandSourceStack source) throws CommandSyntaxException {
MinecraftServer minecraftServer = source.getServer();
if (minecraftServer.isTimeProfilerRunning()) {
throw ERROR_ALREADY_RUNNING.create();
} else {
minecraftServer.startTimeProfiler();
source.sendSuccess(() -> Component.translatable("commands.debug.started"), true);
return 0;
}
}
private static int stop(CommandSourceStack source) throws CommandSyntaxException {
MinecraftServer minecraftServer = source.getServer();
if (!minecraftServer.isTimeProfilerRunning()) {
throw ERROR_NOT_RUNNING.create();
} else {
ProfileResults profileResults = minecraftServer.stopTimeProfiler();
double d = (double)profileResults.getNanoDuration() / TimeUtil.NANOSECONDS_PER_SECOND;
double e = profileResults.getTickDuration() / d;
source.sendSuccess(
() -> Component.translatable(
"commands.debug.stopped", String.format(Locale.ROOT, "%.2f", d), profileResults.getTickDuration(), String.format(Locale.ROOT, "%.2f", e)
),
true
);
return (int)e;
}
}
static class TraceCustomExecutor
extends CustomCommandExecutor.WithErrorHandling<CommandSourceStack>
implements CustomCommandExecutor.CommandAdapter<CommandSourceStack> {
public void runGuarded(
CommandSourceStack commandSourceStack,
ContextChain<CommandSourceStack> contextChain,
ChainModifiers chainModifiers,
ExecutionControl<CommandSourceStack> executionControl
) throws CommandSyntaxException {
if (chainModifiers.isReturn()) {
throw DebugCommand.NO_RETURN_RUN.create();
} else if (executionControl.tracer() != null) {
throw DebugCommand.NO_RECURSIVE_TRACES.create();
} else {
CommandContext<CommandSourceStack> commandContext = contextChain.getTopContext();
Collection<CommandFunction<CommandSourceStack>> collection = FunctionArgument.getFunctions(commandContext, "name");
MinecraftServer minecraftServer = commandSourceStack.getServer();
String string = "debug-trace-" + Util.getFilenameFormattedDateTime() + ".txt";
CommandDispatcher<CommandSourceStack> commandDispatcher = commandSourceStack.getServer().getFunctions().getDispatcher();
int i = 0;
try {
Path path = minecraftServer.getFile("debug");
Files.createDirectories(path);
PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(path.resolve(string), StandardCharsets.UTF_8));
DebugCommand.Tracer tracer = new DebugCommand.Tracer(printWriter);
executionControl.tracer(tracer);
for (CommandFunction<CommandSourceStack> commandFunction : collection) {
try {
CommandSourceStack commandSourceStack2 = commandSourceStack.withSource(tracer).withMaximumPermission(2);
InstantiatedFunction<CommandSourceStack> instantiatedFunction = commandFunction.instantiate(null, commandDispatcher);
executionControl.queueNext(new 1(this, instantiatedFunction, CommandResultCallback.EMPTY, false, printWriter, commandFunction).bind(commandSourceStack2));
i += instantiatedFunction.entries().size();
} catch (FunctionInstantiationException var18) {
commandSourceStack.sendFailure(var18.messageComponent());
}
}
} catch (IOException | UncheckedIOException var19) {
DebugCommand.LOGGER.warn("Tracing failed", (Throwable)var19);
commandSourceStack.sendFailure(Component.translatable("commands.debug.function.traceFailed"));
}
int j = i;
executionControl.queueNext(
(executionContext, frame) -> {
if (collection.size() == 1) {
commandSourceStack.sendSuccess(
() -> Component.translatable(
"commands.debug.function.success.single", j, Component.translationArg(((CommandFunction)collection.iterator().next()).id()), string
),
true
);
} else {
commandSourceStack.sendSuccess(() -> Component.translatable("commands.debug.function.success.multiple", j, collection.size(), string), 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 output) {
this.output = output;
}
private void indentAndSave(int indent) {
this.printIndent(indent);
this.lastIndent = indent;
}
private void printIndent(int indent) {
for (int i = 0; i < indent + 1; i++) {
this.output.write(" ");
}
}
private void newLine() {
if (this.waitingForResult) {
this.output.println();
this.waitingForResult = false;
}
}
@Override
public void onCommand(int depth, String command) {
this.newLine();
this.indentAndSave(depth);
this.output.print("[C] ");
this.output.print(command);
this.waitingForResult = true;
}
@Override
public void onReturn(int depth, String command, int returnValue) {
if (this.waitingForResult) {
this.output.print(" -> ");
this.output.println(returnValue);
this.waitingForResult = false;
} else {
this.indentAndSave(depth);
this.output.print("[R = ");
this.output.print(returnValue);
this.output.print("] ");
this.output.println(command);
}
}
@Override
public void onCall(int depth, ResourceLocation function, int commands) {
this.newLine();
this.indentAndSave(depth);
this.output.print("[F] ");
this.output.print(function);
this.output.print(" size=");
this.output.println(commands);
}
@Override
public void onError(String errorMessage) {
this.newLine();
this.indentAndSave(this.lastIndent + 1);
this.output.print("[E] ");
this.output.print(errorMessage);
}
@Override
public void sendSystemMessage(Component component) {
this.newLine();
this.printIndent(this.lastIndent + 1);
this.output.print("[M] ");
this.output.println(component.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(this.output);
}
}
}