diff options
| author | Fuwn <[email protected]> | 2026-02-18 12:37:24 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-18 12:38:39 -0800 |
| commit | cae0093bfb3f65ff4ed9a7879b0dc2f406794c2c (patch) | |
| tree | fe8cc5982020e2b9f9b6012a6244504c3f9ef7cf /Sora | |
| parent | perf: reduce suggestion and image handling hot-path overhead (diff) | |
| download | sora-testing-cae0093bfb3f65ff4ed9a7879b0dc2f406794c2c.tar.xz sora-testing-cae0093bfb3f65ff4ed9a7879b0dc2f406794c2c.zip | |
perf: memoize post grid derived collections and remove columns cache
Diffstat (limited to 'Sora')
| -rw-r--r-- | Sora/Data/ColumnsDataCache.swift | 17 | ||||
| -rw-r--r-- | Sora/Views/Post/Grid/PostGridView.swift | 130 |
2 files changed, 59 insertions, 88 deletions
diff --git a/Sora/Data/ColumnsDataCache.swift b/Sora/Data/ColumnsDataCache.swift deleted file mode 100644 index bec37fb..0000000 --- a/Sora/Data/ColumnsDataCache.swift +++ /dev/null @@ -1,17 +0,0 @@ -struct ColumnsDataCache: Equatable { - let data: [[BooruPost]] - let columnCount: Int - let posts: [BooruPost] - - static func == (lhs: Self, rhs: Self) -> Bool { - guard lhs.columnCount == rhs.columnCount else { return false } - guard lhs.posts.count == rhs.posts.count else { return false } - guard !lhs.posts.isEmpty, !rhs.posts.isEmpty else { - return lhs.posts.isEmpty == rhs.posts.isEmpty - } - guard lhs.posts.first?.id == rhs.posts.first?.id else { return false } - guard lhs.posts.last?.id == rhs.posts.last?.id else { return false } - - return true - } -} diff --git a/Sora/Views/Post/Grid/PostGridView.swift b/Sora/Views/Post/Grid/PostGridView.swift index 9c24932..03e5575 100644 --- a/Sora/Views/Post/Grid/PostGridView.swift +++ b/Sora/Views/Post/Grid/PostGridView.swift @@ -1,7 +1,6 @@ // swiftlint:disable file_length import SwiftUI -import WaterfallGrid struct PostGridView: View { // swiftlint:disable:this type_body_length @EnvironmentObject var settings: SettingsManager @@ -9,11 +8,9 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length @State private var isSearchHistoryPresented = false @Binding var selectedTab: Int @State private var isSearchablePresented = false - @State private var cachedSuggestions: [Either<BooruTag, BooruSearchQuery>] = [] @State private var suppressNextSearchSubmit = false @State private var searchTask: Task<Void, Never>? @State private var suggestions: [BooruTag] = [] - @State private var cachedColumnsData: ColumnsDataCache? let initialTag: String? @Binding var navigationPath: NavigationPath @State private var localPosts: [BooruPost] = [] @@ -25,6 +22,9 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length @State private var hasAppearedBefore = false @State private var currentLocalTask: Task<Void, Never>? @State private var previousNavigationPathCount = 0 + @State private var displayedPosts: [BooruPost] = [] + @State private var displayedColumnsData: [[BooruPost]] = [] + @State private var folderHierarchy = FolderHierarchy(folders: []) init( selectedTab: Binding<Int>, navigationPath: Binding<NavigationPath>, initialTag: String? = nil @@ -37,14 +37,38 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length @Environment(\.isSearching) private var isSearching - private var activePosts: [BooruPost] { - let posts = initialTag != nil ? localPosts : manager.posts + private var isLoading: Bool { + initialTag != nil ? localIsLoading : manager.isLoading + } - return posts.filter { settings.displayRatings.contains($0.rating) } + private func currentPostsSource() -> [BooruPost] { + initialTag != nil ? localPosts : manager.posts } - private var isLoading: Bool { - initialTag != nil ? localIsLoading : manager.isLoading + private func refreshFolderHierarchy() { + folderHierarchy = FolderHierarchy(folders: settings.folders) + } + + private func columnsData( + for posts: [BooruPost], columnCount: Int + ) -> [[BooruPost]] { + (0..<columnCount).map { columnIndex in + posts.enumerated().compactMap { postIndex, post in + postIndex % columnCount == columnIndex ? post : nil + } + } + } + + private func refreshDisplayedPosts() { + let filteredPosts = currentPostsSource().filter { post in + settings.displayRatings.contains(post.rating) + } + + displayedPosts = filteredPosts + displayedColumnsData = columnsData( + for: filteredPosts, + columnCount: settings.thumbnailGridColumns + ) } private var searchText: Binding<String> { @@ -70,7 +94,7 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length ) } - if activePosts.isEmpty, isLoading { + if displayedPosts.isEmpty, isLoading { placeholderGrid } else { gridView(columnCount: settings.thumbnailGridColumns) @@ -98,67 +122,14 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length @ViewBuilder private func gridView(columnCount: Int) -> some View { - if settings.alternativeThumbnailGrid { - let columnsData = getColumnsData(columnCount: columnCount) - - HStack(alignment: .top) { - ForEach(0..<columnCount, id: \.self) { columnIndex in - LazyVStack { - ForEach(columnsData[columnIndex], id: \.id) { post in - waterfallGridContent(post: post) - .id(post.id) - } - } - .transaction { $0.animation = nil } - } - } - #if os(macOS) - .padding(8) - #else - .padding(.horizontal) - #endif - .transition(.opacity) - } else { - WaterfallGrid(activePosts, id: \.id) { post in - waterfallGridContent(post: post) - .id(post.id) - } - .gridStyle(columns: columnCount) - .transaction { $0.animation = nil } - #if os(macOS) - .padding(8) - #else - .padding(.horizontal) - #endif - .transition(.opacity) - } - } - - private func getColumnsData(columnCount: Int) -> [[BooruPost]] { - if let cached = cachedColumnsData, - cached - == ColumnsDataCache( - data: cached.data, - columnCount: columnCount, - posts: activePosts - ) - { - return cached.data - } - - let computedData = (0..<columnCount).map { columnIndex in - activePosts.enumerated().compactMap { index, post in - index % columnCount == columnIndex ? post : nil - } - } - - cachedColumnsData = ColumnsDataCache( - data: computedData, + ThumbnailGridView( + items: displayedPosts, columnCount: columnCount, - posts: activePosts - ) - - return computedData + useAlternativeGrid: settings.alternativeThumbnailGrid, + columnsData: displayedColumnsData + ) { post in + waterfallGridContent(post: post) + } } var body: some View { @@ -238,7 +209,24 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length previousNavigationPathCount = currentPathCount } + .onChange(of: localPosts) { + refreshDisplayedPosts() + } + .onChange(of: manager.posts) { + refreshDisplayedPosts() + } + .onChange(of: settings.displayRatings) { + refreshDisplayedPosts() + } + .onChange(of: settings.thumbnailGridColumns) { + refreshDisplayedPosts() + } + .onChange(of: settings.folders) { + refreshFolderHierarchy() + } .onAppear { + refreshFolderHierarchy() + refreshDisplayedPosts() previousNavigationPathCount = navigationPath.count if let initialTag { @@ -480,7 +468,7 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length }) { PostGridThumbnailView( post: post, - posts: activePosts, + posts: displayedPosts, isNestedView: initialTag != nil, endOfData: initialTag != nil ? localEndOfData : manager.endOfData, onLoadNextPage: { @@ -513,7 +501,7 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length Menu { FolderMenuView( - folderHierarchy: FolderHierarchy(folders: settings.folders), + folderHierarchy: folderHierarchy, showsTopLevelUncategorized: false, onSelectFolder: { folderIdentifier in settings.addFavorite(post: post, provider: manager.provider, folder: folderIdentifier) |