9
0
mirror of https://github.com/BX-Team/DivineMC.git synced 2025-12-22 16:29:23 +00:00

make parallel world ticking to disable or enable; update mspt command and purpur tps bar

This commit is contained in:
NONPLAYT
2025-03-08 17:25:09 +03:00
parent 9cfad68baf
commit 1614fe9dc6
19 changed files with 609 additions and 331 deletions

View File

@@ -10,7 +10,6 @@ import java.nio.file.Paths;
public class DivineBootstrap {
private static final Logger LOGGER = LoggerFactory.getLogger("bootstrap");
public static final boolean disableTickThreadHardThrow = Boolean.parseBoolean(System.getProperty("DivineMC.disableTickThreadHardThrow", "false"));
public static void boot(final OptionSet options) {
runPreBootTasks();

View File

@@ -152,16 +152,19 @@ public class DivineConfig {
return config.getStringList(key);
}
public static boolean enableParallelWorldTicking = true;
public static int parallelThreadCount = 4;
public static boolean logContainerCreationStacktraces = false;
public static boolean disableHardThrow = false;
private static void parallelWorldTicking() {
parallelThreadCount = getInt("settings.parallel-world-ticking.thread-count", parallelThreadCount);
logContainerCreationStacktraces = getBoolean("settings.parallel-world-ticking.log-container-creation-stacktraces", logContainerCreationStacktraces);
setComment("settings.parallel-world-ticking",
"Parallel World Ticking executes each worlds tick in a separate thread while ensuring that all worlds complete their tick before the next cycle begins.",
enableParallelWorldTicking = getBoolean("settings.parallel-world-ticking.enable", enableParallelWorldTicking,
"Enables Parallel World Ticking, which executes each worlds tick in a separate thread while ensuring that all worlds complete their tick before the next cycle begins.",
"",
"Read more info about this feature at https://bxteam.org/docs/divinemc/features/parallel-world-ticking");
parallelThreadCount = getInt("settings.parallel-world-ticking.thread-count", parallelThreadCount);
logContainerCreationStacktraces = getBoolean("settings.parallel-world-ticking.log-container-creation-stacktraces", logContainerCreationStacktraces);
disableHardThrow = getBoolean("settings.parallel-world-ticking.disable-hard-throw", disableHardThrow,
"Disables annoying 'not on main thread' throws. But, THIS IS NOT RECOMMENDED because you SHOULD FIX THE ISSUES THEMSELVES instead of RISKING DATA CORRUPTION! If you lose something, take the blame on yourself.");
}
public static boolean nativeAccelerationEnabled = true;

View File

@@ -10,6 +10,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.bxteam.divinemc.command.subcommands.MSPTCommand;
import org.bxteam.divinemc.command.subcommands.ReloadCommand;
import org.bxteam.divinemc.command.subcommands.VersionCommand;
import org.jetbrains.annotations.Nullable;
@@ -29,11 +30,13 @@ public final class DivineCommand extends Command {
private static final DivineSubCommand RELOAD_SUBCOMMAND = new ReloadCommand();
private static final DivineSubCommand VERSION_SUBCOMMAND = new VersionCommand();
private static final DivineSubCommand MSPT_SUBCOMMAND = new MSPTCommand();
private static final Map<String, DivineSubCommand> SUBCOMMANDS = Util.make(() -> {
final Map<Set<String>, DivineSubCommand> commands = new HashMap<>();
commands.put(Set.of(ReloadCommand.LITERAL_ARGUMENT), RELOAD_SUBCOMMAND);
commands.put(Set.of(VersionCommand.LITERAL_ARGUMENT), VERSION_SUBCOMMAND);
commands.put(Set.of(MSPTCommand.LITERAL_ARGUMENT), MSPT_SUBCOMMAND);
return commands.entrySet().stream()
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))

View File

@@ -0,0 +1,171 @@
package org.bxteam.divinemc.command.subcommands;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
import org.bxteam.divinemc.DivineConfig;
import org.bxteam.divinemc.command.DivineCommand;
import org.bxteam.divinemc.command.DivineSubCommandPermission;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.LongSummaryStatistics;
import java.util.stream.LongStream;
import static net.kyori.adventure.text.format.NamedTextColor.*;
@DefaultQualifier(NonNull.class)
public final class MSPTCommand extends DivineSubCommandPermission {
public static final String LITERAL_ARGUMENT = "mspt";
public static final String PERM = DivineCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
private static final DecimalFormat DF = new DecimalFormat("########0.0");
private static final Component SLASH = Component.text("/");
public MSPTCommand() {
super(PERM, PermissionDefault.TRUE);
}
@Override
public boolean execute(CommandSender sender, String subCommand, String[] args) {
if (!DivineConfig.enableParallelWorldTicking) {
sender.sendMessage(Component.text("Per-world MSPT tracking is only available when parallel world ticking is enabled.", RED));
sender.sendMessage(Component.text("Please enable it in divinemc.yml to use this command.", GRAY));
return true;
}
boolean compactMode = args.length > 0 && args[0].equalsIgnoreCase("compact");
MinecraftServer server = MinecraftServer.getServer();
if (compactMode) {
displayCompactStats(sender, server);
} else {
sender.sendMessage(Component.text("━━━━━━━━━━━━━ ", GOLD)
.append(Component.text("MSPT Statistics", YELLOW))
.append(Component.text(" ━━━━━━━━━━━━━", GOLD)));
displayServerMSPT(sender, server);
sender.sendMessage(Component.empty());
displayWorldMSPT(sender, server);
}
return true;
}
private void displayCompactStats(CommandSender sender, MinecraftServer server) {
List<Component> serverTimes = eval(server.tickTimes5s.getTimes());
sender.sendMessage(Component.text("Server: ", GOLD)
.append(joinComponents(serverTimes, SLASH)));
List<ServerLevel> worlds = new ArrayList<>();
server.getAllLevels().forEach(worlds::add);
for (int i = 0; i < worlds.size(); i++) {
ServerLevel level = worlds.get(i);
List<Component> worldTimes = eval(level.tickTimes5s.getTimes());
sender.sendMessage(Component.text(level.getWorld().getName() + ": ", GOLD)
.append(joinComponents(worldTimes, SLASH)));
if (i < worlds.size() - 1) {
sender.sendMessage(Component.empty());
}
}
}
private void displayServerMSPT(CommandSender sender, MinecraftServer server) {
sender.sendMessage(Component.text("Server tick times ", GOLD)
.append(Component.text("(avg/min/max)", YELLOW)));
sendTickLine(sender, " 5s: ", eval(server.tickTimes5s.getTimes()), GOLD, SLASH);
sendTickLine(sender, " 10s: ", eval(server.tickTimes10s.getTimes()), GOLD, SLASH);
sendTickLine(sender, " 60s: ", eval(server.tickTimes60s.getTimes()), GOLD, SLASH);
}
private void displayWorldMSPT(CommandSender sender, MinecraftServer server) {
sender.sendMessage(Component.text("World-specific tick times ", GOLD)
.append(Component.text("(avg/min/max)", YELLOW)));
List<ServerLevel> worlds = new ArrayList<>();
server.getAllLevels().forEach(worlds::add);
for (int i = 0; i < worlds.size(); i++) {
ServerLevel level = worlds.get(i);
List<Component> worldTimes = new ArrayList<>();
worldTimes.addAll(eval(level.tickTimes5s.getTimes()));
worldTimes.addAll(eval(level.tickTimes10s.getTimes()));
worldTimes.addAll(eval(level.tickTimes60s.getTimes()));
sender.sendMessage(Component.text("", YELLOW)
.append(Component.text(level.getWorld().getName(), GOLD)));
sendTickLine(sender, " 5s: ", worldTimes.subList(0, 3), GRAY, SLASH);
sendTickLine(sender, " 10s: ", worldTimes.subList(3, 6), GRAY, SLASH);
sendTickLine(sender, " 60s: ", worldTimes.subList(6, 9), GRAY, SLASH);
if (i < worlds.size() - 1) {
sender.sendMessage(Component.empty());
}
}
}
private static List<Component> eval(long[] times) {
LongSummaryStatistics stats = LongStream.of(times)
.filter(value -> value > 0L)
.summaryStatistics();
if (stats.getCount() == 0) {
return Arrays.asList(
Component.text("N/A", GRAY),
Component.text("N/A", GRAY),
Component.text("N/A", GRAY)
);
}
double avg = stats.getAverage() * 1.0E-6;
double min = stats.getMin() * 1.0E-6;
double max = stats.getMax() * 1.0E-6;
return Arrays.asList(getColoredValue(avg), getColoredValue(min), getColoredValue(max));
}
private static Component getColoredValue(double value) {
NamedTextColor color = value >= 50 ? RED
: value >= 40 ? YELLOW
: value >= 30 ? NamedTextColor.GOLD
: value >= 20 ? GREEN
: AQUA;
return Component.text(DF.format(value) + "ms", color);
}
private void sendTickLine(CommandSender sender, String label, List<Component> tickComponents, NamedTextColor labelColor, Component separator) {
sender.sendMessage(Component.text(label, labelColor)
.append(joinComponents(tickComponents, separator)));
}
private Component joinComponents(List<Component> components, Component separator) {
Component result = Component.empty();
for (int i = 0; i < components.size(); i++) {
result = result.append(components.get(i));
if (i < components.size() - 1) {
result = result.append(separator);
}
}
return result;
}
@Override
public List<String> tabComplete(CommandSender sender, String subCommand, String[] args) {
if (!DivineConfig.enableParallelWorldTicking) {
return Collections.emptyList();
}
if (args.length == 1) {
return Collections.singletonList("compact");
}
return Collections.emptyList();
}
}

View File

@@ -1,5 +1,6 @@
package org.bxteam.divinemc.command.subcommands;
import net.kyori.adventure.text.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import org.bukkit.command.Command;
@@ -9,14 +10,15 @@ import org.bukkit.permissions.PermissionDefault;
import org.bxteam.divinemc.command.DivineCommand;
import org.bxteam.divinemc.command.DivineSubCommandPermission;
import org.bxteam.divinemc.DivineConfig;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import java.io.File;
import java.io.IOException;
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;
import static net.kyori.adventure.text.format.NamedTextColor.*;
@DefaultQualifier(NonNull.class)
public final class ReloadCommand extends DivineSubCommandPermission {
public final static String LITERAL_ARGUMENT = "reload";
public static final String PERM = DivineCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
@@ -32,8 +34,8 @@ public final class ReloadCommand extends DivineSubCommandPermission {
}
private void doReload(final CommandSender sender) {
Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED));
Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED));
Command.broadcastCommandMessage(sender, Component.text("Please note that this command is not supported and may cause issues.", RED));
Command.broadcastCommandMessage(sender, Component.text("If you encounter any issues please use the /stop command to restart your server.", RED));
MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
@@ -47,12 +49,12 @@ public final class ReloadCommand extends DivineSubCommandPermission {
try {
level.divineConfig.init();
} catch (IOException e) {
MinecraftServer.LOGGER.error("Failed to reload DivineMC world config for level " + level.dimension().location(), e);
MinecraftServer.LOGGER.error("Failed to reload DivineMC world config for level {}", level.dimension().location(), e);
}
level.resetBreedingCooldowns();
}
server.server.reloadCount++;
Command.broadcastCommandMessage(sender, text("DivineMC config reload complete.", GREEN));
Command.broadcastCommandMessage(sender, Component.text("DivineMC config reload complete.", GREEN));
}
}