From 839293f84d1aa137f0dd9413ceb1564fe276604c Mon Sep 17 00:00:00 2001 From: jolavillette Date: Fri, 24 Jul 2026 20:40:13 +0200 Subject: [PATCH] gui(forums): coalesce full-forum reloads to stop sync-burst UI freezes handleEvent_main_thread() ran a full updateDisplay(true) (updateForum -> getForumPostsHierarchy -> setPosts full reset -> proxy re-sort/re-filter -> tree rebuild) for every incoming GXS event. A sync batch delivers many NEW_MESSAGE events back to back, so the forum was reloaded once per post, freezing the UI for seconds and resetting the model out from under the user's current selection (and racing the async read-status persistence, making a just-read post pop back to bold). Route those events through a single-shot QTimer (300 ms) that is restarted on each event, so a whole burst collapses into one reload once the events settle. An explicit reload (forum switch) cancels any pending deferred one. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../gui/gxsforums/GxsForumThreadWidget.cpp | 23 ++++++++++++++++++- .../src/gui/gxsforums/GxsForumThreadWidget.h | 10 ++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index f55bbb0c0..a7de911c9 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "util/qtthreadsutils.h" #include "util/misc.h" @@ -374,6 +375,12 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget ui->threadTreeWidget->enableColumnCustomize(true); #endif + // Single-shot timer used to coalesce the full-forum reloads requested by + // incoming GXS events (see scheduleForumReload()). + mDeferredReloadTimer = new QTimer(this); + mDeferredReloadTimer->setSingleShot(true); + connect(mDeferredReloadTimer, &QTimer::timeout, this, [this]() { updateDisplay(true); }); + mEventHandlerId = 0; // Needs to be asynced because this function is called by another thread! rsEvents->registerEventsHandler( @@ -402,7 +409,7 @@ void GxsForumThreadWidget::handleEvent_main_thread(std::shared_ptrmForumGroupId == mForumGroup.mMeta.mGroupId) - updateDisplay(true); + scheduleForumReload(); break; case RsForumEventCode::SUBSCRIBE_STATUS_CHANGED: @@ -424,6 +431,16 @@ void GxsForumThreadWidget::handleEvent_main_thread(std::shared_ptrstart(300); +} + void GxsForumThreadWidget::showForumInfo() { mThreadId.clear(); @@ -596,6 +613,10 @@ void GxsForumThreadWidget::updateDisplay(bool complete) } if(complete) // need to update the group data, reload the messages etc. { + // We are reloading now, so drop any reload still pending in the coalescing + // timer (e.g. queued by events for a forum we just switched away from). + mDeferredReloadTimer->stop(); + saveExpandedItems(mSavedExpandedMessages); if(groupId() != mThreadModel->currentGroupId()) diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h index 8e6b1c01b..130f70bd0 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h @@ -29,6 +29,7 @@ #include "util/FontSizeHandler.h" class QSortFilterProxyModel; +class QTimer; class QTreeWidgetItem; class RSTreeWidgetItemCompareRole; class GxsForumsFillThread; @@ -197,6 +198,13 @@ private: void handleEvent_main_thread(std::shared_ptr event); + // Coalesce the full-forum reloads triggered by incoming GXS events: a sync + // burst delivers many NEW_MESSAGE events in a row, and reloading the whole + // forum for each one froze the UI for seconds and reset the model out from + // under the user's selection. Restart a single-shot timer instead so a burst + // results in one reload once the events settle. + void scheduleForumReload(); + private: void setForumDescriptionLoading(); void clearForumDescription(); @@ -239,6 +247,8 @@ private: Ui::GxsForumThreadWidget *ui; RsEventsHandlerId_t mEventHandlerId; + + QTimer *mDeferredReloadTimer; }; #endif // GXSFORUMTHREADWIDGET_H