#ifndef ASYNC_MQTT5_RING_BUFFER_HPP #define ASYNC_MQTT5_RING_BUFFER_HPP #include namespace async_mqtt5::detail { /* Best used LIFO queues. It supports random access iterators, constant time insert and erase operations at the beginning or the end of the buffer and interoperability with std algorithms. Buffer capacity is ensured to be power of 2. */ template > class ring_buffer { public: /* EMPTY | 1 | 2 | FULL |-----------------|-----------------|-----------------|---------------- | ............... | ****........... | ****.......**** | *************** | > | [ > | > [ | ^ | B, F == npos | F B | B F | B == F |-----------------|-----------------|-----------------|---------------- */ using value_type = T; using allocator_type = Allocator; using allocator_traits = std::allocator_traits; using size_type = std::size_t; using reference = value_type&; using const_reference = const value_type&; using pointer = T*; using const_pointer = const T*; template class _iter; using iterator = _iter; using const_iterator = _iter; iterator begin() noexcept; iterator end() noexcept; const_iterator begin() const noexcept; const_iterator end() const noexcept; const_iterator cbegin() const noexcept; const_iterator cend() const noexcept; ring_buffer() = default; template requires std::is_constructible_v explicit ring_buffer(size_type capacity, Alloc&& allocator) : _alloc((Alloc&&)allocator) { reserve(capacity); } template requires std::is_constructible_v explicit ring_buffer(Alloc&& allocator) : _alloc((Alloc&&)allocator) { } explicit ring_buffer(size_type capacity) : ring_buffer(capacity, allocator_type { }) { } ring_buffer(ring_buffer&& other) noexcept : _buff { std::exchange(other._buff, nullptr) }, _front { std::exchange(other._front, -1) }, _back { std::exchange(other._back, 0) }, _capacity { std::exchange(other._capacity, 0) }, _alloc { other._alloc } { } ~ring_buffer() { if (_buff) { clear(); _alloc.deallocate(_buff, _capacity); } } ring_buffer& operator=(ring_buffer&& other) noexcept { if constexpr (allocator_traits::propagate_on_container_move_assignment::value) { clear_and_exchange_with(other); _alloc = other._alloc; } else { if (_alloc == other._alloc) { clear_and_exchange_with(other); } else { clear(); auto s = other.size(); reserve(s); for (size_type i = 0; i < s; ++i) { push_back((value_type&&)other.front()); other.pop_front(); } } } return *this; } size_type capacity() const noexcept { return _capacity; } size_type size() const noexcept { if (empty()) { return 0; } return _back > _front ? _back - _front : (_capacity - _front) + _back; } bool empty() const noexcept { return _front == npos; } bool full() const noexcept { return _front == _back; } reference front() noexcept { return _buff[_front]; } const_reference front() const noexcept { return _buff[_front]; } reference back() noexcept { return _buff[index(_back - 1)]; } const_reference back() const noexcept { return _buff[index(_back - 1)]; } reference operator[](size_type i) noexcept { return _buff[index(_front + i)]; } const_reference operator[](size_type i) const noexcept { return _buff[index(_front + i)]; } void pop_front() noexcept { allocator_traits::destroy(_alloc, &_buff[_front]); _front = index(_front + 1); if (_front == _back) { _front = npos; _back = 0; } } void push_front(const value_type& v) noexcept { grow_if_needed(); _front = _front == npos ? index(_back - 1) : index(_front - 1); allocator_traits::construct(_alloc, &_buff[_front], v); } void push_front(value_type&& v) noexcept { grow_if_needed(); _front = _front == npos ? index(_back - 1) : index(_front - 1); allocator_traits::construct(_alloc, &_buff[_front], (value_type&&)v); } template void emplace_front(Args&&... args) noexcept { grow_if_needed(); _front = _front == npos ? index(_back - 1) : index(_front - 1); allocator_traits::construct(_alloc, &_buff[_front], (Args&&)args...); } void push_back(const value_type& v) noexcept { grow_if_needed(); allocator_traits::construct(_alloc, &_buff[_back], v); if (_front == npos) { _front = _back; } _back = index(_back + 1); } void push_back(value_type&& v) noexcept { grow_if_needed(); allocator_traits::construct(_alloc, &_buff[_back], (value_type&&)v); if (_front == npos) { _front = _back; } _back = index(_back + 1); } template void emplace_back(Args&&... args) { grow_if_needed(); allocator_traits::construct(_alloc, &_buff[_back], (Args&&)args...); if (_front == npos) { _front = _back; } _back = index(_back + 1); } void pop_back() noexcept { _back = index(_back - 1); allocator_traits::destroy(_alloc, &_buff[_back]); if (_front == _back) { _front = npos; _back = 0; } } void clear() noexcept { for (size_type i = 0; i < size(); ++i) { allocator_traits::destroy(_alloc, &_buff[index(i)]); } _front = npos; _back = 0; } void swap(ring_buffer& b) noexcept { _buff = std::exchange(b._buff, _buff); _front = std::exchange(b._front, _front); _back = std::exchange(b._back, _back); _capacity = std::exchange(b._capacity, _capacity); _alloc = std::exchange(b._alloc, _alloc); //?? allocator_traits::propagate_on_container_swap::value } void reserve(size_type new_capacity) noexcept { if (new_capacity <= _capacity) { return; } if ((new_capacity & (new_capacity - 1)) != 0) { #if defined(_MSC_VER) unsigned long msb = 0; _BitScanReverse(&msb, static_cast(new_capacity)); uint32_t lz = 32u - msb - 1; #else uint32_t lz = __builtin_clz(new_capacity); #endif new_capacity = 1ull << (32u - lz); } auto new_buff = _alloc.allocate(new_capacity); auto s = size(); if (_buff) { for (size_type i = 0; i < s; ++i) { auto& v = operator[](i); allocator_traits::construct(_alloc, &new_buff[i], (value_type&&)v); allocator_traits::destroy(_alloc, &v); } _alloc.deallocate(_buff, _capacity); } _buff = new_buff; _back = s; _front = _back == 0 ? npos : 0; _capacity = new_capacity; } private: constexpr size_type index(size_type i) const noexcept { return i & (_capacity - 1); } void grow_if_needed() { if (!_buff || full()) [[unlikely]] reserve(_capacity == 0 ? min_capacity : _capacity * 2); } void clear_and_exchange_with(ring_buffer& other) noexcept { clear(); _alloc.deallocate(_buff, _capacity); _buff = std::exchange(other._buff, nullptr); _front = std::exchange(other._front, npos); _back = std::exchange(other._back, 0); _capacity = std::exchange(other._capacity, 0); } pointer _buff = nullptr; size_type _front = npos; size_type _back = 0; size_type _capacity = 0; [[no_unique_address]] allocator_type _alloc; static constexpr size_type npos = static_cast(-1); static constexpr size_type min_capacity = 4; }; template template class ring_buffer::_iter { public: using value_type = T; using pointer = P; using reference = R; using difference_type = std::ptrdiff_t; _iter() noexcept = default; _iter(const _iter&) noexcept = default; _iter(_iter&&) noexcept = default; _iter& operator=(const _iter&) noexcept = default; _iter& operator=(_iter&&) noexcept = default; reference operator*() const noexcept { return *_p; } pointer operator->() const noexcept { return _p; } _iter& operator++() noexcept { _p = &_b->operator[](++_i); return *this; } _iter operator++(int) noexcept { auto tmp = *this; _p = &_b->operator[](++_i); return tmp; } _iter& operator--() noexcept { _p = &_b->operator[](--_i); return *this; } _iter operator--(int) noexcept { auto tmp = *this; _p = &_b->operator[](++_i); return tmp; } _iter& operator+=(difference_type d) noexcept { _p = &_b->operator[]((_i += d)); return *this; } _iter& operator-=(difference_type d) noexcept { _p = &_b->operator[]((_i -= d)); return *this; } private: friend class ring_buffer; _iter(ring_pointer b, size_type i) : _b(b), _i(i), _p(&b->operator[](i)) { } ring_pointer _b = nullptr; size_type _i = npos; pointer _p = nullptr; friend bool operator==(const _iter& a, const _iter& b) noexcept { return a._i == b._i; } friend auto operator<=>(const _iter& a, const _iter& b) noexcept { return difference_type(a._i - b._i); } friend difference_type operator-(const _iter& a, const _iter& b) noexcept { return a._i - b._i; } friend _iter operator+(const _iter& a, difference_type d) noexcept { return { a._b, a._i + d }; } friend _iter operator-(const _iter& a, difference_type d) noexcept { return { a._b, a._i - d }; } }; template typename ring_buffer::iterator ring_buffer::begin() noexcept { return { this, 0 }; } template typename ring_buffer::iterator ring_buffer::end() noexcept { return { this, size() }; } template typename ring_buffer::const_iterator ring_buffer::begin() const noexcept { return { this, 0 }; } template typename ring_buffer::const_iterator ring_buffer::end() const noexcept { return { this, size() }; } template typename ring_buffer::const_iterator ring_buffer::cbegin() const noexcept { return { this, 0 }; } template typename ring_buffer::const_iterator ring_buffer::cend() const noexcept { return { this, size() }; } } // namespace async_mqtt5::detail namespace std { template void swap( async_mqtt5::detail::ring_buffer& a, async_mqtt5::detail::ring_buffer& b) { a.swap(b); } } #endif // !ASYNC_MQTT5_RING_BUFFER_HPP