From a5f5fe69407aed7422d4f2b48377e469efd56f56 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 13 Jan 2020 17:59:43 -0700 Subject: [PATCH] loottracker: Optimize panel rebuild Removing components in swing can be incredibly slow under serveral circumstances. This adds a special removeAll that can take less time in some cases --- .../plugins/loottracker/LootTrackerPanel.java | 3 +- .../loottracker/LootTrackerPlugin.java | 2 +- .../net/runelite/client/util/SwingUtil.java | 56 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index 22ce4561fa..37b4d8e9f9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -54,6 +54,7 @@ import net.runelite.client.ui.components.PluginErrorPanel; import net.runelite.client.util.ColorUtil; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.QuantityFormatter; +import net.runelite.client.util.SwingUtil; import net.runelite.http.api.loottracker.LootTrackerClient; class LootTrackerPanel extends PluginPanel @@ -488,7 +489,7 @@ class LootTrackerPanel extends PluginPanel */ private void rebuild() { - logsContainer.removeAll(); + SwingUtil.fastRemoveAll(logsContainer); boxes.clear(); if (groupLoot) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index adb5217a4e..0864776476 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -612,7 +612,7 @@ public class LootTrackerPlugin extends Plugin } config.setIgnoredItems(Text.toCSV(ignoredItemSet)); - panel.updateIgnoredRecords(); + // the config changed will update the panel } boolean isIgnored(String name) diff --git a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java index f4e12c9020..5eb7cef4af 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java @@ -26,11 +26,16 @@ package net.runelite.client.util; import java.awt.AWTException; import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.EventQueue; import java.awt.Font; import java.awt.Frame; import java.awt.Image; import java.awt.Insets; +import java.awt.SecondaryLoop; import java.awt.SystemTray; +import java.awt.Toolkit; import java.awt.TrayIcon; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -50,6 +55,7 @@ import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; @@ -293,4 +299,54 @@ public class SwingUtil { button.addItemListener(l -> button.setToolTipText(button.isSelected() ? on : off)); } + + /** + * Removes all of a component's children faster than calling removeAll() on it in many cases + */ + public static void fastRemoveAll(Container c) + { + // If we are not on the EDT this will deadlock, in addition to being totally unsafe + assert SwingUtilities.isEventDispatchThread(); + + // when a component is removed it has to be resized for some reason, but only if it's valid + // so we make sure to invalidate everything before removing it + c.invalidate(); + for (int i = 0; i < c.getComponentCount(); i++) + { + Component ic = c.getComponent(i); + + // removeAll and removeNotify are both recursive, so we have to recurse before them + if (ic instanceof Container) + { + fastRemoveAll((Container) ic); + } + + // each removeNotify needs to remove anything from the event queue that is for that widget + // this however requires taking a lock, and is moderately slow, so we just execute all of + // those events with a secondary event loop + pumpPendingEvents(); + + // call removeNotify early; this is most of the work in removeAll, and generates events that + // the next secondaryLoop will pickup + ic.removeNotify(); + } + + // Actually remove anything + c.removeAll(); + } + + /** + * Run any events currently in the event queue + */ + public static void pumpPendingEvents() + { + EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); + + if (eq.peekEvent() != null) + { + SecondaryLoop l = eq.createSecondaryLoop(); + SwingUtilities.invokeLater(l::exit); + l.enter(); + } + } }