diff --git a/TablePro/Models/Query/RowProvider.swift b/TablePro/Models/Query/RowProvider.swift index 8af31183..fc55807d 100644 --- a/TablePro/Models/Query/RowProvider.swift +++ b/TablePro/Models/Query/RowProvider.swift @@ -78,7 +78,9 @@ final class InMemoryRowProvider: RowProvider { /// Lazy per-cell cache for formatted display values. /// Keyed by source row index (buffer index or offset appended index). + /// Evicted when exceeding maxDisplayCacheSize to bound memory. private var displayCache: [Int: [String?]] = [:] + private static let maxDisplayCacheSize = 20_000 private(set) var columnDefaults: [String: String?] private(set) var columnTypes: [ColumnType] private(set) var columnForeignKeys: [String: ForeignKeyInfo] @@ -214,9 +216,16 @@ final class InMemoryRowProvider: RowProvider { rowCache[col] = CellDisplayFormatter.format(src[col], columnType: ct) } displayCache[cacheKey] = rowCache + evictDisplayCacheIfNeeded(nearKey: cacheKey) return columnIndex < rowCache.count ? rowCache[columnIndex] : nil } + private func evictDisplayCacheIfNeeded(nearKey: Int) { + guard displayCache.count > Self.maxDisplayCacheSize else { return } + let halfSize = Self.maxDisplayCacheSize / 2 + displayCache = displayCache.filter { abs($0.key - nearKey) <= halfSize } + } + @MainActor func preWarmDisplayCache(upTo rowCount: Int) { let count = min(rowCount, totalRowCount) diff --git a/TablePro/Views/Main/Child/MainEditorContentView.swift b/TablePro/Views/Main/Child/MainEditorContentView.swift index 9e57e025..f1907fa4 100644 --- a/TablePro/Views/Main/Child/MainEditorContentView.swift +++ b/TablePro/Views/Main/Child/MainEditorContentView.swift @@ -554,8 +554,8 @@ struct MainEditorContentView: View { return cached.sortedIndices } - // For large datasets sorted async, return nil (unsorted) until cache is ready - if rows.count > 10_000 { + // For datasets sorted async, return nil (unsorted) until cache is ready + if rows.count > 1_000 { return nil } diff --git a/TablePro/Views/Main/MainContentCoordinator.swift b/TablePro/Views/Main/MainContentCoordinator.swift index 44b93a6a..5face82d 100644 --- a/TablePro/Views/Main/MainContentCoordinator.swift +++ b/TablePro/Views/Main/MainContentCoordinator.swift @@ -1170,8 +1170,8 @@ final class MainContentCoordinator { let sortColumns = currentSort.columns let colTypes = tab.columnTypes - if rows.count > 10_000 { - // Large dataset: sort on background thread to avoid UI freeze + if rows.count > 1_000 { + // Sort on background thread to avoid UI freeze activeSortTasks[tabId]?.cancel() activeSortTasks.removeValue(forKey: tabId) tabManager.tabs[tabIndex].isExecuting = true