From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: William Blake Galbreath Date: Sun, 2 Feb 2020 04:00:40 -0600 Subject: [PATCH] Make the GUI graph fancier diff --git a/src/main/java/com/destroystokyo/paper/gui/GraphColor.java b/src/main/java/com/destroystokyo/paper/gui/GraphColor.java new file mode 100644 index 0000000000000000000000000000000000000000..a4e641fdcccd3efcd1a2865dc6dc28d50671b995 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/gui/GraphColor.java @@ -0,0 +1,44 @@ +package com.destroystokyo.paper.gui; + +import java.awt.Color; + +public class GraphColor { + private static final Color[] colorLine = new Color[101]; + private static final Color[] colorFill = new Color[101]; + + static { + for (int i = 0; i < 101; i++) { + Color color = createColor(i); + colorLine[i] = new Color(color.getRed() / 2, color.getGreen() / 2, color.getBlue() / 2, 255); + colorFill[i] = new Color(colorLine[i].getRed(), colorLine[i].getGreen(), colorLine[i].getBlue(), 125); + } + } + + public static Color getLineColor(int percent) { + return colorLine[percent]; + } + + public static Color getFillColor(int percent) { + return colorFill[percent]; + } + + private static Color createColor(int percent) { + if (percent <= 50) { + return new Color(0X00FF00); + } + + int value = 510 - (int) (Math.min(Math.max(0, ((percent - 50) / 50F)), 1) * 510); + + int red, green; + if (value < 255) { + red = 255; + green = (int) (Math.sqrt(value) * 16); + } else { + green = 255; + value = value - 255; + red = 255 - (value * value / 255); + } + + return new Color(red, green, 0); + } +} diff --git a/src/main/java/com/destroystokyo/paper/gui/GraphData.java b/src/main/java/com/destroystokyo/paper/gui/GraphData.java new file mode 100644 index 0000000000000000000000000000000000000000..186fc722965e403f76b1480e1c2381fc34e29049 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/gui/GraphData.java @@ -0,0 +1,47 @@ +package com.destroystokyo.paper.gui; + +import java.awt.Color; + +public class GraphData { + private long total; + private long free; + private long max; + private long usedMem; + private int usedPercent; + + public GraphData(long total, long free, long max) { + this.total = total; + this.free = free; + this.max = max; + this.usedMem = total - free; + this.usedPercent = usedMem == 0 ? 0 : (int) (usedMem * 100L / max); + } + + public long getTotal() { + return total; + } + + public long getFree() { + return free; + } + + public long getMax() { + return max; + } + + public long getUsedMem() { + return usedMem; + } + + public int getUsedPercent() { + return usedPercent; + } + + public Color getFillColor() { + return GraphColor.getFillColor(usedPercent); + } + + public Color getLineColor() { + return GraphColor.getLineColor(usedPercent); + } +} diff --git a/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java b/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..537bc6213545e8ff1b7b51bc4b27fd5b2a740883 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java @@ -0,0 +1,41 @@ +package com.destroystokyo.paper.gui; + +import net.minecraft.server.MinecraftServer; + +import javax.swing.JPanel; +import javax.swing.Timer; +import java.awt.BorderLayout; +import java.awt.Dimension; + +public class GuiStatsComponent extends JPanel { + private final Timer timer; + private final RAMGraph ramGraph; + + public GuiStatsComponent(MinecraftServer server) { + super(new BorderLayout()); + + setOpaque(false); + + ramGraph = new RAMGraph(); + RAMDetails ramDetails = new RAMDetails(server); + + add(ramGraph, "North"); + add(ramDetails, "Center"); + + timer = new Timer(500, (event) -> { + ramGraph.update(); + ramDetails.update(); + }); + timer.start(); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(350, 200); + } + + public void close() { + timer.stop(); + ramGraph.stop(); + } +} diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java new file mode 100644 index 0000000000000000000000000000000000000000..f93373d28d741e1f8a53e07b4e328ce9c4e1657f --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java @@ -0,0 +1,74 @@ +package com.destroystokyo.paper.gui; + +import net.minecraft.Util; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.TimeUtil; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListSelectionModel; +import javax.swing.JList; +import javax.swing.border.EmptyBorder; +import java.awt.Dimension; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import java.util.Vector; + +public class RAMDetails extends JList { + public static final DecimalFormat DECIMAL_FORMAT = Util.make(new DecimalFormat("########0.000"), (format) + -> format.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT))); + + private final MinecraftServer server; + + public RAMDetails(MinecraftServer server) { + this.server = server; + + setBorder(new EmptyBorder(0, 10, 0, 0)); + setFixedCellHeight(20); + setOpaque(false); + + DefaultListCellRenderer renderer = new DefaultListCellRenderer(); + renderer.setOpaque(false); + setCellRenderer(renderer); + + setSelectionModel(new DefaultListSelectionModel() { + @Override + public void setAnchorSelectionIndex(final int anchorIndex) { + } + + @Override + public void setLeadAnchorNotificationEnabled(final boolean flag) { + } + + @Override + public void setLeadSelectionIndex(final int leadIndex) { + } + + @Override + public void setSelectionInterval(final int index0, final int index1) { + } + }); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(350, 100); + } + + public void update() { + GraphData data = RAMGraph.DATA.peekLast(); + Vector vector = new Vector<>(); + vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)"); + vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb"); + vector.add("Avg tick: " + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double) TimeUtil.NANOSECONDS_PER_MILLISECOND) + " ms"); + setListData(vector); + } + + public double getAverage(long[] tickTimes) { + long total = 0L; + for (long value : tickTimes) { + total += value * 1000; + } + return ((double) total / (double) tickTimes.length) * 1.0E-6D; + } +} diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java b/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java new file mode 100644 index 0000000000000000000000000000000000000000..a844669c57290cbdf66245d91fc9d2fbf23ba947 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java @@ -0,0 +1,156 @@ +package com.destroystokyo.paper.gui; + +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.ToolTipManager; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.MouseInfo; +import java.awt.Point; +import java.awt.PointerInfo; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.concurrent.TimeUnit; + +public class RAMGraph extends JComponent { + public static final LinkedList DATA = new LinkedList() { + @Override + public boolean add(GraphData data) { + if (size() >= 348) { + remove(); + } + return super.add(data); + } + }; + + static { + GraphData empty = new GraphData(0, 0, 0); + for (int i = 0; i < 350; i++) { + DATA.add(empty); + } + } + + private final Timer timer; + private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); + + private int currentTick; + + public RAMGraph() { + ToolTipManager.sharedInstance().setInitialDelay(0); + + addMouseListener(new MouseAdapter() { + final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay(); + final int dismissDelayMinutes = (int) TimeUnit.MINUTES.toMillis(10); + + @Override + public void mouseEntered(MouseEvent me) { + ToolTipManager.sharedInstance().setDismissDelay(dismissDelayMinutes); + } + + @Override + public void mouseExited(MouseEvent me) { + ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout); + } + }); + + timer = new Timer(50, (event) -> repaint()); + timer.start(); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(350, 110); + } + + public void update() { + Runtime jvm = Runtime.getRuntime(); + DATA.add(new GraphData(jvm.totalMemory(), jvm.freeMemory(), jvm.maxMemory())); + + PointerInfo pointerInfo = null; + // I think I recall spotting a bug report where this throwed an exception once + // not sure it's of concern here + try { + pointerInfo = MouseInfo.getPointerInfo(); + } catch (NullPointerException | ArrayIndexOutOfBoundsException ignored) { + // https://bugs.openjdk.org/browse/JDK-6840067 + } + if (pointerInfo != null) { + Point point = pointerInfo.getLocation(); + if (point != null) { + Point loc = new Point(point); + SwingUtilities.convertPointFromScreen(loc, this); + if (this.contains(loc)) { + ToolTipManager.sharedInstance().mouseMoved( + new MouseEvent(this, -1, System.currentTimeMillis(), 0, loc.x, loc.y, + point.x, point.y, 0, false, 0)); + } + } + } + + currentTick++; + } + + @Override + public void paint(Graphics graphics) { + graphics.setColor(new Color(0xFFFFFFFF)); + graphics.fillRect(0, 0, 350, 100); + + graphics.setColor(new Color(0x888888)); + graphics.drawLine(1, 25, 348, 25); + graphics.drawLine(1, 50, 348, 50); + graphics.drawLine(1, 75, 348, 75); + + int i = 0; + for (GraphData data : DATA) { + i++; + if ((i + currentTick) % 120 == 0) { + graphics.setColor(new Color(0x888888)); + graphics.drawLine(i, 1, i, 99); + } + int used = data.getUsedPercent(); + if (used > 0) { + Color color = data.getLineColor(); + graphics.setColor(data.getFillColor()); + graphics.fillRect(i, 100 - used, 1, used); + graphics.setColor(color); + graphics.fillRect(i, 100 - used, 1, 1); + } + } + + graphics.setColor(new Color(0xFF000000)); + graphics.drawRect(0, 0, 348, 100); + + Point m = null; + try { + m = getMousePosition(); + } catch (NullPointerException ignored) { + // https://bugs.openjdk.org/browse/JDK-6840067 + } + if (m != null && m.x > 0 && m.x < 348 && m.y > 0 && m.y < 100) { + GraphData data = DATA.get(m.x); + int used = data.getUsedPercent(); + graphics.setColor(new Color(0x000000)); + graphics.drawLine(m.x, 1, m.x, 99); + graphics.drawOval(m.x - 2, 100 - used - 2, 5, 5); + graphics.setColor(data.getLineColor()); + graphics.fillOval(m.x - 2, 100 - used - 2, 5, 5); + setToolTipText(String.format("Used: %s mb (%s%%)
%s", + Math.round(data.getUsedMem() / 1024F / 1024F), + used, getTime(m.x))); + } + } + + public String getTime(int halfSeconds) { + int millis = (348 - halfSeconds) / 2 * 1000; + return TIME_FORMAT.format(new Date((System.currentTimeMillis() - millis))); + } + + public void stop() { + timer.stop(); + } +} diff --git a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java index 84a2c6c397604279ba821286f5c3c855e6041400..8b570b0c3967a22c085f390110cb29cbd9c8feff 100644 --- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java @@ -95,7 +95,7 @@ public class MinecraftServerGui extends JComponent { private JComponent buildInfoPanel() { JPanel jpanel = new JPanel(new BorderLayout()); - StatsComponent guistatscomponent = new StatsComponent(this.server); + com.destroystokyo.paper.gui.GuiStatsComponent guistatscomponent = new com.destroystokyo.paper.gui.GuiStatsComponent(this.server); // Paper - Make GUI graph fancier Collection collection = this.finalizers; // CraftBukkit - decompile error Objects.requireNonNull(guistatscomponent);