From b0a5c6f5e500e9d7ac987b4ca09a131de3182fdc Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Wed, 9 Sep 2026 16:45:28 -0600 Subject: [PATCH] Improve GTK3 Performance --- src/fe-gtk/fe-gtk.h | 2 +- src/fe-gtk/maingui.c | 4 +-- src/fe-gtk/userlistgui.c | 67 ++++++++++++---------------------------- src/fe-gtk/xtext.c | 23 +++++++++----- 4 files changed, 39 insertions(+), 57 deletions(-) diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h index b6619b49..0e35bde3 100644 --- a/src/fe-gtk/fe-gtk.h +++ b/src/fe-gtk/fe-gtk.h @@ -114,7 +114,7 @@ typedef struct restore_gui /* information stored when this tab isn't front-most */ GtkListStore *user_model; /* for filling the GtkTreeView */ - GHashTable *user_row_refs; + GHashTable *user_row_iters; /* User * -> persistent GtkTreeIter * */ void *buffer; /* xtext_Buffer */ char *input_text; /* input text buffer (while not-front tab) */ char *topic_text; /* topic GtkEntry buffer */ diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 621f923f..1affeeae 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -5786,9 +5786,9 @@ void fe_session_callback (session *sess) { gtk_xtext_buffer_free (sess->res->buffer); + if (sess->res->user_row_iters) + g_hash_table_destroy (sess->res->user_row_iters); g_object_unref (G_OBJECT (sess->res->user_model)); - if (sess->res->user_row_refs) - g_hash_table_destroy (sess->res->user_row_refs); if (sess->res->banlist && sess->res->banlist->window) mg_close_gen (NULL, sess->res->banlist->window); diff --git a/src/fe-gtk/userlistgui.c b/src/fe-gtk/userlistgui.c index 79760811..8dbd1b83 100644 --- a/src/fe-gtk/userlistgui.c +++ b/src/fe-gtk/userlistgui.c @@ -218,75 +218,49 @@ scroll_to_iter (GtkTreeIter *iter, GtkTreeView *treeview, GtkTreeModel *model) static GHashTable * userlist_row_map_ensure (session *sess) { - if (!sess->res->user_row_refs) - sess->res->user_row_refs = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) gtk_tree_row_reference_free); + if (!sess->res->user_row_iters) + sess->res->user_row_iters = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) gtk_tree_iter_free); - return sess->res->user_row_refs; + return sess->res->user_row_iters; } static void userlist_row_map_remove (session *sess, struct User *user) { - if (!sess->res->user_row_refs) + if (!sess->res->user_row_iters) return; - g_hash_table_remove (sess->res->user_row_refs, user); + g_hash_table_remove (sess->res->user_row_iters, user); } static void userlist_row_map_set (session *sess, GtkTreeModel *model, struct User *user, GtkTreeIter *iter) { - GtkTreePath *path; - GtkTreeRowReference *ref; - - path = gtk_tree_model_get_path (model, iter); - if (!path) + /* The shared tree view can still show another session's model while a + * tab switch is pending. Never cache an iterator from that model. */ + if (model != GTK_TREE_MODEL (sess->res->user_model)) return; - ref = gtk_tree_row_reference_new (model, path); - gtk_tree_path_free (path); - if (!ref) - return; - - g_hash_table_replace (userlist_row_map_ensure (sess), user, ref); + /* GtkListStore guarantees persistent iterators until their row is + * removed, including across sorting. Unlike row references, these do + * not require every cached position to be updated on each insertion. */ + g_hash_table_replace (userlist_row_map_ensure (sess), user, gtk_tree_iter_copy (iter)); } static gboolean userlist_row_map_get_iter (session *sess, GtkTreeModel *model, struct User *user, GtkTreeIter *iter) { - GtkTreeRowReference *ref; - GtkTreePath *path; - struct User *row_user; + GtkTreeIter *cached; - if (!sess->res->user_row_refs) + if (model != GTK_TREE_MODEL (sess->res->user_model) || !sess->res->user_row_iters) return FALSE; - ref = g_hash_table_lookup (sess->res->user_row_refs, user); - if (!ref) + cached = g_hash_table_lookup (sess->res->user_row_iters, user); + if (!cached) return FALSE; - path = gtk_tree_row_reference_get_path (ref); - if (!path) - { - g_hash_table_remove (sess->res->user_row_refs, user); - return FALSE; - } - - if (!gtk_tree_model_get_iter (model, iter, path)) - { - gtk_tree_path_free (path); - g_hash_table_remove (sess->res->user_row_refs, user); - return FALSE; - } - gtk_tree_path_free (path); - - gtk_tree_model_get (model, iter, COL_USER, &row_user, -1); - if (row_user != user) - { - g_hash_table_remove (sess->res->user_row_refs, user); - return FALSE; - } - + /* Removal and clear invalidate the cache before deleting model rows. */ + *iter = *cached; return TRUE; } @@ -587,7 +561,6 @@ fe_userlist_rehash (session *sess, struct User *user) GTK_TREE_MODEL(sess->res->user_model), user, &sel); if (!iter) return; - userlist_row_map_set (sess, GTK_TREE_MODEL (sess->res->user_model), user, iter); if (prefs.hex_away_track && user->away) { @@ -698,8 +671,8 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel) void fe_userlist_clear (session *sess) { - if (sess->res->user_row_refs) - g_hash_table_remove_all (sess->res->user_row_refs); + if (sess->res->user_row_iters) + g_hash_table_remove_all (sess->res->user_row_iters); gtk_list_store_clear (sess->res->user_model); } diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index 296848ea..ae57391a 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -3130,6 +3130,7 @@ gtk_xtext_class_init (GtkXTextClass * class) typedef struct chunk_s { GSList *slp; + gboolean collect_metadata; int off1, len1, emph; offlen_t meta; } chunk_t; @@ -3142,12 +3143,19 @@ xtext_do_chunk(chunk_t *c) if (c->len1 == 0) return; + /* Copying, searching and saving only need the stripped text. */ + if (!c->collect_metadata) + { + c->len1 = 0; + return; + } + meta = g_new (offlen_t, 1); meta->off = c->off1; meta->len = c->len1; meta->emph = c->emph; meta->width = 0; - c->slp = g_slist_append (c->slp, meta); + c->slp = g_slist_prepend (c->slp, meta); c->len1 = 0; } @@ -3170,6 +3178,7 @@ gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf, new_str = outbuf; c.slp = NULL; + c.collect_metadata = slpp != NULL; c.off1 = 0; c.len1 = 0; c.emph = 0; @@ -3240,9 +3249,7 @@ bad_utf8: /* Normal ending sequence, and give up if bad utf8 */ *newlen = i; if (slpp) - *slpp = c.slp; - else - g_slist_free_full (c.slp, g_free); + *slpp = g_slist_reverse (c.slp); return new_str; } @@ -4341,7 +4348,7 @@ gtk_xtext_lines_taken (xtext_buffer *buf, textentry * ent) if (win_width >= ent->indent + ent->str_width) { - ent->sublines = g_slist_append (ent->sublines, GINT_TO_POINTER (ent->str_len)); + ent->sublines = g_slist_prepend (ent->sublines, GINT_TO_POINTER (ent->str_len)); ent->subline_count = 1; return ent->subline_count; } @@ -4352,13 +4359,15 @@ gtk_xtext_lines_taken (xtext_buffer *buf, textentry * ent) do { len = find_next_wrap (buf->xtext, ent, str, win_width, indent); - ent->sublines = g_slist_append (ent->sublines, GINT_TO_POINTER (str + len - ent->str)); + ent->sublines = g_slist_prepend (ent->sublines, GINT_TO_POINTER (str + len - ent->str)); + ent->subline_count++; indent = buf->indent; str += len; } while (str < ent->str + ent->str_len); - ent->subline_count = g_slist_length (ent->sublines); + /* Preserve display order without walking the growing list per wrap. */ + ent->sublines = g_slist_reverse (ent->sublines); return ent->subline_count; }