From 5bf9b587da23dd450d0f1e3c701800d018957fc8 Mon Sep 17 00:00:00 2001 From: Anthony Laibe Date: Thu, 20 Jan 2022 13:15:36 +0100 Subject: [PATCH] perf(@chat): Do not re-render messages when switching chat --- .../chat_content/messages/view.nim | 6 + src/dev/benchmark.nim | 17 + ui/app/AppLayouts/Chat/stores/RootStore.qml | 1 - .../AppLayouts/Chat/views/ChatColumnView.qml | 329 +++++++++--------- .../AppLayouts/Chat/views/ChatContentView.qml | 2 +- 5 files changed, 188 insertions(+), 167 deletions(-) create mode 100644 src/dev/benchmark.nim diff --git a/src/app/modules/main/chat_section/chat_content/messages/view.nim b/src/app/modules/main/chat_section/chat_content/messages/view.nim index 9cfa2de18a..f72b718d5c 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/view.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/view.nim @@ -70,8 +70,10 @@ QtObject: return self.delegate.getNumberOfPinnedMessages() proc initialMessagesLoadedChanged*(self: View) {.signal.} + proc getInitialMessagesLoaded*(self: View): bool {.slot.} = return self.initialMessagesLoaded + QtProperty[bool] initialMessagesLoaded: read = getInitialMessagesLoaded notify = initialMessagesLoadedChanged @@ -83,8 +85,10 @@ QtObject: self.initialMessagesLoadedChanged() proc loadingHistoryMessagesInProgressChanged*(self: View) {.signal.} + proc getLoadingHistoryMessagesInProgress*(self: View): bool {.slot.} = return self.loadingHistoryMessagesInProgress + QtProperty[bool] loadingHistoryMessagesInProgress: read = getLoadingHistoryMessagesInProgress notify = loadingHistoryMessagesInProgressChanged @@ -100,10 +104,12 @@ QtObject: self.delegate.loadMoreMessages() proc messageSuccessfullySent*(self: View) {.signal.} + proc emitSendingMessageSuccessSignal*(self: View) = self.messageSuccessfullySent() proc sendingMessageFailed*(self: View) {.signal.} + proc emitSendingMessageErrorSignal*(self: View) = self.sendingMessageFailed() diff --git a/src/dev/benchmark.nim b/src/dev/benchmark.nim new file mode 100644 index 0000000000..57645d135b --- /dev/null +++ b/src/dev/benchmark.nim @@ -0,0 +1,17 @@ +import strutils, times + +# benchmark measure execution time of block of code. +# usage: +# import os, strutils, times +# import benchmark +# +# benchmark("name") do: +# ... + +template benchmark*(benchmarkName: string, code: untyped) = + block: + let t0 = epochTime() + code + let elapsed = epochTime() - t0 + let elapsedStr = elapsed.formatFloat(format = ffDecimal, precision = 10) + echo "CPU Time [", benchmarkName, "] ", elapsedStr, "s" \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 64fd97deb9..243e525274 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -22,7 +22,6 @@ QtObject { return chatCommunitySectionModule.getChatContentModule() } - // Contact requests related part property var contactRequestsModel: chatCommunitySectionModule.contactRequestsModel diff --git a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml index 11b9582051..8add25a714 100644 --- a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml @@ -158,65 +158,41 @@ Item { } } - StackLayout { - anchors.fill: parent - currentIndex: { - if(root.activeChatId !== "") - { - for(let i = 1; i < this.children.length; i++) - { - var obj = this.children[i]; - if(obj && obj.chatContentModule) - { - let myChatId = obj.chatContentModule.getMyChatId() - if(myChatId === root.activeChatId || myChatId === root.activeSubItemId) - return i + + EmptyChatPanel { + visible: root.activeChatId === "" + onShareChatKeyClicked: Global.openProfilePopup(userProfile.pubKey); + } - } - } - } + // This is kind of a solution for applying backend refactored changes with the minimal qml changes. + // The best would be if we made qml to follow the struct we have on the backend side. + Repeater { + model: parentModule && parentModule.model + delegate: delegateChooser - return 0 - } - - EmptyChatPanel { - onShareChatKeyClicked: Global.openProfilePopup(userProfile.pubKey); - } - - // This is kind of a solution for applying backend refactored changes with the minimal qml changes. - // The best would be if we made qml to follow the struct we have on the backend side. - Repeater { - model: parentModule && parentModule.model - delegate: delegateChooser - - DelegateChooser { - id: delegateChooser - role: "type" - DelegateChoice { // In case of category - roleValue: Constants.chatType.unknown - delegate: Repeater { - model: { - if (!subItems) { - console.error("We got a category with no subitems. It is possible that the channel had a type unknown") - } - return subItems - } - delegate: ChatContentView { - rootStore: root.rootStore - contactsStore: root.contactsStore - sendTransactionNoEnsModal: cmpSendTransactionNoEns - receiveTransactionModal: cmpReceiveTransaction - sendTransactionWithEnsModal: cmpSendTransactionWithEns - stickersLoaded: root.stickersLoaded - Component.onCompleted: { - parentModule.prepareChatContentModuleForChatId(model.itemId) - chatContentModule = parentModule.getChatContentModule() - } + DelegateChooser { + id: delegateChooser + role: "type" + DelegateChoice { // In case of category + roleValue: Constants.chatType.unknown + delegate: Repeater { + model: { + if (!subItems) { + console.error("We got a category with no subitems. It is possible that the channel had a type unknown") } + return subItems } - } - DelegateChoice { // In all other cases delegate: ChatContentView { + width: parent.width + clip: true + height: { + // dynamically calculate the height of the view, if the active one is the current one + // then set the height to parent otherwise set it to 0 + let myChatId = chatContentModule.getMyChatId() + if(myChatId === root.activeChatId || myChatId === root.activeSubItemId) + return parent.height + return 0 + } rootStore: root.rootStore contactsStore: root.contactsStore sendTransactionNoEnsModal: cmpSendTransactionNoEns @@ -224,12 +200,36 @@ Item { sendTransactionWithEnsModal: cmpSendTransactionWithEns stickersLoaded: root.stickersLoaded Component.onCompleted: { - parentModule.prepareChatContentModuleForChatId(itemId) + parentModule.prepareChatContentModuleForChatId(model.itemId) chatContentModule = parentModule.getChatContentModule() } } } } + DelegateChoice { // In all other cases + delegate: ChatContentView { + width: parent.width + clip: true + height: { + // dynamically calculate the height of the view, if the active one is the current one + // then set the height to parent otherwise set it to 0 + let myChatId = chatContentModule.getMyChatId() + if(myChatId === root.activeChatId || myChatId === root.activeSubItemId) + return parent.height + return 0 + } + rootStore: root.rootStore + contactsStore: root.contactsStore + sendTransactionNoEnsModal: cmpSendTransactionNoEns + receiveTransactionModal: cmpReceiveTransaction + sendTransactionWithEnsModal: cmpSendTransactionWithEns + stickersLoaded: root.stickersLoaded + Component.onCompleted: { + parentModule.prepareChatContentModuleForChatId(itemId) + chatContentModule = parentModule.getChatContentModule() + } + } + } } } @@ -238,118 +238,117 @@ Item { Layout.fillWidth: true Layout.bottomMargin: Style.current.bigPadding isContact: root.isContact - visible: root.activeChatType === Constants.chatType.oneToOne - && (!root.isContact /*|| !contactRequestReceived*/) + visible: root.activeChatType === Constants.chatType.oneToOne && (!root.isContact /*|| !contactRequestReceived*/) onAddContactClicked: { root.rootStore.addContact(root.activeChatId); } } - Component { - id: cmpSendTransactionNoEns - ChatCommandModal { - id: sendTransactionNoEns - store: root.rootStore - contactsStore: root.contactsStore - isContact: root.isContact - onClosed: { - destroy() - } - sendChatCommand: root.requestAddressForTransaction - isRequested: false - //% "Send" - commandTitle: qsTrId("command-button-send") - header.title: commandTitle - //% "Request Address" - finalButtonLabel: qsTrId("request-address") - selectRecipient.selectedRecipient: { - parentModule.prepareChatContentModuleForChatId(activeChatId) - let chatContentModule = parentModule.getChatContentModule() - return { - address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet - alias: chatContentModule.chatDetails.name, // Do we need the alias for real or name works? - identicon: chatContentModule.chatDetails.icon, - name: chatContentModule.chatDetails.name, - type: RecipientSelector.Type.Contact - } - } - selectRecipient.selectedType: RecipientSelector.Type.Contact - selectRecipient.readOnly: true - } - } - - Component { - id: cmpReceiveTransaction - ChatCommandModal { - id: receiveTransaction - store: root.rootStore - contactsStore: root.contactsStore - isContact: root.isContact - onClosed: { - destroy() - } - sendChatCommand: root.requestTransaction - isRequested: true - //% "Request" - commandTitle: qsTrId("wallet-request") - header.title: commandTitle - //% "Request" - finalButtonLabel: qsTrId("wallet-request") - selectRecipient.selectedRecipient: { - parentModule.prepareChatContentModuleForChatId(activeChatId) - let chatContentModule = parentModule.getChatContentModule() - return { - address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet - alias: chatContentModule.chatDetails.name, // Do we need the alias for real or name works? - identicon: chatContentModule.chatDetails.icon, - name: chatContentModule.chatDetails.name, - type: RecipientSelector.Type.Contact - } - } - selectRecipient.selectedType: RecipientSelector.Type.Contact - selectRecipient.readOnly: true - } - } - - Component { - id: cmpSendTransactionWithEns - SendModal { - id: sendTransactionWithEns - store: root.rootStore - contactsStore: root.contactsStore - onOpened: { - // Not Refactored Yet -// root.rootStore.walletModelInst.gasView.getGasPrice() - } - onClosed: { - destroy() - } - isContact: root.isContact - selectRecipient.readOnly: true - selectRecipient.selectedRecipient: { - parentModule.prepareChatContentModuleForChatId(activeChatId) - let chatContentModule = parentModule.getChatContentModule() - - return { - address: "", - alias: chatContentModule.chatDetails.name, // Do we need the alias for real or name works? - identicon: chatContentModule.chatDetails.icon, - name: chatContentModule.chatDetails.name, - type: RecipientSelector.Type.Contact, - ensVerified: true - } - } - selectRecipient.selectedType: RecipientSelector.Type.Contact - } - } - - ActivityCenterPopup { - id: activityCenter - height: root.height - 56 * 2 // TODO get screen size // Taken from old code top bar height was fixed there to 56 - y: 56 + Component { + id: cmpSendTransactionNoEns + ChatCommandModal { + id: sendTransactionNoEns store: root.rootStore - chatSectionModule: root.chatSectionModule + contactsStore: root.contactsStore + isContact: root.isContact + onClosed: { + destroy() + } + sendChatCommand: root.requestAddressForTransaction + isRequested: false + //% "Send" + commandTitle: qsTrId("command-button-send") + header.title: commandTitle + //% "Request Address" + finalButtonLabel: qsTrId("request-address") + selectRecipient.selectedRecipient: { + parentModule.prepareChatContentModuleForChatId(activeChatId) + let chatContentModule = parentModule.getChatContentModule() + return { + address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet + alias: chatContentModule.chatDetails.name, // Do we need the alias for real or name works? + identicon: chatContentModule.chatDetails.icon, + name: chatContentModule.chatDetails.name, + type: RecipientSelector.Type.Contact + } + } + selectRecipient.selectedType: RecipientSelector.Type.Contact + selectRecipient.readOnly: true } + } + + Component { + id: cmpReceiveTransaction + ChatCommandModal { + id: receiveTransaction + store: root.rootStore + contactsStore: root.contactsStore + isContact: root.isContact + onClosed: { + destroy() + } + sendChatCommand: root.requestTransaction + isRequested: true + //% "Request" + commandTitle: qsTrId("wallet-request") + header.title: commandTitle + //% "Request" + finalButtonLabel: qsTrId("wallet-request") + selectRecipient.selectedRecipient: { + parentModule.prepareChatContentModuleForChatId(activeChatId) + let chatContentModule = parentModule.getChatContentModule() + return { + address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet + alias: chatContentModule.chatDetails.name, // Do we need the alias for real or name works? + identicon: chatContentModule.chatDetails.icon, + name: chatContentModule.chatDetails.name, + type: RecipientSelector.Type.Contact + } + } + selectRecipient.selectedType: RecipientSelector.Type.Contact + selectRecipient.readOnly: true + } + } + + Component { + id: cmpSendTransactionWithEns + SendModal { + id: sendTransactionWithEns + store: root.rootStore + contactsStore: root.contactsStore + onOpened: { + // Not Refactored Yet +// root.rootStore.walletModelInst.gasView.getGasPrice() + } + onClosed: { + destroy() + } + isContact: root.isContact + selectRecipient.readOnly: true + selectRecipient.selectedRecipient: { + parentModule.prepareChatContentModuleForChatId(activeChatId) + let chatContentModule = parentModule.getChatContentModule() + + return { + address: "", + alias: chatContentModule.chatDetails.name, // Do we need the alias for real or name works? + identicon: chatContentModule.chatDetails.icon, + name: chatContentModule.chatDetails.name, + type: RecipientSelector.Type.Contact, + ensVerified: true + } + } + selectRecipient.selectedType: RecipientSelector.Type.Contact + } + } + + ActivityCenterPopup { + id: activityCenter + height: root.height - 56 * 2 // TODO get screen size // Taken from old code top bar height was fixed there to 56 + y: 56 + store: root.rootStore + chatSectionModule: root.chatSectionModule + } // Not Refactored Yet // Connections { @@ -361,12 +360,12 @@ Item { // } // } - Connections { - target: systemTray - onMessageClicked: function () { - clickOnNotification() - } + Connections { + target: systemTray + onMessageClicked: function () { + clickOnNotification() } + } // Not Refactored Yet // Connections { diff --git a/ui/app/AppLayouts/Chat/views/ChatContentView.qml b/ui/app/AppLayouts/Chat/views/ChatContentView.qml index 19c44100bd..d83f07febe 100644 --- a/ui/app/AppLayouts/Chat/views/ChatContentView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatContentView.qml @@ -302,7 +302,7 @@ ColumnLayout { } } - MessageStore{ + MessageStore { id: messageStore messageModule: chatContentModule? chatContentModule.messagesModule : null }