mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
refactored SignalVector
This commit is contained in:
parent
2b5c6ffe33
commit
e1838154ff
4 changed files with 161 additions and 195 deletions
|
@ -4,243 +4,210 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <shared_mutex>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
template <typename TVectorItem>
|
template <typename T>
|
||||||
struct SignalVectorItemArgs {
|
struct SignalVectorItemEvent {
|
||||||
const TVectorItem &item;
|
const T &item;
|
||||||
int index;
|
int index;
|
||||||
void *caller;
|
void *caller;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename TVectorItem>
|
template <typename T>
|
||||||
class ReadOnlySignalVector : boost::noncopyable
|
class SignalVector : boost::noncopyable
|
||||||
{
|
{
|
||||||
using VecIt = typename std::vector<TVectorItem>::iterator;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct Iterator
|
pajlada::Signals::Signal<SignalVectorItemEvent<T>> itemInserted;
|
||||||
: public std::iterator<std::input_iterator_tag, TVectorItem> {
|
pajlada::Signals::Signal<SignalVectorItemEvent<T>> itemRemoved;
|
||||||
Iterator(VecIt &&it, std::shared_mutex &mutex)
|
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
||||||
: it_(std::move(it))
|
|
||||||
, lock_(mutex)
|
|
||||||
, mutex_(mutex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator(const Iterator &other)
|
SignalVector()
|
||||||
: it_(other.it_)
|
: readOnly_(new std::vector<T>())
|
||||||
, lock_(other.mutex_)
|
|
||||||
, mutex_(other.mutex_)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator &operator=(const Iterator &other)
|
|
||||||
{
|
|
||||||
this->lock_ = std::shared_lock(other.mutex_.get());
|
|
||||||
this->mutex_ = other.mutex_;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
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::reference_wrapper<std::shared_mutex> mutex_;
|
|
||||||
};
|
|
||||||
|
|
||||||
ReadOnlySignalVector()
|
|
||||||
{
|
{
|
||||||
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
|
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
|
||||||
[this] { this->delayedItemsChanged.invoke(); });
|
[this] { this->delayedItemsChanged.invoke(); });
|
||||||
this->itemsChangedTimer_.setInterval(100);
|
this->itemsChangedTimer_.setInterval(100);
|
||||||
this->itemsChangedTimer_.setSingleShot(true);
|
this->itemsChangedTimer_.setSingleShot(true);
|
||||||
}
|
}
|
||||||
virtual ~ReadOnlySignalVector() = default;
|
|
||||||
|
|
||||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted;
|
SignalVector(std::function<bool(const T &, const T &)> &&compare)
|
||||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
|
: SignalVector()
|
||||||
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
|
||||||
|
|
||||||
Iterator begin() const
|
|
||||||
{
|
{
|
||||||
return Iterator(
|
itemCompare_ = std::move(compare);
|
||||||
const_cast<std::vector<TVectorItem> &>(this->vector_).begin(),
|
|
||||||
this->mutex_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator end() const
|
virtual bool isSorted() const
|
||||||
{
|
{
|
||||||
return Iterator(
|
return bool(this->itemCompare_);
|
||||||
const_cast<std::vector<TVectorItem> &>(this->vector_).end(),
|
|
||||||
this->mutex_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool empty() const
|
/// A read-only version of the vector which can be used concurrently.
|
||||||
|
std::shared_ptr<const std::vector<T>> readOnly()
|
||||||
{
|
{
|
||||||
std::shared_lock lock(this->mutex_);
|
return this->readOnly_;
|
||||||
|
|
||||||
return this->vector_.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<TVectorItem> &getVector() const
|
/// This may only be called from the GUI thread.
|
||||||
|
///
|
||||||
|
/// @param item
|
||||||
|
/// Item to be inserted.
|
||||||
|
/// @param proposedIndex
|
||||||
|
/// Index to insert at. `-1` will append at the end.
|
||||||
|
/// Will be ignored if the vector is sorted.
|
||||||
|
/// @param caller
|
||||||
|
/// Caller id which will be passed in the itemInserted and itemRemoved
|
||||||
|
/// signals.
|
||||||
|
int insert(const T &item, int index = -1, void *caller = nullptr)
|
||||||
{
|
{
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
|
|
||||||
return this->vector_;
|
if (this->isSorted())
|
||||||
|
{
|
||||||
|
auto it = std::lower_bound(this->items_.begin(), this->items_.end(),
|
||||||
|
item, this->itemCompare_);
|
||||||
|
index = it - this->items_.begin();
|
||||||
|
this->items_.insert(it, item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (index == -1)
|
||||||
|
index = this->items_.size();
|
||||||
|
else
|
||||||
|
assert(index >= 0 && index <= this->items_.size());
|
||||||
|
|
||||||
|
this->items_.insert(this->items_.begin() + index, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalVectorItemEvent<T> args{item, index, caller};
|
||||||
|
this->itemInserted.invoke(args);
|
||||||
|
this->itemsChanged_();
|
||||||
|
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TVectorItem> cloneVector() const
|
/// This may only be called from the GUI thread.
|
||||||
|
///
|
||||||
|
/// @param item
|
||||||
|
/// Item to be appended.
|
||||||
|
/// @param caller
|
||||||
|
/// Caller id which will be passed in the itemInserted and itemRemoved
|
||||||
|
/// signals.
|
||||||
|
int append(const T &item, void *caller = nullptr)
|
||||||
{
|
{
|
||||||
std::shared_lock lock(this->mutex_);
|
return this->insertItem(item, -1, caller);
|
||||||
|
|
||||||
return this->vector_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void invokeDelayedItemsChanged()
|
void removeAt(int index, void *caller = nullptr)
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
assert(index >= 0 && index < int(this->items_.size()));
|
||||||
|
|
||||||
|
T item = this->items_[index];
|
||||||
|
this->items_.erase(this->items_.begin() + index);
|
||||||
|
|
||||||
|
SignalVectorItemEvent<T> args{item, index, caller};
|
||||||
|
this->itemRemoved.invoke(args);
|
||||||
|
|
||||||
|
this->itemsChanged_();
|
||||||
|
}
|
||||||
|
|
||||||
|
// compatability
|
||||||
|
[[deprecated]] int insertItem(const T &item, int proposedIndex = -1,
|
||||||
|
void *caller = nullptr)
|
||||||
|
{
|
||||||
|
return this->insert(item, proposedIndex, caller);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[deprecated]] int appendItem(const T &item, void *caller = nullptr)
|
||||||
|
{
|
||||||
|
return this->append(item, caller);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[deprecated]] void removeItem(int index, void *caller = nullptr)
|
||||||
|
{
|
||||||
|
this->removeAt(index, caller);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[deprecated]] const std::vector<T> &getVector() const
|
||||||
{
|
{
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
|
|
||||||
|
return this->items_;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[deprecated]] std::vector<T> cloneVector()
|
||||||
|
{
|
||||||
|
return *this->readOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
// mirror vector functions
|
||||||
|
auto begin() const
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
return this->items_.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end() const
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
return this->items_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
decltype(auto) operator[](size_t index)
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
return this->items[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto empty()
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
return this->items_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void itemsChanged_()
|
||||||
|
{
|
||||||
|
// emit delayed event
|
||||||
if (!this->itemsChangedTimer_.isActive())
|
if (!this->itemsChangedTimer_.isActive())
|
||||||
{
|
{
|
||||||
this->itemsChangedTimer_.start();
|
this->itemsChangedTimer_.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update concurrent version
|
||||||
|
this->readOnly_ = std::make_shared<const std::vector<T>>(this->items_);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool isSorted() const = 0;
|
std::vector<T> items_;
|
||||||
|
std::shared_ptr<const std::vector<T>> readOnly_;
|
||||||
protected:
|
|
||||||
std::vector<TVectorItem> vector_;
|
|
||||||
QTimer itemsChangedTimer_;
|
QTimer itemsChangedTimer_;
|
||||||
mutable std::shared_mutex mutex_;
|
std::function<bool(const T &, const T &)> itemCompare_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename TVectorItem>
|
// compatability
|
||||||
class BaseSignalVector : public ReadOnlySignalVector<TVectorItem>
|
template <typename T>
|
||||||
|
using SignalVectorItemArgs = SignalVectorItemEvent<T>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using ReadOnlySignalVector = SignalVector<T>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using BaseSignalVector = SignalVector<T>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using UnsortedSignalVector = SignalVector<T>;
|
||||||
|
|
||||||
|
template <typename T, typename Compare>
|
||||||
|
class SortedSignalVector : public SignalVector<T>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// returns the actual index of the inserted item
|
SortedSignalVector()
|
||||||
virtual int insertItem(const TVectorItem &item, int proposedIndex = -1,
|
: SignalVector<T>(Compare{})
|
||||||
void *caller = nullptr) = 0;
|
|
||||||
|
|
||||||
void removeItem(int index, void *caller = nullptr)
|
|
||||||
{
|
{
|
||||||
assertInGuiThread();
|
|
||||||
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);
|
|
||||||
|
|
||||||
this->invokeDelayedItemsChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
int appendItem(const TVectorItem &item, void *caller = nullptr)
|
|
||||||
{
|
|
||||||
return this->insertItem(item, -1, caller);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename TVectorItem>
|
|
||||||
class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual int insertItem(const TVectorItem &item, int index = -1,
|
|
||||||
void *caller = nullptr) override
|
|
||||||
{
|
|
||||||
assertInGuiThread();
|
|
||||||
|
|
||||||
{
|
|
||||||
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<TVectorItem> args{item, index, caller};
|
|
||||||
this->itemInserted.invoke(args);
|
|
||||||
this->invokeDelayedItemsChanged();
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool isSorted() const override
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename TVectorItem, typename Compare>
|
|
||||||
class SortedSignalVector : public BaseSignalVector<TVectorItem>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual int insertItem(const TVectorItem &item, int = -1,
|
|
||||||
void *caller = nullptr) override
|
|
||||||
{
|
|
||||||
assertInGuiThread();
|
|
||||||
int index = -1;
|
|
||||||
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
this->invokeDelayedItemsChanged();
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool isSorted() const override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -30,10 +30,10 @@ AccountController::AccountController()
|
||||||
case ProviderId::Twitch: {
|
case ProviderId::Twitch: {
|
||||||
if (args.caller != this)
|
if (args.caller != this)
|
||||||
{
|
{
|
||||||
auto accs = this->twitch.accounts.cloneVector();
|
auto &&accs = this->twitch.accounts;
|
||||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||||
assert(it != accs.end());
|
assert(it != accs.end());
|
||||||
this->twitch.accounts.removeItem(it - accs.begin(), this);
|
this->twitch.accounts.removeAt(it - accs.begin(), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -87,7 +87,7 @@ HighlightBlacklistModel *HighlightController::createBlacklistModel(
|
||||||
|
|
||||||
bool HighlightController::blacklistContains(const QString &username)
|
bool HighlightController::blacklistContains(const QString &username)
|
||||||
{
|
{
|
||||||
for (const auto &blacklistedUser : this->blacklistedUsers)
|
for (const auto &blacklistedUser : *this->blacklistedUsers.readOnly())
|
||||||
{
|
{
|
||||||
if (blacklistedUser.isMatch(username))
|
if (blacklistedUser.isMatch(username))
|
||||||
{
|
{
|
||||||
|
|
|
@ -170,7 +170,8 @@ bool TwitchMessageBuilder::isIgnored() const
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
|
||||||
// TODO(pajlada): Do we need to check if the phrase is valid first?
|
// TODO(pajlada): Do we need to check if the phrase is valid first?
|
||||||
for (const auto &phrase : app->ignores->phrases)
|
auto phrases = app->ignores->phrases.readOnly();
|
||||||
|
for (const auto &phrase : *phrases)
|
||||||
{
|
{
|
||||||
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
|
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
|
||||||
{
|
{
|
||||||
|
@ -765,7 +766,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
||||||
std::vector<std::tuple<int, EmotePtr, EmoteName>> &twitchEmotes)
|
std::vector<std::tuple<int, EmotePtr, EmoteName>> &twitchEmotes)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
const auto &phrases = app->ignores->phrases;
|
auto phrases = app->ignores->phrases.readOnly();
|
||||||
auto removeEmotesInRange =
|
auto removeEmotesInRange =
|
||||||
[](int pos, int len,
|
[](int pos, int len,
|
||||||
std::vector<std::tuple<int, EmotePtr, EmoteName>>
|
std::vector<std::tuple<int, EmotePtr, EmoteName>>
|
||||||
|
@ -828,7 +829,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto &phrase : phrases)
|
for (const auto &phrase : *phrases)
|
||||||
{
|
{
|
||||||
if (phrase.isBlock())
|
if (phrase.isBlock())
|
||||||
{
|
{
|
||||||
|
@ -1056,11 +1057,9 @@ void TwitchMessageBuilder::parseHighlights()
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<HighlightPhrase> userHighlights =
|
|
||||||
app->highlights->highlightedUsers.cloneVector();
|
|
||||||
|
|
||||||
// Highlight because of sender
|
// Highlight because of sender
|
||||||
for (const HighlightPhrase &userHighlight : userHighlights)
|
auto userHighlights = app->highlights->highlightedUsers.readOnly();
|
||||||
|
for (const HighlightPhrase &userHighlight : *userHighlights)
|
||||||
{
|
{
|
||||||
if (!userHighlight.isMatch(this->ircMessage->nick()))
|
if (!userHighlight.isMatch(this->ircMessage->nick()))
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue