From c18a49915b4b98ac7f837a1feca3e243ac44940d Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Sat, 28 Oct 2017 20:46:34 +0300 Subject: [PATCH] Save the changes between syncs in cache - Fixes high cpu issues caused by the serialization of the whole in-memory state. - Display name changes are now visible in the timeline. --- include/ChatPage.h | 24 ++- include/RoomState.h | 6 +- src/Cache.cc | 2 +- src/ChatPage.cc | 297 +++++++++++++++++--------- src/RoomList.cc | 6 +- src/RoomState.cc | 3 + src/Sync.cc | 4 +- src/TimelineView.cc | 2 +- src/TimelineViewManager.cc | 4 +- src/events/PowerLevelsEventContent.cc | 8 +- src/ui/LoadingIndicator.cc | 2 +- src/ui/RippleOverlay.cc | 2 +- 12 files changed, 242 insertions(+), 118 deletions(-) diff --git a/include/ChatPage.h b/include/ChatPage.h index d3790f78..416f7870 100644 --- a/include/ChatPage.h +++ b/include/ChatPage.h @@ -23,6 +23,10 @@ #include #include +#include "MemberEventContent.h" +#include "MessageEvent.h" +#include "StateEvent.h" + class Cache; class MatrixClient; class OverlayModal; @@ -38,6 +42,8 @@ class TimelineViewManager; class TopRoomBar; class TypingDisplay; class UserInfoWidget; +class JoinedRoom; +class LeftRoom; constexpr int CONSENSUS_TIMEOUT = 1000; constexpr int SHOW_CONTENT_TIMEOUT = 3000; @@ -76,8 +82,24 @@ private slots: void removeRoom(const QString &room_id); private: + using UserID = QString; + using RoomStates = QMap; + using JoinedRooms = QMap; + using LeftRooms = QMap; + using Membership = matrix::events::StateEvent; + using Memberships = QMap; + + void removeLeftRooms(const LeftRooms &rooms); + void updateJoinedRooms(const JoinedRooms &rooms); + + Memberships getMemberships(const QJsonArray &events) const; + RoomStates generateMembershipDifference(const JoinedRooms &rooms, + const RoomStates &states) const; + void updateTypingUsers(const QString &roomid, const QList &user_ids); - void updateDisplayNames(const RoomState &state); + void updateUserMetadata(const QJsonArray &events); + void updateUserDisplayName(const Membership &event); + void updateUserAvatarUrl(const Membership &event); void loadStateFromCache(); void deleteConfigs(); void resetUI(); diff --git a/include/RoomState.h b/include/RoomState.h index 57955e56..db1cdc68 100644 --- a/include/RoomState.h +++ b/include/RoomState.h @@ -41,6 +41,9 @@ namespace events = matrix::events; class RoomState { public: + RoomState(); + RoomState(const QJsonArray &events); + // Calculate room data that are not immediatly accessible. Like room name and // avatar. // @@ -71,7 +74,8 @@ public: events::StateEvent topic; // Contains the m.room.member events for all the joined users. - QMap> memberships; + using UserID = QString; + QMap> memberships; private: QUrl avatar_; diff --git a/src/Cache.cc b/src/Cache.cc index de2c7944..99267343 100644 --- a/src/Cache.cc +++ b/src/Cache.cc @@ -107,7 +107,7 @@ Cache::setState(const QString &nextBatchToken, const QMap &s setNextBatchToken(txn, nextBatchToken); - for (auto it = states.constBegin(); it != states.constEnd(); it++) + for (auto it = states.constBegin(); it != states.constEnd(); ++it) insertRoomState(txn, it.key(), it.value()); txn.commit(); diff --git a/src/ChatPage.cc b/src/ChatPage.cc index 150f6007..a756a0f9 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -32,7 +32,6 @@ #include "RoomState.h" #include "SideBarActions.h" #include "Splitter.h" -#include "StateEvent.h" #include "Sync.h" #include "TextInputWidget.h" #include "Theme.h" @@ -361,95 +360,19 @@ ChatPage::syncFailed(const QString &msg) QTimer::singleShot(SYNC_RETRY_TIMEOUT, this, [=]() { client_->sync(); }); } -// TODO: Should be moved in another class that manages this global list. -void -ChatPage::updateDisplayNames(const RoomState &state) -{ - for (const auto member : state.memberships) { - auto displayName = member.content().displayName(); - - if (!displayName.isEmpty()) - TimelineViewManager::DISPLAY_NAMES.insert(member.stateKey(), displayName); - } -} - void ChatPage::syncCompleted(const SyncResponse &response) { - auto joined = response.rooms().join(); + updateJoinedRooms(response.rooms().join()); + removeLeftRooms(response.rooms().leave()); - for (auto it = joined.constBegin(); it != joined.constEnd(); it++) { - updateTypingUsers(it.key(), it.value().typingUserIDs()); - - RoomState room_state; - - // Merge the new updates for rooms that we are tracking. - if (state_manager_.contains(it.key())) { - room_state = state_manager_[it.key()]; - } - - room_state.updateFromEvents(it.value().state().events()); - room_state.updateFromEvents(it.value().timeline().events()); - - updateDisplayNames(room_state); - - if (state_manager_.contains(it.key())) { - // TODO: Use pointers instead of copying. - auto oldState = state_manager_[it.key()]; - oldState.update(room_state); - state_manager_.insert(it.key(), oldState); - } else { - RoomState room_state; - - // Build the current state from the timeline and state events. - room_state.updateFromEvents(it.value().state().events()); - room_state.updateFromEvents(it.value().timeline().events()); - - // Remove redundant memberships. - room_state.removeLeaveMemberships(); - - // Resolve room name and avatar. e.g in case of one-to-one chats. - room_state.resolveName(); - room_state.resolveAvatar(); - - updateDisplayNames(room_state); - - state_manager_.insert(it.key(), room_state); - settingsManager_.insert( - it.key(), QSharedPointer(new RoomSettings(it.key()))); - - for (const auto membership : room_state.memberships) { - auto uid = membership.sender(); - auto url = membership.content().avatarUrl(); - - if (!url.toString().isEmpty()) - AvatarProvider::setAvatarUrl(uid, url); - } - - view_manager_->addRoom(it.value(), it.key()); - } - - if (it.key() == current_room_) - changeTopRoomInfo(it.key()); - - QApplication::processEvents(); - } - - auto leave = response.rooms().leave(); - - for (auto it = leave.constBegin(); it != leave.constEnd(); it++) { - if (state_manager_.contains(it.key())) { - removeRoom(it.key()); - } - } - - QtConcurrent::run(cache_.data(), &Cache::setState, response.nextBatch(), state_manager_); - - client_->setNextBatchToken(response.nextBatch()); + auto stateDiff = generateMembershipDifference(response.rooms().join(), state_manager_); + QtConcurrent::run(cache_.data(), &Cache::setState, response.nextBatch(), stateDiff); room_list_->sync(state_manager_); view_manager_->sync(response.rooms()); + client_->setNextBatchToken(response.nextBatch()); client_->sync(); } @@ -458,7 +381,7 @@ ChatPage::initialSyncCompleted(const SyncResponse &response) { auto joined = response.rooms().join(); - for (auto it = joined.constBegin(); it != joined.constEnd(); it++) { + for (auto it = joined.constBegin(); it != joined.constEnd(); ++it) { RoomState room_state; // Build the current state from the timeline and state events. @@ -472,25 +395,18 @@ ChatPage::initialSyncCompleted(const SyncResponse &response) room_state.resolveName(); room_state.resolveAvatar(); - updateDisplayNames(room_state); - state_manager_.insert(it.key(), room_state); settingsManager_.insert(it.key(), QSharedPointer(new RoomSettings(it.key()))); for (const auto membership : room_state.memberships) { - auto uid = membership.sender(); - auto url = membership.content().avatarUrl(); - - if (!url.toString().isEmpty()) - AvatarProvider::setAvatarUrl(uid, url); + updateUserDisplayName(membership); + updateUserAvatarUrl(membership); } QApplication::processEvents(); } - client_->setNextBatchToken(response.nextBatch()); - QtConcurrent::run(cache_.data(), &Cache::setState, response.nextBatch(), state_manager_); // Populate timelines with messages. @@ -499,6 +415,7 @@ ChatPage::initialSyncCompleted(const SyncResponse &response) // Initialize room list. room_list_->setInitialRooms(settingsManager_, state_manager_); + client_->setNextBatchToken(response.nextBatch()); client_->sync(); emit contentLoaded(); @@ -571,7 +488,7 @@ ChatPage::loadStateFromCache() // Fetch all the joined room's state. auto rooms = cache_->states(); - for (auto it = rooms.constBegin(); it != rooms.constEnd(); it++) { + for (auto it = rooms.constBegin(); it != rooms.constEnd(); ++it) { RoomState room_state = it.value(); // Clean up and prepare state for use. @@ -579,9 +496,6 @@ ChatPage::loadStateFromCache() room_state.resolveName(); room_state.resolveAvatar(); - // Update the global list with user's display names. - updateDisplayNames(room_state); - // Save the current room state. state_manager_.insert(it.key(), room_state); @@ -591,11 +505,8 @@ ChatPage::loadStateFromCache() // Resolve user avatars. for (const auto membership : room_state.memberships) { - auto uid = membership.sender(); - auto url = membership.content().avatarUrl(); - - if (!url.toString().isEmpty()) - AvatarProvider::setAvatarUrl(uid, url); + updateUserDisplayName(membership); + updateUserAvatarUrl(membership); } } @@ -700,4 +611,188 @@ ChatPage::updateTypingUsers(const QString &roomid, const QList &user_id typingUsers_.insert(roomid, users); } +void +ChatPage::updateUserMetadata(const QJsonArray &events) +{ + events::EventType ty; + + for (const auto &event : events) { + try { + ty = events::extractEventType(event.toObject()); + } catch (const DeserializationException &e) { + qWarning() << e.what() << event; + continue; + } + + if (!events::isStateEvent(ty)) + continue; + + try { + switch (ty) { + case events::EventType::RoomMember: { + events::StateEvent member; + member.deserialize(event); + + updateUserAvatarUrl(member); + updateUserDisplayName(member); + + break; + } + default: { + continue; + } + } + } catch (const DeserializationException &e) { + qWarning() << e.what() << event; + continue; + } + } +} + +void +ChatPage::updateUserAvatarUrl(const events::StateEvent &membership) +{ + auto uid = membership.sender(); + auto url = membership.content().avatarUrl(); + + if (!url.toString().isEmpty()) + AvatarProvider::setAvatarUrl(uid, url); +} + +void +ChatPage::updateUserDisplayName(const events::StateEvent &membership) +{ + auto displayName = membership.content().displayName(); + + if (!displayName.isEmpty()) + TimelineViewManager::DISPLAY_NAMES.insert(membership.stateKey(), displayName); +} + +void +ChatPage::removeLeftRooms(const QMap &rooms) +{ + for (auto it = rooms.constBegin(); it != rooms.constEnd(); ++it) { + if (state_manager_.contains(it.key())) + removeRoom(it.key()); + } +} + +void +ChatPage::updateJoinedRooms(const QMap &rooms) +{ + for (auto it = rooms.constBegin(); it != rooms.constEnd(); ++it) { + updateTypingUsers(it.key(), it.value().typingUserIDs()); + + const auto newStateEvents = it.value().state().events(); + const auto newTimelineEvents = it.value().timeline().events(); + + // Merge the new updates for rooms that we are tracking. + if (state_manager_.contains(it.key())) { + auto oldState = &state_manager_[it.key()]; + oldState->updateFromEvents(newStateEvents); + oldState->updateFromEvents(newTimelineEvents); + oldState->resolveName(); + oldState->resolveAvatar(); + } else { + // Build the current state from the timeline and state events. + RoomState room_state; + room_state.updateFromEvents(newStateEvents); + room_state.updateFromEvents(newTimelineEvents); + + // Resolve room name and avatar. e.g in case of one-to-one chats. + room_state.resolveName(); + room_state.resolveAvatar(); + + state_manager_.insert(it.key(), room_state); + + // TODO Doesn't work on the sidebar. + settingsManager_.insert( + it.key(), QSharedPointer(new RoomSettings(it.key()))); + + view_manager_->addRoom(it.value(), it.key()); + } + + updateUserMetadata(newStateEvents); + updateUserMetadata(newTimelineEvents); + + if (it.key() == current_room_) + changeTopRoomInfo(it.key()); + + QApplication::processEvents(); + } +} + +QMap +ChatPage::generateMembershipDifference(const QMap &rooms, + const QMap &states) const +{ + QMap stateDiff; + + for (auto it = rooms.constBegin(); it != rooms.constEnd(); ++it) { + if (!states.contains(it.key())) + continue; + + auto events = it.value().state().events(); + + for (auto event : it.value().timeline().events()) + events.append(event); + + RoomState local; + local.aliases = states[it.key()].aliases; + local.avatar = states[it.key()].avatar; + local.canonical_alias = states[it.key()].canonical_alias; + local.history_visibility = states[it.key()].history_visibility; + local.join_rules = states[it.key()].join_rules; + local.name = states[it.key()].name; + local.power_levels = states[it.key()].power_levels; + local.topic = states[it.key()].topic; + local.memberships = getMemberships(events); + + stateDiff.insert(it.key(), local); + } + + return stateDiff; +} + +using Memberships = QMap>; + +Memberships +ChatPage::getMemberships(const QJsonArray &events) const +{ + Memberships memberships; + + events::EventType ty; + + for (const auto &event : events) { + try { + ty = events::extractEventType(event.toObject()); + } catch (const DeserializationException &e) { + qWarning() << e.what() << event; + continue; + } + + if (!events::isStateEvent(ty)) + continue; + + try { + switch (ty) { + case events::EventType::RoomMember: { + events::StateEvent member; + member.deserialize(event); + memberships.insert(member.stateKey(), member); + break; + } + default: { + continue; + } + } + } catch (const DeserializationException &e) { + qWarning() << e.what() << event; + continue; + } + } + + return memberships; +}; + ChatPage::~ChatPage() {} diff --git a/src/RoomList.cc b/src/RoomList.cc index 73e85ea8..3a95cb17 100644 --- a/src/RoomList.cc +++ b/src/RoomList.cc @@ -140,7 +140,7 @@ RoomList::setInitialRooms(const QMap> &set return; } - for (auto it = states.constBegin(); it != states.constEnd(); it++) { + for (auto it = states.constBegin(); it != states.constEnd(); ++it) { auto room_id = it.key(); auto state = it.value(); @@ -194,7 +194,7 @@ RoomList::openLeaveRoomDialog(const QString &room_id) void RoomList::sync(const QMap &states) { - for (auto it = states.constBegin(); it != states.constEnd(); it++) { + for (auto it = states.constBegin(); it != states.constEnd(); ++it) { auto room_id = it.key(); auto state = it.value(); @@ -231,7 +231,7 @@ RoomList::highlightSelectedRoom(const QString &room_id) calculateUnreadMessageCount(); - for (auto it = rooms_.constBegin(); it != rooms_.constEnd(); it++) { + for (auto it = rooms_.constBegin(); it != rooms_.constEnd(); ++it) { if (it.key() != room_id) { it.value()->setPressedState(false); } else { diff --git a/src/RoomState.cc b/src/RoomState.cc index 8db9b2bc..2b8bcdba 100644 --- a/src/RoomState.cc +++ b/src/RoomState.cc @@ -22,6 +22,9 @@ namespace events = matrix::events; +RoomState::RoomState() {} +RoomState::RoomState(const QJsonArray &events) { updateFromEvents(events); } + void RoomState::resolveName() { diff --git a/src/Sync.cc b/src/Sync.cc index 965f7c3d..416fa0c6 100644 --- a/src/Sync.cc +++ b/src/Sync.cc @@ -83,7 +83,7 @@ Rooms::deserialize(const QJsonValue &data) auto join = object.value("join").toObject(); - for (auto it = join.constBegin(); it != join.constEnd(); it++) { + for (auto it = join.constBegin(); it != join.constEnd(); ++it) { JoinedRoom tmp_room; try { tmp_room.deserialize(it.value()); @@ -108,7 +108,7 @@ Rooms::deserialize(const QJsonValue &data) } auto leave = object.value("leave").toObject(); - for (auto it = leave.constBegin(); it != leave.constEnd(); it++) { + for (auto it = leave.constBegin(); it != leave.constEnd(); ++it) { LeftRoom tmp_room; try { diff --git a/src/TimelineView.cc b/src/TimelineView.cc index ee98fe72..354a725c 100644 --- a/src/TimelineView.cc +++ b/src/TimelineView.cc @@ -577,7 +577,7 @@ TimelineView::isPendingMessage(const QString &eventid, void TimelineView::removePendingMessage(const QString &eventid, const QString &body) { - for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); it++) { + for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); ++it) { int index = std::distance(pending_msgs_.begin(), it); if (it->event_id == eventid || it->body == body) { diff --git a/src/TimelineViewManager.cc b/src/TimelineViewManager.cc index 44b626ed..01e7d9ab 100644 --- a/src/TimelineViewManager.cc +++ b/src/TimelineViewManager.cc @@ -100,7 +100,7 @@ TimelineViewManager::clearAll() void TimelineViewManager::initialize(const Rooms &rooms) { - for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) { + for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); ++it) { addRoom(it.value(), it.key()); } } @@ -148,7 +148,7 @@ TimelineViewManager::addRoom(const QString &room_id) void TimelineViewManager::sync(const Rooms &rooms) { - for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) { + for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); ++it) { auto roomid = it.key(); if (!views_.contains(roomid)) { diff --git a/src/events/PowerLevelsEventContent.cc b/src/events/PowerLevelsEventContent.cc index 02a6ee71..c796f81f 100644 --- a/src/events/PowerLevelsEventContent.cc +++ b/src/events/PowerLevelsEventContent.cc @@ -54,14 +54,14 @@ PowerLevelsEventContent::deserialize(const QJsonValue &data) if (object.value("users").isObject()) { auto users = object.value("users").toObject(); - for (auto it = users.constBegin(); it != users.constEnd(); it++) + for (auto it = users.constBegin(); it != users.constEnd(); ++it) users_.insert(it.key(), it.value().toInt()); } if (object.value("events").isObject()) { auto events = object.value("events").toObject(); - for (auto it = events.constBegin(); it != events.constEnd(); it++) + for (auto it = events.constBegin(); it != events.constEnd(); ++it) events_.insert(it.key(), it.value().toInt()); } } @@ -83,10 +83,10 @@ PowerLevelsEventContent::serialize() const QJsonObject users; QJsonObject events; - for (auto it = users_.constBegin(); it != users_.constEnd(); it++) + for (auto it = users_.constBegin(); it != users_.constEnd(); ++it) users.insert(it.key(), it.value()); - for (auto it = events_.constBegin(); it != events_.constEnd(); it++) + for (auto it = events_.constBegin(); it != events_.constEnd(); ++it) events.insert(it.key(), it.value()); object["users"] = users; diff --git a/src/ui/LoadingIndicator.cc b/src/ui/LoadingIndicator.cc index 71312d32..f64151ce 100644 --- a/src/ui/LoadingIndicator.cc +++ b/src/ui/LoadingIndicator.cc @@ -41,7 +41,7 @@ LoadingIndicator::paintEvent(QPaintEvent *e) int capsuleRadius = (outerRadius - innerRadius) / 2; - for (int i = 0; i < 8; i++) { + for (int i = 0; i < 8; ++i) { QColor color = color_; color.setAlphaF(1.0f - (i / 8.0f)); diff --git a/src/ui/RippleOverlay.cc b/src/ui/RippleOverlay.cc index 26d432f6..a3567db2 100644 --- a/src/ui/RippleOverlay.cc +++ b/src/ui/RippleOverlay.cc @@ -48,7 +48,7 @@ RippleOverlay::paintEvent(QPaintEvent *event) if (use_clip_) painter.setClipPath(clip_path_); - for (auto it = ripples_.constBegin(); it != ripples_.constEnd(); it++) + for (auto it = ripples_.constBegin(); it != ripples_.constEnd(); ++it) paintRipple(&painter, *it); }