mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
lock SignalVector with shared_lock to allow reading on other threads
This commit is contained in:
parent
4e4c7d4c0b
commit
fff979b3c0
12 changed files with 120 additions and 35 deletions
|
@ -163,7 +163,7 @@ void CompletionModel::refresh(const QString &prefix)
|
|||
}
|
||||
|
||||
// Commands
|
||||
for (auto &command : getApp()->commands->items_.getVector())
|
||||
for (auto &command : getApp()->commands->items_)
|
||||
{
|
||||
addString(command.name, TaggedString::Command);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <QTimer>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
|
@ -20,7 +21,57 @@ struct SignalVectorItemArgs {
|
|||
template <typename TVectorItem>
|
||||
class ReadOnlySignalVector : boost::noncopyable
|
||||
{
|
||||
using VecIt = typename std::vector<TVectorItem>::iterator;
|
||||
|
||||
public:
|
||||
struct Iterator
|
||||
: public std::iterator<std::input_iterator_tag, TVectorItem> {
|
||||
Iterator(VecIt &&it, std::shared_mutex &mutex)
|
||||
: it_(std::move(it))
|
||||
, lock_(mutex)
|
||||
, mutex_(mutex)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(const Iterator &other)
|
||||
: it_(other.it_)
|
||||
, lock_(other.mutex_)
|
||||
, mutex_(other.mutex_)
|
||||
{
|
||||
}
|
||||
|
||||
TVectorItem &operator*()
|
||||
{
|
||||
return it_.operator*();
|
||||
}
|
||||
|
||||
Iterator &operator++()
|
||||
{
|
||||
++this->it_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other)
|
||||
{
|
||||
return this->it_ == other.it_;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other)
|
||||
{
|
||||
return this->it_ != other.it_;
|
||||
}
|
||||
|
||||
auto operator-(const Iterator &other)
|
||||
{
|
||||
return this->it_ - other.it_;
|
||||
}
|
||||
|
||||
private:
|
||||
VecIt it_;
|
||||
std::shared_lock<std::shared_mutex> lock_;
|
||||
std::shared_mutex &mutex_;
|
||||
};
|
||||
|
||||
ReadOnlySignalVector()
|
||||
{
|
||||
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
|
||||
|
@ -34,6 +85,27 @@ public:
|
|||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
|
||||
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
||||
|
||||
Iterator begin() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).begin(),
|
||||
this->mutex_);
|
||||
}
|
||||
|
||||
Iterator end() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).end(),
|
||||
this->mutex_);
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_.empty();
|
||||
}
|
||||
|
||||
const std::vector<TVectorItem> &getVector() const
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
@ -56,6 +128,7 @@ public:
|
|||
protected:
|
||||
std::vector<TVectorItem> vector_;
|
||||
QTimer itemsChangedTimer_;
|
||||
mutable std::shared_mutex mutex_;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
|
@ -69,10 +142,15 @@ public:
|
|||
void removeItem(int index, void *caller = nullptr)
|
||||
{
|
||||
assertInGuiThread();
|
||||
assert(index >= 0 && index < this->vector_.size());
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
assert(index >= 0 && index < int(this->vector_.size()));
|
||||
|
||||
TVectorItem item = this->vector_[index];
|
||||
|
||||
this->vector_.erase(this->vector_.begin() + index);
|
||||
lock.unlock(); // manual unlock
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemRemoved.invoke(args);
|
||||
|
||||
|
@ -93,6 +171,9 @@ public:
|
|||
void *caller = nullptr) override
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
{
|
||||
std::unique_lock lock(this->mutex_);
|
||||
if (index == -1)
|
||||
{
|
||||
index = this->vector_.size();
|
||||
|
@ -103,6 +184,7 @@ public:
|
|||
}
|
||||
|
||||
this->vector_.insert(this->vector_.begin() + index, item);
|
||||
}
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
|
@ -124,11 +206,16 @@ public:
|
|||
void *caller = nullptr) override
|
||||
{
|
||||
assertInGuiThread();
|
||||
int index = -1;
|
||||
|
||||
auto it = std::lower_bound(this->vector_.begin(), this->vector_.end(),
|
||||
item, Compare{});
|
||||
int index = it - this->vector_.begin();
|
||||
{
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
auto it = std::lower_bound(this->vector_.begin(),
|
||||
this->vector_.end(), item, Compare{});
|
||||
index = it - this->vector_.begin();
|
||||
this->vector_.insert(it, item);
|
||||
}
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
|
|
|
@ -16,7 +16,7 @@ AccountController::AccountController()
|
|||
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
|
||||
if (args.caller != this)
|
||||
{
|
||||
auto &accs = this->twitch.accounts.getVector();
|
||||
auto &accs = this->twitch.accounts;
|
||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||
assert(it != accs.end());
|
||||
|
||||
|
@ -29,7 +29,7 @@ AccountController::AccountController()
|
|||
{
|
||||
case ProviderId::Twitch:
|
||||
{
|
||||
auto &accs = this->twitch.accounts.getVector();
|
||||
auto &accs = this->twitch.accounts;
|
||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||
assert(it != accs.end());
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||
auto addFirstMatchToMap = [this](auto args) {
|
||||
this->commandsMap_.remove(args.item.name);
|
||||
|
||||
for (const Command &cmd : this->items_.getVector())
|
||||
for (const Command &cmd : this->items_)
|
||||
{
|
||||
if (cmd.name == args.item.name)
|
||||
{
|
||||
|
@ -185,7 +185,7 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||
|
||||
int maxSpaces = 0;
|
||||
|
||||
for (const Command &cmd : this->items_.getVector())
|
||||
for (const Command &cmd : this->items_)
|
||||
{
|
||||
auto localMaxSpaces = cmd.name.count(' ');
|
||||
if (localMaxSpaces > maxSpaces)
|
||||
|
|
|
@ -64,7 +64,7 @@ UserHighlightModel *HighlightController::createUserModel(QObject *parent)
|
|||
|
||||
bool HighlightController::isHighlightedUser(const QString &username)
|
||||
{
|
||||
const auto &userItems = this->highlightedUsers.getVector();
|
||||
const auto &userItems = this->highlightedUsers;
|
||||
for (const auto &highlightedUser : userItems)
|
||||
{
|
||||
if (highlightedUser.isMatch(username))
|
||||
|
@ -87,9 +87,7 @@ HighlightBlacklistModel *HighlightController::createBlacklistModel(
|
|||
|
||||
bool HighlightController::blacklistContains(const QString &username)
|
||||
{
|
||||
std::vector<HighlightBlacklistUser> blacklistItems =
|
||||
this->blacklistedUsers.getVector();
|
||||
for (const auto &blacklistedUser : blacklistItems)
|
||||
for (const auto &blacklistedUser : this->blacklistedUsers)
|
||||
{
|
||||
if (blacklistedUser.isMatch(username))
|
||||
{
|
||||
|
|
|
@ -107,7 +107,7 @@ public:
|
|||
if (!this->emotesChecked_)
|
||||
{
|
||||
const auto &accvec =
|
||||
getApp()->accounts->twitch.accounts.getVector();
|
||||
getApp()->accounts->twitch.accounts;
|
||||
for (const auto &acc : accvec)
|
||||
{
|
||||
const auto &accemotes = *acc->accessEmotes();
|
||||
|
|
|
@ -41,7 +41,7 @@ void NotificationController::initialize(Settings &settings, Paths &paths)
|
|||
|
||||
this->channelMap[Platform::Mixer].delayedItemsChanged.connect([this] { //
|
||||
this->mixerSetting_.setValue(
|
||||
this->channelMap[Platform::Mixer].getVector());
|
||||
this->channelMap[Platform::Mixer]);
|
||||
});*/
|
||||
|
||||
liveStatusTimer_ = new QTimer();
|
||||
|
@ -69,7 +69,7 @@ void NotificationController::updateChannelNotification(
|
|||
bool NotificationController::isChannelNotified(const QString &channelName,
|
||||
Platform p)
|
||||
{
|
||||
for (const auto &channel : this->channelMap[p].getVector())
|
||||
for (const auto &channel : this->channelMap[p])
|
||||
{
|
||||
if (channelName.toLower() == channel.toLower())
|
||||
{
|
||||
|
|
|
@ -25,7 +25,7 @@ PingModel *PingController::createModel(QObject *parent)
|
|||
|
||||
bool PingController::isMuted(const QString &channelName)
|
||||
{
|
||||
for (const auto &channel : this->channelVector.getVector())
|
||||
for (const auto &channel : this->channelVector)
|
||||
{
|
||||
if (channelName.toLower() == channel.toLower())
|
||||
{
|
||||
|
|
|
@ -348,7 +348,7 @@ void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
|
|||
int(container.getScale() * 16));
|
||||
|
||||
for (const auto &action :
|
||||
getApp()->moderationActions->items.getVector())
|
||||
getApp()->moderationActions->items)
|
||||
{
|
||||
if (auto image = action.getImage())
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@ std::vector<QString> TwitchAccountManager::getUsernames() const
|
|||
|
||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
for (const auto &user : this->accounts.getVector())
|
||||
for (const auto &user : this->accounts)
|
||||
{
|
||||
userNames.push_back(user->getUserName());
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
|
|||
{
|
||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
for (const auto &user : this->accounts.getVector())
|
||||
for (const auto &user : this->accounts)
|
||||
{
|
||||
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
|
|
|
@ -60,7 +60,7 @@ bool TwitchMessageBuilder::isIgnored() const
|
|||
auto app = getApp();
|
||||
|
||||
// TODO(pajlada): Do we need to check if the phrase is valid first?
|
||||
for (const auto &phrase : app->ignores->phrases.getVector())
|
||||
for (const auto &phrase : app->ignores->phrases)
|
||||
{
|
||||
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
|
||||
{
|
||||
|
@ -221,7 +221,7 @@ MessagePtr TwitchMessageBuilder::build()
|
|||
}
|
||||
}
|
||||
auto app = getApp();
|
||||
const auto &phrases = app->ignores->phrases.getVector();
|
||||
const auto &phrases = app->ignores->phrases;
|
||||
auto removeEmotesInRange =
|
||||
[](int pos, int len,
|
||||
std::vector<std::tuple<int, EmotePtr, EmoteName>>
|
||||
|
|
|
@ -177,7 +177,7 @@ void SplitHeader::initializeLayout()
|
|||
{
|
||||
case Qt::LeftButton:
|
||||
if (getApp()
|
||||
->moderationActions->items.getVector()
|
||||
->moderationActions->items
|
||||
.empty())
|
||||
{
|
||||
getApp()->windows->showSettingsDialog(
|
||||
|
@ -224,7 +224,7 @@ void SplitHeader::initializeLayout()
|
|||
// update moderation button when items changed
|
||||
this->managedConnect(
|
||||
getApp()->moderationActions->items.delayedItemsChanged, [this] {
|
||||
if (getApp()->moderationActions->items.getVector().empty())
|
||||
if (getApp()->moderationActions->items.empty())
|
||||
{
|
||||
if (this->split_->getModerationMode())
|
||||
this->split_->setModerationMode(true);
|
||||
|
@ -509,7 +509,7 @@ void SplitHeader::updateModerationModeIcon()
|
|||
{
|
||||
auto moderationMode =
|
||||
this->split_->getModerationMode() &&
|
||||
!getApp()->moderationActions->items.getVector().empty();
|
||||
!getApp()->moderationActions->items.empty();
|
||||
|
||||
this->moderationButton_->setPixmap(
|
||||
moderationMode ? getApp()->resources->buttons.modModeEnabled
|
||||
|
|
Loading…
Reference in a new issue