performance

This commit is contained in:
Avently
2025-01-17 13:25:03 -08:00
parent 86d20f6b9f
commit 1047c44f69
4 changed files with 65 additions and 31 deletions
@@ -62,23 +62,29 @@ func apiLoadMessages(
chatState.unreadAfterNewestLoaded = navInfo.afterUnread
}
case let .before(paginationChatItemId, _):
logger.debug("LALALA 0")
newItems.append(contentsOf: oldItems)
let indexInCurrentItems = oldItems.firstIndex(where: { $0.id == paginationChatItemId })
guard let indexInCurrentItems else { return }
let (newIds, _) = mapItemsToIds(chat.chatItems)
logger.debug("LALALA 1")
let wasSize = newItems.count
let visibleItemIndexes = await MainActor.run { visibleItemIndexesNonReversed() }
logger.debug("LALALA 2")
let modifiedSplits = removeDuplicatesAndModifySplitsOnBeforePagination(
unreadAfterItemId, &newItems, newIds, chatState.splits, visibleItemIndexes
)
logger.debug("LALALA 3")
let insertAt = max((indexInCurrentItems - (wasSize - newItems.count) + modifiedSplits.trimmedIds.count), 0)
newItems.insert(contentsOf: chat.chatItems, at: insertAt)
logger.debug("LALALA 4")
let newReversed: [ChatItem] = newItems.reversed()
await MainActor.run {
ItemsModel.shared.reversedChatItems = newReversed
chatState.splits = modifiedSplits.newSplits
chatState.moveUnreadAfterItem(modifiedSplits.oldUnreadSplitIndex, modifiedSplits.newUnreadSplitIndex, oldItems)
}
logger.debug("LALALA 5")
case let .after(paginationChatItemId, _):
newItems.append(contentsOf: oldItems)
let indexInCurrentItems = oldItems.firstIndex(where: { $0.id == paginationChatItemId })
@@ -49,7 +49,7 @@ struct MergedItems {
}
let revealed = item.mergeCategory == nil || revealedItems.contains(item.id)
if recent != nil, case let .grouped(items, _, _, _, mergeCategory, unreadIds, _) = recent, mergeCategory == category, let first = items.boxedValue.first, !revealedItems.contains(first.item.id) && !itemIsSplit {
if recent != nil, case let .grouped(items, _, _, _, mergeCategory, unreadIds, _, _) = recent, mergeCategory == category, let first = items.boxedValue.first, !revealedItems.contains(first.item.id) && !itemIsSplit {
let listItem = ListItem(item: item, prevItem: prev, nextItem: next, unreadBefore: unreadBefore)
items.boxedValue.append(listItem)
@@ -79,13 +79,15 @@ struct MergedItems {
rangeInReversed: lastRangeInReversedForMergedItems!,
mergeCategory: item.mergeCategory,
unreadIds: BoxedValue(item.isRcvNew ? Set(arrayLiteral: item.id) : Set()),
startIndexInReversedItems: index
startIndexInReversedItems: index,
hash: listItem.genHash()
)
} else {
lastRangeInReversedForMergedItems = nil
recent = MergedItem.single(
item: listItem,
startIndexInReversedItems: index
startIndexInReversedItems: index,
hash: listItem.genHash()
)
}
mergedItems.append(recent!)
@@ -117,19 +119,25 @@ struct MergedItems {
enum MergedItem: Hashable, Equatable {
// equatable and hashable implementations allows to NSDiffableDataSourceSnapshot to see the difference and correcrly scroll items we want. Without any of it, the scroll position will be random in ReverseList
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.hashValue == rhs.hashValue
lhs.hash == rhs.hash
}
var hashValue: Int { self.newest().item.hashValue }
func hash(into hasher: inout Hasher) {
hasher.combine("\(self.newest().item)")
hasher.combine(hash)
}
var hash: String {
switch self {
case .single(_, _, let hash): hash
case .grouped(_, _, _, _, _, _, _, let hash): hash
}
}
// the item that is always single, cannot be grouped and always revealed
case single(
item: ListItem,
startIndexInReversedItems: Int
startIndexInReversedItems: Int,
hash: String
)
/** The item that can contain multiple items or just one depending on revealed state. When the whole group of merged items is revealed,
@@ -146,11 +154,12 @@ enum MergedItem: Hashable, Equatable {
rangeInReversed: BoxedValue<ClosedRange<Int>>,
mergeCategory: CIMergeCategory?,
unreadIds: BoxedValue<Set<Int64>>,
startIndexInReversedItems: Int
startIndexInReversedItems: Int,
hash: String
)
func reveal(_ reveal: Bool, _ revealedItems: Binding<Set<Int64>>) {
if case .grouped(let items, _, let revealedIdsWithinGroup, _, _, _, _) = self {
if case .grouped(let items, _, let revealedIdsWithinGroup, _, _, _, _, _) = self {
var newRevealed = revealedItems.wrappedValue
var i = 0
if reveal {
@@ -172,37 +181,37 @@ enum MergedItem: Hashable, Equatable {
var startIndexInReversedItems: Int {
get {
switch self {
case let .single(_, startIndexInReversedItems): startIndexInReversedItems
case let .grouped(_, _, _, _, _, _, startIndexInReversedItems): startIndexInReversedItems
case let .single(_, startIndexInReversedItems, _): startIndexInReversedItems
case let .grouped(_, _, _, _, _, _, startIndexInReversedItems, _): startIndexInReversedItems
}
}
}
func hasUnread() -> Bool {
switch self {
case let .single(item, _): item.item.isRcvNew
case let .grouped(_, _, _, _, _, unreadIds, _): !unreadIds.boxedValue.isEmpty
case let .single(item, _, _): item.item.isRcvNew
case let .grouped(_, _, _, _, _, unreadIds, _, _): !unreadIds.boxedValue.isEmpty
}
}
func newest() -> ListItem {
switch self {
case let .single(item, _): item
case let .grouped(items, _, _, _, _, _, _): items.boxedValue[0]
case let .single(item, _, _): item
case let .grouped(items, _, _, _, _, _, _, _): items.boxedValue[0]
}
}
func oldest() -> ListItem {
switch self {
case let .single(item, _): item
case let .grouped(items, _, _, _, _, _, _): items.boxedValue[items.boxedValue.count - 1]
case let .single(item, _, _): item
case let .grouped(items, _, _, _, _, _, _, _): items.boxedValue[items.boxedValue.count - 1]
}
}
func lastIndexInReversed() -> Int {
switch self {
case .single: startIndexInReversedItems
case let .grouped(items, _, _, _, _, _, _): startIndexInReversedItems + items.boxedValue.count - 1
case let .grouped(items, _, _, _, _, _, _, _): startIndexInReversedItems + items.boxedValue.count - 1
}
}
}
@@ -223,6 +232,10 @@ struct ListItem: Hashable {
let nextItem: ChatItem?
// how many unread items before (older than) this one (excluding this one)
let unreadBefore: Int
func genHash() -> String {
"\(item.meta.itemId) \(item.meta.updatedAt.hashValue) \(item.reactions.count) \(item.meta.isRcvNew) \(unreadBefore)"
}
}
class ActiveChatState {
@@ -288,13 +301,12 @@ class BoxedValue<T: Hashable>: Equatable, Hashable {
extension ReverseList.Controller {
@MainActor
func visibleItemIndexesNonReversed(_ mergedItems: Binding<MergedItems>) -> ClosedRange<Int> {
func visibleItemIndexesNonReversed(_ items: [MergedItem]) -> ClosedRange<Int> {
let zero = 0 ... 0
if mergedItems.wrappedValue.items.count == 0 {
if items.isEmpty {
return zero
}
let listState = getListState(mergedItems.wrappedValue.items) ?? ListState()
let items = mergedItems.wrappedValue.items
let listState = getListState(items) ?? ListState()
let newest = items.count > listState.firstVisibleItemIndex ? items[listState.firstVisibleItemIndex].startIndexInReversedItems : nil
let oldest = items.count > listState.firstVisibleItemIndex ? items[listState.lastVisibleItemIndex].lastIndexInReversed() : nil
guard let newest, let oldest else {
+4 -4
View File
@@ -447,8 +447,8 @@ struct ChatView: View {
// LALAL CAN I CHANGE BINDING LIKE THIS IN ignoreLoadingRequests?
ReverseList(mergedItems: $mergedItems, revealedItems: $revealedItems, unreadCount: Binding.constant(chat.chatStats.unreadCount), scrollState: $scrollModel.state, loadingMoreItems: $loadingMoreItems, allowLoadMoreItems: $allowLoadMoreItems, ignoreLoadingRequests: searchValueIsEmpty ? $ignoreLoadingRequests : Binding.constant([])) { index, mergedItem in
let ci = switch mergedItem {
case let .single(item, _): item.item
case let .grouped(items, _, _, _, _, _, _): items.boxedValue.last!.item
case let .single(item, _, _): item.item
case let .grouped(items, _, _, _, _, _, _, _): items.boxedValue.last!.item
}
let voiceNoFrame = voiceWithoutFrame(ci)
let maxWidth = cInfo.chatType == .group
@@ -675,7 +675,7 @@ struct ChatView: View {
.onTapGesture { scrollModel.scrollToBottom() }
}
}
.if(loadingMoreItems) { $0.hidden() }
.disabled(loadingMoreItems)
.padding()
.frame(maxWidth: .infinity, alignment: .trailing)
}
@@ -1044,7 +1044,7 @@ struct ChatView: View {
let last = isLastItem ? im.reversedChatItems.last : nil
let listItem = merged.newest()
let item = listItem.item
let range: ClosedRange<Int>? = if case let .grouped(_, _, _, rangeInReversed, _, _, _) = merged {
let range: ClosedRange<Int>? = if case let .grouped(_, _, _, rangeInReversed, _, _, _, _) = merged {
rangeInReversed.boxedValue
} else {
nil
+20 -4
View File
@@ -133,6 +133,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
cell.transform = .verticalFlip
cell.selectionStyle = .none
cell.backgroundColor = .clear
_ = logger.debug("LALAL ASK FOR CELL \(indexPath.row)")
return cell
}
@@ -255,11 +256,14 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
}
func update(_ items: [MergedItem]) {
logger.debug("LALAL STEP 0 \(items.count)")
let prevSnapshot = dataSource.snapshot()
logger.debug("LALAL STEP 1")
let wasCount = prevSnapshot.numberOfItems
let willBeCount = items.count
let c = 1
let insertedSeveralNewestItems = wasCount != 0 && willBeCount - wasCount == c && prevSnapshot.itemIdentifiers.first!.hashValue == items[c].hashValue
logger.debug("LALAL STEP 2")
logger.debug("LALAL WAS \(wasCount) will be \(items.count)")
if items == prevSnapshot.itemIdentifiers {
logger.debug("LALAL SAME ITEMS, not rebuilding the tableview")
@@ -270,7 +274,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
var snapshot = NSDiffableDataSourceSnapshot<Section, MergedItem>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
logger.debug("LALAL STEP 3")
dataSource.defaultRowAnimation = .none
let wasContentHeight = tableView.contentSize.height
@@ -279,6 +283,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
let wasFirstVisibleRow = listState?.firstVisibleItemIndex ?? 0//tableView.indexPathsForVisibleRows?.first?.row ?? 0
let wasFirstVisibleOffset = listState?.firstVisibleItemOffset ?? 0
// Sets content offset on initial load
logger.debug("LALAL STEP 4")
if wasCount == 0 {
dataSource.apply(
snapshot,
@@ -292,6 +297,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
animated: false
)
}
logger.debug("LALAL STEP 5 0")
} else if wasCount != snapshot.numberOfItems {
logger.debug("LALAL drag \(self.tableView.isDragging), decel \(self.tableView.isDecelerating)")
if useSmoothScrolling && tableView.isDecelerating {
@@ -303,6 +309,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
)
tableView.endUpdates()
tableView.panGestureRecognizer.isEnabled = true
logger.debug("LALAL STEP 5 1")
} else {
// remember current translation
var translationToApply: CGPoint? = nil
@@ -354,9 +361,17 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
self.tableView.setContentOffset(CGPointMake(o.x + t.x, o.y + t.y), animated: true)
}
}
logger.debug("LALAL STEP 5 2")
}
} else {
dataSource.apply(
snapshot,
animatingDifferences: false
)
logger.debug("LALAL STEP 5 3")
}
updateFloatingButtons.send()
logger.debug("LALAL STEP 6")
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
@@ -479,7 +494,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
if index == nil {
let pagination = ChatPagination.around(chatItemId: itemId, count: ChatPagination.PRELOAD_COUNT * 2)
let oldSize = ItemsModel.shared.reversedChatItems.count
_ = await self.representer.loadItems(true, pagination, { self.visibleItemIndexesNonReversed(Binding.constant(self.representer.mergedItems))})
_ = await self.representer.loadItems(true, pagination, { self.visibleItemIndexesNonReversed(self.representer.mergedItems.items)})
var repeatsLeft = 50
while oldSize == ItemsModel.shared.reversedChatItems.count && repeatsLeft > 0 {
try await Task.sleep(nanoseconds: 20_000000)
@@ -511,9 +526,10 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
logger.debug("LALAL LOADING before \(String(describing: self.representer.scrollState))")
prevFirstVisible = state.firstVisibleItemIndex
Task {
await preloadItems(self.representer.mergedItems, self.representer.allowLoadMoreItems, state, self.representer.$ignoreLoadingRequests) { pagination in
let mergedItems = self.representer.mergedItems
await preloadItems(mergedItems, self.representer.allowLoadMoreItems, state, self.representer.$ignoreLoadingRequests) { pagination in
logger.debug("LALAL LOADING INSIDE")
let triedToLoad = await self.representer.loadItems(false, pagination, { self.visibleItemIndexesNonReversed(Binding.constant(self.representer.mergedItems)) })
let triedToLoad = await self.representer.loadItems(false, pagination, { self.visibleItemIndexesNonReversed(mergedItems.items) })
return triedToLoad
}
}