#pragma once #include #include #include #include #include #include #include "debug/AssertInGuiThread.hpp" namespace chatterino { template struct SignalVectorItemArgs { const TVectorItem &item; int index; void *caller; }; 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_) { } 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 lock_; std::reference_wrapper mutex_; }; ReadOnlySignalVector() { QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout, [this] { this->delayedItemsChanged.invoke(); }); this->itemsChangedTimer_.setInterval(100); this->itemsChangedTimer_.setSingleShot(true); } virtual ~ReadOnlySignalVector() = default; pajlada::Signals::Signal> itemInserted; 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(); return this->vector_; } std::vector cloneVector() const { std::shared_lock lock(this->mutex_); return this->vector_; } void invokeDelayedItemsChanged() { assertInGuiThread(); if (!this->itemsChangedTimer_.isActive()) { this->itemsChangedTimer_.start(); } } virtual bool isSorted() const = 0; protected: std::vector vector_; QTimer itemsChangedTimer_; mutable std::shared_mutex mutex_; }; template class BaseSignalVector : public ReadOnlySignalVector { public: // returns the actual index of the inserted item virtual int insertItem(const TVectorItem &item, int proposedIndex = -1, 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 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 class UnsortedSignalVector : public BaseSignalVector { 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 args{item, index, caller}; this->itemInserted.invoke(args); this->invokeDelayedItemsChanged(); return index; } virtual bool isSorted() const override { return false; } }; template class SortedSignalVector : public BaseSignalVector { 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 args{item, index, caller}; this->itemInserted.invoke(args); this->invokeDelayedItemsChanged(); return index; } virtual bool isSorted() const override { return true; } }; } // namespace chatterino