mirror of
https://github.com/boostorg/mqtt5.git
synced 2025-10-04 12:50:54 +02:00
Summary: resolves T12767 Reviewers: ivica Reviewed By: ivica Subscribers: miljen Tags: #spacetime Maniphest Tasks: T12767 Differential Revision: https://repo.mireo.local/D25970
417 lines
10 KiB
C++
417 lines
10 KiB
C++
#ifndef ASYNC_MQTT5_RING_BUFFER_HPP
|
|
#define ASYNC_MQTT5_RING_BUFFER_HPP
|
|
|
|
#include <memory>
|
|
|
|
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 <typename T, typename Allocator = std::allocator<T>>
|
|
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<allocator_type>;
|
|
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 <typename P, typename R, typename ring_pointer> class _iter;
|
|
|
|
using iterator = _iter<pointer, reference, ring_buffer*>;
|
|
using const_iterator = _iter<const_pointer, const_reference, const ring_buffer*>;
|
|
|
|
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 <typename Alloc> requires std::is_constructible_v<allocator_type, const Alloc&>
|
|
explicit ring_buffer(size_type capacity, Alloc&& allocator) : _alloc((Alloc&&)allocator) {
|
|
reserve(capacity);
|
|
}
|
|
|
|
template <typename Alloc> requires std::is_constructible_v<allocator_type, const Alloc&>
|
|
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 <typename... Args>
|
|
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 <typename... Args>
|
|
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<unsigned long>(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<size_type>(-1);
|
|
static constexpr size_type min_capacity = 4;
|
|
};
|
|
|
|
template <typename T, typename Alloc> template <typename P, typename R, typename ring_pointer>
|
|
class ring_buffer<T, Alloc>::_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 T, typename Alloc>
|
|
typename ring_buffer<T, Alloc>::iterator ring_buffer<T, Alloc>::begin() noexcept {
|
|
return { this, 0 };
|
|
}
|
|
|
|
template <typename T, typename Alloc>
|
|
typename ring_buffer<T, Alloc>::iterator ring_buffer<T, Alloc>::end() noexcept {
|
|
return { this, size() };
|
|
}
|
|
|
|
template <typename T, typename Alloc>
|
|
typename ring_buffer<T, Alloc>::const_iterator ring_buffer<T, Alloc>::begin() const noexcept {
|
|
return { this, 0 };
|
|
}
|
|
|
|
template <typename T, typename Alloc>
|
|
typename ring_buffer<T, Alloc>::const_iterator ring_buffer<T, Alloc>::end() const noexcept {
|
|
return { this, size() };
|
|
}
|
|
|
|
template <typename T, typename Alloc>
|
|
typename ring_buffer<T, Alloc>::const_iterator ring_buffer<T, Alloc>::cbegin() const noexcept {
|
|
return { this, 0 };
|
|
}
|
|
|
|
template <typename T, typename Alloc>
|
|
typename ring_buffer<T, Alloc>::const_iterator ring_buffer<T, Alloc>::cend() const noexcept {
|
|
return { this, size() };
|
|
}
|
|
|
|
} // namespace async_mqtt5::detail
|
|
|
|
namespace std {
|
|
|
|
template <typename T, typename Alloc>
|
|
void swap(
|
|
async_mqtt5::detail::ring_buffer<T, Alloc>& a,
|
|
async_mqtt5::detail::ring_buffer<T, Alloc>& b)
|
|
{
|
|
a.swap(b);
|
|
}
|
|
|
|
}
|
|
|
|
#endif // !ASYNC_MQTT5_RING_BUFFER_HPP
|