fix(gui): rebuild the download list in one pass when filtering or switching category - #670
Merged
got3nks merged 1 commit intoJul 28, 2026
Conversation
…tching category Typing in the transfer-window text filter froze the GUI for seconds on a large queue -- ~10 s per keystroke with 10k downloads, with keystrokes stacking up (issue amule-project#669). SetFilterText() re-evaluated every model item and called ShowFile(file, visible) for each one. Every row that had to disappear went through RemoveItemData(), which does a full RebuildRowIndex() plus a RefreshFromRow() -- O(n) per removal, so O(n^2) for the pass. The shared-files filter is instant because it does the opposite: it clears the model and re-appends the passing items, finishing with a single FinishBulkLoad(). Give the download list the same shape. RebuildVisibleList() clears, appends every item passing the current category + text filter, and finishes with one FinishBulkLoad() (one SetItemCount, one index rebuild, one sort) -- O(n log n) and a single repaint. ShowFileList(), the filter and ChangeCategory() all route through it, so the identical O(n^2) in the category switch is fixed too and the three paths no longer duplicate the visibility loop. ShowFilesCount(diff) now delegates to SetFilesCount(count) so the rebuild can set the count absolutely without duplicating the label update. The two lists had also grown a filter each: identical m_filterText members, identical SetFilterText() bodies and byte-identical PassesTextFilter() implementations differing only in the parameter type. That now lives once in CMuleVirtualListCtrl -- m_filterText, SetFilterText() and a string-based MatchesFilter(), plus a RebuildFilteredView() hook each list overrides with its own rebuild. The base stays agnostic about what its rows represent. A clear-and-re-append rebuild renumbers every row, and the virtual control tracks selection and focus by row index, so both would be lost -- the incremental path kept them for free by leaving surviving rows untouched. CMuleVirtualListCtrl grows SaveSelection()/RestoreSelection() for this; SortList() already did the same save-and-reapply inline and now shares them, as does CSharedFilesCtrl::ShowFileList(), which fixes the pre-existing loss of selection when editing the shared-files filter.
got3nks
force-pushed
the
fix/download-filter-bulk-rebuild
branch
from
July 28, 2026 13:05
935d15d to
5fd29bb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #669.
Typing in the transfer-window text filter froze the GUI for seconds on a large queue — @ghysler reports ~10 s per keystroke with 10k downloads, with keystrokes stacking up. The shared-files filter is instant at the same scale.
Root cause
SetFilterText()re-evaluated every model item and calledShowFile(file, visible)for each. Every row that had to disappear went throughRemoveItemData(), which does a fullRebuildRowIndex()plus aRefreshFromRow()— O(n) per removal, so O(n²) for one keystroke. The shared-files filter is fast because it does the opposite: clears the model, re-appends the passing items, and finishes with a singleFinishBulkLoad().ChangeCategory()had the identical shape, so switching category on a large queue was equally slow. That wasn't reported, but it's the same bug and is fixed here.Fix
RebuildVisibleList()clears, appends every item passing the current category + text filter, and finishes with oneFinishBulkLoad()(oneSetItemCount, one index rebuild, one sort) — O(n log n) and a single repaint.ShowFileList(), the filter andChangeCategory()all route through it, so the three paths no longer duplicate the visibility loop.ShowFilesCount(diff)delegates to a newSetFilesCount(count)so the rebuild can set the count absolutely without duplicating the label update.De-duplicating the filter
The two lists had grown a filter each: identical
m_filterTextmembers, identicalSetFilterText()bodies, and byte-identicalPassesTextFilter()implementations differing only in the parameter type (const CPartFile*vsconst CKnownFile*).That now lives once in
CMuleVirtualListCtrl—m_filterText,SetFilterText()and a string-basedMatchesFilter(), plus aRebuildFilteredView()hook each list overrides with its own rebuild.MatchesFilter()takes a name rather than a file so the generic control keeps knowing nothing about what its rows represent, and each subclass is left owning only what is genuinely specific: which model it walks and what it matches on. (CSearchListCtrl's filter is a regex on the non-virtual base and is untouched.)Selection preservation
A clear-and-re-append rebuild renumbers every row, and the virtual control tracks selection and focus by row index — so both would be dropped, where the incremental path kept them for free by leaving surviving rows untouched.
CMuleVirtualListCtrlgrowsSaveSelection()/RestoreSelection()for this.SortList()already did the same save-and-reapply inline and now shares them, and so doesCSharedFilesCtrl::ShowFileList()— which fixes a pre-existing bug of its own: editing the shared-files filter dropped the selection.Testing
Built
amuleandamuleguiclean. The algorithmic change is the substance here; a 10k-download reproduction wasn't available locally, so a real-world check on the reporter's setup is welcome before this is relied on.