Monado OpenXR Runtime
u_template_historybuf.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 Ringbuffer implementation for keeping track of the past state of things
7 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
8 * @author Moses Turner <moses@collabora.com>
9 * @ingroup aux_util
10 */
11
12#pragma once
13
15#include "u_iterator_base.hpp"
16
17#include <limits>
18#include <array>
19
20namespace xrt::auxiliary::util {
21
22namespace detail {
23 template <typename T, size_t MaxSize> class HistoryBufConstIterator;
24 template <typename T, size_t MaxSize> class HistoryBufIterator;
25} // namespace detail
26
27/*!
28 * @brief Stores some number of values in a ring buffer, overwriting the earliest-pushed-remaining element if out of
29 * room.
30 *
31 * Note this should only store value types, since there's no way to destroy elements other than overwriting them, and
32 * all elements are default-initialized upon construction of the container.
33 *
34 * @note Unlike @ref m_relation_history which is based on this, this data structure is
35 * **not inherently safe for concurrent/threaded use**: there are no locks built-in.
36 */
37template <typename T, size_t MaxSize> class HistoryBuffer
38{
39public:
40 //! Is the buffer empty?
41 bool
42 empty() const noexcept;
43
44 //! How many elements are in the buffer?
45 size_t
46 size() const noexcept;
47
48
49 /*!
50 * @brief Put something at the back, overwriting whatever was at the front if necessary.
51 *
52 * This is permitted to invalidate iterators. They won't be poisoned, but they will return something you don't
53 * expect.
54 */
55 void
56 push_back(const T &element);
57
58 /*!
59 * @brief Logically remove the newest element from the buffer.
60 *
61 * This is permitted to invalidate iterators. They won't be poisoned,
62 * but they will return something you don't expect.
63 *
64 * @return true if there was something to pop.
65 */
66 bool
67 pop_back() noexcept
68 {
69 return helper_.pop_back();
70 }
71
72 /*!
73 * @brief Logically remove the oldest element from the buffer.
74 *
75 * The value still remains in the backing container until overwritten, but it isn't accessible anymore.
76 *
77 * This invalidates iterators. They won't be poisoned, but they will return something you don't expect.
78 */
79 void
80 pop_front() noexcept
81 {
82 helper_.pop_front();
83 }
84
85 /*!
86 * @brief Use a value at a given age, where age 0 is the most recent value, age 1 precedes it, etc.
87 * (reverse chronological order)
88 *
89 * Out of bounds accesses will return nullptr.
90 */
91 T *
92 get_at_age(size_t age) noexcept;
93
94 //! @overload
95 const T *
96 get_at_age(size_t age) const noexcept;
97
98 /*!
99 * @brief Like get_at_age() but values larger than the oldest age are clamped.
100 */
101 T *
102 get_at_clamped_age(size_t age) noexcept
103 {
104 size_t inner_index = 0;
105 if (helper_.clamped_age_to_inner_index(age, inner_index)) {
106 return &internalBuffer[inner_index];
107 }
108 return nullptr;
109 }
110
111 //! @overload
112 const T *
113 get_at_clamped_age(size_t age) const noexcept
114 {
115 size_t inner_index = 0;
116 if (helper_.clamped_age_to_inner_index(age, inner_index)) {
117 return &internalBuffer[inner_index];
118 }
119 return nullptr;
120 }
121
122 /*!
123 * @brief Use a value at a given index, where 0 is the least-recent value still stored, index 1 follows it,
124 * etc. (chronological order)
125 *
126 * Out of bounds accesses will return nullptr.
127 */
128 T *
129 get_at_index(size_t index) noexcept;
130
131 //! @overload
132 const T *
133 get_at_index(size_t index) const noexcept;
134
137
138 //! Get a const iterator for the oldest element.
140 cbegin() const noexcept;
141
142 //! Get a "past the end" (past the newest) const iterator
144 cend() const noexcept;
145
146 //! Get a const iterator for the oldest element.
148 begin() const noexcept;
149
150 //! Get a "past the end" (past the newest) const iterator
152 end() const noexcept;
153
154 //! Get an iterator for the oldest element.
156 begin() noexcept;
157
158 //! Get a "past the end" (past the newest) iterator
160 end() noexcept;
161
162 /*!
163 * @brief Gets a reference to the front (oldest) element in the buffer.
164 * @throws std::logic_error if buffer is empty
165 */
166 T &
168
169 //! @overload
170 const T &
171 front() const;
172
173 /*!
174 * @brief Gets a reference to the back (newest) element in the buffer.
175 * @throws std::logic_error if buffer is empty
176 */
177 T &
179
180 //! @overload
181 const T &
182 back() const;
183
184 void
185 clear();
186
187private:
188 // Make sure all valid indices can be represented in a signed integer of the same size
189 static_assert(MaxSize < (std::numeric_limits<size_t>::max() >> 1), "Cannot use most significant bit");
190
191 using container_t = std::array<T, MaxSize>;
192 container_t internalBuffer{};
193 detail::RingBufferHelper helper_{MaxSize};
194};
195
196
197template <typename T, size_t MaxSize>
198void
199HistoryBuffer<T, MaxSize>::clear()
200{
201 helper_.clear();
202}
203
204template <typename T, size_t MaxSize>
205inline bool
207{
208 return helper_.empty();
209}
210
211template <typename T, size_t MaxSize>
212inline size_t
214{
215 return helper_.size();
216}
217
218template <typename T, size_t MaxSize>
219inline void
221{
222 auto inner_index = helper_.push_back_location();
223 internalBuffer[inner_index] = element;
224}
225
226template <typename T, size_t MaxSize>
227inline T *
229{
230 size_t inner_index = 0;
231 if (helper_.age_to_inner_index(age, inner_index)) {
232 return &internalBuffer[inner_index];
233 }
234 return nullptr;
235}
236template <typename T, size_t MaxSize>
237inline const T *
239{
240 size_t inner_index = 0;
241 if (helper_.age_to_inner_index(age, inner_index)) {
242 return &internalBuffer[inner_index];
243 }
244 return nullptr;
245}
246
247template <typename T, size_t MaxSize>
248inline T *
250{
251 size_t inner_index = 0;
252 if (helper_.index_to_inner_index(index, inner_index)) {
253 return &internalBuffer[inner_index];
254 }
255 return nullptr;
256}
257
258template <typename T, size_t MaxSize>
259inline const T *
260HistoryBuffer<T, MaxSize>::get_at_index(size_t index) const noexcept
261{
262 size_t inner_index = 0;
263 if (helper_.index_to_inner_index(index, inner_index)) {
264 return &internalBuffer[inner_index];
265 }
266 return nullptr;
267}
268
269template <typename T, size_t MaxSize>
270inline T &
272{
273 if (empty()) {
274 throw std::logic_error("Cannot get the front of an empty buffer");
275 }
276 return internalBuffer[helper_.front_inner_index()];
277}
278
279template <typename T, size_t MaxSize>
280inline const T &
282{
283 if (empty()) {
284 throw std::logic_error("Cannot get the front of an empty buffer");
285 }
286 return internalBuffer[helper_.front_inner_index()];
287}
288
289template <typename T, size_t MaxSize>
290inline T &
292{
293 if (empty()) {
294 throw std::logic_error("Cannot get the back of an empty buffer");
295 }
296 return internalBuffer[helper_.back_inner_index()];
297}
298
299template <typename T, size_t MaxSize>
300inline const T &
302{
303 if (empty()) {
304 throw std::logic_error("Cannot get the back of an empty buffer");
305 }
306 return internalBuffer[helper_.back_inner_index()];
307}
308
309
310} // namespace xrt::auxiliary::util
311
Stores some number of values in a ring buffer, overwriting the earliest-pushed-remaining element if o...
Definition: u_template_historybuf.hpp:38
void pop_front() noexcept
Logically remove the oldest element from the buffer.
Definition: u_template_historybuf.hpp:80
const T * get_at_clamped_age(size_t age) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: u_template_historybuf.hpp:113
bool empty() const noexcept
Is the buffer empty?
Definition: u_template_historybuf.hpp:206
bool pop_back() noexcept
Logically remove the newest element from the buffer.
Definition: u_template_historybuf.hpp:67
T * get_at_index(size_t index) noexcept
Use a value at a given index, where 0 is the least-recent value still stored, index 1 follows it,...
Definition: u_template_historybuf.hpp:249
const_iterator end() const noexcept
Get a "past the end" (past the newest) const iterator.
Definition: u_template_historybuf_const_iterator.inl:251
const_iterator cbegin() const noexcept
Get a const iterator for the oldest element.
Definition: u_template_historybuf_const_iterator.inl:227
T & back()
Gets a reference to the back (newest) element in the buffer.
Definition: u_template_historybuf.hpp:291
const T * get_at_index(size_t index) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: u_template_historybuf.hpp:260
size_t size() const noexcept
How many elements are in the buffer?
Definition: u_template_historybuf.hpp:213
const T * get_at_age(size_t age) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: u_template_historybuf.hpp:238
const_iterator cend() const noexcept
Get a "past the end" (past the newest) const iterator.
Definition: u_template_historybuf_const_iterator.inl:237
const_iterator begin() const noexcept
Get a const iterator for the oldest element.
Definition: u_template_historybuf_const_iterator.inl:244
void push_back(const T &element)
Put something at the back, overwriting whatever was at the front if necessary.
Definition: u_template_historybuf.hpp:220
T * get_at_clamped_age(size_t age) noexcept
Like get_at_age() but values larger than the oldest age are clamped.
Definition: u_template_historybuf.hpp:102
T & front()
Gets a reference to the front (oldest) element in the buffer.
Definition: u_template_historybuf.hpp:271
T * get_at_age(size_t age) noexcept
Use a value at a given age, where age 0 is the most recent value, age 1 precedes it,...
Definition: u_template_historybuf.hpp:228
Class template for const_iterator for HistoryBuffer.
Definition: u_template_historybuf_const_iterator.inl:29
Class template for iterator for HistoryBuffer.
Definition: u_template_historybuf_iterator.inl:29
All the bookkeeping for adapting a fixed-size array to a ring buffer.
Definition: u_template_historybuf_impl_helpers.hpp:46
bool pop_back() noexcept
Record the logical removal of the back element, if any.
Definition: u_template_historybuf_impl_helpers.hpp:217
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
A template class to serve as the base of iterator and const_iterator types for things with "random ac...
const_iterator details for ring buffer
All the "element-type-independent" code (helper objects, base classes) for a ringbuffer implementatio...
iterator details for ring buffer