diff --git a/src/xrt/auxiliary/util/u_iterator_base.hpp b/src/xrt/auxiliary/util/u_iterator_base.hpp new file mode 100644 index 000000000..61e5d1b09 --- /dev/null +++ b/src/xrt/auxiliary/util/u_iterator_base.hpp @@ -0,0 +1,360 @@ +// Copyright 2021-2022, Collabora, Ltd. +// +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * + * @brief A template class to serve as the base of iterator and const_iterator + * types for things with "random access". + * @author Ryan Pavlik <ryan.pavlik@collabora.com> + * @ingroup aux_util + */ + +#pragma once + +#include <stdexcept> +#include <limits> +#include <iterator> + +namespace xrt::auxiliary::util { +/*! + * @brief Template for base class used by "random-access" iterators and const_iterators, providing all the functionality + * that is independent of element type and const-ness of the iterator. + * + * Using inheritance instead of composition here to more easily satisfy the C++ standard's requirements for + * iterators (e.g. that const iterators and iterators are comparable, etc.) + * + * All invalid instances will compare as equal, as required by the spec, but they are not all equivalent. You can freely + * go "past the end" (they will be invalid, so cannot dereference, but you can get them back to valid), but you can't go + * "past the beginning". That is, you can do `*(buf.end() - 1)` successfully if your buffer has at least one element, + * even though `buf.end()` is invalid. + * + * @tparam ContainerOrHelper Your container or some member thereof that provides a size() method. If it's a helper + * instead of the actual container, make it const. + * + */ +template <typename ContainerOrHelper> class RandomAccessIteratorBase +{ +public: + using iterator_category = std::random_access_iterator_tag; + using difference_type = std::ptrdiff_t; + + + //! Is this iterator valid? + bool + valid() const noexcept; + + //! Is this iterator valid? + explicit operator bool() const noexcept + { + return valid(); + } + + //! What is the index stored by this iterator? + size_t + index() const noexcept + { + return index_; + } + + //! Is this iterator pointing "past the end" of the container? + bool + is_past_the_end() const noexcept + { + return container_ != nullptr && index_ >= container_->size(); + } + + /*! + * @brief True if this iterator is "irrecoverably" invalid (aka, cleared/default constructed). + * + * Implies !valid() but is stronger. `buf.end().is_cleared()` is false. + */ + bool + is_cleared() const noexcept + { + return container_ == nullptr; + } + + /*! + * @brief Compute the difference between two iterators. + * + * - If both are cleared, the result is 0. + * - Otherwise the result is the difference in index. + * + * @throws std::logic_error if exactly one of the iterators is cleared + * @throws std::out_of_range if at least one of the iterators has an index larger than the maximum value of + * std::ptrdiff_t. + */ + std::ptrdiff_t + operator-(const RandomAccessIteratorBase<ContainerOrHelper> &other) const; + + /*! + * @brief Increment by an arbitrary value. + */ + RandomAccessIteratorBase & + operator+=(std::ptrdiff_t n); + + /*! + * @brief Decrement by an arbitrary value. + */ + RandomAccessIteratorBase & + operator-=(std::ptrdiff_t n); + + /*! + * @brief Add some arbitrary amount to a copy of this iterator. + */ + RandomAccessIteratorBase + operator+(std::ptrdiff_t n) const; + + /*! + * @brief Subtract some arbitrary amount from a copy of this iterator. + */ + RandomAccessIteratorBase + operator-(std::ptrdiff_t n) const; + + //! Factory function: construct the "begin" iterator + static RandomAccessIteratorBase + begin(ContainerOrHelper &container) + { + return RandomAccessIteratorBase(container, 0); + } + + //! Factory function: construct the "past the end" iterator that can be decremented safely + static RandomAccessIteratorBase + end(ContainerOrHelper &container) + { + return RandomAccessIteratorBase(container, container.size()); + } + + + /** + * @brief Default constructor - initializes to "cleared" aka irrecoverably invalid + */ + RandomAccessIteratorBase() = default; + + /** + * @brief Constructor from a helper/container and index + * + * @param container The helper or container we will iterate through. + * @param index An index - may be out of range. + */ + explicit RandomAccessIteratorBase(ContainerOrHelper &container, size_t index); + + + using const_iterator_base = std::conditional_t<std::is_const<ContainerOrHelper>::value, + RandomAccessIteratorBase, + RandomAccessIteratorBase<std::add_const_t<ContainerOrHelper>>>; + + /** + * @brief Get a const iterator base pointing to the same element as this element. + * + * @return const_iterator_base + */ + const_iterator_base + as_const() const + { + if (is_cleared()) { + return {}; + } + + return const_iterator_base{*container_, index_}; + } + +protected: + //! for internal use + RandomAccessIteratorBase(ContainerOrHelper *container, size_t index) : container_(container), index_(index) {} + + //! Increment an arbitrary amount + void + increment_n(std::size_t n); + + //! Decrement an arbitrary amount + void + decrement_n(std::size_t n); + + //! Access the container or helper + ContainerOrHelper * + container() const noexcept + { + return container_; + } + +private: + /** + * @brief The container or helper we're associated with. + * + * If we were created knowing a container, this pointer is non-null. + * Used to determine if an index is in bounds. + * If this is null, the iterator is irrecoverably invalid. + */ + ContainerOrHelper *container_{nullptr}; + + //! This is the index in the container. May be out-of-range. + size_t index_{0}; +}; + +/*! + * @brief Equality comparison operator for @ref RandomAccessIteratorBase + * + * @relates RandomAccessIteratorBase + */ +template <typename ContainerOrHelper> +static inline bool +operator==(RandomAccessIteratorBase<ContainerOrHelper> const &lhs, + RandomAccessIteratorBase<ContainerOrHelper> const &rhs) noexcept +{ + const bool lhs_valid = lhs.valid(); + const bool rhs_valid = rhs.valid(); + if (!lhs_valid && !rhs_valid) { + // all invalid iterators compare equal. + return true; + } + if (lhs_valid != rhs_valid) { + // valid is never equal to invalid. + return false; + } + // OK, so both are valid. Now, we look at the index + return lhs.index() == rhs.index(); +} + +/*! + * @overload + */ +template <typename ContainerOrHelper> +static inline bool +operator==(RandomAccessIteratorBase<const ContainerOrHelper> const &lhs, + RandomAccessIteratorBase<ContainerOrHelper> const &rhs) noexcept +{ + return lhs == rhs.as_const(); +} +/*! + * @overload + */ +template <typename ContainerOrHelper> +static inline bool +operator==(RandomAccessIteratorBase<ContainerOrHelper> const &lhs, + RandomAccessIteratorBase<const ContainerOrHelper> const &rhs) noexcept +{ + return lhs.as_const() == rhs; +} + +/*! + * @brief Inequality comparison operator for @ref RandomAccessIteratorBase + * + * @relates RandomAccessIteratorBase + */ +template <typename ContainerOrHelper> +static inline bool +operator!=(RandomAccessIteratorBase<ContainerOrHelper> const &lhs, + RandomAccessIteratorBase<ContainerOrHelper> const &rhs) noexcept +{ + return !(lhs == rhs); +} + +/*! + * @overload + */ +template <typename ContainerOrHelper> +static inline bool +operator!=(RandomAccessIteratorBase<const ContainerOrHelper> const &lhs, + RandomAccessIteratorBase<ContainerOrHelper> const &rhs) noexcept +{ + return !(lhs == rhs.as_const()); +} + +/*! + * @overload + */ +template <typename ContainerOrHelper> +static inline bool +operator!=(RandomAccessIteratorBase<ContainerOrHelper> const &lhs, + RandomAccessIteratorBase<const ContainerOrHelper> const &rhs) noexcept +{ + return !(lhs.as_const() == rhs); +} + +template <typename ContainerOrHelper> +inline bool +RandomAccessIteratorBase<ContainerOrHelper>::valid() const noexcept +{ + return container_ != nullptr && index_ < container_->size(); +} + +template <typename ContainerOrHelper> +inline RandomAccessIteratorBase<ContainerOrHelper> & +RandomAccessIteratorBase<ContainerOrHelper>::operator+=(std::ptrdiff_t n) +{ + if (n < 0) { + decrement_n(static_cast<size_t>(-1 * n)); + } else { + increment_n(static_cast<size_t>(n)); + } + return *this; +} + +template <typename ContainerOrHelper> +inline RandomAccessIteratorBase<ContainerOrHelper> & +RandomAccessIteratorBase<ContainerOrHelper>::operator-=(std::ptrdiff_t n) +{ + if (n < 0) { + increment_n(static_cast<size_t>(-1 * n)); + } else { + decrement_n(static_cast<size_t>(n)); + } + return *this; +} + +template <typename ContainerOrHelper> +inline void +RandomAccessIteratorBase<ContainerOrHelper>::increment_n(std::size_t n) +{ + // being cleared is permanent + if (is_cleared()) + return; + index_ += n; +} + +template <typename ContainerOrHelper> +inline void +RandomAccessIteratorBase<ContainerOrHelper>::decrement_n(std::size_t n) +{ + // being cleared is permanent + if (is_cleared()) + return; + if (n > index_) { + // would move backward past the beginning which you can't recover from. So, clear it. + *this = {}; + return; + } + index_ -= n; +} + +template <typename ContainerOrHelper> +inline std::ptrdiff_t +RandomAccessIteratorBase<ContainerOrHelper>::operator-(const RandomAccessIteratorBase<ContainerOrHelper> &other) const +{ + const bool self_cleared = is_cleared(); + const bool other_cleared = other.is_cleared(); + if (self_cleared && other_cleared) { + // If both cleared, they're at the same place. + return 0; + } + if (self_cleared || other_cleared) { + // If only one is cleared, we can't do this. + throw std::logic_error( + "Tried to find the difference between a cleared iterator and a non-cleared iterator."); + } + constexpr size_t max_ptrdiff = static_cast<size_t>(std::numeric_limits<std::ptrdiff_t>::max()); + if (index_ > max_ptrdiff || other.index_ > max_ptrdiff) { + throw std::out_of_range("An index exceeded the maximum value of the signed version of the index type."); + } + // Otherwise subtract the index values. + return static_cast<std::ptrdiff_t>(index_) - static_cast<std::ptrdiff_t>(other.index_); +} + +template <typename ContainerOrHelper> +inline RandomAccessIteratorBase<ContainerOrHelper>::RandomAccessIteratorBase(ContainerOrHelper &container, size_t index) + : container_(&container), index_(index) +{} + +} // namespace xrt::auxiliary::util diff --git a/src/xrt/auxiliary/util/u_template_historybuf.hpp b/src/xrt/auxiliary/util/u_template_historybuf.hpp index 5d1684dbc..bde9bc9de 100644 --- a/src/xrt/auxiliary/util/u_template_historybuf.hpp +++ b/src/xrt/auxiliary/util/u_template_historybuf.hpp @@ -1,636 +1,27 @@ -// Copyright 2021, Collabora, Ltd. +// Copyright 2021-2022, Collabora, Ltd. +// // SPDX-License-Identifier: BSL-1.0 /*! * @file * @brief Ringbuffer implementation for keeping track of the past state of things - * @author Moses Turner <moses@collabora.com> * @author Ryan Pavlik <ryan.pavlik@collabora.com> + * @author Moses Turner <moses@collabora.com> * @ingroup aux_util */ #pragma once -#include <algorithm> -#include <array> +#include "u_template_historybuf_impl_helpers.hpp" +#include "u_iterator_base.hpp" + #include <limits> -#include <stdexcept> -#include <assert.h> - - -//| -4 | -3 | -2 | -1 | Top | Garbage | -// OR -//| -4 | -3 | -2 | -1 | Top | -7 | -6 | -5 | +#include <array> namespace xrt::auxiliary::util { -template <typename T, size_t MaxSize> class HistoryBuffer; - namespace detail { - //! All the bookkeeping for adapting a fixed-size array to a ring buffer. - template <size_t MaxSize> class RingBufferHelper - { - private: - size_t latest_idx_ = 0; - size_t length_ = 0; - - //! Get the inner index of the front value: assumes not empty! - size_t - front_impl_() const noexcept - { - assert(!empty()); - // length will not exceed MaxSize, so this will not underflow - return (latest_idx_ + MaxSize - length_ + 1) % MaxSize; - } - - public: - //! Get the inner index for a given age (if possible) - bool - age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept - { - - if (empty()) { - return false; - } - if (age >= length_) { - return false; - } - // latest_idx_ is the same as latest_idx_ + MaxSize (when modulo MaxSize) so we add it to handle - // underflow with unsigned values - out_inner_idx = (latest_idx_ + MaxSize - age) % MaxSize; - return true; - } - - //! Get the inner index for a given index (if possible) - bool - index_to_inner_index(size_t index, size_t &out_inner_idx) const noexcept - { - - if (empty()) { - return false; - } - if (index >= length_) { - return false; - } - // Just add to the front index and take modulo MaxSize - out_inner_idx = (front_impl_() + index) % MaxSize; - return true; - } - - //! Is the buffer empty? - bool - empty() const noexcept - { - return length_ == 0; - } - - //! How many elements are in the buffer? - size_t - size() const noexcept - { - return length_; - } - - /*! - * @brief Update internal state for pushing an element to the back, and return the inner index to store - * the element at. - */ - size_t - push_back_location() noexcept - { - latest_idx_ = (latest_idx_ + 1) % MaxSize; - length_ = std::min(length_ + 1, MaxSize); - return latest_idx_; - } - - /*! - * @brief Record the logical removal of the front element, if any. - * - * Does nothing if the buffer is empty. - */ - void - pop_front() noexcept - { - if (!empty()) { - length_--; - } - } - - //! Get the inner index of the front value, or MaxSize if empty. - size_t - front_inner_index() const noexcept - { - if (empty()) { - return MaxSize; - } - return front_impl_(); - } - - //! Get the inner index of the back (latest) value, or MaxSize if empty. - size_t - back_inner_index() const noexcept - { - if (empty()) { - return MaxSize; - } - return latest_idx_; - } - }; - - /*! - * @brief Base class used by iterators and const_iterators of HistoryBuffer, providing substantial parts of the - * functionality. - * - * Using inheritance instead of composition here to more easily satisfy the C++ standard's requirements for - * iterators (e.g. that const iterators and iterators are comparable, etc.) - * - * All invalid instances will compare as equal, but they are not all equivalent: the "past the end" iterator is - * invalid to dereference, but is allowed to be decremented. - */ - template <size_t MaxSize> class RingBufferIteratorBase - { - static_assert(MaxSize < (std::numeric_limits<size_t>::max() >> 1), "Cannot use most significant bit"); - // for internal use - RingBufferIteratorBase(const RingBufferHelper<MaxSize> *helper, size_t index) - : buf_helper_(*helper), index_(index) - {} - - public: - //! Construct the "past the end" iterator that can be decremented safely - static RingBufferIteratorBase - end(const RingBufferHelper<MaxSize> &helper) - { - return RingBufferIteratorBase(helper, helper.size()); - } - - //! Is this iterator valid? - bool - valid() const noexcept; - - //! Is this iterator valid? - explicit operator bool() const noexcept - { - return valid(); - } - - //! What is the index stored by this iterator? - size_t - index() const noexcept - { - return index_; - } - - /*! - * @brief True if this iterator is "irrecoverably" invalid (aka, cleared/default constructed). - * - * Implies !valid() - */ - bool - is_cleared() const noexcept - { - return index_ > MaxSize; - } - - protected: - //! default constructor - RingBufferIteratorBase() = default; - - //! Constructor from a helper and index - explicit RingBufferIteratorBase(const RingBufferHelper<MaxSize> &helper, size_t index) - : buf_helper_(&helper), index_(index % MaxSize) - {} - - - /*! - * Clear member variables so we are bytewise identical to a default constructed instance, and thus - * irrecoverably invalid. - */ - void - clear() noexcept - { - *this = {}; - } - - /*! - * @brief Increment the internal index, given appropriate conditions. - * - * If we were already out of range (invalid), the members are cleared so we are irrecoverably invalid. - * However, you can reach the "past-the-end" element using this function and still decrement away from - * it. - */ - void - increment() noexcept - { - if (valid()) { - index_++; - } else { - // make sure we can't "back out" of invalid - clear(); - } - } - - /*! - * @brief Decrement the internal index, given appropriate conditions. - * - * If our index is not 0 and not our sentinel (MaxSize), we decrement. - * (This is a broader condition than "valid()": we want to be able to decrement() on the "past-the-end" - * iterator to get the last element, etc.) If we were already out of range (invalid) the members are - * cleared so we are irrecoverably invalid. - */ - void - decrement() noexcept - { - if (index_ > 0 && !is_cleared()) { - index_--; - } else { - // make sure we can't "back out" of invalid - clear(); - } - } - - public: - /*! - * @brief Compute the difference between two iterators. - * - * - If both are invalid, the result is 0. - * - If both are either valid or the "past-the-end" iterator, the result is the difference in (logical, - * not inner) index. - * - * @throws std::logic_error if one of the above conditions is not met. - */ - std::ptrdiff_t - operator-(const RingBufferIteratorBase<MaxSize> &other) const; - - /*! - * @brief Increment by an arbitrary value. - */ - RingBufferIteratorBase & - operator+=(std::ptrdiff_t n) - { - if (is_cleared()) { - return *this; - } - - size_t n_clean = remap_to_range(n); - index_ = (index_ + n_clean) % MaxSize; - return *this; - } - - /*! - * @brief Decrement by an arbitrary value. - */ - RingBufferIteratorBase & - operator-=(std::ptrdiff_t n) - { - if (is_cleared()) { - return *this; - } - size_t n_clean = remap_to_range(n); - index_ = (index_ + MaxSize - n_clean) % MaxSize; - return *this; - } - /*! - * @brief Add a count. - */ - RingBufferIteratorBase - operator+(std::ptrdiff_t n) const - { - RingBufferIteratorBase ret = *this; - ret += n; - return ret; - } - - /*! - * @brief Subtract a count. - */ - RingBufferIteratorBase - operator-(std::ptrdiff_t n) const - { - RingBufferIteratorBase ret = *this; - ret -= n; - return ret; - } - - private: - //! Returns a value in [0, MaxSize) - static size_t - remap_to_range(std::ptrdiff_t n) - { - if (n < 0) { - size_t ret = MaxSize - (static_cast<size_t>(-1 * n) % MaxSize); - assert(ret < MaxSize); - return ret; - } - return static_cast<size_t>(n % MaxSize); - } - const RingBufferHelper<MaxSize> *buf_helper_{nullptr}; - size_t index_{MaxSize + 1}; - }; - - /*! - * Equality comparison operator for @ref RingBufferIteratorBase, which is the base of all HistoryBuffer iterator - * types. - */ - template <size_t MaxSize> - static inline bool - operator==(RingBufferIteratorBase<MaxSize> const &lhs, RingBufferIteratorBase<MaxSize> const &rhs) noexcept - { - const bool lhs_valid = lhs.valid(); - const bool rhs_valid = rhs.valid(); - if (!lhs_valid && !rhs_valid) { - // all invalid iterators compare equal. - return true; - } - if (lhs_valid != rhs_valid) { - // valid is never equal to invalid. - return false; - } - // OK, so both are valid. Now, we look at the index - return lhs.index() == rhs.index(); - } - - /*! - * Inequality comparison operator for @ref RingBufferIteratorBase, which is the base of all HistoryBuffer - * iterator types. - */ - template <size_t MaxSize> - static inline bool - operator!=(RingBufferIteratorBase<MaxSize> const &lhs, RingBufferIteratorBase<MaxSize> const &rhs) noexcept - { - return !(lhs == rhs); - } - + template <typename T, size_t MaxSize> class HistoryBufConstIterator; template <typename T, size_t MaxSize> class HistoryBufIterator; - - /** - * @brief Class template for const_iterator for HistoryBuffer - * - * @tparam T Container element type - must match HistoryBuffer - * @tparam MaxSize Maximum number of elements - must match HistoryBuffer - */ - template <typename T, size_t MaxSize> class HistoryBufConstIterator : public RingBufferIteratorBase<MaxSize> - { - using base = RingBufferIteratorBase<MaxSize>; - // for use internally - HistoryBufConstIterator(const HistoryBuffer<T, MaxSize> *container, - RingBufferIteratorBase<MaxSize> &&iter_base) - : base(std::move(iter_base)), container_(container) - {} - - public: - using iterator_category = std::random_access_iterator_tag; - using value_type = const T; - using difference_type = std::ptrdiff_t; - using pointer = const T *; - using reference = const T &; - //! Default-construct an (invalid) iterator. - HistoryBufConstIterator() = default; - - //! Construct from a container, its helper, and an index: mostly for internal use. - HistoryBufConstIterator(const HistoryBuffer<T, MaxSize> &container, - const RingBufferHelper<MaxSize> &helper, - size_t index); - - //! Implicit conversion from a non-const iterator - HistoryBufConstIterator(const HistoryBufIterator<T, MaxSize> &nonconst); - - //! Construct the "past the end" iterator that can be decremented safely - static HistoryBufConstIterator - end(const HistoryBuffer<T, MaxSize> &container, const RingBufferHelper<MaxSize> &helper) - { - return {&container, RingBufferIteratorBase<MaxSize>::end(helper)}; - } - - //! Is this iterator valid? - bool - valid() const noexcept - { - return container_ != nullptr && base::valid(); - } - - //! Is this iterator valid? - explicit operator bool() const noexcept - { - return valid(); - } - - //! Dereference operator: throws std::out_of_range if invalid - reference - operator*() const; - - //! Smart pointer operator: returns nullptr if invalid - pointer - operator->() const noexcept; - - //! Pre-increment: Advance, then return self. - HistoryBufConstIterator & - operator++() - { - base::increment(); - return *this; - } - - //! Post-increment: return a copy of initial state after incrementing self - HistoryBufConstIterator - operator++(int) - { - HistoryBufConstIterator tmp = *this; - base::increment(); - return tmp; - } - - //! Pre-decrement: Subtract, then return self. - HistoryBufConstIterator & - operator--() - { - base::decrement(); - return *this; - } - - //! Post-decrement: return a copy of initial state after decrementing self - HistoryBufConstIterator - operator--(int) - { - HistoryBufConstIterator tmp = *this; - base::decrement(); - return tmp; - } - - - using base::operator-; - - HistoryBufConstIterator & - operator+=(std::ptrdiff_t n) noexcept - { - static_cast<base &>(*this) += n; - - return *this; - } - - HistoryBufConstIterator & - operator-=(std::ptrdiff_t n) noexcept - { - static_cast<base &>(*this) -= n; - - return *this; - } - - //! Increment a copy of the iterator a given number of steps. - HistoryBufConstIterator - operator+(std::ptrdiff_t n) const noexcept - { - HistoryBufConstIterator ret(*this); - ret += n; - return ret; - } - - //! Decrement a copy of the iterator a given number of steps. - HistoryBufConstIterator - operator-(std::ptrdiff_t n) const noexcept - { - HistoryBufConstIterator ret(*this); - ret -= n; - return ret; - } - - private: - const HistoryBuffer<T, MaxSize> *container_{nullptr}; - }; - - - /** - * @brief Class template for iterator for HistoryBuffer - * - * @see HistoryBufConstIterator for the const_iterator version - * - * @tparam T Container element type - must match HistoryBuffer - * @tparam MaxSize Maximum number of elements - must match HistoryBuffer - */ - template <typename T, size_t MaxSize> class HistoryBufIterator : public RingBufferIteratorBase<MaxSize> - { - using base = RingBufferIteratorBase<MaxSize>; - - // for use internally - HistoryBufIterator(HistoryBuffer<T, MaxSize> *container, RingBufferIteratorBase<MaxSize> &&iter_base) - : base(std::move(iter_base)), container_(container) - {} - - public: - using iterator_category = std::random_access_iterator_tag; - using value_type = T; - using difference_type = std::ptrdiff_t; - using pointer = T *; - using reference = T &; - - //! Default-construct an (invalid) iterator. - HistoryBufIterator() = default; - - //! Construct from a container, its helper, and an index: mostly for internal use. - HistoryBufIterator(HistoryBuffer<T, MaxSize> &container, - const RingBufferHelper<MaxSize> &helper, - size_t index); - - //! Construct the "past the end" iterator that can be decremented safely - static HistoryBufIterator - end(HistoryBuffer<T, MaxSize> &container, const RingBufferHelper<MaxSize> &helper) - { - return {&container, RingBufferIteratorBase<MaxSize>::end(helper)}; - } - - //! Is this iterator valid? - bool - valid() const noexcept - { - return container_ != nullptr && base::valid(); - } - - //! Is this iterator valid? - explicit operator bool() const noexcept - { - return valid(); - } - - //! Dereference operator: throws std::out_of_range if invalid - reference - operator*() const; - - //! Smart pointer operator: returns nullptr if invalid - pointer - operator->() const noexcept; - - //! Pre-increment: Advance, then return self. - HistoryBufIterator & - operator++() - { - base::increment(); - return *this; - } - - //! Post-increment: return a copy of initial state after incrementing self - HistoryBufIterator - operator++(int) - { - HistoryBufIterator tmp = *this; - base::increment(); - return tmp; - } - - //! Pre-decrement: Subtract, then return self. - HistoryBufIterator & - operator--() - { - base::decrement(); - return *this; - } - - //! Post-decrement: return a copy of initial state after decrementing self - HistoryBufIterator - operator--(int) - { - HistoryBufIterator tmp = *this; - base::decrement(); - return tmp; - } - using base::operator-; - - HistoryBufIterator & - operator+=(std::ptrdiff_t n) noexcept - { - static_cast<base &>(*this) += n; - - return *this; - } - - HistoryBufIterator & - operator-=(std::ptrdiff_t n) noexcept - { - static_cast<base &>(*this) -= n; - - return *this; - } - - //! Increment a copy of the iterator a given number of steps. - HistoryBufIterator - operator+(std::ptrdiff_t n) const noexcept - { - HistoryBufIterator ret(*this); - ret += n; - return ret; - } - - //! Decrement a copy of the iterator a given number of steps. - HistoryBufIterator - operator-(std::ptrdiff_t n) const noexcept - { - HistoryBufIterator ret(*this); - ret -= n; - return ret; - } - - private: - HistoryBuffer<T, MaxSize> *container_{nullptr}; - }; - - } // namespace detail /*! @@ -642,26 +33,14 @@ namespace detail { */ template <typename T, size_t MaxSize> class HistoryBuffer { -private: - using container_t = std::array<T, MaxSize>; - container_t internalBuffer{}; - detail::RingBufferHelper<MaxSize> helper_; - - static_assert(MaxSize < (std::numeric_limits<size_t>::max() >> 1), "Cannot use most significant bit"); - public: //! Is the buffer empty? bool - empty() const noexcept - { - return helper_.empty(); - } + empty() const noexcept; + //! How many elements are in the buffer? size_t - size() const noexcept - { - return helper_.size(); - } + size() const noexcept; /*! @@ -671,19 +50,7 @@ public: * expect. */ void - push_back(const T &element) - { - auto inner_index = helper_.push_back_location(); - internalBuffer[inner_index] = element; - } - - //! @overload - void - push_back(T &&element) - { - auto inner_index = helper_.push_back_location(); - internalBuffer[inner_index] = std::move(element); - } + push_back(const T &element); /*! * @brief Logically remove the oldest element from the buffer. @@ -693,244 +60,203 @@ public: * This invalidates iterators. They won't be poisoned, but they will return something you don't expect. */ void - pop_front() + pop_front() noexcept { helper_.pop_front(); } /*! * @brief Access something at a given age, where age 0 is the most recent value, age 1 precedes it, etc. + * (reverse chronological order) * * Out of bounds accesses will return nullptr. */ T * - get_at_age(size_t age) noexcept - { - size_t inner_index = 0; - if (helper_.age_to_inner_index(age, inner_index)) { - return &internalBuffer[inner_index]; - } - return nullptr; - } + get_at_age(size_t age) noexcept; //! @overload const T * - get_at_age(size_t age) const noexcept - { - size_t inner_index = 0; - if (helper_.age_to_inner_index(age, inner_index)) { - return &internalBuffer[inner_index]; - } - return nullptr; - } + get_at_age(size_t age) const noexcept; /*! * @brief Access something at a given index, where 0 is the least-recent value still stored, index 1 follows it, - * etc. + * etc. (chronological order) * * Out of bounds accesses will return nullptr. */ T * - get_at_index(size_t index) noexcept - { - size_t inner_index = 0; - if (helper_.index_to_inner_index(index, inner_index)) { - return &internalBuffer[inner_index]; - } - return nullptr; - } + get_at_index(size_t index) noexcept; //! @overload const T * - get_at_index(size_t index) const noexcept - { - size_t inner_index = 0; - if (helper_.index_to_inner_index(index, inner_index)) { - return &internalBuffer[inner_index]; - } - return nullptr; - } - - - - //! Access the ring buffer helper, mostly for implementation usage only. - const detail::RingBufferHelper<MaxSize> & - helper() const noexcept - { - return helper_; - } + get_at_index(size_t index) const noexcept; using iterator = detail::HistoryBufIterator<T, MaxSize>; using const_iterator = detail::HistoryBufConstIterator<T, MaxSize>; - //! Get an iterator for the least-recent element. + //! Get a const iterator for the oldest element. + const_iterator + cbegin() const noexcept; + + //! Get a "past the end" (past the newest) const iterator + const_iterator + cend() const noexcept; + + //! Get a const iterator for the oldest element. + const_iterator + begin() const noexcept; + + //! Get a "past the end" (past the newest) const iterator + const_iterator + end() const noexcept; + + //! Get an iterator for the oldest element. iterator - begin() noexcept - { - return iterator(*this, helper_, 0); - } + begin() noexcept; - //! Get a "past the end" iterator + //! Get a "past the end" (past the newest) iterator iterator - end() noexcept - { - return iterator::end(*this, helper_); - } - - //! Get a const iterator for the least-recent element. - const_iterator - cbegin() const noexcept - { - return const_iterator(*this, helper_, 0); - } - - //! Get a "past the end" const iterator - const_iterator - cend() const noexcept - { - return const_iterator::end(*this, helper_); - } - - //! Get a const iterator for the least-recent element. - const_iterator - begin() const noexcept - { - return cbegin(); - } - - //! Get a "past the end" const iterator - const_iterator - end() const noexcept - { - return cend(); - } + end() noexcept; /*! - * @brief Gets a reference to the front (least-recent) element in the buffer. + * @brief Gets a reference to the front (oldest) element in the buffer. * @throws std::logic_error if buffer is empty */ T & - front() - { - if (empty()) { - throw std::logic_error("Cannot get the front of an empty buffer"); - } - return internalBuffer[helper_.front_inner_index()]; - } + front(); + //! @overload const T & - front() const - { - if (empty()) { - throw std::logic_error("Cannot get the front of an empty buffer"); - } - return internalBuffer[helper_.front_inner_index()]; - } + front() const; /*! - * @brief Gets a reference to the back (most-recent) element in the buffer. + * @brief Gets a reference to the back (newest) element in the buffer. * @throws std::logic_error if buffer is empty */ T & - back() - { - if (empty()) { - throw std::logic_error("Cannot get the back of an empty buffer"); - } - return internalBuffer[helper_.back_inner_index()]; - } + back(); //! @overload const T & - back() const - { - if (empty()) { - throw std::logic_error("Cannot get the back of an empty buffer"); - } - return internalBuffer[helper_.back_inner_index()]; - } + back() const; + +private: + // Make sure all valid indices can be represented in a signed integer of the same size + static_assert(MaxSize < (std::numeric_limits<size_t>::max() >> 1), "Cannot use most significant bit"); + + using container_t = std::array<T, MaxSize>; + container_t internalBuffer{}; + detail::RingBufferHelper<MaxSize> helper_; }; -namespace detail { +template <typename T, size_t MaxSize> +inline bool +HistoryBuffer<T, MaxSize>::empty() const noexcept +{ + return helper_.empty(); +} - template <size_t MaxSize> - inline bool - RingBufferIteratorBase<MaxSize>::valid() const noexcept - { - return index_ < MaxSize && buf_helper_ != nullptr && index_ < buf_helper_->size(); +template <typename T, size_t MaxSize> +inline size_t +HistoryBuffer<T, MaxSize>::size() const noexcept +{ + return helper_.size(); +} + +template <typename T, size_t MaxSize> +inline void +HistoryBuffer<T, MaxSize>::push_back(const T &element) +{ + auto inner_index = helper_.push_back_location(); + internalBuffer[inner_index] = element; +} + +template <typename T, size_t MaxSize> +inline T * +HistoryBuffer<T, MaxSize>::get_at_age(size_t age) noexcept +{ + size_t inner_index = 0; + if (helper_.age_to_inner_index(age, inner_index)) { + return &internalBuffer[inner_index]; } - - template <size_t MaxSize> - inline std::ptrdiff_t - detail::RingBufferIteratorBase<MaxSize>::operator-(const RingBufferIteratorBase<MaxSize> &other) const - { - const bool self_valid = valid(); - const bool other_valid = other.valid(); - if (!self_valid && !other_valid) { - return 0; - } - if (index_ < MaxSize && other.index_ < MaxSize) { - return static_cast<std::ptrdiff_t>(index_) - static_cast<std::ptrdiff_t>(other.index_); - } - throw std::logic_error( - "Tried to find the difference between an iterator that has no concrete index, and one that does."); + return nullptr; +} +template <typename T, size_t MaxSize> +inline const T * +HistoryBuffer<T, MaxSize>::get_at_age(size_t age) const noexcept +{ + size_t inner_index = 0; + if (helper_.age_to_inner_index(age, inner_index)) { + return &internalBuffer[inner_index]; } + return nullptr; +} - template <typename T, size_t MaxSize> - inline HistoryBufIterator<T, MaxSize>::HistoryBufIterator(HistoryBuffer<T, MaxSize> &container, - const RingBufferHelper<MaxSize> &helper, - size_t index) - : base(helper, index), container_(&container) - {} - - template <typename T, size_t MaxSize> - inline typename HistoryBufIterator<T, MaxSize>::reference - HistoryBufIterator<T, MaxSize>::operator*() const - { - auto ptr = container_->get_at_index(base::index()); - if (ptr == nullptr) { - throw std::out_of_range("Iterator index out of range"); - } - return *ptr; +template <typename T, size_t MaxSize> +inline T * +HistoryBuffer<T, MaxSize>::get_at_index(size_t index) noexcept +{ + size_t inner_index = 0; + if (helper_.index_to_inner_index(index, inner_index)) { + return &internalBuffer[inner_index]; } + return nullptr; +} - template <typename T, size_t MaxSize> - inline typename HistoryBufIterator<T, MaxSize>::pointer - HistoryBufIterator<T, MaxSize>::operator->() const noexcept - { - return container_->get_at_index(base::index()); +template <typename T, size_t MaxSize> +inline const T * +HistoryBuffer<T, MaxSize>::get_at_index(size_t index) const noexcept +{ + size_t inner_index = 0; + if (helper_.index_to_inner_index(index, inner_index)) { + return &internalBuffer[inner_index]; } + return nullptr; +} - template <typename T, size_t MaxSize> - inline HistoryBufConstIterator<T, MaxSize>::HistoryBufConstIterator(const HistoryBuffer<T, MaxSize> &container, - const RingBufferHelper<MaxSize> &helper, - size_t index) - : base(helper, index), container_(&container) - {} - - template <typename T, size_t MaxSize> - inline detail::HistoryBufConstIterator<T, MaxSize>::HistoryBufConstIterator( - const HistoryBufIterator<T, MaxSize> &nonconst) - : base(nonconst), container_(nonconst.container_) - {} - - template <typename T, size_t MaxSize> - inline typename HistoryBufConstIterator<T, MaxSize>::reference - HistoryBufConstIterator<T, MaxSize>::operator*() const - { - auto ptr = container_->get_at_index(base::index()); - if (ptr == nullptr) { - throw std::out_of_range("Iterator index out of range"); - } - return *ptr; +template <typename T, size_t MaxSize> +inline T & +HistoryBuffer<T, MaxSize>::front() +{ + if (empty()) { + throw std::logic_error("Cannot get the front of an empty buffer"); } + return internalBuffer[helper_.front_inner_index()]; +} - template <typename T, size_t MaxSize> - inline typename HistoryBufConstIterator<T, MaxSize>::pointer - HistoryBufConstIterator<T, MaxSize>::operator->() const noexcept - { - return container_->get_at_index(base::index()); +template <typename T, size_t MaxSize> +inline const T & +HistoryBuffer<T, MaxSize>::front() const +{ + if (empty()) { + throw std::logic_error("Cannot get the front of an empty buffer"); } -} // namespace detail + return internalBuffer[helper_.front_inner_index()]; +} + +template <typename T, size_t MaxSize> +inline T & +HistoryBuffer<T, MaxSize>::back() +{ + if (empty()) { + throw std::logic_error("Cannot get the back of an empty buffer"); + } + return internalBuffer[helper_.back_inner_index()]; +} + +template <typename T, size_t MaxSize> +inline const T & +HistoryBuffer<T, MaxSize>::back() const +{ + if (empty()) { + throw std::logic_error("Cannot get the back of an empty buffer"); + } + return internalBuffer[helper_.back_inner_index()]; +} + + } // namespace xrt::auxiliary::util + +#include "u_template_historybuf_const_iterator.inl" +#include "u_template_historybuf_iterator.inl" diff --git a/src/xrt/auxiliary/util/u_template_historybuf_const_iterator.inl b/src/xrt/auxiliary/util/u_template_historybuf_const_iterator.inl new file mode 100644 index 000000000..cef7d992d --- /dev/null +++ b/src/xrt/auxiliary/util/u_template_historybuf_const_iterator.inl @@ -0,0 +1,257 @@ +// Copyright 2021-2022, Collabora, Ltd. +// +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief const_iterator details for ring buffer + * @author Ryan Pavlik <ryan.pavlik@collabora.com> + * @ingroup aux_util + */ + +#include <stddef.h> +#include <type_traits> + +namespace xrt::auxiliary::util { +template <typename T, size_t MaxSize> class HistoryBuffer; + +namespace detail { + /** + * @brief Class template for const_iterator for HistoryBuffer + * + * @tparam T Container element type - must match HistoryBuffer + * @tparam MaxSize Maximum number of elements - must match HistoryBuffer + */ + template <typename T, size_t MaxSize> + class HistoryBufConstIterator : public RandomAccessIteratorBase<const RingBufferHelper<MaxSize>> + { + using base = RandomAccessIteratorBase<const RingBufferHelper<MaxSize>>; + friend class HistoryBuffer<T, MaxSize>; + friend class HistoryBufIterator<T, MaxSize>; + + public: + using container_type = const HistoryBuffer<T, MaxSize>; + using typename base::difference_type; + using typename base::iterator_category; + using value_type = const T; + using pointer = const T *; + using reference = const T &; + + //! Default-construct an (invalid) iterator. + HistoryBufConstIterator() = default; + + // copy and move as you wish + HistoryBufConstIterator(HistoryBufConstIterator const &) = default; + HistoryBufConstIterator(HistoryBufConstIterator &&) = default; + HistoryBufConstIterator & + operator=(HistoryBufConstIterator const &) = default; + HistoryBufConstIterator & + operator=(HistoryBufConstIterator &&) = default; + + //! Implicit conversion from a non-const iterator + HistoryBufConstIterator(const HistoryBufIterator<T, MaxSize> &other); + + //! Is this iterator valid? + bool + valid() const noexcept + { + return container_ != nullptr && base::valid(); + } + + //! Is this iterator valid? + explicit operator bool() const noexcept + { + return valid(); + } + + //! Access the container: for internal use + container_type * + container() const noexcept + { + return container_; + } + + //! Dereference operator: throws std::out_of_range if invalid + reference + operator*() const; + + //! Smart pointer operator: returns nullptr if invalid + pointer + operator->() const noexcept; + + //! Pre-increment: Advance, then return self. + HistoryBufConstIterator & + operator++(); + + //! Post-increment: return a copy of initial state after incrementing self + HistoryBufConstIterator + operator++(int); + + //! Pre-decrement: Subtract, then return self. + HistoryBufConstIterator & + operator--(); + + //! Post-decrement: return a copy of initial state after decrementing self + HistoryBufConstIterator + operator--(int); + + // Use the base class implementation of subtracting one iterator from another + using base::operator-; + + //! Increment by an arbitrary amount. + HistoryBufConstIterator & + operator+=(std::ptrdiff_t n) noexcept; + + //! Decrement by an arbitrary amount. + HistoryBufConstIterator & + operator-=(std::ptrdiff_t n) noexcept; + + //! Increment a copy of the iterator by an arbitrary amount. + HistoryBufConstIterator + operator+(std::ptrdiff_t n) const noexcept; + + //! Decrement a copy of the iterator by an arbitrary amount. + HistoryBufConstIterator + operator-(std::ptrdiff_t n) const noexcept; + + private: + //! Factory for a "begin" iterator from a container and its helper: mostly for internal use. + static HistoryBufConstIterator + begin(container_type &container, const RingBufferHelper<MaxSize> &helper) + { + return {&container, std::move(base::begin(helper))}; + } + + //! Construct the "past the end" iterator that can be decremented safely + static HistoryBufConstIterator + end(container_type &container, const RingBufferHelper<MaxSize> &helper) + { + return {&container, std::move(base::end(helper))}; + } + + // for use internally + HistoryBufConstIterator(container_type *container, base &&iter_base) + : base(std::move(iter_base)), container_(container) + {} + container_type *container_{nullptr}; + }; + + template <typename T, size_t MaxSize> + inline typename HistoryBufConstIterator<T, MaxSize>::reference + HistoryBufConstIterator<T, MaxSize>::operator*() const + { + auto ptr = container_->get_at_index(base::index()); + if (ptr == nullptr) { + throw std::out_of_range("Iterator index out of range"); + } + return *ptr; + } + + template <typename T, size_t MaxSize> + inline typename HistoryBufConstIterator<T, MaxSize>::pointer + HistoryBufConstIterator<T, MaxSize>::operator->() const noexcept + { + return container_->get_at_index(base::index()); + } + + template <typename T, size_t MaxSize> + inline HistoryBufConstIterator<T, MaxSize> & + HistoryBufConstIterator<T, MaxSize>::operator++() + { + this->increment_n(1); + return *this; + } + + template <typename T, size_t MaxSize> + inline HistoryBufConstIterator<T, MaxSize> + HistoryBufConstIterator<T, MaxSize>::operator++(int) + { + HistoryBufConstIterator tmp = *this; + this->increment_n(1); + return tmp; + } + + template <typename T, size_t MaxSize> + inline HistoryBufConstIterator<T, MaxSize> & + HistoryBufConstIterator<T, MaxSize>::operator--() + { + this->decrement_n(1); + return *this; + } + + template <typename T, size_t MaxSize> + inline HistoryBufConstIterator<T, MaxSize> + HistoryBufConstIterator<T, MaxSize>::operator--(int) + { + HistoryBufConstIterator tmp = *this; + this->decrement_n(1); + return tmp; + } + + template <typename T, size_t MaxSize> + inline HistoryBufConstIterator<T, MaxSize> & + HistoryBufConstIterator<T, MaxSize>::operator+=(std::ptrdiff_t n) noexcept + { + static_cast<base &>(*this) += n; + return *this; + } + + template <typename T, size_t MaxSize> + inline HistoryBufConstIterator<T, MaxSize> & + HistoryBufConstIterator<T, MaxSize>::operator-=(std::ptrdiff_t n) noexcept + { + static_cast<base &>(*this) -= n; + return *this; + } + + template <typename T, size_t MaxSize> + inline HistoryBufConstIterator<T, MaxSize> + HistoryBufConstIterator<T, MaxSize>::operator+(std::ptrdiff_t n) const noexcept + { + HistoryBufConstIterator ret(*this); + ret += n; + return ret; + } + + template <typename T, size_t MaxSize> + inline HistoryBufConstIterator<T, MaxSize> + HistoryBufConstIterator<T, MaxSize>::operator-(std::ptrdiff_t n) const noexcept + { + HistoryBufConstIterator ret(*this); + ret -= n; + return ret; + } +} // namespace detail + + +template <typename T, size_t MaxSize> +inline typename HistoryBuffer<T, MaxSize>::const_iterator +HistoryBuffer<T, MaxSize>::cbegin() const noexcept +{ + static_assert(std::is_same<typename std::iterator_traits<const_iterator>::iterator_category, + std::random_access_iterator_tag>::value, + "Iterator should be random access"); + return const_iterator::begin(*this, helper_); +} + +template <typename T, size_t MaxSize> +inline typename HistoryBuffer<T, MaxSize>::const_iterator +HistoryBuffer<T, MaxSize>::cend() const noexcept +{ + return const_iterator::end(*this, helper_); +} + +template <typename T, size_t MaxSize> +inline typename HistoryBuffer<T, MaxSize>::const_iterator +HistoryBuffer<T, MaxSize>::begin() const noexcept +{ + return cbegin(); +} + +template <typename T, size_t MaxSize> +inline typename HistoryBuffer<T, MaxSize>::const_iterator +HistoryBuffer<T, MaxSize>::end() const noexcept +{ + return cend(); +} + +} // namespace xrt::auxiliary::util diff --git a/src/xrt/auxiliary/util/u_template_historybuf_impl_helpers.hpp b/src/xrt/auxiliary/util/u_template_historybuf_impl_helpers.hpp new file mode 100644 index 000000000..39b8b8481 --- /dev/null +++ b/src/xrt/auxiliary/util/u_template_historybuf_impl_helpers.hpp @@ -0,0 +1,195 @@ +// Copyright 2021-2022, Collabora, Ltd. +// +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief All the "element-type-independent" code (helper objects, base classes) for a ringbuffer implementation on top + * of a fixed size array + * @author Ryan Pavlik <ryan.pavlik@collabora.com> + * @author Moses Turner <moses@collabora.com> + * @ingroup aux_util + */ + +#pragma once + +#include <algorithm> +#include <assert.h> +#include <stdlib.h> + + +//| -4 | -3 | -2 | -1 | Top | Garbage | +// OR +//| -4 | -3 | -2 | -1 | Top | -7 | -6 | -5 | + +namespace xrt::auxiliary::util::detail { +/** + * @brief All the bookkeeping for adapting a fixed-size array to a ring buffer. + * + * This is all the guts of the ring buffer except for the actual buffer. + * We split it out to + * - reduce code size (this can be shared among multiple types) + * - separate concerns (keeping track of the indices separate from owning the buffer) + * - allow easier implementation of both const iterators and non-const iterators + * + * There are a few types of "index": + * + * - just "index": an index where the least-recently-added element still remaining is numbered 0, the next + * oldest is 1, etc. (Chronological) + * - "age": Reverse chronological order: 0 means most-recently-added, 1 means the one before it, etc. + * - "inner" index: the index in the underlying array/buffer. It's called "inner" because the consumer of the + * ring buffer should not ever deal with this index, it's an implementation detail. + */ +template <size_t MaxSize> class RingBufferHelper +{ +public: + //! Get the inner index for a given age (if possible) + bool + age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept; + + //! Get the inner index for a given index (if possible) + bool + index_to_inner_index(size_t index, size_t &out_inner_idx) const noexcept; + + //! Is the buffer empty? + bool + empty() const noexcept + { + return length_ == 0; + } + + //! How many elements are in the buffer? + size_t + size() const noexcept + { + return length_; + } + + /*! + * @brief Update internal state for pushing an element to the back, and return the inner index to store + * the element at. + * + * This is the implementation of "push_back" excluding all the messy "actually dealing with the data" + * part ;-) + */ + size_t + push_back_location() noexcept; + + /*! + * @brief Record the logical removal of the front element, if any. + * + * Does nothing if the buffer is empty. Does not actually modify the value stored in the backing array. + */ + void + pop_front() noexcept; + + //! Get the inner index of the front (oldest) value, or MaxSize if empty. + size_t + front_inner_index() const noexcept; + + //! Get the inner index of the back (newest) value, or MaxSize if empty. + size_t + back_inner_index() const noexcept; + +private: + //! The inner index containing the most recently added element, if any + size_t latest_inner_idx_ = 0; + + //! The number of elements populated. + size_t length_ = 0; + + /** + * @brief Get the inner index of the front (oldest) value: assumes not empty! + * + * For internal use in this class only. + * + * @see front_inner_index() for the "safe" equivalent (that wraps this with error handling) + */ + size_t + front_impl_() const noexcept; +}; + + +template <size_t MaxSize> +inline size_t +RingBufferHelper<MaxSize>::front_impl_() const noexcept +{ + assert(!empty()); + // length will not exceed MaxSize, so this will not underflow + return (latest_inner_idx_ + MaxSize - length_ + 1) % MaxSize; +} + +template <size_t MaxSize> +inline bool +RingBufferHelper<MaxSize>::age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept +{ + + if (empty()) { + return false; + } + if (age >= length_) { + return false; + } + // latest_inner_idx_ is the same as (latest_inner_idx_ + MaxSize) % MaxSize so we add MaxSize to + // prevent underflow with unsigned values + out_inner_idx = (latest_inner_idx_ + MaxSize - age) % MaxSize; + return true; +} + +template <size_t MaxSize> +inline bool +RingBufferHelper<MaxSize>::index_to_inner_index(size_t index, size_t &out_inner_idx) const noexcept +{ + + if (empty()) { + return false; + } + if (index >= length_) { + return false; + } + // Just add to the front (oldest) index and take modulo MaxSize + out_inner_idx = (front_impl_() + index) % MaxSize; + return true; +} + +template <size_t MaxSize> +inline size_t +RingBufferHelper<MaxSize>::push_back_location() noexcept +{ + // We always increment the latest inner index modulo MaxSize + latest_inner_idx_ = (latest_inner_idx_ + 1) % MaxSize; + // Length cannot exceed MaxSize. If it already was MaxSize, that just means we're overwriting something at + // latest_inner_idx_ + length_ = std::min(length_ + 1, MaxSize); + return latest_inner_idx_; +} + +template <size_t MaxSize> +inline void +RingBufferHelper<MaxSize>::pop_front() noexcept +{ + if (!empty()) { + length_--; + } +} + +template <size_t MaxSize> +inline size_t +RingBufferHelper<MaxSize>::front_inner_index() const noexcept +{ + if (empty()) { + return MaxSize; + } + return front_impl_(); +} + +template <size_t MaxSize> +inline size_t +RingBufferHelper<MaxSize>::back_inner_index() const noexcept +{ + if (empty()) { + return MaxSize; + } + return latest_inner_idx_; +} + +} // namespace xrt::auxiliary::util::detail diff --git a/src/xrt/auxiliary/util/u_template_historybuf_iterator.inl b/src/xrt/auxiliary/util/u_template_historybuf_iterator.inl new file mode 100644 index 000000000..a4737463a --- /dev/null +++ b/src/xrt/auxiliary/util/u_template_historybuf_iterator.inl @@ -0,0 +1,245 @@ +// Copyright 2021-2022, Collabora, Ltd. +// +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief iterator details for ring buffer + * @author Ryan Pavlik <ryan.pavlik@collabora.com> + * @ingroup aux_util + */ + +#include <stddef.h> +#include <type_traits> + +namespace xrt::auxiliary::util { +template <typename T, size_t MaxSize> class HistoryBuffer; + +namespace detail { + /** + * @brief Class template for iterator for HistoryBuffer + * + * @tparam T Container element type - must match HistoryBuffer + * @tparam MaxSize Maximum number of elements - must match HistoryBuffer + */ + template <typename T, size_t MaxSize> + class HistoryBufIterator : public RandomAccessIteratorBase<const RingBufferHelper<MaxSize>> + { + using base = RandomAccessIteratorBase<const RingBufferHelper<MaxSize>>; + friend class HistoryBuffer<T, MaxSize>; + + public: + using container_type = HistoryBuffer<T, MaxSize>; + using typename base::difference_type; + using typename base::iterator_category; + using value_type = T; + using pointer = T *; + using reference = T &; + + //! Default-construct an (invalid) iterator. + HistoryBufIterator() = default; + + // copy and move as you wish + HistoryBufIterator(HistoryBufIterator const &) = default; + HistoryBufIterator(HistoryBufIterator &&) = default; + HistoryBufIterator & + operator=(HistoryBufIterator const &) = default; + HistoryBufIterator & + operator=(HistoryBufIterator &&) = default; + + //! Is this iterator valid? + bool + valid() const noexcept + { + return container_ != nullptr && base::valid(); + } + + //! Is this iterator valid? + explicit operator bool() const noexcept + { + return valid(); + } + + //! Access the container: for internal use + container_type * + container() const noexcept + { + return container_; + } + + //! Dereference operator: throws std::out_of_range if invalid + reference + operator*() const; + + //! Smart pointer operator: returns nullptr if invalid + pointer + operator->() const noexcept; + + //! Pre-increment: Advance, then return self. + HistoryBufIterator & + operator++(); + + //! Post-increment: return a copy of initial state after incrementing self + HistoryBufIterator + operator++(int); + + //! Pre-decrement: Subtract, then return self. + HistoryBufIterator & + operator--(); + + //! Post-decrement: return a copy of initial state after decrementing self + HistoryBufIterator + operator--(int); + + // Use the base class implementation of subtracting one iterator from another + using base::operator-; + + //! Increment by an arbitrary amount. + HistoryBufIterator & + operator+=(std::ptrdiff_t n) noexcept; + + //! Decrement by an arbitrary amount. + HistoryBufIterator & + operator-=(std::ptrdiff_t n) noexcept; + + //! Increment a copy of the iterator by an arbitrary amount. + HistoryBufIterator + operator+(std::ptrdiff_t n) const noexcept; + + //! Decrement a copy of the iterator by an arbitrary amount. + HistoryBufIterator + operator-(std::ptrdiff_t n) const noexcept; + + private: + //! Factory for a "begin" iterator from a container and its helper: mostly for internal use. + static HistoryBufIterator + begin(container_type &container, const RingBufferHelper<MaxSize> &helper) + { + return HistoryBufIterator{&container, std::move(base::begin(helper))}; + } + + //! Construct the "past the end" iterator that can be decremented safely + static HistoryBufIterator + end(container_type &container, const RingBufferHelper<MaxSize> &helper) + { + return HistoryBufIterator{&container, std::move(base::end(helper))}; + } + + // for use internally + HistoryBufIterator(container_type *container, base &&iter_base) + : base(std::move(iter_base)), container_(container) + {} + container_type *container_{nullptr}; + }; + + template <typename T, size_t MaxSize> + inline typename HistoryBufIterator<T, MaxSize>::reference + HistoryBufIterator<T, MaxSize>::operator*() const + { + auto ptr = container_->get_at_index(base::index()); + if (ptr == nullptr) { + throw std::out_of_range("Iterator index out of range"); + } + return *ptr; + } + + template <typename T, size_t MaxSize> + inline typename HistoryBufIterator<T, MaxSize>::pointer + HistoryBufIterator<T, MaxSize>::operator->() const noexcept + { + return container_->get_at_index(base::index()); + } + + template <typename T, size_t MaxSize> + inline HistoryBufIterator<T, MaxSize> & + HistoryBufIterator<T, MaxSize>::operator++() + { + this->increment_n(1); + return *this; + } + + template <typename T, size_t MaxSize> + inline HistoryBufIterator<T, MaxSize> + HistoryBufIterator<T, MaxSize>::operator++(int) + { + HistoryBufIterator tmp = *this; + this->increment_n(1); + return tmp; + } + + template <typename T, size_t MaxSize> + inline HistoryBufIterator<T, MaxSize> & + HistoryBufIterator<T, MaxSize>::operator--() + { + this->decrement_n(1); + return *this; + } + + template <typename T, size_t MaxSize> + inline HistoryBufIterator<T, MaxSize> + HistoryBufIterator<T, MaxSize>::operator--(int) + { + HistoryBufIterator tmp = *this; + this->decrement_n(1); + return tmp; + } + + template <typename T, size_t MaxSize> + inline HistoryBufIterator<T, MaxSize> & + HistoryBufIterator<T, MaxSize>::operator+=(std::ptrdiff_t n) noexcept + { + static_cast<base &>(*this) += n; + return *this; + } + + template <typename T, size_t MaxSize> + inline HistoryBufIterator<T, MaxSize> & + HistoryBufIterator<T, MaxSize>::operator-=(std::ptrdiff_t n) noexcept + { + static_cast<base &>(*this) -= n; + return *this; + } + + template <typename T, size_t MaxSize> + inline HistoryBufIterator<T, MaxSize> + HistoryBufIterator<T, MaxSize>::operator+(std::ptrdiff_t n) const noexcept + { + HistoryBufIterator ret(*this); + ret += n; + return ret; + } + + template <typename T, size_t MaxSize> + inline HistoryBufIterator<T, MaxSize> + HistoryBufIterator<T, MaxSize>::operator-(std::ptrdiff_t n) const noexcept + { + HistoryBufIterator ret(*this); + ret -= n; + return ret; + } + + // Conversion constructor for const iterator + template <typename T, size_t MaxSize> + inline HistoryBufConstIterator<T, MaxSize>::HistoryBufConstIterator(const HistoryBufIterator<T, MaxSize> &other) + : HistoryBufConstIterator(other.container(), base{other}) + {} +} // namespace detail + + +template <typename T, size_t MaxSize> +inline typename HistoryBuffer<T, MaxSize>::iterator +HistoryBuffer<T, MaxSize>::begin() noexcept +{ + static_assert(std::is_same<typename std::iterator_traits<iterator>::iterator_category, + std::random_access_iterator_tag>::value, + "Iterator should be random access"); + return iterator::begin(*this, helper_); +} + +template <typename T, size_t MaxSize> +inline typename HistoryBuffer<T, MaxSize>::iterator +HistoryBuffer<T, MaxSize>::end() noexcept +{ + return iterator::end(*this, helper_); +} + +} // namespace xrt::auxiliary::util diff --git a/tests/tests_history_buf.cpp b/tests/tests_history_buf.cpp index 08ab4cbfc..aaf829d08 100644 --- a/tests/tests_history_buf.cpp +++ b/tests/tests_history_buf.cpp @@ -11,9 +11,9 @@ #include <util/u_template_historybuf.hpp> using xrt::auxiliary::util::HistoryBuffer; -template <size_t MaxSize> +template <typename Container> static inline std::ostream & -operator<<(std::ostream &os, const xrt::auxiliary::util::detail::RingBufferIteratorBase<MaxSize> &iter_base) +operator<<(std::ostream &os, const xrt::auxiliary::util::RandomAccessIteratorBase<Container> &iter_base) { os << "Iterator@[" << iter_base.index() << "]"; return os; @@ -349,10 +349,10 @@ TEST_CASE("IteratorBase") buffer.push_back(2); buffer.push_back(4); using namespace xrt::auxiliary::util; - using iterator = typename HistoryBuffer<int, 4>::iterator; - iterator default_constructed{}; - iterator begin_constructed = buffer.begin(); - iterator end_constructed = buffer.end(); + using const_iterator = typename HistoryBuffer<int, 4>::const_iterator; + const_iterator default_constructed{}; + const_iterator begin_constructed = buffer.begin(); + const_iterator end_constructed = buffer.end(); SECTION("Check default constructed") { @@ -369,6 +369,7 @@ TEST_CASE("IteratorBase") { CHECK_FALSE(end_constructed.valid()); CHECK_FALSE(end_constructed.is_cleared()); - CHECK((++end_constructed).is_cleared()); + // if we go past the end, we can go backwards into validity. + CHECK_FALSE((++end_constructed).is_cleared()); } }