Update presence dynamically and reduce allocations

This commit is contained in:
Nicolas Werner 2021-12-30 04:54:03 +01:00
parent 4428388b3f
commit 9a9dbda571
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
11 changed files with 145 additions and 79 deletions

View file

@ -328,6 +328,7 @@ set(SRC_FILES
src/timeline/TimelineModel.cpp
src/timeline/DelegateChooser.cpp
src/timeline/Permissions.cpp
src/timeline/PresenceEmitter.cpp
src/timeline/RoomlistModel.cpp
# UI components
@ -535,6 +536,7 @@ qt5_wrap_cpp(MOC_HEADERS
src/timeline/TimelineModel.h
src/timeline/DelegateChooser.h
src/timeline/Permissions.h
src/timeline/PresenceEmitter.h
src/timeline/RoomlistModel.h
# UI components

View file

@ -87,14 +87,18 @@ Rectangle {
}
Rectangle {
id: onlineIndicator
anchors.bottom: avatar.bottom
anchors.right: avatar.right
visible: !!userid
height: avatar.height / 6
width: height
radius: Settings.avatarCircles ? height / 2 : height / 8
color: {
switch (TimelineManager.userPresence(userid)) {
color: updatePresence()
function updatePresence() {
switch (Presence.userPresence(userid)) {
case "online":
return "#00cc66";
case "unavailable":
@ -102,7 +106,15 @@ Rectangle {
case "offline":
default:
// return "#a82353" don't show anything if offline, since it is confusing, if presence is disabled
"transparent";
return "transparent";
}
}
Connections {
target: Presence
function onPresenceChanged(id) {
if (id == userid) onlineIndicator.color = onlineIndicator.updatePresence();
}
}
}

View file

@ -329,12 +329,21 @@ ScrollView {
}
Label {
id: statusMsg
color: Nheko.colors.buttonText
text: TimelineManager.userStatus(userId)
text: Presence.userStatus(userId)
textFormat: Text.PlainText
elide: Text.ElideRight
width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - Nheko.avatarSize
font.italic: true
Connections {
target: Presence
function onPresenceChanged(id) {
if (id == userId) statusMsg.text = Presence.userStatus(userId);
}
}
}
}

View file

@ -3897,53 +3897,26 @@ Cache::avatarUrl(const QString &room_id, const QString &user_id)
return QString();
}
mtx::presence::PresenceState
Cache::presenceState(const std::string &user_id)
mtx::events::presence::Presence
Cache::presence(const std::string &user_id)
{
if (user_id.empty())
return {};
std::string_view presenceVal;
auto txn = lmdb::txn::begin(env_);
auto txn = ro_txn(env_);
auto db = getPresenceDb(txn);
auto res = db.get(txn, user_id, presenceVal);
mtx::presence::PresenceState state = mtx::presence::offline;
mtx::events::presence::Presence presence_{};
presence_.presence = mtx::presence::PresenceState::offline;
if (res) {
mtx::events::presence::Presence presence =
json::parse(std::string_view(presenceVal.data(), presenceVal.size()));
state = presence.presence;
presence_ = json::parse(std::string_view(presenceVal.data(), presenceVal.size()));
}
txn.commit();
return state;
}
std::string
Cache::statusMessage(const std::string &user_id)
{
if (user_id.empty())
return {};
std::string_view presenceVal;
auto txn = lmdb::txn::begin(env_);
auto db = getPresenceDb(txn);
auto res = db.get(txn, user_id, presenceVal);
std::string status_msg;
if (res) {
mtx::events::presence::Presence presence = json::parse(presenceVal);
status_msg = presence.status_msg;
}
txn.commit();
return status_msg;
return presence_;
}
void
@ -4747,17 +4720,12 @@ avatarUrl(const QString &room_id, const QString &user_id)
return instance_->avatarUrl(room_id, user_id);
}
mtx::presence::PresenceState
presenceState(const std::string &user_id)
mtx::events::presence::Presence
presence(const std::string &user_id)
{
if (!instance_)
return {};
return instance_->presenceState(user_id);
}
std::string
statusMessage(const std::string &user_id)
{
return instance_->statusMessage(user_id);
return instance_->presence(user_id);
}
// user cache stores user keys

View file

@ -38,10 +38,8 @@ QString
avatarUrl(const QString &room_id, const QString &user_id);
// presence
mtx::presence::PresenceState
presenceState(const std::string &user_id);
std::string
statusMessage(const std::string &user_id);
mtx::events::presence::Presence
presence(const std::string &user_id);
// user cache stores user keys
std::optional<UserKeyCache>

View file

@ -43,8 +43,7 @@ public:
QString avatarUrl(const QString &room_id, const QString &user_id);
// presence
mtx::presence::PresenceState presenceState(const std::string &user_id);
std::string statusMessage(const std::string &user_id);
mtx::events::presence::Presence presence(const std::string &user_id);
// user cache stores user keys
std::map<std::string, std::optional<UserKeyCache>>

View file

@ -877,7 +877,7 @@ ChatPage::receivedSessionKey(const std::string &room_id, const std::string &sess
QString
ChatPage::status() const
{
return QString::fromStdString(cache::statusMessage(utils::localUser().toStdString()));
return QString::fromStdString(cache::presence(utils::localUser().toStdString()).status_msg);
}
void

View file

@ -0,0 +1,73 @@
#include "PresenceEmitter.h"
#include <QCache>
#include <Utils.h>
#include "Cache.h"
namespace {
struct CacheEntry
{
QString status;
mtx::presence::PresenceState state;
};
}
static QCache<QString, CacheEntry> presences;
static QString
presenceToStr(mtx::presence::PresenceState state)
{
switch (state) {
case mtx::presence::PresenceState::offline:
return QStringLiteral("offline");
case mtx::presence::PresenceState::unavailable:
return QStringLiteral("unavailable");
case mtx::presence::PresenceState::online:
default:
return QStringLiteral("online");
}
}
static CacheEntry *
pullPresence(const QString &id)
{
auto p = cache::presence(id.toStdString());
auto c = new CacheEntry{
utils::replaceEmoji(QString::fromStdString(p.status_msg).toHtmlEscaped()), p.presence};
presences.insert(id, c);
return c;
}
void
PresenceEmitter::sync(
const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presences_)
{
for (const auto &p : presences_) {
auto id = QString::fromStdString(p.sender);
presences.remove(id);
emit presenceChanged(std::move(id));
}
}
QString
PresenceEmitter::userPresence(QString id) const
{
if (id.isEmpty())
return {};
else if (auto p = presences[id])
return presenceToStr(p->state);
else
return presenceToStr(pullPresence(id)->state);
}
QString
PresenceEmitter::userStatus(QString id) const
{
if (id.isEmpty())
return {};
else if (auto p = presences[id])
return p->status;
else
return pullPresence(id)->status;
}

View file

@ -0,0 +1,26 @@
#pragma once
#include <QObject>
#include <vector>
#include <mtx/events.hpp>
#include <mtx/events/presence.hpp>
class PresenceEmitter : public QObject
{
Q_OBJECT
public:
PresenceEmitter(QObject *p = nullptr)
: QObject(p)
{}
void sync(const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presences);
Q_INVOKABLE QString userPresence(QString id) const;
Q_INVOKABLE QString userStatus(QString id) const;
signals:
void presenceChanged(QString userid);
};

View file

@ -124,29 +124,6 @@ TimelineViewManager::userColor(QString id, QColor background)
return userColors.value(idx);
}
QString
TimelineViewManager::userPresence(QString id) const
{
if (id.isEmpty())
return {};
else
switch (cache::presenceState(id.toStdString())) {
case mtx::presence::PresenceState::offline:
return QStringLiteral("offline");
case mtx::presence::PresenceState::unavailable:
return QStringLiteral("unavailable");
case mtx::presence::PresenceState::online:
default:
return QStringLiteral("online");
}
}
QString
TimelineViewManager::userStatus(QString id) const
{
return QString::fromStdString(cache::statusMessage(id.toStdString()));
}
TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent)
: QObject(parent)
, imgProvider(new MxcImageProvider())
@ -157,6 +134,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
, communities_(new CommunitiesModel(this))
, callManager_(callManager)
, verificationManager_(new VerificationManager(this))
, presenceEmitter(new PresenceEmitter(this))
{
qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>();
qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>();
@ -280,6 +258,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
return new Nheko();
});
qmlRegisterSingletonInstance("im.nheko", 1, 0, "VerificationManager", verificationManager_);
qmlRegisterSingletonInstance("im.nheko", 1, 0, "Presence", presenceEmitter);
qmlRegisterSingletonType<SelfVerificationStatus>(
"im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * {
auto ptr = new SelfVerificationStatus();
@ -407,6 +386,7 @@ TimelineViewManager::sync(const mtx::responses::Sync &sync_)
{
this->rooms_->sync(sync_);
this->communities_->sync(sync_);
this->presenceEmitter->sync(sync_.presence);
if (isInitialSync_) {
this->isInitialSync_ = false;

View file

@ -24,6 +24,7 @@
#include "emoji/Provider.h"
#include "encryption/VerificationManager.h"
#include "timeline/CommunitiesModel.h"
#include "timeline/PresenceEmitter.h"
#include "timeline/RoomlistModel.h"
#include "voip/CallManager.h"
#include "voip/WebRTCSession.h"
@ -64,9 +65,6 @@ public:
Q_INVOKABLE QString escapeEmoji(QString str) const;
Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); }
Q_INVOKABLE QString userPresence(QString id) const;
Q_INVOKABLE QString userStatus(QString id) const;
Q_INVOKABLE void openRoomMembers(TimelineModel *room);
Q_INVOKABLE void openRoomSettings(QString room_id);
Q_INVOKABLE void openInviteUsers(QString roomId);
@ -146,6 +144,7 @@ private:
// don't move this above the rooms_
CallManager *callManager_ = nullptr;
VerificationManager *verificationManager_ = nullptr;
PresenceEmitter *presenceEmitter = nullptr;
QHash<QPair<QString, quint64>, QColor> userColors;
};