Skip to content

Commit

Permalink
optimize chatter list (Chatterino#2814)
Browse files Browse the repository at this point in the history
* optimize chatter list

* changelog

* Fix tests

Co-authored-by: Rasmus Karlsson <[email protected]>
  • Loading branch information
fourtf and pajlada authored May 24, 2021
1 parent 7659dc2 commit 3fddafb
Show file tree
Hide file tree
Showing 19 changed files with 372 additions and 533 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Minor: Added moderation buttons to search popup when searching in a split with moderation mode enabled. (#2148, #2803)
- Minor: Made "#channel" in `/mentions` tab show in usercards and in the search popup. (#2802)
- Minor: Added settings to disable custom FrankerFaceZ VIP/mod badges. (#2693, #2759)
- Minor: Limit the number of recent chatters to improve memory usage and reduce freezes. (#2796, #2814)
- Minor: Added `/popout` command. Usage: `/popout [channel]`. It opens browser chat for the provided channel. Can also be used without arguments to open current channels browser chat. (#2556, #2812)
- Bugfix: Fixed FFZ emote links for global emotes (#2807, #2808)

Expand Down
4 changes: 2 additions & 2 deletions chatterino.pro
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ SOURCES += \
src/common/Args.cpp \
src/common/Channel.cpp \
src/common/ChannelChatters.cpp \
src/common/ChatterSet.cpp \
src/common/ChatterinoSetting.cpp \
src/common/CompletionModel.cpp \
src/common/Credentials.cpp \
Expand All @@ -139,7 +140,6 @@ SOURCES += \
src/common/NetworkPrivate.cpp \
src/common/NetworkRequest.cpp \
src/common/NetworkResult.cpp \
src/common/UsernameSet.cpp \
src/common/Version.cpp \
src/common/WindowDescriptors.cpp \
src/common/QLogging.cpp \
Expand Down Expand Up @@ -340,6 +340,7 @@ HEADERS += \
src/common/Atomic.hpp \
src/common/Channel.hpp \
src/common/ChannelChatters.hpp \
src/common/ChatterSet.hpp \
src/common/ChatterinoSetting.hpp \
src/common/Common.hpp \
src/common/CompletionModel.hpp \
Expand All @@ -363,7 +364,6 @@ HEADERS += \
src/common/SignalVectorModel.hpp \
src/common/Singleton.hpp \
src/common/UniqueAccess.hpp \
src/common/UsernameSet.hpp \
src/common/Version.hpp \
src/common/QLogging.hpp \
src/controllers/accounts/Account.hpp \
Expand Down
35 changes: 35 additions & 0 deletions lib/lrucache/.clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Language: Cpp

AccessModifierOffset: -4
AlignEscapedNewlinesLeft: true
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLambdasOnASingleLine: Empty
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakBeforeMultilineStrings: false
BasedOnStyle: Google
BraceWrapping: {
AfterClass: 'true'
AfterControlStatement: 'true'
AfterFunction: 'true'
AfterNamespace: 'false'
BeforeCatch: 'true'
BeforeElse: 'true'
}
BreakBeforeBraces: Custom
BreakConstructorInitializersBeforeComma: true
ColumnLimit: 80
ConstructorInitializerAllOnOneLineOrOnePerLine: false
DerivePointerBinding: false
FixNamespaceComments: true
IndentCaseLabels: true
IndentWidth: 4
IndentWrappedFunctionNames: true
IndentPPDirectives: AfterHash
IncludeBlocks: Preserve
NamespaceIndentation: Inner
PointerBindsToType: false
SpacesBeforeTrailingComments: 2
Standard: Auto
ReflowComments: false
153 changes: 98 additions & 55 deletions lib/lrucache/lrucache/lrucache.hpp
Original file line number Diff line number Diff line change
@@ -1,72 +1,115 @@
/*
/*
* File: lrucache.hpp
* Author: Alexander Ponomarev
* Original Author: Alexander Ponomarev
*
* Created on June 20, 2013, 5:09 PM
*/

#ifndef _LRUCACHE_HPP_INCLUDED_
#define _LRUCACHE_HPP_INCLUDED_
#define _LRUCACHE_HPP_INCLUDED_

#include <unordered_map>
#include <list>
#include <cstddef>
#include <list>
#include <stdexcept>
#include <unordered_map>

namespace cache {

template<typename key_t, typename value_t>
class lru_cache {
template <typename key_t, typename value_t>
class lru_cache
{
public:
typedef typename std::pair<key_t, value_t> key_value_pair_t;
typedef typename std::list<key_value_pair_t>::iterator list_iterator_t;

lru_cache(size_t max_size) :
_max_size(max_size) {
}

void put(const key_t& key, const value_t& value) {
auto it = _cache_items_map.find(key);
_cache_items_list.push_front(key_value_pair_t(key, value));
if (it != _cache_items_map.end()) {
_cache_items_list.erase(it->second);
_cache_items_map.erase(it);
}
_cache_items_map[key] = _cache_items_list.begin();

if (_cache_items_map.size() > _max_size) {
auto last = _cache_items_list.end();
last--;
_cache_items_map.erase(last->first);
_cache_items_list.pop_back();
}
}

const value_t& get(const key_t& key) {
auto it = _cache_items_map.find(key);
if (it == _cache_items_map.end()) {
throw std::range_error("There is no such key in cache");
} else {
_cache_items_list.splice(_cache_items_list.begin(), _cache_items_list, it->second);
return it->second->second;
}
}

bool exists(const key_t& key) const {
return _cache_items_map.find(key) != _cache_items_map.end();
}

size_t size() const {
return _cache_items_map.size();
}

typedef typename std::pair<key_t, value_t> key_value_pair_t;
typedef typename std::list<key_value_pair_t>::iterator list_iterator_t;

lru_cache(size_t max_size)
: _max_size(max_size)
{
}

// copy doesn't make sense since we reference the linked list elements directly
lru_cache(lru_cache<key_t, value_t> &) = delete;
lru_cache<key_t, value_t> &operator=(lru_cache<key_t, value_t> &) = delete;

// move
lru_cache(lru_cache<key_t, value_t> &&other)
: _cache_items_list(std::move(other._cache_items_list))
, _cache_items_map(std::move(other._cache_items_map))
{
other._cache_items_list.clear();
other._cache_items_map.clear();
}

lru_cache<key_t, value_t> &operator=(lru_cache<key_t, value_t> &&other)
{
_cache_items_list = std::move(other._cache_items_list);
_cache_items_map = std::move(other._cache_items_map);
other._cache_items_list.clear();
other._cache_items_map.clear();
return *this;
}

void put(const key_t &key, const value_t &value)
{
auto it = _cache_items_map.find(key);
_cache_items_list.push_front(key_value_pair_t(key, value));
if (it != _cache_items_map.end())
{
_cache_items_list.erase(it->second);
_cache_items_map.erase(it);
}
_cache_items_map[key] = _cache_items_list.begin();

if (_cache_items_map.size() > _max_size)
{
auto last = _cache_items_list.end();
last--;
_cache_items_map.erase(last->first);
_cache_items_list.pop_back();
}
}

const value_t &get(const key_t &key)
{
auto it = _cache_items_map.find(key);
if (it == _cache_items_map.end())
{
throw std::range_error("There is no such key in cache");
}
else
{
_cache_items_list.splice(_cache_items_list.begin(),
_cache_items_list, it->second);
return it->second->second;
}
}

bool exists(const key_t &key) const
{
return _cache_items_map.find(key) != _cache_items_map.end();
}

size_t size() const
{
return _cache_items_map.size();
}

auto begin() const
{
return _cache_items_list.begin();
}

auto end() const
{
return _cache_items_list.end();
}

private:
std::list<key_value_pair_t> _cache_items_list;
std::unordered_map<key_t, list_iterator_t> _cache_items_map;
size_t _max_size;
std::list<key_value_pair_t> _cache_items_list;
std::unordered_map<key_t, list_iterator_t> _cache_items_map;
size_t _max_size;
};

} // namespace cache

#endif /* _LRUCACHE_HPP_INCLUDED_ */
} // namespace cache

#endif /* _LRUCACHE_HPP_INCLUDED_ */
4 changes: 2 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ set(SOURCE_FILES main.cpp
common/ChannelChatters.hpp
common/ChatterinoSetting.cpp
common/ChatterinoSetting.hpp
common/ChatterSet.cpp
common/ChatterSet.hpp
common/CompletionModel.cpp
common/CompletionModel.hpp
common/Credentials.cpp
Expand All @@ -42,8 +44,6 @@ set(SOURCE_FILES main.cpp
common/NetworkResult.hpp
common/QLogging.cpp
common/QLogging.hpp
common/UsernameSet.cpp
common/UsernameSet.hpp
common/Version.cpp
common/Version.hpp
common/WindowDescriptors.cpp
Expand Down
11 changes: 7 additions & 4 deletions src/common/ChannelChatters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ ChannelChatters::ChannelChatters(Channel &channel)
{
}

SharedAccessGuard<const UsernameSet> ChannelChatters::accessChatters() const
SharedAccessGuard<const ChatterSet> ChannelChatters::accessChatters() const
{
return this->chatters_.accessConst();
}

void ChannelChatters::addRecentChatter(const QString &user)
{
this->chatters_.access()->insert(user);
auto chatters = this->chatters_.access();
chatters->addRecentChatter(user);
}

void ChannelChatters::addJoinedUser(const QString &user)
Expand Down Expand Up @@ -66,9 +67,11 @@ void ChannelChatters::addPartedUser(const QString &user)
}
}

void ChannelChatters::setChatters(UsernameSet &&set)
void ChannelChatters::updateOnlineChatters(
const std::unordered_set<QString> &chatters)
{
this->chatters_.access()->merge(std::move(set));
auto chatters_ = this->chatters_.access();
chatters_->updateOnlineChatters(chatters);
}

const QColor ChannelChatters::getUserColor(const QString &user)
Expand Down
11 changes: 5 additions & 6 deletions src/common/ChannelChatters.hpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
#pragma once

#include "common/Channel.hpp"
#include "common/ChatterSet.hpp"
#include "common/UniqueAccess.hpp"
#include "common/UsernameSet.hpp"
#include "util/QStringHash.hpp"

#include "lrucache/lrucache.hpp"
#include "util/QStringHash.hpp"

#include <QRgb>

Expand All @@ -17,22 +16,22 @@ class ChannelChatters
ChannelChatters(Channel &channel);
virtual ~ChannelChatters() = default; // add vtable

SharedAccessGuard<const UsernameSet> accessChatters() const;
SharedAccessGuard<const ChatterSet> accessChatters() const;

void addRecentChatter(const QString &user);
void addJoinedUser(const QString &user);
void addPartedUser(const QString &user);
void setChatters(UsernameSet &&set);
const QColor getUserColor(const QString &user);
void setUserColor(const QString &user, const QColor &color);
void updateOnlineChatters(const std::unordered_set<QString> &chatters);

private:
static constexpr int maxChatterColorCount = 5000;

Channel &channel_;

// maps 2 char prefix to set of names
UniqueAccess<UsernameSet> chatters_;
UniqueAccess<ChatterSet> chatters_;
UniqueAccess<cache::lru_cache<QString, QRgb>> chatterColors_;

// combines multiple joins/parts into one message
Expand Down
58 changes: 58 additions & 0 deletions src/common/ChatterSet.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include "common/ChatterSet.hpp"

#include <tuple>
#include "debug/Benchmark.hpp"

namespace chatterino {

ChatterSet::ChatterSet()
: items(chatterLimit)
{
}

void ChatterSet::addRecentChatter(const QString &userName)
{
this->items.put(userName.toLower(), userName);
}

void ChatterSet::updateOnlineChatters(
const std::unordered_set<QString> &lowerCaseUsernames)
{
BenchmarkGuard bench("update online chatters");

// Create a new lru cache without the users that are not present anymore.
cache::lru_cache<QString, QString> tmp(chatterLimit);

for (auto &&chatter : lowerCaseUsernames)
{
if (this->items.exists(chatter))
tmp.put(chatter, this->items.get(chatter));

// Less chatters than the limit => try to preserve as many as possible.
else if (lowerCaseUsernames.size() < chatterLimit)
tmp.put(chatter, chatter);
}

this->items = std::move(tmp);
}

bool ChatterSet::contains(const QString &userName) const
{
return this->items.exists(userName.toLower());
}

std::vector<QString> ChatterSet::filterByPrefix(const QString &prefix) const
{
QString lowerPrefix = prefix.toLower();
std::vector<QString> result;

for (auto &&item : this->items)
{
if (item.first.startsWith(lowerPrefix))
result.push_back(item.second);
}

return result;
}

} // namespace chatterino
Loading

0 comments on commit 3fddafb

Please sign in to comment.