From ef9ebe3fd33a9596c5395860fcfaad1d02535f91 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 15 Jun 2022 02:13:17 +0200 Subject: [PATCH] Speedup startup by caching last message timestamp The cache is only approximate, i.e. it doesn't skip edits and similar, but this might be good enough? Also no migration right now. Speeds up startup by about 5x on my system. Half the startup time is now loading the powerlevels for each room. We can probably lazily load those too in the future. --- src/Cache.cpp | 88 +++++++++++++++++++++++++++------- src/CacheStructs.h | 6 ++- src/timeline/RoomlistModel.cpp | 4 +- src/timeline/TimelineModel.cpp | 16 +++++-- src/timeline/TimelineModel.h | 4 +- 5 files changed, 95 insertions(+), 23 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 90c04e61..2b83fbb5 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1570,6 +1570,47 @@ Cache::updateState(const std::string &room, const mtx::responses::StateEvents &s txn.commit(); } +namespace { +template +auto +isMessage(const mtx::events::RoomEvent &e) + -> std::enable_if_t::value, bool> +{ + return true; +} + +template +auto +isMessage(const mtx::events::Event &) +{ + return false; +} + +template +auto +isMessage(const mtx::events::EncryptedEvent &) +{ + return true; +} + +auto +isMessage(const mtx::events::RoomEvent &) +{ + return true; +} + +auto +isMessage(const mtx::events::RoomEvent &) +{ + return true; +} +auto +isMessage(const mtx::events::RoomEvent &) +{ + return true; +} +} + void Cache::saveState(const mtx::responses::Sync &res) { @@ -1623,6 +1664,25 @@ Cache::saveState(const mtx::responses::Sync &res) saveTimelineMessages(txn, eventsDb, room.first, room.second.timeline); RoomInfo updatedInfo; + { + // retrieve the old tags and modification ts + std::string_view data; + if (roomsDb_.get(txn, room.first, data)) { + try { + RoomInfo tmp = json::parse(std::string_view(data.data(), data.size())); + updatedInfo.tags = std::move(tmp.tags); + + updatedInfo.approximate_last_modification_ts = + tmp.approximate_last_modification_ts; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", + room.first, + std::string(data.data(), data.size()), + e.what()); + } + } + } + updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString(); updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString(); updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString(); @@ -1666,7 +1726,6 @@ Cache::saveState(const mtx::responses::Sync &res) rooms_with_space_updates.insert(room.first); } - bool has_new_tags = false; // Process the account_data associated with this room if (!room.second.account_data.events.empty()) { auto accountDataDb = getAccountDataDb(txn, room.first); @@ -1691,7 +1750,8 @@ Cache::saveState(const mtx::responses::Sync &res) // for tag events if (std::holds_alternative>(evt)) { auto tags_evt = std::get>(evt); - has_new_tags = true; + + updatedInfo.tags.clear(); for (const auto &tag : tags_evt.content.tags) { updatedInfo.tags.push_back(tag.first); } @@ -1704,20 +1764,12 @@ Cache::saveState(const mtx::responses::Sync &res) } } } - if (!has_new_tags) { - // retrieve the old tags, they haven't changed - std::string_view data; - if (roomsDb_.get(txn, room.first, data)) { - try { - RoomInfo tmp = json::parse(std::string_view(data.data(), data.size())); - updatedInfo.tags = tmp.tags; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", - room.first, - std::string(data.data(), data.size()), - e.what()); - } - } + + for (const auto &e : room.second.timeline.events) { + if (!std::visit([](const auto &e) -> bool { return isMessage(e); }, e)) + continue; + updatedInfo.approximate_last_modification_ts = + mtx::accessors::origin_server_ts(e).toMSecsSinceEpoch(); } roomsDb_.put(txn, room.first, json(updatedInfo).dump()); @@ -4709,6 +4761,8 @@ to_json(json &j, const RoomInfo &info) j["join_rule"] = info.join_rule; j["guest_access"] = info.guest_access; + j["app_l_ts"] = info.approximate_last_modification_ts; + j["notification_count"] = info.notification_count; j["highlight_count"] = info.highlight_count; @@ -4732,6 +4786,8 @@ from_json(const json &j, RoomInfo &info) info.join_rule = j.at("join_rule"); info.guest_access = j.at("guest_access"); + info.approximate_last_modification_ts = j.value("app_l_ts", 0); + info.notification_count = j.value("notification_count", 0); info.highlight_count = j.value("highlight_count", 0); diff --git a/src/CacheStructs.h b/src/CacheStructs.h index b3917058..43055145 100644 --- a/src/CacheStructs.h +++ b/src/CacheStructs.h @@ -49,7 +49,7 @@ struct DescInfo QString userid; QString body; QString descriptiveTime; - uint64_t timestamp; + uint64_t timestamp = 0; QDateTime datetime; }; @@ -89,6 +89,10 @@ struct RoomInfo //! The list of tags associated with this room std::vector tags; + //! An approximate timestamp of when the last message was sent in the room. + //! Use the TimelineModel::lastMessage for an accurate timestamp. + uint64_t approximate_last_modification_ts = 0; + uint16_t highlight_count = 0; uint16_t notification_count = 0; }; diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index 1e0b4c23..972f061d 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -121,7 +121,7 @@ RoomlistModel::data(const QModelIndex &index, int role) const case Roles::Time: return room->lastMessage().descriptiveTime; case Roles::Timestamp: - return QVariant{static_cast(room->lastMessage().timestamp)}; + return QVariant{static_cast(room->lastMessageTimestamp())}; case Roles::HasUnreadMessages: return this->roomReadStatus.count(roomid) && this->roomReadStatus.at(roomid); case Roles::HasLoudNotification: @@ -333,7 +333,7 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification) emit totalUnreadMessageCountUpdated(total_unread_msgs); }); - newRoom->updateLastMessage(); + // newRoom->updateLastMessage(); std::vector previewsToAdd; if (newRoom->isSpace()) { diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 142ca793..578d63b7 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -353,14 +353,13 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , manager_(manager) , permissions_{room_id_} { - lastMessage_.timestamp = 0; - this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); auto roomInfo = cache::singleRoomInfo(room_id_.toStdString()); this->isSpace_ = roomInfo.is_space; this->notification_count = roomInfo.notification_count; this->highlight_count = roomInfo.highlight_count; + lastMessage_.timestamp = roomInfo.approximate_last_modification_ts; // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it // needs to be @@ -1025,10 +1024,21 @@ isYourJoin(const mtx::events::Event &) return false; } +DescInfo +TimelineModel::lastMessage() const +{ + if (lastMessage_.event_id.isEmpty()) + QTimer::singleShot(0, this, &TimelineModel::updateLastMessage); + + return lastMessage_; +} + void TimelineModel::updateLastMessage() { - for (auto it = events.size() - 1; it >= 0; --it) { + // only try to generate a preview for the last 1000 messages + auto end = std::max(events.size() - 1001, 0); + for (auto it = events.size() - 1; it >= end; --it) { auto event = events.get(it, decryptDescription); if (!event) continue; diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index dae64094..ec9a34f1 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -311,7 +311,9 @@ public: void sendMessageEvent(const T &content, mtx::events::EventType eventType); RelatedInfo relatedInfo(const QString &id); - DescInfo lastMessage() const { return lastMessage_; } + DescInfo lastMessage() const; + uint64_t lastMessageTimestamp() const { return lastMessage_.timestamp; } + bool isSpace() const { return isSpace_; } bool isEncrypted() const { return isEncrypted_; } crypto::Trust trustlevel() const;