diff --git a/src/main/java/net/neoforged/neoforge/server/command/TPSCommand.java b/src/main/java/net/neoforged/neoforge/server/command/TPSCommand.java index eb50fd2963..4703bda119 100644 --- a/src/main/java/net/neoforged/neoforge/server/command/TPSCommand.java +++ b/src/main/java/net/neoforged/neoforge/server/command/TPSCommand.java @@ -5,18 +5,27 @@ package net.neoforged.neoforge.server.command; +import com.google.common.math.Stats; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import java.text.DecimalFormat; +import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.DimensionArgument; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.CommonColors; +import net.minecraft.util.Mth; import net.minecraft.util.TimeUtil; -import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.TickRateManager; +import org.jetbrains.annotations.Nullable; +import org.joml.Math; class TPSCommand { private static final DecimalFormat TIME_FORMATTER = new DecimalFormat("########0.000"); @@ -24,40 +33,66 @@ class TPSCommand { static ArgumentBuilder register() { return Commands.literal("tps") - .requires(cs -> cs.hasPermission(0)) //permission - .then(Commands.argument("dim", DimensionArgument.dimension()) - .executes(ctx -> sendTime(ctx.getSource(), DimensionArgument.getDimension(ctx, "dim")))) + .then(Commands.argument("dimension", DimensionArgument.dimension()) + .executes(ctx -> sendTime(ctx, DimensionArgument.getDimension(ctx, "dimension")))) .executes(ctx -> { - for (ServerLevel dim : ctx.getSource().getServer().getAllLevels()) - sendTime(ctx.getSource(), dim); + for (ServerLevel dimension : ctx.getSource().getServer().getAllLevels()) { + sendTime(ctx, dimension); + } - long[] times = ctx.getSource().getServer().getTickTimesNanos(); - double meanTickTime = mean(times) * 1.0E-6D; - double meanTPS = TimeUtil.MILLISECONDS_PER_SECOND / Math.max(meanTickTime, ctx.getSource().getServer().tickRateManager().millisecondsPerTick()); - ctx.getSource().sendSuccess(() -> Component.translatable("commands.neoforge.tps.summary.all", TIME_FORMATTER.format(meanTickTime), TIME_FORMATTER.format(meanTPS)), false); - - return 0; + sendTime(ctx, null); + return Command.SINGLE_SUCCESS; }); } - private static int sendTime(CommandSourceStack cs, ServerLevel dim) throws CommandSyntaxException { - long[] times = cs.getServer().getTickTime(dim.dimension()); + private static int sendTime(CommandContext context, @Nullable ServerLevel dimension) throws CommandSyntaxException { + var src = context.getSource(); + src.sendSuccess(() -> createComponent(src.getServer(), dimension), false); + return Command.SINGLE_SUCCESS; + } + + private static Component createComponent(MinecraftServer server, @Nullable ServerLevel dimension) { + long[] times; + TickRateManager tickRateManager; + + if (dimension == null) { + times = server.getTickTimesNanos(); + tickRateManager = server.tickRateManager(); + } else { + var dimensionTimes = server.getTickTime(dimension.dimension()); + times = dimensionTimes == null ? UNLOADED : dimensionTimes; + tickRateManager = dimension.tickRateManager(); + } + + var tickTime = Stats.meanOf(times) / TimeUtil.NANOSECONDS_PER_MILLISECOND; + var tps = TimeUtil.MILLISECONDS_PER_SECOND / Math.max(tickTime, tickRateManager.millisecondsPerTick()); + var tickTimeComponent = Component.literal(TIME_FORMATTER.format(tickTime)).withColor(CommonColors.LIGHT_GRAY); + var tpsComponent = Component.literal(TIME_FORMATTER.format(tps)).withColor(calculateTPSColor(tickRateManager, tps)); + + MutableComponent component; + + if (dimension == null) { + component = Component.translatable("commands.neoforge.tps.overall", tpsComponent, tickTimeComponent); + } else { + var dimensionType = dimension.dimensionTypeRegistration(); - if (times == null) // Null means the world is unloaded. Not invalid. That's taken care of by DimensionArgument itself. - times = UNLOADED; + var dimensionName = Component.empty().append(dimension.getDescription()).withStyle(style -> style + .withColor(ChatFormatting.GREEN) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable( + "commands.neoforge.tps.dimension.tooltip", + dimension.dimension().location().toString(), + dimensionType.getRegisteredName())))); - final Registry reg = cs.registryAccess().registryOrThrow(Registries.DIMENSION_TYPE); - double worldTickTime = mean(times) * 1.0E-6D; - double worldTPS = TimeUtil.MILLISECONDS_PER_SECOND / Math.max(worldTickTime, dim.tickRateManager().millisecondsPerTick()); - cs.sendSuccess(() -> Component.translatable("commands.neoforge.tps.summary.named", dim.getDescription(), reg.getKey(dim.dimensionType()).toString(), TIME_FORMATTER.format(worldTickTime), TIME_FORMATTER.format(worldTPS)), false); + component = Component.translatable("commands.neoforge.tps.dimension", dimensionName, tpsComponent, tickTimeComponent); + } - return 1; + return component.withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("commands.neoforge.tps.tooltip", tickRateManager.tickrate())))); } - private static long mean(long[] values) { - long sum = 0L; - for (long v : values) - sum += v; - return sum / values.length; + private static int calculateTPSColor(TickRateManager tickRateManager, double tps) { + // Improved color blending code thanks to sciwhiz12 + float maxTPS = TimeUtil.MILLISECONDS_PER_SECOND / tickRateManager.millisecondsPerTick(); + // 0 degrees (0F) is red, 120 degrees (0.33F) is green + return Mth.hsvToRgb((float) (Mth.inverseLerp(tps, 0, maxTPS) * 0.33F), 1F, 1F); } } diff --git a/src/main/resources/assets/neoforge/lang/en_us.json b/src/main/resources/assets/neoforge/lang/en_us.json index 46e4288763..5e0e4c29db 100644 --- a/src/main/resources/assets/neoforge/lang/en_us.json +++ b/src/main/resources/assets/neoforge/lang/en_us.json @@ -80,11 +80,11 @@ "commands.neoforge.setdim.invalid.dim": "The dimension ID specified (%1$s) is not valid.", "commands.neoforge.setdim.invalid.nochange": "The entity selected (%1$s) is already in the dimension specified (%2$s).", "commands.neoforge.setdim.deprecated": "This command is deprecated for removal in 1.17, use %s instead.", - "commands.neoforge.tps.invalid": "Invalid dimension %1$s Possible values: %2$s", - "commands.neoforge.tps.summary.all": "Overall: Mean tick time: %1$s ms. Mean TPS: %2$s", + "commands.neoforge.tps.overall": "Overall: %s TPS (%s ms/tick)", + "commands.neoforge.tps.tooltip": "Mean TPS; higher is better. Target TPS: %s", + "commands.neoforge.tps.dimension": "%s: %s TPS (%s ms/tick)", + "commands.neoforge.tps.dimension.tooltip": "%s (Dimension Type: %s)", "commands.neoforge.mods.list": "Mod List: %1$s", - "commands.neoforge.tps.summary.basic": "Dim %1$s: Mean tick time: %2$s ms. Mean TPS: %3$s", - "commands.neoforge.tps.summary.named": "Dim %1$s (%2$s): Mean tick time: %3$s ms. Mean TPS: %4$s", "commands.neoforge.tracking.entity.enabled": "Entity tracking enabled for %d seconds.", "commands.neoforge.tracking.entity.reset": "Entity timings data has been cleared!", "commands.neoforge.tracking.invalid": "Invalid tracking data.",