From 1dd096e77fedc3dd63fb8bd9947a061daf082d00 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 2 Feb 2026 07:08:01 -0600 Subject: [PATCH] perf: faster arrow key navigation in torrent list (#8315) (#8323) * perf: when selection changes, only update the rows whose selection changed previously we were updating all rows * perf: coalesce pending reloads in TorrentTableView * refactor: return a copy rather thabn a mutable index * refactor: move the assignment out of the if statement * make symmetricDifference an NSIndexSet method --- macosx/TorrentTableView.mm | 53 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/macosx/TorrentTableView.mm b/macosx/TorrentTableView.mm index b6e1b6bb0..020d7c6b2 100644 --- a/macosx/TorrentTableView.mm +++ b/macosx/TorrentTableView.mm @@ -28,6 +28,29 @@ static CGFloat const kErrorImageSize = 20.0; static NSTimeInterval const kToggleProgressSeconds = 0.175; +@interface NSIndexSet (Transmission) +- (NSIndexSet*)symmetricDifference:(NSIndexSet*)otherSet; +@end + +@implementation NSIndexSet (Transmission) + +- (NSIndexSet*)symmetricDifference:(NSIndexSet*)otherSet +{ + NSMutableIndexSet* result = [self mutableCopy]; + [result addIndexes:otherSet]; + + [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL*) { + if ([otherSet containsIndex:idx]) + { + [result removeIndex:idx]; + } + }]; + + return [result copy]; +} + +@end + @interface TorrentTableView () @property(nonatomic) IBOutlet Controller* fController; @@ -49,6 +72,8 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; @property(nonatomic) NSDictionary* fHoverEventDict; +@property(nonatomic) NSMutableIndexSet* fPendingSelectionReloadRows; + @end @implementation TorrentTableView @@ -430,8 +455,32 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175; - (void)outlineViewSelectionDidChange:(NSNotification*)notification { - self.fSelectedRowIndexes = self.selectedRowIndexes; - [self reloadVisibleRows]; + NSIndexSet* oldSelection = self.fSelectedRowIndexes ?: [NSIndexSet indexSet]; + NSIndexSet* newSelection = self.selectedRowIndexes; + self.fSelectedRowIndexes = newSelection; + + NSIndexSet* changedRows = [oldSelection symmetricDifference:newSelection]; + if (changedRows.count > 0) + { + if (!self.fPendingSelectionReloadRows) + { + self.fPendingSelectionReloadRows = [[NSMutableIndexSet alloc] init]; + [self performSelector:@selector(flushSelectionReload) withObject:nil afterDelay:0 inModes:@[ NSRunLoopCommonModes ]]; + } + + [self.fPendingSelectionReloadRows addIndexes:changedRows]; + } +} + +- (void)flushSelectionReload +{ + NSMutableIndexSet* rows = self.fPendingSelectionReloadRows; + self.fPendingSelectionReloadRows = nil; + + if (rows.count > 0) + { + [self reloadDataForRowIndexes:rows columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + } } - (void)outlineViewItemDidExpand:(NSNotification*)notification