Monado OpenXR Runtime
u_template_historybuf_impl_helpers.hpp
Go to the documentation of this file.
1// Copyright 2021-2022, Collabora, Ltd.
2//
3// SPDX-License-Identifier: BSL-1.0
4/*!
5 * @file
6 * @brief All the "element-type-independent" code (helper objects, base classes) for a ringbuffer implementation on top
7 * of a fixed size array
8 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
9 * @author Moses Turner <moses@collabora.com>
10 * @ingroup aux_util
11 */
12
13#pragma once
14
15// IWYU pragma: private, include "util/u_template_historybuf.hpp"
16
17#include <algorithm>
18#include <assert.h>
19#include <stdlib.h>
20
21
22//| -4 | -3 | -2 | -1 | Top | Garbage |
23// OR
24//| -4 | -3 | -2 | -1 | Top | -7 | -6 | -5 |
25
26namespace xrt::auxiliary::util::detail {
27
28/**
29 * @brief All the bookkeeping for adapting a fixed-size array to a ring buffer.
30 *
31 * This is all the guts of the ring buffer except for the actual buffer.
32 * We split it out to
33 * - reduce code size (this can be shared among multiple types)
34 * - separate concerns (keeping track of the indices separate from owning the buffer)
35 * - allow easier implementation of both const iterators and non-const iterators
36 *
37 * There are a few types of "index":
38 *
39 * - just "index": an index where the least-recently-added element still remaining is numbered 0, the next
40 * oldest is 1, etc. (Chronological)
41 * - "age": Reverse chronological order: 0 means most-recently-added, 1 means the one before it, etc.
42 * - "inner" index: the index in the underlying array/buffer. It's called "inner" because the consumer of the
43 * ring buffer should not ever deal with this index, it's an implementation detail.
44 */
46{
47public:
48 //! Construct for a given size
49 explicit constexpr RingBufferHelper(size_t capacity) : capacity_(capacity) {}
50 RingBufferHelper(RingBufferHelper const &) = default;
53 operator=(RingBufferHelper const &) = default;
55 operator=(RingBufferHelper &&) = default;
56
57 //! Get the inner index for a given age (if possible)
58 bool
59 age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept;
60
61 //! Get the inner index for a given age, clamping it if out of bounds
62 bool
63 clamped_age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept;
64
65 //! Get the inner index for a given index (if possible)
66 bool
67 index_to_inner_index(size_t index, size_t &out_inner_idx) const noexcept;
68
69 //! Is the buffer empty?
70 bool
71 empty() const noexcept
72 {
73 return length_ == 0;
74 }
75
76 //! How many elements are in the buffer?
77 size_t
78 size() const noexcept
79 {
80 return length_;
81 }
82
83 /*!
84 * @brief Update internal state for pushing an element to the back, and return the inner index to store
85 * the element at.
86 *
87 * This is the implementation of "push_back" excluding all the messy "actually dealing with the data"
88 * part ;-)
89 */
90 size_t
91 push_back_location() noexcept;
92
93 /*!
94 * @brief Record the logical removal of the front element, if any.
95 *
96 * Does nothing if the buffer is empty. Does not actually modify the value stored in the backing array.
97 */
98 void
99 pop_front() noexcept;
100
101 /*!
102 * @brief Record the logical removal of the back element, if any.
103 *
104 * Returns false if the buffer is empty. Does not actually modify the
105 * value stored in the backing array.
106 */
107 bool
108 pop_back() noexcept;
109
110 //! Get the inner index of the front (oldest) value, or capacity_ if empty.
111 size_t
112 front_inner_index() const noexcept;
113
114 //! Get the inner index of the back (newest) value, or capacity_ if empty.
115 size_t
116 back_inner_index() const noexcept;
117
118 void
119 clear();
120
121private:
122 // Would be const, but that would mess up our ability to copy/move containers using this.
123 size_t capacity_;
124
125 //! The inner index containing the most recently added element, if any
126 size_t latest_inner_idx_ = 0;
127
128 //! The number of elements populated.
129 size_t length_ = 0;
130
131 /**
132 * @brief Get the inner index of the front (oldest) value: assumes not empty!
133 *
134 * For internal use in this class only.
135 *
136 * @see front_inner_index() for the "safe" equivalent (that wraps this with error handling)
137 */
138 size_t
139 front_impl_() const noexcept;
140};
141
142
143inline void
144RingBufferHelper::clear()
145{
146 this->latest_inner_idx_ = 0;
147 this->length_ = 0;
148}
149
150inline size_t
151RingBufferHelper::front_impl_() const noexcept
152{
153 assert(!empty());
154 // length will not exceed capacity_, so this will not underflow
155 return (latest_inner_idx_ + capacity_ - length_ + 1) % capacity_;
156}
157
158inline bool
159RingBufferHelper::age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept
160{
161
162 if (empty()) {
163 return false;
164 }
165 if (age >= length_) {
166 return false;
167 }
168 // latest_inner_idx_ is the same as (latest_inner_idx_ + capacity_) % capacity_ so we add capacity_ to
169 // prevent underflow with unsigned values
170 out_inner_idx = (latest_inner_idx_ + capacity_ - age) % capacity_;
171 return true;
172}
173
174inline bool
175RingBufferHelper::clamped_age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept
176{
177 if (empty()) {
178 return false;
179 }
180 return age_to_inner_index((std::min)(age, length_ - 1), out_inner_idx);
181}
182
183inline bool
184RingBufferHelper::index_to_inner_index(size_t index, size_t &out_inner_idx) const noexcept
185{
186
187 if (empty()) {
188 return false;
189 }
190 if (index >= length_) {
191 return false;
192 }
193 // add to the front (oldest) index and take modulo capacity_
194 out_inner_idx = (front_impl_() + index) % capacity_;
195 return true;
196}
197
198inline size_t
200{
201 // We always increment the latest inner index modulo capacity_
202 latest_inner_idx_ = (latest_inner_idx_ + 1) % capacity_;
203 // Length cannot exceed capacity_. If it already was capacity_, that means we're overwriting something at
204 // latest_inner_idx_
205 length_ = std::min(length_ + 1, capacity_);
206 return latest_inner_idx_;
207}
208
209inline void
211{
212 if (!empty()) {
213 length_--;
214 }
215}
216inline bool
218{
219 if (empty()) {
220 return false;
221 }
222 // adding capacity before -1 to avoid overflow
223 latest_inner_idx_ = (latest_inner_idx_ + capacity_ - 1) % capacity_;
224 length_--;
225 return true;
226}
227inline size_t
229{
230 if (empty()) {
231 return capacity_;
232 }
233 return front_impl_();
234}
235
236inline size_t
238{
239 if (empty()) {
240 return capacity_;
241 }
242 return latest_inner_idx_;
243}
244
245} // namespace xrt::auxiliary::util::detail
All the bookkeeping for adapting a fixed-size array to a ring buffer.
Definition: u_template_historybuf_impl_helpers.hpp:46
constexpr RingBufferHelper(size_t capacity)
Construct for a given size.
Definition: u_template_historybuf_impl_helpers.hpp:49
size_t push_back_location() noexcept
Update internal state for pushing an element to the back, and return the inner index to store the ele...
Definition: u_template_historybuf_impl_helpers.hpp:199
bool pop_back() noexcept
Record the logical removal of the back element, if any.
Definition: u_template_historybuf_impl_helpers.hpp:217
bool empty() const noexcept
Is the buffer empty?
Definition: u_template_historybuf_impl_helpers.hpp:71
size_t front_inner_index() const noexcept
Get the inner index of the front (oldest) value, or capacity_ if empty.
Definition: u_template_historybuf_impl_helpers.hpp:228
size_t back_inner_index() const noexcept
Get the inner index of the back (newest) value, or capacity_ if empty.
Definition: u_template_historybuf_impl_helpers.hpp:237
void pop_front() noexcept
Record the logical removal of the front element, if any.
Definition: u_template_historybuf_impl_helpers.hpp:210
bool clamped_age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept
Get the inner index for a given age, clamping it if out of bounds.
Definition: u_template_historybuf_impl_helpers.hpp:175
size_t size() const noexcept
How many elements are in the buffer?
Definition: u_template_historybuf_impl_helpers.hpp:78
bool age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept
Get the inner index for a given age (if possible)
Definition: u_template_historybuf_impl_helpers.hpp:159
bool index_to_inner_index(size_t index, size_t &out_inner_idx) const noexcept
Get the inner index for a given index (if possible)
Definition: u_template_historybuf_impl_helpers.hpp:184