OXYGENHUB
◆ main openmsx / src / utils / circular_buffer.hh
11.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
/**
2
 * This code is heavily based on boost circular_buffer:
3
 *    http://www.boost.org/doc/libs/1_55_0/doc/html/circular_buffer.html
4
 * The interface of this version and the original boost version is (as much as
5
 * possible identical). I did leave out some of the more fancy methods like
6
 * insert/remove in the middle of the buffer or linearize the buffer. If we
7
 * ever need those we can add them of course.
8
 * So (for now) this class represents a dynamically sized circular buffer with
9
 * (efficient) {push,pop}_{front,back} and operator[] methods. It also offers
10
 * random access iterators. For much more details check the boost
11
 * documentation.
12
 */
13
#ifndef CIRCULAR_BUFFER_HH
14
#define CIRCULAR_BUFFER_HH
15
16
#include <algorithm>
17
#include <cassert>
18
#include <cstddef>
19
#include <cstdlib>
20
#include <iterator>
21
#include <ranges>
22
#include <utility>
23
24
/** Random access iterator for circular_buffer. */
25
template<typename BUF, typename T> class cb_iterator
26
{
27
public:
28
	using value_type = T;
29
	using pointer    = T*;
30
	using reference  = T&;
31
	using difference_type = ptrdiff_t;
32
	using iterator_category = std::random_access_iterator_tag;
33
34
	cb_iterator() = default;
35
	cb_iterator(const cb_iterator& it) = default;
36
	cb_iterator(const BUF* buf_, T* p_) : buf(buf_), p(p_) {}
37
38
	cb_iterator& operator=(const cb_iterator& it) = default;
39
40
	[[nodiscard]] T& operator*()  const { return *p; }
41
	T* operator->() const { return  p; }
42
43
	[[nodiscard]] friend difference_type operator-(const cb_iterator& l, const cb_iterator& r) {
44
		return l.index(l.p) - r.index(r.p);
45
	}
46
47
	cb_iterator& operator++() {
48
		buf->increment(p);
49
		if (p == buf->last) p = nullptr;
50
		return *this;
51
	}
52
	cb_iterator& operator--() {
53
		if (p == nullptr) p = buf->last;
54
		buf->decrement(p);
55
		return *this;
56
	}
57
	cb_iterator operator++(int) { auto tmp = *this; ++*this; return tmp; }
58
	cb_iterator operator--(int) { auto tmp = *this; --*this; return tmp; }
59
60
	cb_iterator& operator+=(difference_type n) {
61
		if (n > 0) {
62
			p = buf->add(p,  n);
63
			if (p == buf->last) p = nullptr;
64
		} else if (n < 0) {
65
			if (p == nullptr) p = buf->last;
66
			p = buf->sub(p, -n);
67
		} else {
68
			// nothing, but _must_ be handled separately
69
		}
70
		return *this;
71
	}
72
	cb_iterator& operator-=(difference_type n) { *this += -n; return *this; }
73
74
	[[nodiscard]] friend cb_iterator operator+(cb_iterator it, difference_type n) { it += n; return it; }
75
	[[nodiscard]] friend cb_iterator operator+(difference_type n, cb_iterator it) { it += n; return it; }
76
	[[nodiscard]] friend cb_iterator operator-(cb_iterator it, difference_type n) { it -= n; return it; }
77
78
	[[nodiscard]] T& operator[](difference_type n) const { return *(*this + n); }
79
80
	[[nodiscard]] bool operator== (const cb_iterator& it) const { return p == it.p; }
81
	[[nodiscard]] auto operator<=>(const cb_iterator& it) const { return index(p) <=> index(it.p); }
82
83
private:
84
	[[nodiscard]] size_t index(const T* q) const {
85
		return q ? buf->index(q) : buf->size();
86
	}
87
88
	const BUF* buf = nullptr;
89
	T* p = nullptr; // invariant: end-iterator    -> nullptr,
90
	                //            other iterators -> pointer to element
91
};
92
93
/** Circular buffer class, based on boost::circular_buffer/ */
94
template<typename T> class circular_buffer
95
{
96
public:
97
	using               iterator = cb_iterator<circular_buffer<T>,       T>;
98
	using         const_iterator = cb_iterator<circular_buffer<T>, const T>;
99
	using       reverse_iterator = std::reverse_iterator<      iterator>;
100
	using const_reverse_iterator = std::reverse_iterator<const_iterator>;
101
	using value_type = T;
102
	using pointer    = T*;
103
	using reference  = T&;
104
	using difference_type = ptrdiff_t;
105
	using size_type       = size_t;
106
	static_assert(std::random_access_iterator<iterator>);
107
108
	circular_buffer() = default;
109
110
	explicit circular_buffer(size_t buffer_capacity)
111
	{
112
		buf = allocate(buffer_capacity);
113
		stop = buf + buffer_capacity;
114
		first = last = buf;
115
	}
116
117
	circular_buffer(const circular_buffer& cb)
118
		: siz(cb.size())
119
	{
120
		buf = allocate(cb.capacity());
121
		stop = buf + cb.capacity();
122
		first = buf;
123
		try {
124
			last = uninitialized_copy(cb.begin(), cb.end(), buf);
125
		} catch (...) {
126
			free(buf);
127
			throw;
128
		}
129
		if (last == stop) last = buf;
130
	}
131
132
	circular_buffer(circular_buffer&& cb) noexcept
133
	{
134
		cb.swap(*this);
135
	}
136
137
	~circular_buffer() {
138
		destroy();
139
	}
140
141
	circular_buffer& operator=(const circular_buffer& cb) {
142
		if (this == &cb) return *this;
143
		T* buff = allocate(cb.capacity());
144
		try {
145
			reset(buff,
146
			      uninitialized_copy(cb.begin(), cb.end(), buff),
147
			      cb.capacity());
148
		} catch (...) {
149
			free(buff);
150
			throw;
151
		}
152
		return *this;
153
	}
154
155
	circular_buffer& operator=(circular_buffer&& cb) noexcept {
156
		cb.swap(*this);
157
		circular_buffer().swap(cb);
158
		return *this;
159
	}
160
161
	void swap(circular_buffer& cb) noexcept {
162
		std::swap(buf,   cb.buf);
163
		std::swap(stop,  cb.stop);
164
		std::swap(first, cb.first);
165
		std::swap(last,  cb.last);
166
		std::swap(siz,   cb.siz);
167
	}
168
169
	[[nodiscard]] auto begin() {
170
		return iterator(this, empty() ? nullptr : first);
171
	}
172
	[[nodiscard]] auto begin() const {
173
		return const_iterator(this, empty() ? nullptr : first);
174
	}
175
	[[nodiscard]] auto end()          { return iterator      (this, nullptr); }
176
	[[nodiscard]] auto end()    const { return const_iterator(this, nullptr); }
177
	[[nodiscard]] auto rbegin()       { return       reverse_iterator(end()); }
178
	[[nodiscard]] auto rbegin() const { return const_reverse_iterator(end()); }
179
	[[nodiscard]] auto rend()         { return       reverse_iterator(begin()); }
180
	[[nodiscard]] auto rend()   const { return const_reverse_iterator(begin()); }
181
182
	[[nodiscard]] auto& operator[](size_t i)       { return *add(first, i); }
183
	[[nodiscard]] auto& operator[](size_t i) const { return *add(first, i); }
184
185
	[[nodiscard]] auto& front()       { return *first; }
186
	[[nodiscard]] auto& front() const { return *first; }
187
	[[nodiscard]] auto& back()       { return *((last == buf ? stop : last) - 1); }
188
	[[nodiscard]] auto& back() const { return *((last == buf ? stop : last) - 1); }
189
190
	[[nodiscard]] size_t size() const { return siz; }
191
	[[nodiscard]] bool empty() const { return size() == 0; }
192
	[[nodiscard]] bool full() const { return capacity() == size(); }
193
	[[nodiscard]] size_t reserve() const { return capacity() - size(); }
194
	[[nodiscard]] size_t capacity() const { return stop - buf; }
195
196
	void set_capacity(size_t new_capacity) {
197
		if (new_capacity == capacity()) return;
198
		T* new_buf = allocate(new_capacity);
199
		iterator b = begin();
200
		try {
201
			reset(new_buf,
202
			      uninitialized_move_n(b, std::min(new_capacity, size()),
203
			                           new_buf),
204
			      new_capacity);
205
		} catch (...) {
206
			free(new_buf);
207
			throw;
208
		}
209
	}
210
211
	template<typename T2>
212
	void push_back(T2&& t) {
213
		::new (last) T(std::forward<T2>(t));
214
		increment(last);
215
		++siz;
216
	}
217
218
	template<typename T2>
219
	void push_front(T2&& t) {
220
		try {
221
			decrement(first);
222
			::new (first) T(std::forward<T2>(t));
223
			++siz;
224
		} catch (...) {
225
			increment(first);
226
			throw;
227
		}
228
	}
229
230
	void push_back(std::initializer_list<T> list) {
231
		for (auto& e : list) push_back(e);
232
	}
233
234
	void pop_back() {
235
		decrement(last);
236
		last->~T();
237
		--siz;
238
	}
239
240
	void pop_front() {
241
		first->~T();
242
		increment(first);
243
		--siz;
244
	}
245
246
	// Remove the first 'n' elements, like std::string_view::remove_prefix().
247
	void remove_prefix(size_t n) {
248
		assert(n <= size());
249
		for (size_t i = 0; i < n; ++i) {
250
			first->~T();
251
			increment(first);
252
		}
253
		siz -= n;
254
	}
255
256
	void clear() {
257
		for (size_t i = 0; i < size(); ++i, increment(first)) {
258
			first->~T();
259
		}
260
		siz = 0;
261
	}
262
263
private:
264
	[[nodiscard]] T* uninitialized_copy(const_iterator b, const_iterator e, T* dst) {
265
		T* next = dst;
266
		try {
267
			while (b != e) {
268
				::new (dst) T(*b);
269
				++b; ++dst;
270
			}
271
		} catch (...) {
272
			while (next != dst) {
273
				next->~T();
274
				++next;
275
			}
276
			throw;
277
		}
278
		return dst;
279
	}
280
281
	[[nodiscard]] T* uninitialized_move_n(iterator src, size_t n, T* dst) {
282
		while (n) {
283
			::new (dst) T(std::move(*src));
284
			++src; ++dst; --n;
285
		}
286
		return dst;
287
	}
288
289
	void increment(auto*& p) const {
290
		if (++p == stop) p = buf;
291
	}
292
	void decrement(auto*& p) const {
293
		if (p == buf) p = stop;
294
		--p;
295
	}
296
	[[nodiscard]] auto* add(auto* p, difference_type n) const {
297
		p += n;
298
		if (p >= stop) p -= capacity();
299
		return p;
300
	}
301
	[[nodiscard]] auto* sub(auto* p, difference_type n) const {
302
		p -= n;
303
		if (p < buf) p += capacity();
304
		return p;
305
	}
306
307
	[[nodiscard]] size_t index(const T* p) const {
308
		return (p >= first)
309
			? (p - first)
310
			: (stop - first) + (p - buf);
311
	}
312
313
	[[nodiscard]] T* allocate(size_t n) {
314
		return (n == 0) ? nullptr
315
		                : static_cast<T*>(malloc(n * sizeof(T)));
316
	}
317
318
	void destroy() {
319
		clear();
320
		free(buf);
321
	}
322
323
	void reset(T* new_buf, T* new_last, size_t new_capacity) {
324
		destroy();
325
		siz = new_last - new_buf;
326
		first = buf = new_buf;
327
		stop = buf + new_capacity;
328
		last = new_last == stop ? buf : new_last;
329
	}
330
331
private:
332
	T* buf = nullptr;   // start of allocated area
333
	T* stop = nullptr;  // end of allocated area (exclusive)
334
	T* first = nullptr; // position of the 1st element in the buffer
335
	T* last = nullptr;  // position past the last element
336
	                    // note: both for a full or empty buffer first==last
337
	size_t siz = 0; // number of elements in the buffer
338
339
	template<typename BUF, typename T2> friend class cb_iterator;
340
};
341
342
343
/** This implements a queue on top of circular_buffer (not part of boost).
344
  * It will automatically grow the buffer when its capacity is too small
345
  * while inserting new elements. */
346
template<typename T> class cb_queue
347
{
348
public:
349
	using             value_type = typename circular_buffer<T>::value_type;
350
	using               iterator = typename circular_buffer<T>::iterator;
351
	using         const_iterator = typename circular_buffer<T>::const_iterator;
352
	using       reverse_iterator = typename circular_buffer<T>::reverse_iterator;
353
	using const_reverse_iterator = typename circular_buffer<T>::const_reverse_iterator;
354
355
	cb_queue() = default;
356
	explicit cb_queue(size_t capacity)
357
		: buf(capacity) {}
358
359
	template<typename U>
360
	void push_back(U&& u) { growFor(1); buf.push_back(std::forward<U>(u)); }
361
362
	template<typename U>
363
	void push_back(std::initializer_list<U> list) { push_back_range(list); }
364
365
	// Appends all elements of the given range at once. The buffer is
366
	// grown at most once, unlike repeated push_back() calls.
367
	template<typename Range>
368
	void push_back_range(Range&& range) {
369
		growFor(static_cast<size_t>(std::ranges::distance(range)));
370
		for (auto&& e : range) {
371
			buf.push_back(std::forward<decltype(e)>(e));
372
		}
373
	}
374
375
	T pop_front() {
376
		T t = std::move(buf.front());
377
		buf.pop_front();
378
		return t;
379
	}
380
381
	[[nodiscard]] const T& front() const { return buf.front(); }
382
	[[nodiscard]] const T& back() const  { return buf.back();  }
383
	[[nodiscard]] const T& operator[](size_t i) const { return buf[i]; }
384
385
	[[nodiscard]] auto  begin()       { return buf.begin();  }
386
	[[nodiscard]] auto  end()         { return buf.end();    }
387
	[[nodiscard]] auto  begin() const { return buf.begin();  }
388
	[[nodiscard]] auto  end()   const { return buf.end();    }
389
	[[nodiscard]] auto rbegin()       { return buf.rbegin(); }
390
	[[nodiscard]] auto rbegin() const { return buf.rbegin(); }
391
	[[nodiscard]] auto rend()         { return buf.rend();   }
392
	[[nodiscard]] auto rend()   const { return buf.rend();   }
393
394
	[[nodiscard]] size_t size() const { return buf.size(); }
395
	[[nodiscard]] bool empty() const { return buf.empty(); }
396
	void clear() { buf.clear(); }
397
398
	// Remove the first 'n' elements, like std::string_view::remove_prefix().
399
	void remove_prefix(size_t n) { buf.remove_prefix(n); }
400
401
	[[nodiscard]] auto& getBuffer()       { return buf; }
402
	[[nodiscard]] auto& getBuffer() const { return buf; }
403
404
private:
405
	// Grows the buffer so that (at least) 'extra' more elements fit.
406
	void growFor(size_t extra) {
407
		size_t needed = size() + extra;
408
		if (needed <= buf.capacity()) return;
409
		buf.set_capacity(std::max({4uz, buf.capacity() * 2, needed}));
410
	}
411
412
	circular_buffer<T> buf;
413
};
414
415
#endif