From 09291b00e4a2b44264f390c58556b1b4dc561a9a Mon Sep 17 00:00:00 2001 From: Samsuik Date: Fri, 21 Feb 2025 01:08:11 +0000 Subject: [PATCH] Add sakura debug command --- .../sakura/command/BaseSubCommand.java | 7 +- .../samsuik/sakura/command/SakuraCommand.java | 13 ++-- .../sakura/command/SakuraCommands.java | 17 +++-- .../command/subcommands/ConfigCommand.java | 5 +- .../command/subcommands/DebugCommand.java | 74 +++++++++++++++++++ .../command/subcommands/FPSCommand.java | 5 +- .../command/subcommands/VisualCommand.java | 5 +- .../sakura/listener/LevelTickScheduler.java | 37 +++++++++- .../sakura/redstone/RedstoneNetwork.java | 9 ++- .../sakura/redstone/RedstoneWireCache.java | 4 + 10 files changed, 146 insertions(+), 30 deletions(-) create mode 100644 sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/DebugCommand.java diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java b/sakura-server/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java index 07e8007..0fb9c8a 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java @@ -2,16 +2,14 @@ package me.samsuik.sakura.command; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Function; -@DefaultQualifier(NonNull.class) +@NullMarked public abstract class BaseSubCommand extends Command { public BaseSubCommand(String name) { super(name); @@ -34,7 +32,6 @@ public abstract class BaseSubCommand extends Command { } @Override - @NotNull public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { List completions = new ArrayList<>(0); diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommand.java b/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommand.java index f46883c..a30d5d3 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommand.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommand.java @@ -7,9 +7,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.minecraft.server.MinecraftServer; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; import java.util.ArrayList; import java.util.Arrays; @@ -17,7 +15,7 @@ import java.util.Collections; import java.util.List; import java.util.stream.Stream; -@DefaultQualifier(NonNull.class) +@NullMarked public final class SakuraCommand extends Command { private static final Component HEADER_MESSAGE = MiniMessage.miniMessage().deserialize(""" . @@ -37,7 +35,7 @@ public final class SakuraCommand extends Command { @Override public boolean execute(CommandSender sender, String commandLabel, String[] args) { if (args.length > 0) { - List commands = new ArrayList<>(SakuraCommands.COMMANDS.values()); + List commands = new ArrayList<>(SakuraCommands.SUB_COMMANDS); // This part is copied from the VersionCommand SubCommand in paper Command internalVersion = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); @@ -59,7 +57,7 @@ public final class SakuraCommand extends Command { private void sendHelpMessage(CommandSender sender) { sender.sendMessage(HEADER_MESSAGE); - Stream uniqueCommands = SakuraCommands.COMMANDS.values() + Stream uniqueCommands = SakuraCommands.SUB_COMMANDS .stream() .filter(command -> command != this); @@ -70,14 +68,13 @@ public final class SakuraCommand extends Command { sender.sendMessage(Component.text("'", NamedTextColor.DARK_PURPLE)); } - @NotNull @Override public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { if (!this.testPermissionSilent(sender)) { return Collections.emptyList(); } - return SakuraCommands.COMMANDS.values().stream() + return SakuraCommands.SUB_COMMANDS.stream() .filter(command -> command != this) .map(Command::getName) .filter(name -> args.length <= 1 || name.startsWith(args[args.length - 1])) diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java b/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java index ee42cca..1485a02 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java @@ -1,18 +1,21 @@ package me.samsuik.sakura.command; -import me.samsuik.sakura.command.subcommands.ConfigCommand; -import me.samsuik.sakura.command.subcommands.TPSCommand; -import me.samsuik.sakura.command.subcommands.FPSCommand; -import me.samsuik.sakura.command.subcommands.VisualCommand; +import me.samsuik.sakura.command.subcommands.*; import me.samsuik.sakura.player.visibility.VisibilityTypes; import net.minecraft.server.MinecraftServer; import org.bukkit.command.Command; +import org.jspecify.annotations.NullMarked; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +@NullMarked public final class SakuraCommands { static final Map COMMANDS = new HashMap<>(); + static final Set SUB_COMMANDS = new HashSet<>(); + static { COMMANDS.put("sakura", new SakuraCommand("sakura")); COMMANDS.put("config", new ConfigCommand("config")); @@ -20,11 +23,13 @@ public final class SakuraCommands { COMMANDS.put("fps", new FPSCommand("fps")); COMMANDS.put("tntvisibility", new VisualCommand(VisibilityTypes.TNT, "tnttoggle")); COMMANDS.put("sandvisibility", new VisualCommand(VisibilityTypes.SAND, "sandtoggle")); + SUB_COMMANDS.addAll(COMMANDS.values()); + SUB_COMMANDS.add(new DebugCommand("debug")); } public static void registerCommands(MinecraftServer server) { - COMMANDS.forEach((s, command) -> { - server.server.getCommandMap().register(s, "sakura", command); + COMMANDS.forEach((name, command) -> { + server.server.getCommandMap().register(name, "sakura", command); }); } } diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java index c41f188..2c221c7 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java @@ -5,14 +5,13 @@ import net.minecraft.server.MinecraftServer; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.CraftServer; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; +import org.jspecify.annotations.NullMarked; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.format.NamedTextColor.GREEN; import static net.kyori.adventure.text.format.NamedTextColor.RED; -@DefaultQualifier(NonNull.class) +@NullMarked public final class ConfigCommand extends BaseSubCommand { public ConfigCommand(String name) { super(name); diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/DebugCommand.java b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/DebugCommand.java new file mode 100644 index 0000000..8bfb75c --- /dev/null +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/DebugCommand.java @@ -0,0 +1,74 @@ +package me.samsuik.sakura.command.subcommands; + +import me.samsuik.sakura.command.BaseSubCommand; +import me.samsuik.sakura.redstone.RedstoneNetwork; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.Level; +import org.bukkit.DyeColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.util.CraftLocation; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +@NullMarked +public final class DebugCommand extends BaseSubCommand { + public DebugCommand(String name) { + super(name); + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (!(sender instanceof Player player) || args.length == 0) { + return; + } + + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + if (args[0].equalsIgnoreCase("redstone-cache")) { + this.showCachedWires(player, nmsPlayer.level()); + } + } + + @Override + public void tabComplete(List list, String[] args) throws IllegalArgumentException { + list.add("redstone-cache"); + } + + private void showCachedWires(Player player, Level level) { + Set locations = new HashSet<>(); + for (RedstoneNetwork network : level.redstoneWireCache.getNetworkCache().values()) { + byte randomColour = (byte) ThreadLocalRandom.current().nextInt(16); + DyeColor dyeColour = DyeColor.getByWoolData(randomColour); + Material material = Material.matchMaterial(dyeColour.name() + "_WOOL"); + + if (!network.isRegistered()) { + continue; + } + + for (BlockPos pos : network.getWirePositions()) { + Location location = CraftLocation.toBukkit(pos, level); + if (player.getLocation().distance(location) >= 64.0) { + continue; + } + player.sendBlockChange(location, material.createBlockData()); + locations.add(location); + } + } + + player.sendRichMessage("Displaying %dx cached redstone wires".formatted(locations.size())); + + level.levelTickScheduler.delayedTask(() -> { + for (Location loc : locations) { + player.sendBlockChange(loc, loc.getBlock().getBlockData()); + } + }, 1200); + } +} diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java index a85f84e..b31c7e5 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/FPSCommand.java @@ -4,10 +4,9 @@ import me.samsuik.sakura.command.BaseSubCommand; import me.samsuik.sakura.player.visibility.VisibilityGui; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; +import org.jspecify.annotations.NullMarked; -@DefaultQualifier(NonNull.class) +@NullMarked public final class FPSCommand extends BaseSubCommand { private final VisibilityGui visibilityGui = new VisibilityGui(); diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java index cd9ebbb..ee0d5c6 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/VisualCommand.java @@ -8,12 +8,11 @@ import me.samsuik.sakura.player.visibility.VisibilityType; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; +import org.jspecify.annotations.NullMarked; import java.util.Arrays; -@DefaultQualifier(NonNull.class) +@NullMarked public final class VisualCommand extends BaseSubCommand { private final VisibilityType type; diff --git a/sakura-server/src/main/java/me/samsuik/sakura/listener/LevelTickScheduler.java b/sakura-server/src/main/java/me/samsuik/sakura/listener/LevelTickScheduler.java index 22c9e16..e827943 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/listener/LevelTickScheduler.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/listener/LevelTickScheduler.java @@ -7,13 +7,30 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.minecraft.world.level.Level; import org.jspecify.annotations.NullMarked; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; @NullMarked public final class LevelTickScheduler { private final Int2ObjectMap> tickTasks = new Int2ObjectLinkedOpenHashMap<>(); private final Object2IntMap taskIntervals = new Object2IntOpenHashMap<>(); + private final Deque removeLater = new ArrayDeque<>(); + + public void delayedTask(Runnable runnable, int delay) { + this.registerNewTask(new TickTask() { + private int cycles = 0; + + @Override + public void run(long tick) { + if (this.cycles++ >= delay) { + runnable.run(); + LevelTickScheduler.this.removeLater.add(this); + } + } + }, 0); + } public void registerNewTask(Runnable runnable, int interval) { this.registerNewTask(tick -> runnable.run(), interval); @@ -23,7 +40,24 @@ public final class LevelTickScheduler { int safeInterval = Math.max(interval + 1, 1); this.tickTasks.computeIfAbsent(safeInterval, i -> new ArrayList<>()) .add(task); - this.taskIntervals.put(task, Math.max(safeInterval + 1, 1)); + this.taskIntervals.put(task, safeInterval); + } + + private void removeTasks() { + TickTask tickTask; + while ((tickTask = this.removeLater.poll()) != null) { + this.removeTask(tickTask); + } + } + + private void removeTask(TickTask task) { + int interval = this.taskIntervals.removeInt(task); + if (interval > 0) { + this.tickTasks.computeIfPresent(interval, (i, tasks) -> { + tasks.remove(task); + return tasks.isEmpty() ? null : tasks; + }); + } } private void runTasks(List tasks, long gameTime) { @@ -39,6 +73,7 @@ public final class LevelTickScheduler { this.runTasks(this.tickTasks.get(interval), gameTime); } } + this.removeTasks(); } public interface TickTask { diff --git a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java index d9af4b3..f5f3875 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java @@ -1,5 +1,6 @@ package me.samsuik.sakura.redstone; +import io.papermc.paper.configuration.WorldConfiguration; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.objects.*; import me.samsuik.sakura.utils.TickExpiry; @@ -41,11 +42,17 @@ public final class RedstoneNetwork { return newBlock.getBlock() != oldBlock.getBlock(); } + public List getWirePositions() { + return this.wireUpdates.stream() + .map(RedstoneWireUpdate::getPosition) + .toList(); + } + public TickExpiry getExpiry() { return this.expiry; } - private boolean isRegistered() { + public boolean isRegistered() { return !this.listeners.isEmpty(); } diff --git a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java index f0ea3f8..e0fab83 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java @@ -28,6 +28,10 @@ public final class RedstoneWireCache { this.level = level; } + public Map getNetworkCache() { + return this.networkCache; + } + public boolean isWireUpdating(BlockPos pos) { return this.updatingNetwork != null && this.updatingNetwork.hasWire(pos); }