ScummVM API documentation
ringbuffer.h
1/* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22#ifndef _BACKENDS_MIXER_ANDROID_RINGBUFFER_H_
23#define _BACKENDS_MIXER_ANDROID_RINGBUFFER_H_
24
25#include <assert.h>
26#include <stddef.h>
27#include <string.h>
28#include <new>
29#include <atomic>
30
34template<typename T>
36public:
37 RingBuffer(size_t n) : _size(n + 1), _buffer(new T[_size]), _pending_read(0), _pending_write(0), _read(0), _write(0), _last(0) { }
38 RingBuffer(const RingBuffer<T> &) = delete;
39
40 /*
41 * Contruct a new RingBuffer moving data from the old one
42 * The other RingBuffer must not be in use and destroyed afterwards.
43 *
44 * When the new RingBuffer is smaller than the previous one, the older samples are dropped.
45 */
46 RingBuffer(size_t n, RingBuffer<T> &&o) : RingBuffer(n) {
47 // First make the RingBuffer was in a valid state and make it invalid
48 const size_t write = o._write.exchange(-1);
49 const size_t read = o._read.exchange(-1);
50 assert(o._pending_write == write);
51 o._pending_write = 0;
52 assert(o._pending_read == read);
53 o._pending_read = -1;
54
55 T const *buffer = o._buffer;
56 o._buffer = nullptr;
57
58 // From here, o is completely invalid
59
60 if (read == write) {
61 // Empty queue: nothing to move
62 delete[] buffer;
63 return;
64 }
65 if (read < write) {
66 // Cap the kept data to our own buffer size
67 size_t nread = read;
68 if (nread + n < write) {
69 nread = write - n;
70 }
71 _pending_write = write - nread;
72
73 memcpy(&_buffer[0], &buffer[nread], _pending_write * sizeof(T));
74
75 _last.store(_pending_write, std::memory_order_relaxed);
76 _write.store(_pending_write, std::memory_order_release);
77 delete[] buffer;
78 return;
79 }
80
81 // read > write: the buffer is in two parts
82 if (n <= write) {
83 // Easy: we can take the last n samples in one shot
84 _pending_write = n;
85 memcpy(&_buffer[0], &buffer[write - n], n * sizeof(T));
86
87 _last.store(_pending_write, std::memory_order_relaxed);
88 _write.store(_pending_write, std::memory_order_release);
89
90 delete[] buffer;
91 return;
92 }
93
94 // n > write
95 size_t last = o._last.load(std::memory_order_relaxed);
96 size_t end_part_sz = last - read;
97 if (end_part_sz > (n - write)) {
98 end_part_sz = n - write;
99 }
100
101 // First, copy the end of the buffer up to last, then copy the whole beginning
102 _pending_write = end_part_sz + write;
103 memcpy(&_buffer[0], &buffer[last - end_part_sz], end_part_sz * sizeof(T));
104 memcpy(&_buffer[end_part_sz], &buffer[0], write * sizeof(T));
105
106 _last.store(_pending_write, std::memory_order_relaxed);
107 _write.store(_pending_write, std::memory_order_release);
108
109 delete[] buffer;
110 }
111
112 ~RingBuffer() { delete[] _buffer; }
113
114 /*
115 * Try to produce at least n elements.
116 * The ring-buffer will adjust n with the real element count
117 * which should be produced.
118 * In case of failure, nullptr is returned.
119 *
120 * When successful, n is guaranteed to be at least what has been queried.
121 * A pointer to the buffer to fill is returned.
122 */
123 T *try_produce(size_t *n) {
124 size_t real_n = *n;
125 assert(real_n > 0);
126
127 size_t write = _write.load(std::memory_order_relaxed);
128 size_t read = _read.load(std::memory_order_acquire);
129 assert(_pending_write == write);
130
131 // Try to acquire at at least real_n records
132 if (read <= write) {
133 if (write + real_n <= _size) {
134 real_n = _size - write;
135 *n = real_n;
136 _wraparound_write = false;
137 _pending_write = write + real_n;
138 return &_buffer[write];
139 } else if (real_n < read) { // Don't go up to read: that would make believe it's empty
140 real_n = read - 1;
141 *n = real_n;
142 _wraparound_write = true;
143 _pending_write = real_n;
144 return &_buffer[0];
145 } else {
146 return nullptr;
147 }
148 } else {
149 if (write + real_n < read) { // Don't go up to read: that would make believe it's empty
150 real_n = read - write - 1;
151 *n = real_n;
152 _wraparound_write = false;
153 _pending_write = write + real_n;
154 return &_buffer[write];
155 } else {
156 return nullptr;
157 }
158 }
159 }
160
161 /*
162 * Indicate that n samples have been produced.
163 * n must be less than or equal to what have been returned by try_produce.
164 */
165 void produced(size_t n) {
166 size_t write = _write.load(std::memory_order_relaxed);
167 size_t pending_write;
168 if (_wraparound_write) {
169 pending_write = n;
170 _last.store(write, std::memory_order_relaxed);
171 } else {
172 pending_write = write + n;
173 }
174 // Make sure we didn't overshoot
175 assert(_pending_write >= pending_write);
176 if (pending_write > _last.load(std::memory_order_relaxed)) {
177 _last.store(pending_write, std::memory_order_relaxed);
178 }
179 _pending_write = pending_write;
180 _write.store(pending_write, std::memory_order_release);
181 }
182
183 /*
184 * Make the buffer empty.
185 *
186 * Called from the producer side.
187 * NO consume MUST be ongoing.
188 */
189 void flush() {
190 size_t write = _write.load(std::memory_order_relaxed);
191 size_t read = _read.load(std::memory_order_acquire);
192 assert(_pending_write == write);
193
194 // This check may succeed while the other thread is consuming
195 // This is an imperfect failsafe
196 assert(_pending_read == read);
197
198 _last.store(read, std::memory_order_relaxed);
199 _pending_write = read;
200 _write.store(read, std::memory_order_release);
201 }
202
203 /*
204 * Try to consume at most n elements.
205 * If there is less than n elements (or if the buffer is not contiguous), adjusts n to the real count.
206 * If there is no element available, returns nullptr.
207 *
208 * Loop over try_consume until it returns nullptr to fetch all the expected elements.
209 */
210 T *try_consume(size_t *n) {
211 size_t real_n = *n;
212 assert(real_n > 0);
213
214 size_t read = _read.load(std::memory_order_relaxed);
215 assert(_pending_read == read);
216
217 // Try to acquire at most n records
218 size_t write = _write.load(std::memory_order_acquire);
219
220 if (read == write) {
221 // Empty queue: nothing to return
222 return nullptr;
223 } else if (read < write) {
224 if (read + real_n > write) {
225 real_n = write - read;
226 }
227 *n = real_n;
228 _pending_read = read + real_n;
229 return &_buffer[read];
230 } else {
231 size_t last = _last.load(std::memory_order_relaxed);
232 if (read == last) { // This happens when we read up to the end the last time: consider read as 0
233 if (0 == write) {
234 // Empty queue: nothing to return
235 return nullptr;
236 }
237 if (real_n > write) {
238 real_n = write;
239 }
240 *n = real_n;
241 _pending_read = real_n;
242 return &_buffer[0];
243 } else if (read + real_n < last) {
244 *n = real_n;
245 _pending_read = read + real_n;
246 return &_buffer[read];
247 } else {
248 *n = last - read;
249 _pending_read = 0;
250 return &_buffer[read];
251 }
252 }
253 }
254
255 /*
256 * Indicate that the previous consume request has been done.
257 *
258 * Frees the buffer for more produced samples.
259 */
260 void consumed() {
261 _read.store(_pending_read, std::memory_order_release);
262 }
263
264private:
265 const size_t _size;
266 T *_buffer;
267
268 size_t _pending_read;
269 size_t _pending_write;
270 bool _wraparound_write;
271
272 // LLVM between 8 and 10 wrongfully said they supported this
273#if 0 && __cpp_lib_hardware_interference_size
274 static constexpr size_t hardware_destructive_interference_size = std::hardware_destructive_interference_size;
275#else
276 // 64 is a good fit for most platforms
277 static constexpr size_t hardware_destructive_interference_size = 64;
278#endif
279
280 alignas(hardware_destructive_interference_size) std::atomic<size_t> _read;
281 alignas(hardware_destructive_interference_size) std::atomic<size_t> _write;
282 alignas(hardware_destructive_interference_size) std::atomic<size_t> _last;
283};
284
285#endif
Definition: ringbuffer.h:35