Improve UserListModel performance and other preparations
In a future patch I want to add support for viewing banned/invited users, and it's also been mentioned that UserListModel is quite slow too. The biggest cost is sorting the member list (power level and alphabetically) and this happened in a few different ways: * When the member list updated * The user switches rooms * Misc events such as the palette changing But this was pretty inefficient, because internally Quotient::Room keeps a list of members, and we kept re-sorting that same list. Our connections were also too broad and despite having signals for members joining and leaving we just reloaded the entire list anyway. So my new solution is to keep the list persistently sorted in NeoChatRoom, and reload that in UserListModel. This model also keeps track of *all* members - including ones that left - which will be used for the aforementioned feature. So UserFilterModel now filters out only the joined members, and that will be configurable in the future. I also added two new roles to UserListModel for membership and color respectively (which makes some dead code useful again) and fixed us overwriting the built-in Qt roles accidentally.
This commit is contained in:
@@ -11,6 +11,9 @@ bool UserFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceP
|
|||||||
if (!m_allowEmpty && m_filterText.length() < 1) {
|
if (!m_allowEmpty && m_filterText.length() < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::MembershipRole).value<Quotient::Membership>() != Quotient::Membership::Join) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
return sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||||
|| sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::UserIdRole).toString().contains(m_filterText, Qt::CaseInsensitive);
|
|| sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::UserIdRole).toString().contains(m_filterText, Qt::CaseInsensitive);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,17 +40,12 @@ void UserListModel::setRoom(NeoChatRoom *room)
|
|||||||
|
|
||||||
if (m_currentRoom) {
|
if (m_currentRoom) {
|
||||||
connect(m_currentRoom, &Room::memberJoined, this, &UserListModel::memberJoined);
|
connect(m_currentRoom, &Room::memberJoined, this, &UserListModel::memberJoined);
|
||||||
connect(m_currentRoom, &Room::memberLeft, this, &UserListModel::memberLeft);
|
|
||||||
connect(m_currentRoom, &Room::memberNameUpdated, this, [this](RoomMember member) {
|
connect(m_currentRoom, &Room::memberNameUpdated, this, [this](RoomMember member) {
|
||||||
refreshMember(member, {DisplayNameRole});
|
refreshMember(member, {DisplayNameRole});
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
|
connect(m_currentRoom, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
|
||||||
refreshMember(member, {AvatarRole});
|
refreshMember(member, {AvatarRole});
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::memberListChanged, this, [this]() {
|
|
||||||
// this is slow
|
|
||||||
UserListModel::refreshAllMembers();
|
|
||||||
});
|
|
||||||
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
||||||
setRoom(nullptr);
|
setRoom(nullptr);
|
||||||
});
|
});
|
||||||
@@ -79,7 +74,7 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
|||||||
"users.count()";
|
"users.count()";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto memberId = m_members.at(index.row());
|
const auto &memberId = m_members.at(index.row());
|
||||||
if (role == DisplayNameRole) {
|
if (role == DisplayNameRole) {
|
||||||
return m_currentRoom->member(memberId).disambiguatedName();
|
return m_currentRoom->member(memberId).disambiguatedName();
|
||||||
}
|
}
|
||||||
@@ -124,6 +119,12 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
|||||||
if (role == IsCreatorRole) {
|
if (role == IsCreatorRole) {
|
||||||
return m_currentRoom->isCreator(memberId);
|
return m_currentRoom->isCreator(memberId);
|
||||||
}
|
}
|
||||||
|
if (role == MembershipRole) {
|
||||||
|
return QVariant::fromValue(m_currentRoom->member(memberId).membershipState());
|
||||||
|
}
|
||||||
|
if (role == ColorRole) {
|
||||||
|
return m_currentRoom->member(memberId).color();
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -139,7 +140,8 @@ int UserListModel::rowCount(const QModelIndex &parent) const
|
|||||||
bool UserListModel::event(QEvent *event)
|
bool UserListModel::event(QEvent *event)
|
||||||
{
|
{
|
||||||
if (event->type() == QEvent::ApplicationPaletteChange) {
|
if (event->type() == QEvent::ApplicationPaletteChange) {
|
||||||
refreshAllMembers();
|
// Quotient::RoomMember::color needs to be recalculated for the new palette
|
||||||
|
Q_EMIT dataChanged(index(0, 0), index(m_members.size(), 0), {ColorRole});
|
||||||
}
|
}
|
||||||
return QObject::event(event);
|
return QObject::event(event);
|
||||||
}
|
}
|
||||||
@@ -156,18 +158,6 @@ void UserListModel::memberJoined(const Quotient::RoomMember &member)
|
|||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserListModel::memberLeft(const Quotient::RoomMember &member)
|
|
||||||
{
|
|
||||||
auto pos = findUserPos(member);
|
|
||||||
if (pos != m_members.size()) {
|
|
||||||
beginRemoveRows(QModelIndex(), pos, pos);
|
|
||||||
m_members.removeAt(pos);
|
|
||||||
endRemoveRows();
|
|
||||||
} else {
|
|
||||||
qWarning() << "Trying to remove a room member not in the user list";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UserListModel::refreshMember(const Quotient::RoomMember &member, const QList<int> &roles)
|
void UserListModel::refreshMember(const Quotient::RoomMember &member, const QList<int> &roles)
|
||||||
{
|
{
|
||||||
auto pos = findUserPos(member);
|
auto pos = findUserPos(member);
|
||||||
@@ -181,21 +171,10 @@ void UserListModel::refreshMember(const Quotient::RoomMember &member, const QLis
|
|||||||
void UserListModel::refreshAllMembers()
|
void UserListModel::refreshAllMembers()
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
|
||||||
if (m_currentRoom != nullptr) {
|
if (m_currentRoom != nullptr) {
|
||||||
m_members = m_currentRoom->joinedMemberIds();
|
m_members = m_currentRoom->sortedMemberIds();
|
||||||
MemberSorter sorter;
|
} else {
|
||||||
std::sort(m_members.begin(), m_members.end(), [&sorter, this](const auto &left, const auto &right) {
|
m_members.clear();
|
||||||
const auto leftPl = m_currentRoom->memberEffectivePowerLevel(left);
|
|
||||||
const auto rightPl = m_currentRoom->memberEffectivePowerLevel(right);
|
|
||||||
if (leftPl > rightPl) {
|
|
||||||
return true;
|
|
||||||
} else if (rightPl > leftPl) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sorter(m_currentRoom->member(left), m_currentRoom->member(right));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
endResetModel();
|
endResetModel();
|
||||||
Q_EMIT usersRefreshed();
|
Q_EMIT usersRefreshed();
|
||||||
@@ -211,8 +190,8 @@ int UserListModel::findUserPos(const QString &userId) const
|
|||||||
if (!m_currentRoom) {
|
if (!m_currentRoom) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const auto pos = std::find_if(m_members.cbegin(), m_members.cend(), [&userId](const QString &memberId) {
|
const auto pos = std::find_if(m_members.cbegin(), m_members.cend(), [&userId](const QString &member) {
|
||||||
return userId == memberId;
|
return userId == member;
|
||||||
});
|
});
|
||||||
return pos - m_members.cbegin();
|
return pos - m_members.cbegin();
|
||||||
}
|
}
|
||||||
@@ -228,6 +207,8 @@ QHash<int, QByteArray> UserListModel::roleNames() const
|
|||||||
roles[PowerLevelRole] = "powerLevel";
|
roles[PowerLevelRole] = "powerLevel";
|
||||||
roles[PowerLevelStringRole] = "powerLevelString";
|
roles[PowerLevelStringRole] = "powerLevelString";
|
||||||
roles[IsCreatorRole] = "isCreator";
|
roles[IsCreatorRole] = "isCreator";
|
||||||
|
roles[MembershipRole] = "membership";
|
||||||
|
roles[ColorRole] = "color";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,12 +43,14 @@ public:
|
|||||||
*/
|
*/
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
DisplayNameRole = Qt::DisplayRole, /**< The user's display name in the current room. */
|
DisplayNameRole = Qt::DisplayRole, /**< The user's display name in the current room. */
|
||||||
UserIdRole, /**< Matrix ID of the user. */
|
UserIdRole = Qt::UserRole, /**< Matrix ID of the user. */
|
||||||
AvatarRole, /**< The source URL for the user's avatar in the current room. */
|
AvatarRole, /**< The source URL for the user's avatar in the current room. */
|
||||||
ObjectRole, /**< The QObject for the user. */
|
ObjectRole, /**< The QObject for the user. */
|
||||||
PowerLevelRole, /**< The user's power level in the current room. */
|
PowerLevelRole, /**< The user's power level in the current room. */
|
||||||
PowerLevelStringRole, /**< The name of the user's power level in the current room. */
|
PowerLevelStringRole, /**< The name of the user's power level in the current room. */
|
||||||
IsCreatorRole, /**< Whether this user is considered a creator of the current room. */
|
IsCreatorRole, /**< Whether this user is considered a creator of the current room. */
|
||||||
|
MembershipRole, /**< The membership state of this user. */
|
||||||
|
ColorRole, /**< The color of this user. */
|
||||||
};
|
};
|
||||||
Q_ENUM(EventRoles)
|
Q_ENUM(EventRoles)
|
||||||
|
|
||||||
@@ -89,7 +91,6 @@ protected:
|
|||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void memberJoined(const Quotient::RoomMember &member);
|
void memberJoined(const Quotient::RoomMember &member);
|
||||||
void memberLeft(const Quotient::RoomMember &member);
|
|
||||||
void refreshMember(const Quotient::RoomMember &member, const QList<int> &roles = {});
|
void refreshMember(const Quotient::RoomMember &member, const QList<int> &roles = {});
|
||||||
void refreshAllMembers();
|
void refreshAllMembers();
|
||||||
|
|
||||||
|
|||||||
@@ -172,6 +172,10 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
Q_ASSERT(neochatconnection);
|
Q_ASSERT(neochatconnection);
|
||||||
connect(neochatconnection, &NeoChatConnection::globalUrlPreviewEnabledChanged, this, &NeoChatRoom::urlPreviewEnabledChanged);
|
connect(neochatconnection, &NeoChatConnection::globalUrlPreviewEnabledChanged, this, &NeoChatRoom::urlPreviewEnabledChanged);
|
||||||
connect(this, &Room::fullyReadMarkerMoved, this, &NeoChatRoom::invalidateLastUnreadHighlightId);
|
connect(this, &Room::fullyReadMarkerMoved, this, &NeoChatRoom::invalidateLastUnreadHighlightId);
|
||||||
|
|
||||||
|
// Wait until the initial member list is available before sorting
|
||||||
|
connect(this, &Room::memberListChanged, this, &NeoChatRoom::refreshAllMembers, Qt::SingleShotConnection);
|
||||||
|
connect(this, &Room::memberJoined, this, &NeoChatRoom::insertMemberSorted);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::visible() const
|
bool NeoChatRoom::visible() const
|
||||||
@@ -1910,6 +1914,34 @@ void NeoChatRoom::invalidateLastUnreadHighlightId(const QString &fromEventId, co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::refreshAllMembers()
|
||||||
|
{
|
||||||
|
m_sortedMemberIds = memberIds();
|
||||||
|
|
||||||
|
MemberSorter sorter;
|
||||||
|
std::ranges::sort(m_sortedMemberIds, [this, &sorter](const auto &left, const auto &right) {
|
||||||
|
const auto leftPl = memberEffectivePowerLevel(left);
|
||||||
|
const auto rightPl = memberEffectivePowerLevel(right);
|
||||||
|
if (leftPl > rightPl) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (rightPl > leftPl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorter(left, right);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::insertMemberSorted(const Quotient::RoomMember member)
|
||||||
|
{
|
||||||
|
if (m_sortedMemberIds.contains(member.id())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sortedMemberIds.append(member.id());
|
||||||
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::spaceHasUnreadMessages() const
|
bool NeoChatRoom::spaceHasUnreadMessages() const
|
||||||
{
|
{
|
||||||
if (!isSpace()) {
|
if (!isSpace()) {
|
||||||
@@ -1924,6 +1956,11 @@ void NeoChatRoom::markAllChildrenMessagesAsRead()
|
|||||||
if (isSpace()) {
|
if (isSpace()) {
|
||||||
SpaceHierarchyCache::instance().markAllChildrenMessagesAsRead(id());
|
SpaceHierarchyCache::instance().markAllChildrenMessagesAsRead(id());
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
QList<QString> NeoChatRoom::sortedMemberIds() const
|
||||||
|
{
|
||||||
|
return m_sortedMemberIds;
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_neochatroom.cpp"
|
#include "moc_neochatroom.cpp"
|
||||||
|
|||||||
@@ -671,6 +671,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
Q_INVOKABLE void markAllChildrenMessagesAsRead();
|
Q_INVOKABLE void markAllChildrenMessagesAsRead();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return List of members in this room, sorted by power level and then by name.
|
||||||
|
*/
|
||||||
|
QList<QString> sortedMemberIds() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_visible = false;
|
bool m_visible = false;
|
||||||
|
|
||||||
@@ -707,6 +712,7 @@ private:
|
|||||||
void loadPinnedMessage();
|
void loadPinnedMessage();
|
||||||
|
|
||||||
QString m_lastUnreadHighlightId;
|
QString m_lastUnreadHighlightId;
|
||||||
|
QList<QString> m_sortedMemberIds;
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void updatePushNotificationState(QString type);
|
void updatePushNotificationState(QString type);
|
||||||
@@ -715,6 +721,10 @@ private Q_SLOTS:
|
|||||||
|
|
||||||
void invalidateLastUnreadHighlightId(const QString &fromEventId, const QString &toEventId);
|
void invalidateLastUnreadHighlightId(const QString &fromEventId, const QString &toEventId);
|
||||||
|
|
||||||
|
void refreshAllMembers();
|
||||||
|
|
||||||
|
void insertMemberSorted(Quotient::RoomMember member);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void cachedInputChanged();
|
void cachedInputChanged();
|
||||||
void busyChanged();
|
void busyChanged();
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ QQC2.ScrollView {
|
|||||||
required property url avatar
|
required property url avatar
|
||||||
required property int powerLevel
|
required property int powerLevel
|
||||||
required property string powerLevelString
|
required property string powerLevelString
|
||||||
|
required property color color
|
||||||
|
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 2
|
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
@@ -304,6 +305,7 @@ QQC2.ScrollView {
|
|||||||
}
|
}
|
||||||
source: userDelegate.avatar
|
source: userDelegate.avatar
|
||||||
name: userDelegate.userId
|
name: userDelegate.userId
|
||||||
|
color: userDelegate.color
|
||||||
|
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user