diff --git a/src/common/CompletionModel.cpp b/src/common/CompletionModel.cpp index 92bda0e83..5f9dfff08 100644 --- a/src/common/CompletionModel.cpp +++ b/src/common/CompletionModel.cpp @@ -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); } diff --git a/src/common/SignalVector.hpp b/src/common/SignalVector.hpp index 262718d0f..db03df265 100644 --- a/src/common/SignalVector.hpp +++ b/src/common/SignalVector.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "debug/AssertInGuiThread.hpp" @@ -20,7 +21,57 @@ struct SignalVectorItemArgs { template class ReadOnlySignalVector : boost::noncopyable { + using VecIt = typename std::vector::iterator; + public: + struct Iterator + : public std::iterator { + 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 lock_; + std::shared_mutex &mutex_; + }; + ReadOnlySignalVector() { QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout, @@ -34,6 +85,27 @@ public: pajlada::Signals::Signal> itemRemoved; pajlada::Signals::NoArgSignal delayedItemsChanged; + Iterator begin() const + { + return Iterator( + const_cast &>(this->vector_).begin(), + this->mutex_); + } + + Iterator end() const + { + return Iterator( + const_cast &>(this->vector_).end(), + this->mutex_); + } + + bool empty() const + { + std::shared_lock lock(this->mutex_); + + return this->vector_.empty(); + } + const std::vector &getVector() const { assertInGuiThread(); @@ -56,6 +128,7 @@ public: protected: std::vector vector_; QTimer itemsChangedTimer_; + mutable std::shared_mutex mutex_; }; template @@ -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 args{item, index, caller}; this->itemRemoved.invoke(args); @@ -93,16 +171,20 @@ public: void *caller = nullptr) override { assertInGuiThread(); - if (index == -1) - { - index = this->vector_.size(); - } - else - { - assert(index >= 0 && index <= this->vector_.size()); - } - this->vector_.insert(this->vector_.begin() + index, item); + { + std::unique_lock lock(this->mutex_); + if (index == -1) + { + index = this->vector_.size(); + } + else + { + assert(index >= 0 && index <= this->vector_.size()); + } + + this->vector_.insert(this->vector_.begin() + index, item); + } SignalVectorItemArgs 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(); - this->vector_.insert(it, item); + { + 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 args{item, index, caller}; this->itemInserted.invoke(args); diff --git a/src/controllers/accounts/AccountController.cpp b/src/controllers/accounts/AccountController.cpp index 468f7433e..0d2695b5d 100644 --- a/src/controllers/accounts/AccountController.cpp +++ b/src/controllers/accounts/AccountController.cpp @@ -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()); diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp index ae46f6e2e..b6fe9b8e8 100644 --- a/src/controllers/commands/CommandController.cpp +++ b/src/controllers/commands/CommandController.cpp @@ -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) diff --git a/src/controllers/highlights/HighlightController.cpp b/src/controllers/highlights/HighlightController.cpp index 36caa4925..8cad2d7cd 100644 --- a/src/controllers/highlights/HighlightController.cpp +++ b/src/controllers/highlights/HighlightController.cpp @@ -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 blacklistItems = - this->blacklistedUsers.getVector(); - for (const auto &blacklistedUser : blacklistItems) + for (const auto &blacklistedUser : this->blacklistedUsers) { if (blacklistedUser.isMatch(username)) { diff --git a/src/controllers/ignores/IgnorePhrase.hpp b/src/controllers/ignores/IgnorePhrase.hpp index 892f8ec39..4eea81f96 100644 --- a/src/controllers/ignores/IgnorePhrase.hpp +++ b/src/controllers/ignores/IgnorePhrase.hpp @@ -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(); diff --git a/src/controllers/notifications/NotificationController.cpp b/src/controllers/notifications/NotificationController.cpp index 081084fba..1b3e64143 100644 --- a/src/controllers/notifications/NotificationController.cpp +++ b/src/controllers/notifications/NotificationController.cpp @@ -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()) { diff --git a/src/controllers/pings/PingController.cpp b/src/controllers/pings/PingController.cpp index 0f0ed37d8..b61d6e795 100644 --- a/src/controllers/pings/PingController.cpp +++ b/src/controllers/pings/PingController.cpp @@ -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()) { diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp index e5920fb6f..774d569b9 100644 --- a/src/messages/MessageElement.cpp +++ b/src/messages/MessageElement.cpp @@ -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()) { diff --git a/src/providers/twitch/TwitchAccountManager.cpp b/src/providers/twitch/TwitchAccountManager.cpp index 5b7a2c431..b6fdc5c68 100644 --- a/src/providers/twitch/TwitchAccountManager.cpp +++ b/src/providers/twitch/TwitchAccountManager.cpp @@ -35,7 +35,7 @@ std::vector TwitchAccountManager::getUsernames() const std::lock_guard 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 TwitchAccountManager::findUserByUsername( { std::lock_guard lock(this->mutex_); - for (const auto &user : this->accounts.getVector()) + for (const auto &user : this->accounts) { if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) { diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 9ba46c8f1..0ff3e74e2 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -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> diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index fe3457899..8ee3e34b0 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -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