Keep the window responsive while the channel tree loads

Connecting to a server with ~560 channels froze the UI for over two
seconds: every model event queued its own full tree rebuild, and each
rebuild expanded row by row with Swing's variable-height layout cache,
which measures every row on each expansion.

Coalesce model-change bursts into one rebuild and switch the tree to the
large-model layout (it already has a fixed row height), which only
measures rows on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 23:21:52 +00:00
parent 3187608579
commit c743efc3b6
2 changed files with 9 additions and 0 deletions

View File

@@ -8,6 +8,7 @@ import com.ts3client.net.TeamspeakConnection;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Marshals {@link ConnectionListener} callbacks onto the EDT and fans them out to * Marshals {@link ConnectionListener} callbacks onto the EDT and fans them out to
@@ -32,6 +33,8 @@ final class ServerTabConnectionEvents implements ConnectionListener {
private final ServerTab tab; private final ServerTab tab;
private final Listener listener; private final Listener listener;
private final AtomicBoolean modelChangePending = new AtomicBoolean();
private TeamspeakConnection conn; private TeamspeakConnection conn;
private ServerTreePanel treePanel; private ServerTreePanel treePanel;
private ChatPanel chatPanel; private ChatPanel chatPanel;
@@ -101,7 +104,10 @@ final class ServerTabConnectionEvents implements ConnectionListener {
@Override @Override
public void onModelChanged() { public void onModelChanged() {
// Events arrive in bursts (hundreds while connecting); one rebuild covers all of them.
if (!modelChangePending.compareAndSet(false, true)) return;
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
modelChangePending.set(false);
treePanel.rebuild(); treePanel.rebuild();
treeActions.renderInfo(); treeActions.renderInfo();
treeActions.refreshGroupsDialog(); treeActions.refreshGroupsDialog();

View File

@@ -170,6 +170,9 @@ public final class ServerTreePanel extends JScrollPane {
ui.setRightChildIndent(10); ui.setRightChildIndent(10);
} }
tree.setRowHeight(UIScale.scale(20)); tree.setRowHeight(UIScale.scale(20));
// Lays out only the rows on screen. The default cache measures every row as it is
// expanded, which freezes the window for seconds on servers with hundreds of channels.
tree.setLargeModel(true);
tree.setBackground(Theme.treeBg()); tree.setBackground(Theme.treeBg());
tree.setFont(Theme.uiFont()); tree.setFont(Theme.uiFont());
ServerTreeCellRenderer renderer = new ServerTreeCellRenderer(id -> id == selfClientId, contacts, settings); ServerTreeCellRenderer renderer = new ServerTreeCellRenderer(id -> id == selfClientId, contacts, settings);