summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-18 12:37:24 -0800
committerFuwn <[email protected]>2026-02-18 12:38:39 -0800
commitcae0093bfb3f65ff4ed9a7879b0dc2f406794c2c (patch)
treefe8cc5982020e2b9f9b6012a6244504c3f9ef7cf
parentperf: reduce suggestion and image handling hot-path overhead (diff)
downloadsora-testing-cae0093bfb3f65ff4ed9a7879b0dc2f406794c2c.tar.xz
sora-testing-cae0093bfb3f65ff4ed9a7879b0dc2f406794c2c.zip
perf: memoize post grid derived collections and remove columns cache
-rw-r--r--Sora/Data/ColumnsDataCache.swift17
-rw-r--r--Sora/Views/Post/Grid/PostGridView.swift130
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)