perf(chat_section): improve onChatsLoaded performance (#16186)

Fixes #16181

This commit improves the time taken by onChatsLoaded that is called on login.
The culprit is `buildChatSectionUI` in the chat_section module.
There is no silver bullet here. It's slow because the community is big and has a lot of channels to load and generate.

This commit helps by removing some old code that was inefficient (calculating if a channel has a permission instead of just using the `tokenGated` property for example).
It also adds all the section items in one go instead of one another.

There are some other small improvements, but again, no way to make it way faster. Thankfully, that time is spent with the loading spinner at the same time.
This commit is contained in:
Jonathan Rainville 2024-08-28 11:21:13 -04:00 committed by GitHub
parent 658fe2a5b4
commit 087c3c6088
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 60 additions and 63 deletions

View File

@ -437,7 +437,7 @@ proc getChatsAndBuildUI*(self: Controller) =
community = self.getMyCommunity()
let normalChats = self.chatService.getChatsForCommunity(community.id)
# TODO remove this once we do this refactor https://github.com/status-im/status-desktop/issues/14219
# TODO remove this once we do this refactor https://github.com/status-im/status-desktop/issues/11694
var fullChats: seq[ChatDto] = @[]
for communityChat in community.chats:
for chat in normalChats:
@ -714,12 +714,6 @@ proc getColorHash*(self: Controller, pubkey: string): ColorHashDto =
proc getColorId*(self: Controller, pubkey: string): int =
procs_from_visual_identity_service.colorIdOf(pubkey)
proc checkChatHasPermissions*(self: Controller, communityId: string, chatId: string): bool =
return self.communityService.checkChatHasPermissions(communityId, chatId)
proc checkChatIsLocked*(self: Controller, communityId: string, chatId: string): bool =
return self.communityService.checkChatIsLocked(communityId, chatId)
proc createOrEditCommunityTokenPermission*(self: Controller, communityId: string, tokenPermission: CommunityTokenPermissionDto) =
self.communityService.createOrEditCommunityTokenPermission(communityId, tokenPermission)

View File

@ -80,7 +80,8 @@ method addOrUpdateChat*(self: AccessInterface,
mailserversService: mailservers_service.Service,
sharedUrlsService: shared_urls_service.Service,
setChatAsActive: bool = true,
insertIntoModel: bool = true
insertIntoModel: bool = true,
isSectionBuild: bool = false,
): Item {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -323,7 +323,7 @@ proc `loaderActive=`*(self: var Item, value: bool) =
proc isCategory*(self: Item): bool =
self.`type` == CATEGORY_TYPE
proc isLocked*(self: Item): bool =
proc locked*(self: Item): bool =
self.locked
proc `locked=`*(self: Item, value: bool) =

View File

@ -210,7 +210,7 @@ QtObject:
of ModelRole.LoaderActive:
result = newQVariant(item.loaderActive)
of ModelRole.Locked:
result = newQVariant(item.isLocked)
result = newQVariant(item.locked)
of ModelRole.RequiresPermissions:
result = newQVariant(item.requiresPermissions)
of ModelRole.CanPost:
@ -366,7 +366,7 @@ QtObject:
if index == -1:
return
if (self.items[index].isLocked == locked):
if (self.items[index].locked == locked):
return
self.items[index].locked = locked

View File

@ -88,7 +88,9 @@ method addOrUpdateChat(self: Module,
sharedUrlsService: shared_urls_service.Service,
setChatAsActive: bool = true,
insertIntoModel: bool = true,
isSectionBuild: bool = false,
): chat_item.Item
proc updateParentBadgeNotifications(self: Module)
proc newModule*(
delegate: delegate_interface.AccessInterface,
@ -333,9 +335,12 @@ proc buildChatSectionUI(
mailserversService,
sharedUrlsService,
setChatAsActive = false,
insertIntoModel = false
insertIntoModel = false,
isSectionBuild = true,
))
self.updateParentBadgeNotifications()
self.view.chatsModel.setData(items)
self.setActiveItem(selectedItemId)
@ -431,6 +436,7 @@ method onChatsLoaded*(
sharedUrlsService: shared_urls_service.Service,
) =
self.chatsLoaded = true
self.buildChatSectionUI(community, chats, events, settingsService, nodeConfigurationService,
contactService, chatService, communityService, messageService, mailserversService, sharedUrlsService)
@ -570,21 +576,17 @@ proc updateParentBadgeNotifications(self: Module) =
unviewedMentionsCount
)
proc updateChatLocked(self: Module, chatId: string) =
proc updateChatLocked(self: Module, communityChat: ChatDto) =
if not self.controller.isCommunity():
return
let communityId = self.controller.getMySectionId()
let locked = self.controller.checkChatIsLocked(communityId, chatId)
self.view.chatsModel().setItemLocked(chatId, locked)
self.view.chatsModel().setItemLocked(communityChat.id, communityChat.tokenGated and not communityChat.canPost)
proc updatePermissionsRequiredOnChat(self: Module, chatId: string) =
proc updatePermissionsRequiredOnChat(self: Module, communityChat: ChatDto) =
if not self.controller.isCommunity():
return
let communityId = self.controller.getMySectionId
let requiresPermissions = self.controller.checkChatHasPermissions(communityId, chatId)
self.view.chatsModel().setItemPermissionsRequired(chatId, requiresPermissions)
self.view.chatsModel().setItemPermissionsRequired(communityChat.id, communityChat.tokenGated)
proc updateBadgeNotifications(self: Module, chat: ChatDto, hasUnreadMessages: bool, unviewedMentionsCount: int) =
proc updateBadgeNotifications(self: Module, chat: ChatDto, hasUnreadMessages: bool, unviewedMentionsCount: int, skipParentBadge: bool = false) =
let chatId = chat.id
if self.chatsLoaded:
@ -601,7 +603,9 @@ proc updateBadgeNotifications(self: Module, chat: ChatDto, hasUnreadMessages: bo
let hasUnreadMessages = self.controller.categoryHasUnreadMessages(communityChat.communityId, communityChat.categoryId)
self.view.chatsModel().setCategoryHasUnreadMessages(communityChat.categoryId, hasUnreadMessages)
self.updateParentBadgeNotifications()
# We skip evaluation the parent badge when initiating the section, since it's heavy and can be done at the end
if not skipParentBadge:
self.updateParentBadgeNotifications()
method updateLastMessageTimestamp*(self: Module, chatId: string, lastMessageTimestamp: int) =
self.view.chatsModel().updateLastMessageTimestampOnItemById(chatId, lastMessageTimestamp)
@ -701,6 +705,7 @@ proc getChatItemFromChatDto(
var hideIfPermissionsNotMet = false
var viewersCanPostReactions = true
var missingEncryptionKey = false
var tokenGated = false
if self.controller.isCommunity:
# NOTE: workaround for new community chat, which is delivered in chatDto before the community will know about that
if community.hasCommunityChat(chatDto.id):
@ -712,6 +717,7 @@ proc getChatItemFromChatDto(
hideIfPermissionsNotMet = communityChat.hideIfPermissionsNotMet
viewersCanPostReactions = communityChat.viewersCanPostReactions
missingEncryptionKey = communityChat.missingEncryptionKey
tokenGated = communityChat.tokenGated
else:
canPost = chatDto.canPost
canView = chatDto.canView
@ -744,14 +750,8 @@ proc getChatItemFromChatDto(
categoryOpened,
onlineStatus = onlineStatus,
loaderActive = setChatAsActive,
locked = if self.controller.isCommunity:
self.controller.checkChatIsLocked(self.controller.getMySectionId(), chatDto.id)
else:
false,
requiresPermissions = if self.controller.isCommunity:
self.controller.checkChatHasPermissions(self.controller.getMySectionId(), chatDto.id)
else:
false,
locked = tokenGated and not canPost,
requiresPermissions = tokenGated,
canPost = canPost,
canView = canView,
canPostReactions = canPostReactions,
@ -957,9 +957,9 @@ method changeMutedOnChat*(self: Module, chatId: string, muted: bool) =
proc changeCanPostValues*(self: Module, chatId: string, canPost, canView, canPostReactions, viewersCanPostReactions: bool) =
self.view.chatsModel().changeCanPostValues(chatId, canPost, canView, canPostReactions, viewersCanPostReactions)
proc updateChatsRequiredPermissions(self: Module, chats: seq[ChatDto]) =
for chat in chats:
self.updatePermissionsRequiredOnChat(chat.id)
proc updateChatsRequiredPermissions(self: Module, communityChats: seq[ChatDto]) =
for communityChat in communityChats:
self.updatePermissionsRequiredOnChat(communityChat)
proc displayTokenPermissionChangeNotification(self: Module, title: string, message: string, community: CommunityDto, tokenPermission: CommunityTokenPermissionDto) =
if self.showPermissionUpdateNotification(community, tokenPermission):
@ -969,17 +969,17 @@ method onCommunityTokenPermissionDeleted*(self: Module, communityId: string, tok
self.view.tokenPermissionsModel.removeItemWithId(tokenPermission.id)
self.reevaluateRequiresTokenPermissionToJoin()
let community = self.controller.getMyCommunity()
let chats = community.getCommunityChats(tokenPermission.chatIds)
let communityChats = community.getCommunityChats(tokenPermission.chatIds)
self.updateChatsRequiredPermissions(chats)
self.updateChatsRequiredPermissions(communityChats)
self.displayTokenPermissionChangeNotification("Community permission deleted", "A token permission has been removed", community, tokenPermission)
method onCommunityTokenPermissionCreated*(self: Module, communityId: string, tokenPermission: CommunityTokenPermissionDto) =
let community = self.controller.getMyCommunity()
let chats = community.getCommunityChats(tokenPermission.chatIds)
let tokenPermissionItem = buildTokenPermissionItem(tokenPermission, chats)
let communityChats = community.getCommunityChats(tokenPermission.chatIds)
let tokenPermissionItem = buildTokenPermissionItem(tokenPermission, communityChats)
self.updateChatsRequiredPermissions(chats)
self.updateChatsRequiredPermissions(communityChats)
self.view.tokenPermissionsModel.addItem(tokenPermissionItem)
self.reevaluateRequiresTokenPermissionToJoin()
self.displayTokenPermissionChangeNotification("Community permission created", "A token permission has been added", community, tokenPermission)
@ -1079,8 +1079,9 @@ proc updateChannelPermissionViewData*(
let viewOnlyUpdated = self.updateTokenPermissionModel(viewOnlyPermissions.permissions, community)
let viewAndPostUpdated = self.updateTokenPermissionModel(viewAndPostPermissions.permissions, community)
if viewOnlyUpdated or viewAndPostUpdated:
self.updatePermissionsRequiredOnChat(chatId)
self.updateChatLocked(chatId)
let communityChat = community.getCommunityChat(chatId)
self.updatePermissionsRequiredOnChat(communityChat)
self.updateChatLocked(communityChat)
if self.chatContentModules.hasKey(chatId):
self.chatContentModules[chatId].setPermissionsCheckOngoing(false)
@ -1452,13 +1453,13 @@ method addOrUpdateChat(self: Module,
sharedUrlsService: shared_urls_service.Service,
setChatAsActive: bool = true,
insertIntoModel: bool = true,
isSectionBuild: bool = false,
): chat_item.Item =
let sectionId = self.controller.getMySectionId()
if belongsToCommunity and sectionId != chat.communityId or
not belongsToCommunity and sectionId != singletonInstance.userProfile.getPubKey():
return
self.updateBadgeNotifications(chat, chat.unviewedMessagesCount > 0, chat.unviewedMentionsCount)
self.updateBadgeNotifications(chat, chat.unviewedMessagesCount > 0, chat.unviewedMentionsCount, skipParentBadge = isSectionBuild)
if not self.chatsLoaded:
return
@ -1478,8 +1479,8 @@ method addOrUpdateChat(self: Module,
self.changeMutedOnChat(chat.id, chat.muted)
self.changeCanPostValues(chat.id, result.canPost, result.canView, result.canPostReactions, result.viewersCanPostReactions)
self.updatePermissionsRequiredOnChat(chat.id)
self.updateChatLocked(chat.id)
self.view.chatsModel().setItemPermissionsRequired(chat.id, result.requiresPermissions)
self.view.chatsModel().setItemLocked(chat.id, result.locked)
self.view.chatsModel().updateMissingEncryptionKey(chat.id, result.missingEncryptionKey)
if (chat.chatType == ChatType.PrivateGroupChat):
self.onGroupChatDetailsUpdated(chat.id, chat.name, chat.color, chat.icon)

View File

@ -656,6 +656,8 @@ method onChatsLoaded*[T](
myPubKey,
sectionIsMuted = false
)
var items: seq[SectionItem] = @[]
let personalChatSectionItem = initItem(
myPubKey,
sectionType = SectionType.Chat,
@ -671,7 +673,7 @@ method onChatsLoaded*[T](
isMember = true,
muted = false,
)
self.view.model().addItem(personalChatSectionItem)
items.add(personalChatSectionItem)
if activeSectionId == personalChatSectionItem.id:
activeSection = personalChatSectionItem
@ -699,12 +701,14 @@ method onChatsLoaded*[T](
networkService
)
let communitySectionItem = self.createCommunitySectionItem(community)
self.view.model().addItem(communitySectionItem)
items.add(communitySectionItem)
if activeSectionId == communitySectionItem.id:
activeSection = communitySectionItem
self.chatSectionModules[community.id].load()
self.view.model().addItems(items)
# Set active section if it is one of the channel sections
if not activeSection.isEmpty():
self.setActiveSection(activeSection)

View File

@ -224,7 +224,6 @@ QtObject:
return true
return false
proc addItem*(self: SectionModel, item: SectionItem) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
@ -247,6 +246,20 @@ QtObject:
self.countChanged()
proc addItems*(self: SectionModel, items: seq[SectionItem]) =
if items.len == 0:
return
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
let first = self.items.len
let last = first + items.len - 1
self.beginInsertRows(parentModelIndex, first, last)
self.items.add(items)
self.endInsertRows()
self.countChanged()
proc getItemIndex*(self: SectionModel, id: string): int =
var i = 0
for item in self.items:

View File

@ -2247,22 +2247,6 @@ QtObject:
else:
return community.declinedRequestsToJoin[indexDeclined].publicKey
proc checkChatIsLocked*(self: Service, communityId: string, chatId: string): bool =
if not self.communities.hasKey(communityId):
return false
let community = self.getCommunityById(communityId)
return community.channelPermissions.channels.hasKey(chatId) and not community.channelPermissions.channels[chatId].viewAndPostPermissions.satisfied
proc checkChatHasPermissions*(self: Service, communityId: string, chatId: string): bool =
let community = self.getCommunityById(communityId)
for id, tokenPermission in community.tokenPermissions:
if TokenPermissionType(tokenPermission.`type`) == TokenPermissionType.View or TokenPermissionType(tokenPermission.`type`) == TokenPermissionType.ViewAndPost:
for id in tokenPermission.chatIds:
if id == chatId:
return true
return false
proc shareCommunityUrlWithChatKey*(self: Service, communityId: string): string =
try:
let response = status_go.shareCommunityUrlWithChatKey(communityId)