Skip to content

Commit

Permalink
Add ctick command
Browse files Browse the repository at this point in the history
  • Loading branch information
xpple committed Feb 4, 2025
1 parent ccf136d commit 0518b86
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
import net.earthcomputer.clientcommands.event.ClientConnectionEvents;
import net.earthcomputer.clientcommands.features.CommandExecutionCustomPayload;
import net.earthcomputer.clientcommands.features.FishingCracker;
import net.earthcomputer.clientcommands.features.ServerBrandManager;
import net.earthcomputer.clientcommands.util.MappingsHelper;
import net.earthcomputer.clientcommands.features.PlayerRandCracker;
import net.earthcomputer.clientcommands.features.Relogger;
import net.earthcomputer.clientcommands.features.ServerBrandManager;
import net.earthcomputer.clientcommands.util.MappingsHelper;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
Expand Down Expand Up @@ -74,6 +74,7 @@ public void onInitializeClient() {

// Events
ClientCommandRegistrationCallback.EVENT.register(ClientCommands::registerCommands);
CTickCommand.registerEvents();
FishingCracker.registerEvents();
PlayerRandCracker.registerEvents();
ServerBrandManager.registerEvents();
Expand Down Expand Up @@ -139,6 +140,7 @@ public static void registerCommands(CommandDispatcher<FabricClientCommandSource>
CStopSoundCommand.register(dispatcher);
CTeleportCommand.register(dispatcher);
CTellRawCommand.register(dispatcher, context);
CTickCommand.register(dispatcher);
CTimeCommand.register(dispatcher);
CTitleCommand.register(dispatcher, context);
FindBlockCommand.register(dispatcher, context);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package net.earthcomputer.clientcommands.command;

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.earthcomputer.clientcommands.event.MoreClientEvents;
import net.earthcomputer.clientcommands.task.SimpleTask;
import net.earthcomputer.clientcommands.task.TaskManager;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSetTimePacket;
import net.minecraft.util.TimeUtil;

import java.text.DecimalFormat;

import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*;

public class CTickCommand {

private static final DecimalFormat DEC_FMT = new DecimalFormat("0.00");

private static final String TASK_NAME = "ctick";

private static TickMeasuringTask currentMeasurer = null;

public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(literal("ctick")
.then(literal("client")
.then(literal("tps")
.executes(ctx -> getTpsClient(ctx.getSource())))
.then(literal("mspt")
.executes(ctx -> getMsptClient(ctx.getSource()))))
.then(literal("server")
.then(literal("tps")
.executes(ctx -> getTpsServer(ctx.getSource())))
.then(literal("mspt")
.executes(ctx -> getMsptServer(ctx.getSource())))));
}

private static int getTpsClient(FabricClientCommandSource source) throws CommandSyntaxException {
stopPreviousTask();

TickMeasuringTask measurer = new TickMeasuringTask(true, false);
TaskManager.addTask(TASK_NAME, measurer);
currentMeasurer = measurer;

float tps = source.getWorld().tickRateManager().tickrate();
source.sendFeedback(Component.translatable("commands.ctick.client.tps.expectedTps", tps));
return (int) tps;
}

private static int getMsptClient(FabricClientCommandSource source) throws CommandSyntaxException {
stopPreviousTask();

TickMeasuringTask measurer = new TickMeasuringTask(false, false);
TaskManager.addTask(TASK_NAME, measurer);
currentMeasurer = measurer;

float mspt = TimeUtil.MILLISECONDS_PER_SECOND / source.getWorld().tickRateManager().tickrate();
source.sendFeedback(Component.translatable("commands.ctick.client.mspt.expectedMspt", mspt));
return (int) mspt;
}

private static int getTpsServer(FabricClientCommandSource source) throws CommandSyntaxException {
stopPreviousTask();

boolean isIntegratedServer = source.getClient().hasSingleplayerServer();
TickMeasuringTask measurer = new TickMeasuringTask(true, !isIntegratedServer);
TaskManager.addTask(TASK_NAME, measurer);
currentMeasurer = measurer;

float tps = source.getWorld().tickRateManager().tickrate();
source.sendFeedback(Component.translatable("commands.ctick.server.tps.expectedTps", tps));
return (int) tps;
}

private static int getMsptServer(FabricClientCommandSource source) throws CommandSyntaxException {
stopPreviousTask();

boolean isIntegratedServer = source.getClient().hasSingleplayerServer();
TickMeasuringTask measurer = new TickMeasuringTask(false, !isIntegratedServer);
TaskManager.addTask(TASK_NAME, measurer);
currentMeasurer = measurer;

float mspt = TimeUtil.MILLISECONDS_PER_SECOND / source.getWorld().tickRateManager().tickrate();
source.sendFeedback(Component.translatable("commands.ctick.server.mspt.expectedMspt", mspt));
return (int) mspt;
}

private static void stopPreviousTask() {
if (currentMeasurer != null) {
currentMeasurer._break();
TaskManager.removeTask(TASK_NAME);
currentMeasurer = null;
}
}

// see https://github.com/Earthcomputer/clientcommands/blob/c1e37665739f0e1d6aeb826b9d4e45b7adb5d876/src/main/java/net/earthcomputer/clientcommands/command/CommandTick.java#L175-L255
private static class TickMeasuringTask extends SimpleTask {

private static final int PERIOD = 100;

private static final Minecraft minecraft = Minecraft.getInstance();

private int tickCount = 0;
private long totalTickTime = 0;
private long startTickTime;
private boolean hadFirstTick = false;
private long firstTickStart;
private long lastTickStart;

private final boolean tps;
private final boolean forceInaccurate;

public TickMeasuringTask(boolean tps, boolean forceInaccurate) {
this.tps = tps;
this.forceInaccurate = forceInaccurate;
}

public void incrTickCount(int count) {
if (!hadFirstTick) {
firstTickStart = System.nanoTime();
hadFirstTick = true;
} else {
tickCount += count;
}
}

public void startTick() {
startTickTime = System.nanoTime();
if (!hadFirstTick) {
firstTickStart = startTickTime;
hadFirstTick = true;
}
}

public void endTick() {
if (hadFirstTick) {
totalTickTime += System.nanoTime() - startTickTime;
tickCount++;
}
}

@Override
protected void onTick() {
if (tickCount >= PERIOD) {
lastTickStart = System.nanoTime();
_break();
}
}

@Override
public void initialize() {
minecraft.player.displayClientMessage(Component.translatable("commands.ctick.measuring"), false);
}

@Override
public void onCompleted() {
if (tps) {
long totalTime = lastTickStart - firstTickStart;
double tps = 1_000_000_000D * tickCount / totalTime;
minecraft.player.displayClientMessage(Component.translatable("commands.ctick.tps", totalTime == 0 ? Component.translatable("commands.ctick.tps.immeasurable") : DEC_FMT.format(tps)), false);
} else if (forceInaccurate) {
long totalTime = lastTickStart - firstTickStart;
double mspt = totalTime / (1_000_000D * tickCount);
minecraft.player.displayClientMessage(Component.translatable("commands.ctick.mspt", DEC_FMT.format(mspt)), false);
minecraft.player.displayClientMessage(Component.translatable("commands.ctick.mspt.inaccurate"), false);
} else {
double mspt = totalTickTime / (1_000_000D * tickCount);
minecraft.player.displayClientMessage(Component.translatable("commands.ctick.mspt", DEC_FMT.format(mspt)), false);
}
}

@Override
public boolean condition() {
return tickCount <= 1200;
}
}

public static void registerEvents() {
ClientTickEvents.START_CLIENT_TICK.register(minecraft -> {
if (currentMeasurer != null && !currentMeasurer.isCompleted() && !currentMeasurer.forceInaccurate) {
currentMeasurer.startTick();
}
});

ClientTickEvents.END_CLIENT_TICK.register(minecraft -> {
if (currentMeasurer != null && !currentMeasurer.isCompleted() && !currentMeasurer.forceInaccurate) {
currentMeasurer.endTick();
}
});

MoreClientEvents.TIME_SYNC.register(new MoreClientEvents.TimeSync() {
private long lastTick = -1;

@Override
public void onTimeSync(ClientboundSetTimePacket packet) {
if (currentMeasurer != null && !currentMeasurer.isCompleted() && currentMeasurer.forceInaccurate) {
long tick = packet.gameTime();
if (lastTick != -1) {
int deltaTick = (int) (tick - lastTick);
currentMeasurer.incrTickCount(deltaTick);
}
lastTick = tick;
}
}
});
}
}
10 changes: 10 additions & 0 deletions src/main/resources/assets/clientcommands/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,16 @@
"commands.ctask.stop.noMatch": "No matching tasks",
"commands.ctask.stop.success": "Stopped %s tasks",

"commands.ctick.client.mspt.expectedMspt": "The expected client MSPT is %s",
"commands.ctick.client.tps.expectedTps": "The expected client TPS is %s",
"commands.ctick.measuring": "Measuring...",
"commands.ctick.mspt": "Milliseconds per tick = %s",
"commands.ctick.mspt.inaccurate": "MSPT is inaccurate above 20TPS",
"commands.ctick.server.mspt.expectedMspt": "The expected server MSPT is %s",
"commands.ctick.server.tps.expectedTps": "The expected server TPS is %s",
"commands.ctick.tps": "Ticks per second = %s",
"commands.ctick.tps.immeasurable": "Immeasurable",

"commands.ctictactoe.name": "Tic-tac-toe",

"commands.ctime.reset.success": "The time now matches the server",
Expand Down

0 comments on commit 0518b86

Please sign in to comment.